diff --git a/chapter_paperbook/index.html b/chapter_paperbook/index.html index 986cbd588..4f364fd9f 100644 --- a/chapter_paperbook/index.html +++ b/chapter_paperbook/index.html @@ -24,7 +24,7 @@ - + @@ -211,7 +211,7 @@
  • - + English
  • diff --git a/en/404.html b/en/404.html index 585157fe8..da9aa1e53 100644 --- a/en/404.html +++ b/en/404.html @@ -4480,78 +4480,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/assets/hero/cover_render.png b/en/assets/hero/cover_render.png deleted file mode 100644 index ec1364272..000000000 Binary files a/en/assets/hero/cover_render.png and /dev/null differ diff --git a/en/chapter_appendix/contribution/index.html b/en/chapter_appendix/contribution/index.html index f492bebd1..31c8d11a4 100644 --- a/en/chapter_appendix/contribution/index.html +++ b/en/chapter_appendix/contribution/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_appendix/index.html b/en/chapter_appendix/index.html index e406fbbe7..f5098ec4b 100644 --- a/en/chapter_appendix/index.html +++ b/en/chapter_appendix/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_appendix/installation/index.html b/en/chapter_appendix/installation/index.html index 89604c6a2..088ad2a09 100644 --- a/en/chapter_appendix/installation/index.html +++ b/en/chapter_appendix/installation/index.html @@ -4674,78 +4674,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_appendix/terminology/index.html b/en/chapter_appendix/terminology/index.html index 05107d8c8..37d87661c 100644 --- a/en/chapter_appendix/terminology/index.html +++ b/en/chapter_appendix/terminology/index.html @@ -4499,78 +4499,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_array_and_linkedlist/array/index.html b/en/chapter_array_and_linkedlist/array/index.html index 76b8eca92..fd58ea529 100644 --- a/en/chapter_array_and_linkedlist/array/index.html +++ b/en/chapter_array_and_linkedlist/array/index.html @@ -4652,78 +4652,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_array_and_linkedlist/exercises/index.html b/en/chapter_array_and_linkedlist/exercises/index.html index 086b9b6b6..b8a888a1b 100644 --- a/en/chapter_array_and_linkedlist/exercises/index.html +++ b/en/chapter_array_and_linkedlist/exercises/index.html @@ -4625,78 +4625,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_array_and_linkedlist/index.html b/en/chapter_array_and_linkedlist/index.html index 01ff0e1e6..56ddf55aa 100644 --- a/en/chapter_array_and_linkedlist/index.html +++ b/en/chapter_array_and_linkedlist/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_array_and_linkedlist/linked_list/index.html b/en/chapter_array_and_linkedlist/linked_list/index.html index 05ac0b607..0876357cd 100644 --- a/en/chapter_array_and_linkedlist/linked_list/index.html +++ b/en/chapter_array_and_linkedlist/linked_list/index.html @@ -4641,78 +4641,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_array_and_linkedlist/list/index.html b/en/chapter_array_and_linkedlist/list/index.html index 7b523111a..4f5dfd9b9 100644 --- a/en/chapter_array_and_linkedlist/list/index.html +++ b/en/chapter_array_and_linkedlist/list/index.html @@ -4630,78 +4630,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_array_and_linkedlist/ram_and_cache/index.html b/en/chapter_array_and_linkedlist/ram_and_cache/index.html index 517e6aa4a..c511d5b24 100644 --- a/en/chapter_array_and_linkedlist/ram_and_cache/index.html +++ b/en/chapter_array_and_linkedlist/ram_and_cache/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_array_and_linkedlist/summary/index.html b/en/chapter_array_and_linkedlist/summary/index.html index 4d9a8c9db..ee866a900 100644 --- a/en/chapter_array_and_linkedlist/summary/index.html +++ b/en/chapter_array_and_linkedlist/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_backtracking/backtracking_algorithm/index.html b/en/chapter_backtracking/backtracking_algorithm/index.html index ce5c16c5e..2ff9acb5c 100644 --- a/en/chapter_backtracking/backtracking_algorithm/index.html +++ b/en/chapter_backtracking/backtracking_algorithm/index.html @@ -4602,78 +4602,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_backtracking/exercises/index.html b/en/chapter_backtracking/exercises/index.html index ebac382ad..6b179a490 100644 --- a/en/chapter_backtracking/exercises/index.html +++ b/en/chapter_backtracking/exercises/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_backtracking/index.html b/en/chapter_backtracking/index.html index 2b2725570..9624f565f 100644 --- a/en/chapter_backtracking/index.html +++ b/en/chapter_backtracking/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_backtracking/n_queens_problem/index.html b/en/chapter_backtracking/n_queens_problem/index.html index 2958cffd1..11e0c2660 100644 --- a/en/chapter_backtracking/n_queens_problem/index.html +++ b/en/chapter_backtracking/n_queens_problem/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_backtracking/permutations_problem/index.html b/en/chapter_backtracking/permutations_problem/index.html index 27b7168bb..9594d5ab1 100644 --- a/en/chapter_backtracking/permutations_problem/index.html +++ b/en/chapter_backtracking/permutations_problem/index.html @@ -4625,78 +4625,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_backtracking/subset_sum_problem/index.html b/en/chapter_backtracking/subset_sum_problem/index.html index 40c78d04b..2902d79e5 100644 --- a/en/chapter_backtracking/subset_sum_problem/index.html +++ b/en/chapter_backtracking/subset_sum_problem/index.html @@ -4625,78 +4625,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_backtracking/summary/index.html b/en/chapter_backtracking/summary/index.html index 58b3c0992..74bc2b01c 100644 --- a/en/chapter_backtracking/summary/index.html +++ b/en/chapter_backtracking/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_computational_complexity/exercises/index.html b/en/chapter_computational_complexity/exercises/index.html index 7ef58d30a..93c57fef6 100644 --- a/en/chapter_computational_complexity/exercises/index.html +++ b/en/chapter_computational_complexity/exercises/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_computational_complexity/index.html b/en/chapter_computational_complexity/index.html index 766c995e7..55f65a7be 100644 --- a/en/chapter_computational_complexity/index.html +++ b/en/chapter_computational_complexity/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_computational_complexity/iteration_and_recursion/index.html b/en/chapter_computational_complexity/iteration_and_recursion/index.html index 0b208c06b..689c004af 100644 --- a/en/chapter_computational_complexity/iteration_and_recursion/index.html +++ b/en/chapter_computational_complexity/iteration_and_recursion/index.html @@ -4647,78 +4647,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_computational_complexity/performance_evaluation/index.html b/en/chapter_computational_complexity/performance_evaluation/index.html index 4d6723390..1d44307d7 100644 --- a/en/chapter_computational_complexity/performance_evaluation/index.html +++ b/en/chapter_computational_complexity/performance_evaluation/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_computational_complexity/space_complexity/index.html b/en/chapter_computational_complexity/space_complexity/index.html index af1d480a7..40eefa897 100644 --- a/en/chapter_computational_complexity/space_complexity/index.html +++ b/en/chapter_computational_complexity/space_complexity/index.html @@ -4641,78 +4641,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_computational_complexity/summary/index.html b/en/chapter_computational_complexity/summary/index.html index 9c639812e..e5a970b38 100644 --- a/en/chapter_computational_complexity/summary/index.html +++ b/en/chapter_computational_complexity/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_computational_complexity/time_complexity/index.html b/en/chapter_computational_complexity/time_complexity/index.html index 09ce510f5..b9c8d5839 100644 --- a/en/chapter_computational_complexity/time_complexity/index.html +++ b/en/chapter_computational_complexity/time_complexity/index.html @@ -4702,78 +4702,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_data_structure/basic_data_types/index.html b/en/chapter_data_structure/basic_data_types/index.html index 3453acdfa..29859bd6a 100644 --- a/en/chapter_data_structure/basic_data_types/index.html +++ b/en/chapter_data_structure/basic_data_types/index.html @@ -4499,78 +4499,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_data_structure/character_encoding/index.html b/en/chapter_data_structure/character_encoding/index.html index ddf64936f..8525c7b06 100644 --- a/en/chapter_data_structure/character_encoding/index.html +++ b/en/chapter_data_structure/character_encoding/index.html @@ -4591,78 +4591,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_data_structure/classification_of_data_structure/index.html b/en/chapter_data_structure/classification_of_data_structure/index.html index 65c968c4d..beb93ff46 100644 --- a/en/chapter_data_structure/classification_of_data_structure/index.html +++ b/en/chapter_data_structure/classification_of_data_structure/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_data_structure/exercises/index.html b/en/chapter_data_structure/exercises/index.html index ce7fe80e9..f0229b326 100644 --- a/en/chapter_data_structure/exercises/index.html +++ b/en/chapter_data_structure/exercises/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_data_structure/index.html b/en/chapter_data_structure/index.html index 57e4dc905..2a33cada3 100644 --- a/en/chapter_data_structure/index.html +++ b/en/chapter_data_structure/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_data_structure/number_encoding/index.html b/en/chapter_data_structure/number_encoding/index.html index 93e9b2b53..7fc0772d7 100644 --- a/en/chapter_data_structure/number_encoding/index.html +++ b/en/chapter_data_structure/number_encoding/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_data_structure/summary/index.html b/en/chapter_data_structure/summary/index.html index 172c854ad..f264a4b07 100644 --- a/en/chapter_data_structure/summary/index.html +++ b/en/chapter_data_structure/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_divide_and_conquer/binary_search_recur/index.html b/en/chapter_divide_and_conquer/binary_search_recur/index.html index 8b2b76b6d..9e6168dff 100644 --- a/en/chapter_divide_and_conquer/binary_search_recur/index.html +++ b/en/chapter_divide_and_conquer/binary_search_recur/index.html @@ -4547,78 +4547,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html b/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html index 6d44996c4..b4d5fdc5c 100644 --- a/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html +++ b/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html @@ -4580,78 +4580,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_divide_and_conquer/divide_and_conquer/index.html b/en/chapter_divide_and_conquer/divide_and_conquer/index.html index 021d1746a..9a41789af 100644 --- a/en/chapter_divide_and_conquer/divide_and_conquer/index.html +++ b/en/chapter_divide_and_conquer/divide_and_conquer/index.html @@ -4597,78 +4597,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_divide_and_conquer/exercises/index.html b/en/chapter_divide_and_conquer/exercises/index.html index 823601131..241e65d08 100644 --- a/en/chapter_divide_and_conquer/exercises/index.html +++ b/en/chapter_divide_and_conquer/exercises/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_divide_and_conquer/hanota_problem/index.html b/en/chapter_divide_and_conquer/hanota_problem/index.html index 8fa5a3293..656710028 100644 --- a/en/chapter_divide_and_conquer/hanota_problem/index.html +++ b/en/chapter_divide_and_conquer/hanota_problem/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_divide_and_conquer/index.html b/en/chapter_divide_and_conquer/index.html index 1d6051808..c85b28742 100644 --- a/en/chapter_divide_and_conquer/index.html +++ b/en/chapter_divide_and_conquer/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_divide_and_conquer/summary/index.html b/en/chapter_divide_and_conquer/summary/index.html index b6a7ac812..ef90755a8 100644 --- a/en/chapter_divide_and_conquer/summary/index.html +++ b/en/chapter_divide_and_conquer/summary/index.html @@ -4547,78 +4547,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_dynamic_programming/dp_problem_features/index.html b/en/chapter_dynamic_programming/dp_problem_features/index.html index 65e4b4cba..09a7c7d50 100644 --- a/en/chapter_dynamic_programming/dp_problem_features/index.html +++ b/en/chapter_dynamic_programming/dp_problem_features/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_dynamic_programming/dp_solution_pipeline/index.html b/en/chapter_dynamic_programming/dp_solution_pipeline/index.html index 16a3d6986..2c68c0f10 100644 --- a/en/chapter_dynamic_programming/dp_solution_pipeline/index.html +++ b/en/chapter_dynamic_programming/dp_solution_pipeline/index.html @@ -4608,78 +4608,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_dynamic_programming/edit_distance_problem/index.html b/en/chapter_dynamic_programming/edit_distance_problem/index.html index 497faa7bd..18eb909d0 100644 --- a/en/chapter_dynamic_programming/edit_distance_problem/index.html +++ b/en/chapter_dynamic_programming/edit_distance_problem/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_dynamic_programming/exercises/index.html b/en/chapter_dynamic_programming/exercises/index.html index 748b2d788..9e1dc2d8e 100644 --- a/en/chapter_dynamic_programming/exercises/index.html +++ b/en/chapter_dynamic_programming/exercises/index.html @@ -4625,78 +4625,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_dynamic_programming/index.html b/en/chapter_dynamic_programming/index.html index ac19adc1e..b9eae4766 100644 --- a/en/chapter_dynamic_programming/index.html +++ b/en/chapter_dynamic_programming/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html b/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html index 66b8b48c9..f0f9a1120 100644 --- a/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html +++ b/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html @@ -4580,78 +4580,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_dynamic_programming/knapsack_problem/index.html b/en/chapter_dynamic_programming/knapsack_problem/index.html index 46e04a842..8f9f89d87 100644 --- a/en/chapter_dynamic_programming/knapsack_problem/index.html +++ b/en/chapter_dynamic_programming/knapsack_problem/index.html @@ -4580,78 +4580,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_dynamic_programming/summary/index.html b/en/chapter_dynamic_programming/summary/index.html index d75eedbb2..cf66ba76c 100644 --- a/en/chapter_dynamic_programming/summary/index.html +++ b/en/chapter_dynamic_programming/summary/index.html @@ -4547,78 +4547,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html b/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html index a7026c22a..bebc9fe93 100644 --- a/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html +++ b/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html @@ -4686,78 +4686,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_graph/exercises/index.html b/en/chapter_graph/exercises/index.html index c38346861..4e1e4d119 100644 --- a/en/chapter_graph/exercises/index.html +++ b/en/chapter_graph/exercises/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_graph/graph/index.html b/en/chapter_graph/graph/index.html index ed813099c..472516832 100644 --- a/en/chapter_graph/graph/index.html +++ b/en/chapter_graph/graph/index.html @@ -4597,78 +4597,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_graph/graph_operations/index.html b/en/chapter_graph/graph_operations/index.html index 7d3ae1a32..b52c2552f 100644 --- a/en/chapter_graph/graph_operations/index.html +++ b/en/chapter_graph/graph_operations/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_graph/graph_traversal/index.html b/en/chapter_graph/graph_traversal/index.html index 89b639988..2acb1624c 100644 --- a/en/chapter_graph/graph_traversal/index.html +++ b/en/chapter_graph/graph_traversal/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_graph/index.html b/en/chapter_graph/index.html index 5582553a7..c7cbe4f13 100644 --- a/en/chapter_graph/index.html +++ b/en/chapter_graph/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_graph/summary/index.html b/en/chapter_graph/summary/index.html index c27f356bb..7558f8f18 100644 --- a/en/chapter_graph/summary/index.html +++ b/en/chapter_graph/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_greedy/exercises/index.html b/en/chapter_greedy/exercises/index.html index ace62424e..720ccb896 100644 --- a/en/chapter_greedy/exercises/index.html +++ b/en/chapter_greedy/exercises/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_greedy/fractional_knapsack_problem/index.html b/en/chapter_greedy/fractional_knapsack_problem/index.html index e1a91b25b..f86fefcf1 100644 --- a/en/chapter_greedy/fractional_knapsack_problem/index.html +++ b/en/chapter_greedy/fractional_knapsack_problem/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_greedy/greedy_algorithm/index.html b/en/chapter_greedy/greedy_algorithm/index.html index dcc2cd174..8c53cf8c9 100644 --- a/en/chapter_greedy/greedy_algorithm/index.html +++ b/en/chapter_greedy/greedy_algorithm/index.html @@ -4580,78 +4580,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_greedy/index.html b/en/chapter_greedy/index.html index 4d4fb33a7..ec7c5ee45 100644 --- a/en/chapter_greedy/index.html +++ b/en/chapter_greedy/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_greedy/max_capacity_problem/index.html b/en/chapter_greedy/max_capacity_problem/index.html index 07bf6cbd2..7c825399f 100644 --- a/en/chapter_greedy/max_capacity_problem/index.html +++ b/en/chapter_greedy/max_capacity_problem/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_greedy/max_product_cutting_problem/index.html b/en/chapter_greedy/max_product_cutting_problem/index.html index a63fe4cc1..ea727d4f4 100644 --- a/en/chapter_greedy/max_product_cutting_problem/index.html +++ b/en/chapter_greedy/max_product_cutting_problem/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_greedy/summary/index.html b/en/chapter_greedy/summary/index.html index aed810a51..6686c085b 100644 --- a/en/chapter_greedy/summary/index.html +++ b/en/chapter_greedy/summary/index.html @@ -4547,78 +4547,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_hashing/exercises/index.html b/en/chapter_hashing/exercises/index.html index b0ff52056..5118634fc 100644 --- a/en/chapter_hashing/exercises/index.html +++ b/en/chapter_hashing/exercises/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_hashing/hash_algorithm/index.html b/en/chapter_hashing/hash_algorithm/index.html index 717b6acdb..58a812bf4 100644 --- a/en/chapter_hashing/hash_algorithm/index.html +++ b/en/chapter_hashing/hash_algorithm/index.html @@ -4580,78 +4580,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_hashing/hash_collision/index.html b/en/chapter_hashing/hash_collision/index.html index 196ba7750..0c0ccb4a6 100644 --- a/en/chapter_hashing/hash_collision/index.html +++ b/en/chapter_hashing/hash_collision/index.html @@ -4608,78 +4608,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_hashing/hash_map/index.html b/en/chapter_hashing/hash_map/index.html index 78867b30d..1a49c3710 100644 --- a/en/chapter_hashing/hash_map/index.html +++ b/en/chapter_hashing/hash_map/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_hashing/index.html b/en/chapter_hashing/index.html index b0ac6848b..148c42206 100644 --- a/en/chapter_hashing/index.html +++ b/en/chapter_hashing/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_hashing/summary/index.html b/en/chapter_hashing/summary/index.html index e8e967661..ce169ebaa 100644 --- a/en/chapter_hashing/summary/index.html +++ b/en/chapter_hashing/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_heap/build_heap/index.html b/en/chapter_heap/build_heap/index.html index 1844e4515..6186c5490 100644 --- a/en/chapter_heap/build_heap/index.html +++ b/en/chapter_heap/build_heap/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_heap/exercises/index.html b/en/chapter_heap/exercises/index.html index d82da1823..a1dfc61a4 100644 --- a/en/chapter_heap/exercises/index.html +++ b/en/chapter_heap/exercises/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_heap/heap/index.html b/en/chapter_heap/heap/index.html index 19619a23f..e7f0eb63e 100644 --- a/en/chapter_heap/heap/index.html +++ b/en/chapter_heap/heap/index.html @@ -4619,78 +4619,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_heap/index.html b/en/chapter_heap/index.html index 23b86a179..6f0234f1b 100644 --- a/en/chapter_heap/index.html +++ b/en/chapter_heap/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_heap/summary/index.html b/en/chapter_heap/summary/index.html index eb7e398dd..2848ac90e 100644 --- a/en/chapter_heap/summary/index.html +++ b/en/chapter_heap/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_heap/top_k/index.html b/en/chapter_heap/top_k/index.html index a98e73cf5..a081772d3 100644 --- a/en/chapter_heap/top_k/index.html +++ b/en/chapter_heap/top_k/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_hello_algo/index.html b/en/chapter_hello_algo/index.html index a068a08d1..2a6169640 100644 --- a/en/chapter_hello_algo/index.html +++ b/en/chapter_hello_algo/index.html @@ -4487,78 +4487,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_introduction/algorithms_are_everywhere/index.html b/en/chapter_introduction/algorithms_are_everywhere/index.html index f01513709..e75cce98e 100644 --- a/en/chapter_introduction/algorithms_are_everywhere/index.html +++ b/en/chapter_introduction/algorithms_are_everywhere/index.html @@ -4499,78 +4499,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_introduction/index.html b/en/chapter_introduction/index.html index e6d839f04..1ec8290f5 100644 --- a/en/chapter_introduction/index.html +++ b/en/chapter_introduction/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_introduction/summary/index.html b/en/chapter_introduction/summary/index.html index e7f5eb303..6dca6eac3 100644 --- a/en/chapter_introduction/summary/index.html +++ b/en/chapter_introduction/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_introduction/what_is_dsa/index.html b/en/chapter_introduction/what_is_dsa/index.html index 8d047a96d..51d9dd26f 100644 --- a/en/chapter_introduction/what_is_dsa/index.html +++ b/en/chapter_introduction/what_is_dsa/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_paperbook/index.assets/book_jd_link.jpg b/en/chapter_paperbook/index.assets/book_jd_link.jpg deleted file mode 100644 index 3b8d8a58d..000000000 Binary files a/en/chapter_paperbook/index.assets/book_jd_link.jpg and /dev/null differ diff --git a/en/chapter_paperbook/index.assets/paper_book_avl_tree.jpg b/en/chapter_paperbook/index.assets/paper_book_avl_tree.jpg deleted file mode 100644 index c559a76be..000000000 Binary files a/en/chapter_paperbook/index.assets/paper_book_avl_tree.jpg and /dev/null differ diff --git a/en/chapter_paperbook/index.assets/paper_book_chapter_heap.jpg b/en/chapter_paperbook/index.assets/paper_book_chapter_heap.jpg deleted file mode 100644 index 9789e45b1..000000000 Binary files a/en/chapter_paperbook/index.assets/paper_book_chapter_heap.jpg and /dev/null differ diff --git a/en/chapter_paperbook/index.assets/paper_book_overview.jpg b/en/chapter_paperbook/index.assets/paper_book_overview.jpg deleted file mode 100644 index 400e37f55..000000000 Binary files a/en/chapter_paperbook/index.assets/paper_book_overview.jpg and /dev/null differ diff --git a/en/chapter_paperbook/index.html b/en/chapter_paperbook/index.html deleted file mode 100644 index 5dbe0b161..000000000 --- a/en/chapter_paperbook/index.html +++ /dev/null @@ -1,4927 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Print edition (Chinese) - Hello Algo - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - Skip to content - - -
    -
    - - - -
    - - - - - - -
    - - -
    - -
    - - - - - - -
    -
    - - - -
    -
    -
    - - - - - -
    -
    -
    - - - -
    -
    - - - - - - - - -
    - -
    - - -
    - - - - -
    -
    -
    - - - -
    - -
    - - - - - - - - - - - - - - - - - - - - - -

    Print edition (Chinese)

    -

    After a long period of refinement, the Simplified Chinese print edition of Hello Algo is finally here! A line of poetry captures how I feel:

    -

    Chase the wind and moon without pause; beyond the open plains lie the mountains of spring.

    - -

    -

    The following video introduces the print edition and shares some of my thoughts on:

    -
      -
    • The importance of learning data structures and algorithms.
    • -
    • Why I chose Python for the print edition.
    • -
    • What sharing knowledge means to me.
    • -
    -
    -

    I am new to making videos. Please show your support with a like, a coin, and a favorite—thank you!

    -
    -
    - -
    - -

    Here are some snapshots of the print edition:

    -

    -

    -

    Strengths and limitations

    -

    Here are a few things about the print edition that may pleasantly surprise you:

    -
      -
    • Full-color printing makes the most of the book's animation-style illustrations.
    • -
    • Carefully selected paper reproduces colors faithfully while preserving the distinctive feel of a printed book.
    • -
    • Formatting is more standardized than in the web edition; for example, formulas in figures are italicized.
    • -
    • A fold-out mind map and bookmarks are included at no extra cost.
    • -
    • The print, web, and PDF editions have synchronized content, so you can switch freely between them.
    • -
    -
    -

    Tip

    -

    Synchronizing the print and web editions is difficult, so there may be minor differences. Thank you for your understanding!

    -
    -

    Of course, there are also a few points to consider before buying:

    -
      -
    • The book uses Python, which may not be your main language. You can treat Python as pseudocode and focus on understanding the ideas.
    • -
    • Full-color printing greatly improves the reading experience for illustrations and code, but costs somewhat more than black-and-white printing.
    • -
    -
    -

    Tip

    -

    “Print quality” and “price” are like “time efficiency” and “space efficiency” in algorithms: it is difficult to optimize both. In my view, print quality corresponds to time efficiency and deserves greater priority.

    -
    -

    Where to buy

    -

    If you are interested in the Simplified Chinese print edition, consider getting a copy. We have arranged a 50% discount on the new book. Follow this link or scan the QR code below:

    -

    -

    Afterword

    -

    At first, I underestimated the work involved in publishing a print edition. I assumed that as long as the open-source project was well maintained, some form of automation could generate the printed book. Experience showed that producing a printed book differs greatly from updating an open-source project, and moving between the two requires considerable extra work.

    -

    There is still a long road from a first draft to a manuscript ready for publication. It takes close collaboration and sustained refinement by the author and the publisher's planning, editorial, design, and marketing teams. My thanks go to Turing's commissioning editor Junhua Wang and everyone at Posts & Telecom Press and Turing Community who took part in publishing this book!

    -

    I hope this book will help you!

    - - - - - - - - - - - - - - - - - - -
    Share your insights, questions, or suggestions in the comments
    - - - - - - - -
    -
    - - - - - -
    - -
    - - - -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/en/chapter_preface/about_the_book/index.html b/en/chapter_preface/about_the_book/index.html index cce56bfaa..a7f583bf9 100644 --- a/en/chapter_preface/about_the_book/index.html +++ b/en/chapter_preface/about_the_book/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_preface/index.html b/en/chapter_preface/index.html index 71bf6a542..d64758afe 100644 --- a/en/chapter_preface/index.html +++ b/en/chapter_preface/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_preface/suggestions/index.html b/en/chapter_preface/suggestions/index.html index 4789fba0c..2a81dfb70 100644 --- a/en/chapter_preface/suggestions/index.html +++ b/en/chapter_preface/suggestions/index.html @@ -4591,78 +4591,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_preface/summary/index.html b/en/chapter_preface/summary/index.html index 8faba5540..d55b9815e 100644 --- a/en/chapter_preface/summary/index.html +++ b/en/chapter_preface/summary/index.html @@ -4547,78 +4547,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_reference/index.html b/en/chapter_reference/index.html index 019486210..53c956c02 100644 --- a/en/chapter_reference/index.html +++ b/en/chapter_reference/index.html @@ -18,8 +18,6 @@ - - @@ -4489,78 +4487,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - @@ -4678,27 +4604,6 @@ aria-label="Footer" - - - diff --git a/en/chapter_searching/binary_search/index.html b/en/chapter_searching/binary_search/index.html index 7c3dbe52a..c1a5b0c74 100644 --- a/en/chapter_searching/binary_search/index.html +++ b/en/chapter_searching/binary_search/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_searching/binary_search_edge/index.html b/en/chapter_searching/binary_search_edge/index.html index 98ac36697..c35382a12 100644 --- a/en/chapter_searching/binary_search_edge/index.html +++ b/en/chapter_searching/binary_search_edge/index.html @@ -4586,78 +4586,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_searching/binary_search_insertion/index.html b/en/chapter_searching/binary_search_insertion/index.html index 5e486cc28..26e840342 100644 --- a/en/chapter_searching/binary_search_insertion/index.html +++ b/en/chapter_searching/binary_search_insertion/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_searching/exercises/index.html b/en/chapter_searching/exercises/index.html index 6386b5d3b..19eaf94c5 100644 --- a/en/chapter_searching/exercises/index.html +++ b/en/chapter_searching/exercises/index.html @@ -4625,78 +4625,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_searching/index.html b/en/chapter_searching/index.html index a0a15bafe..5526883c0 100644 --- a/en/chapter_searching/index.html +++ b/en/chapter_searching/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_searching/replace_linear_by_hashing/index.html b/en/chapter_searching/replace_linear_by_hashing/index.html index 235910c25..9ff74f8bd 100644 --- a/en/chapter_searching/replace_linear_by_hashing/index.html +++ b/en/chapter_searching/replace_linear_by_hashing/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_searching/searching_algorithm_revisited/index.html b/en/chapter_searching/searching_algorithm_revisited/index.html index cd1eef89a..abd5f4ddf 100644 --- a/en/chapter_searching/searching_algorithm_revisited/index.html +++ b/en/chapter_searching/searching_algorithm_revisited/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_searching/summary/index.html b/en/chapter_searching/summary/index.html index 9e55742b8..a0d76ee0b 100644 --- a/en/chapter_searching/summary/index.html +++ b/en/chapter_searching/summary/index.html @@ -4547,78 +4547,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/bubble_sort/index.html b/en/chapter_sorting/bubble_sort/index.html index 1ff13a438..b4db41027 100644 --- a/en/chapter_sorting/bubble_sort/index.html +++ b/en/chapter_sorting/bubble_sort/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/bucket_sort/index.html b/en/chapter_sorting/bucket_sort/index.html index b511f05ce..d07f28747 100644 --- a/en/chapter_sorting/bucket_sort/index.html +++ b/en/chapter_sorting/bucket_sort/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/counting_sort/index.html b/en/chapter_sorting/counting_sort/index.html index d5d8fb900..05f4e6c07 100644 --- a/en/chapter_sorting/counting_sort/index.html +++ b/en/chapter_sorting/counting_sort/index.html @@ -4580,78 +4580,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/exercises/index.html b/en/chapter_sorting/exercises/index.html index b0ff4201b..928c6cf54 100644 --- a/en/chapter_sorting/exercises/index.html +++ b/en/chapter_sorting/exercises/index.html @@ -4625,78 +4625,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/heap_sort/index.html b/en/chapter_sorting/heap_sort/index.html index 4ef1533bf..2ea18e594 100644 --- a/en/chapter_sorting/heap_sort/index.html +++ b/en/chapter_sorting/heap_sort/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/index.html b/en/chapter_sorting/index.html index e084d41bc..86dfadd0b 100644 --- a/en/chapter_sorting/index.html +++ b/en/chapter_sorting/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/insertion_sort/index.html b/en/chapter_sorting/insertion_sort/index.html index 540cf991d..8c612d37f 100644 --- a/en/chapter_sorting/insertion_sort/index.html +++ b/en/chapter_sorting/insertion_sort/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/merge_sort/index.html b/en/chapter_sorting/merge_sort/index.html index 440de4e71..1d9eb2d93 100644 --- a/en/chapter_sorting/merge_sort/index.html +++ b/en/chapter_sorting/merge_sort/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/quick_sort/index.html b/en/chapter_sorting/quick_sort/index.html index 2a82d1ece..5e2707933 100644 --- a/en/chapter_sorting/quick_sort/index.html +++ b/en/chapter_sorting/quick_sort/index.html @@ -4591,78 +4591,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/radix_sort/index.html b/en/chapter_sorting/radix_sort/index.html index 86deae91e..f961dadee 100644 --- a/en/chapter_sorting/radix_sort/index.html +++ b/en/chapter_sorting/radix_sort/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/selection_sort/index.html b/en/chapter_sorting/selection_sort/index.html index 80e338e92..a2578aa06 100644 --- a/en/chapter_sorting/selection_sort/index.html +++ b/en/chapter_sorting/selection_sort/index.html @@ -4547,78 +4547,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/sorting_algorithm/index.html b/en/chapter_sorting/sorting_algorithm/index.html index 39e5dd445..fd065960a 100644 --- a/en/chapter_sorting/sorting_algorithm/index.html +++ b/en/chapter_sorting/sorting_algorithm/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_sorting/summary/index.html b/en/chapter_sorting/summary/index.html index 61001ca97..fadccb305 100644 --- a/en/chapter_sorting/summary/index.html +++ b/en/chapter_sorting/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_stack_and_queue/deque/index.html b/en/chapter_stack_and_queue/deque/index.html index 7480f768c..b70bd24aa 100644 --- a/en/chapter_stack_and_queue/deque/index.html +++ b/en/chapter_stack_and_queue/deque/index.html @@ -4597,78 +4597,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_stack_and_queue/exercises/index.html b/en/chapter_stack_and_queue/exercises/index.html index 511a87a2b..63e6389b6 100644 --- a/en/chapter_stack_and_queue/exercises/index.html +++ b/en/chapter_stack_and_queue/exercises/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_stack_and_queue/index.html b/en/chapter_stack_and_queue/index.html index 44e03cc0b..078054630 100644 --- a/en/chapter_stack_and_queue/index.html +++ b/en/chapter_stack_and_queue/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_stack_and_queue/queue/index.html b/en/chapter_stack_and_queue/queue/index.html index 6d22a2c30..1ec83fd30 100644 --- a/en/chapter_stack_and_queue/queue/index.html +++ b/en/chapter_stack_and_queue/queue/index.html @@ -4597,78 +4597,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_stack_and_queue/stack/index.html b/en/chapter_stack_and_queue/stack/index.html index cf8d1267c..700e81c42 100644 --- a/en/chapter_stack_and_queue/stack/index.html +++ b/en/chapter_stack_and_queue/stack/index.html @@ -4608,78 +4608,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_stack_and_queue/summary/index.html b/en/chapter_stack_and_queue/summary/index.html index f781514f1..dba15e747 100644 --- a/en/chapter_stack_and_queue/summary/index.html +++ b/en/chapter_stack_and_queue/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_tree/array_representation_of_tree/index.html b/en/chapter_tree/array_representation_of_tree/index.html index 1347bf075..881834764 100644 --- a/en/chapter_tree/array_representation_of_tree/index.html +++ b/en/chapter_tree/array_representation_of_tree/index.html @@ -4569,78 +4569,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_tree/avl_tree/index.html b/en/chapter_tree/avl_tree/index.html index 11fb6ca74..edcc1e39d 100644 --- a/en/chapter_tree/avl_tree/index.html +++ b/en/chapter_tree/avl_tree/index.html @@ -4708,78 +4708,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_tree/binary_search_tree/index.html b/en/chapter_tree/binary_search_tree/index.html index eaee6c9a9..5bc3e741e 100644 --- a/en/chapter_tree/binary_search_tree/index.html +++ b/en/chapter_tree/binary_search_tree/index.html @@ -4619,78 +4619,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_tree/binary_tree/index.html b/en/chapter_tree/binary_tree/index.html index a19aff22d..b4174b60d 100644 --- a/en/chapter_tree/binary_tree/index.html +++ b/en/chapter_tree/binary_tree/index.html @@ -4658,78 +4658,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_tree/binary_tree_traversal/index.html b/en/chapter_tree/binary_tree_traversal/index.html index 97f7eed21..341445250 100644 --- a/en/chapter_tree/binary_tree_traversal/index.html +++ b/en/chapter_tree/binary_tree_traversal/index.html @@ -4614,78 +4614,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_tree/exercises/index.html b/en/chapter_tree/exercises/index.html index 992283474..0d3638df8 100644 --- a/en/chapter_tree/exercises/index.html +++ b/en/chapter_tree/exercises/index.html @@ -4636,78 +4636,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_tree/index.html b/en/chapter_tree/index.html index 55c5ce7d5..444ea0014 100644 --- a/en/chapter_tree/index.html +++ b/en/chapter_tree/index.html @@ -4489,78 +4489,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/chapter_tree/summary/index.html b/en/chapter_tree/summary/index.html index 3861b0e99..b0e5f993f 100644 --- a/en/chapter_tree/summary/index.html +++ b/en/chapter_tree/summary/index.html @@ -4558,78 +4558,6 @@ - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - -
  • - - - diff --git a/en/index.html b/en/index.html index 312efc8f7..6b8078368 100644 --- a/en/index.html +++ b/en/index.html @@ -261,22 +261,6 @@
    -
    @@ -1835,31 +1819,6 @@ -
  • - - - -
  • diff --git a/en/javascripts/animation_player.js b/en/javascripts/animation_player.js index f7d562d25..83ba15a53 100644 --- a/en/javascripts/animation_player.js +++ b/en/javascripts/animation_player.js @@ -301,4 +301,4 @@ initAutoSlide(); } })(); -/*! update cache: 20261030193205 */ +/*! update cache: 20261030195404 */ diff --git a/en/javascripts/katex.js b/en/javascripts/katex.js index bae96d480..272265ed7 100644 --- a/en/javascripts/katex.js +++ b/en/javascripts/katex.js @@ -8,4 +8,4 @@ document$.subscribe(({ body }) => { ], }); }); -/*! update cache: 20261030193205 */ +/*! update cache: 20261030195404 */ diff --git a/en/javascripts/mathjax.js b/en/javascripts/mathjax.js index 316903eb4..cddc5a1c5 100644 --- a/en/javascripts/mathjax.js +++ b/en/javascripts/mathjax.js @@ -15,4 +15,4 @@ window.MathJax = { document$.subscribe(() => { MathJax.typesetPromise(); }); -/*! update cache: 20261030193205 */ +/*! update cache: 20261030195404 */ diff --git a/en/javascripts/starfield.js b/en/javascripts/starfield.js index bd3614fd1..484ebd814 100644 --- a/en/javascripts/starfield.js +++ b/en/javascripts/starfield.js @@ -469,4 +469,4 @@ return Starfield; }); -/*! update cache: 20261030193205 */ +/*! update cache: 20261030195404 */ diff --git a/en/search.json b/en/search.json index c834b4677..c01185398 100644 --- a/en/search.json +++ b/en/search.json @@ -1 +1 @@ -{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"chapter_appendix/","level":1,"title":"Chapter 16.   Appendix","text":"","path":["Chapter 16.   Appendix"],"tags":[]},{"location":"chapter_appendix/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 16.1   Setting up a programming environment
    • 16.2   Contribute to the book
    • 16.3   Glossary
    ","path":["Chapter 16.   Appendix"],"tags":[]},{"location":"chapter_appendix/contribution/","level":1,"title":"16.2   Contribute to the book","text":"

    Despite the author's best efforts, this book may contain omissions and errors. Thank you for your understanding. If you find typos, broken links, missing content, ambiguous wording, unclear explanations, or poorly organized passages, please help us correct them so that we can provide a better learning resource.

    To thank everyone for their generous contributions to the open-source community, the GitHub IDs of all contributors are displayed on the home pages of the book's repository, website, and PDF edition.

    The beauty of open source

    Printed books often have long intervals between print runs, making their content difficult to update.

    In this open-source book, updates can be made within days or even hours.

    ","path":["Chapter 16.   Appendix","16.2   Contribute to the book"],"tags":[]},{"location":"chapter_appendix/contribution/#1-small-edits","level":3,"title":"1.   Small edits","text":"

    As shown in Figure 16-3, each page has an edit icon in its upper-right corner. Follow these steps to edit text or code.

    1. Click the edit icon. If you are prompted to fork the repository, agree to do so.
    2. Edit the Markdown source file, check that the content is correct, and keep the formatting as consistent as possible.
    3. Enter a description of your changes at the bottom of the page, then click \"Propose file change\". On the next page, click \"Create pull request\" to submit your pull request.

    Figure 16-3   Page edit button

    Images cannot be edited directly. Please describe the problem in a new issue or a comment, and we will redraw and replace the image as soon as possible.

    ","path":["Chapter 16.   Appendix","16.2   Contribute to the book"],"tags":[]},{"location":"chapter_appendix/contribution/#2-creating-content","level":3,"title":"2.   Creating content","text":"

    If you would like to contribute to this open-source project, for example by translating code into another programming language or expanding an article, follow this pull request workflow.

    1. Sign in to GitHub and fork the book's code repository to your own account.
    2. Open your fork's repository page and use git clone to clone it to your computer.
    3. Create your content locally and test it thoroughly to verify that the code is correct.
    4. Commit your local changes, then push them to your remote repository.
    5. Refresh the repository page and click \"Create pull request\" to submit your pull request.
    ","path":["Chapter 16.   Appendix","16.2   Contribute to the book"],"tags":[]},{"location":"chapter_appendix/contribution/#3-deploying-with-docker","level":3,"title":"3.   Deploying with Docker","text":"

    Run the following Docker command in the hello-algo root directory to access the English edition at http://localhost:8000/en/:

    docker-compose up -d\n

    Use the following command to remove the deployment:

    docker-compose down\n
    ","path":["Chapter 16.   Appendix","16.2   Contribute to the book"],"tags":[]},{"location":"chapter_appendix/installation/","level":1,"title":"16.1   Setting up a programming environment","text":"","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#1611-install-an-ide","level":2,"title":"16.1.1   Install an IDE","text":"

    We recommend VS Code, a lightweight, open-source integrated development environment (IDE). Visit the VS Code website, then download and install the version for your operating system.

    Figure 16-1   Downloading VS Code from the official website

    VS Code has an extensive ecosystem of extensions that support running and debugging code in most programming languages. For example, installing the Python Extension Pack lets you debug Python code. The installation steps are shown in Figure 16-2.

    Figure 16-2   Installing VS Code extensions

    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#1612-install-a-language-environment","level":2,"title":"16.1.2   Install a language environment","text":"","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#1-python","level":3,"title":"1.   Python","text":"
    1. Download and install Miniconda3. Python 3.10 or later is required.
    2. Search for python in the VS Code Extensions Marketplace and install Python Extension Pack.
    3. (Optional) Run pip install black in a terminal to install a code formatter.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#2-cc","level":3,"title":"2.   C/C++","text":"
    1. On Windows, install MinGW (setup guide). macOS comes with Clang, so no installation is needed.
    2. Search for c++ in the VS Code Extensions Marketplace and install C/C++ Extension Pack.
    3. (Optional) Open Settings, search for the Clang_format_fallback Style formatting option, and set it to { BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#3-java","level":3,"title":"3.   Java","text":"
    1. Download and install OpenJDK (a version later than JDK 9 is required).
    2. Search for java in the VS Code Extensions Marketplace and install Extension Pack for Java.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#4-c","level":3,"title":"4.   C#","text":"
    1. Download and install .NET 8.0.
    2. Search for C# Dev Kit in the VS Code Extensions Marketplace and install C# Dev Kit (setup guide).
    3. You can also use Visual Studio (installation guide).
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#5-go","level":3,"title":"5.   Go","text":"
    1. Download and install Go.
    2. Search for go in the VS Code Extensions Marketplace and install Go.
    3. Press Ctrl + Shift + P to open the Command Palette, enter go, select Go: Install/Update Tools, then select and install all the tools.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#6-swift","level":3,"title":"6.   Swift","text":"
    1. Download and install Swift.
    2. Search for swift in the VS Code Extensions Marketplace and install Swift for Visual Studio Code.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#7-javascript","level":3,"title":"7.   JavaScript","text":"
    1. Download and install Node.js.
    2. (Optional) Search for Prettier in the VS Code Extensions Marketplace and install this code formatter.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#8-typescript","level":3,"title":"8.   TypeScript","text":"
    1. Follow the JavaScript environment setup steps.
    2. Install TypeScript Execute (tsx).
    3. Search for typescript in the VS Code Extensions Marketplace and install Pretty TypeScript Errors.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#9-dart","level":3,"title":"9.   Dart","text":"
    1. Download and install Dart.
    2. Search for dart in the VS Code Extensions Marketplace and install Dart.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#10-rust","level":3,"title":"10.   Rust","text":"
    1. Download and install Rust.
    2. Search for rust in the VS Code Extensions Marketplace and install rust-analyzer.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/terminology/","level":1,"title":"16.3   Glossary","text":"

    Table 16-1 lists important terms used in this book. Familiarity with their English names will help when reading technical literature in English.

    Table 16-1   Key terms in data structures and algorithms

    English Simplified Chinese algorithm 算法 data structure 数据结构 code 代码 file 文件 function 函数 method 方法 variable 变量 asymptotic complexity analysis 渐近复杂度分析 time complexity 时间复杂度 space complexity 空间复杂度 loop 循环 iteration 迭代 recursion 递归 tail recursion 尾递归 recursion tree 递归树 big-\\(O\\) notation 大 \\(O\\) 记号 asymptotic upper bound 渐近上界 sign-magnitude 原码 one's complement 反码 two's complement 补码 array 数组 index 索引 linked list 链表 linked list node, list node 链表节点 head node 头节点 tail node 尾节点 list 列表 dynamic array 动态数组 hard disk 硬盘 random-access memory (RAM) 内存 cache memory 缓存 cache miss 缓存未命中 cache hit rate 缓存命中率 stack 栈 top of the stack 栈顶 bottom of the stack 栈底 queue 队列 double-ended queue 双向队列 front of the queue 队首 rear of the queue 队尾 hash table 哈希表 hash set 哈希集合 bucket 桶 hash function 哈希函数 hash collision 哈希冲突 load factor 负载因子 separate chaining 链式地址 open addressing 开放寻址 linear probing 线性探测 lazy deletion 懒删除 binary tree 二叉树 tree node 树节点 left-child node 左子节点 right-child node 右子节点 parent node 父节点 left subtree 左子树 right subtree 右子树 root node 根节点 leaf node 叶节点 edge 边 level 层 degree 度 height 高度 depth 深度 perfect binary tree 完美二叉树 complete binary tree 完全二叉树 full binary tree 完满二叉树 balanced binary tree 平衡二叉树 binary search tree 二叉搜索树 AVL tree AVL 树 red-black tree 红黑树 level-order traversal 层序遍历 breadth-first traversal 广度优先遍历 depth-first traversal 深度优先遍历 preorder traversal 前序遍历 inorder traversal 中序遍历 postorder traversal 后序遍历 balanced binary search tree 平衡二叉搜索树 balance factor 平衡因子 heap 堆 max heap 大顶堆 min heap 小顶堆 priority queue 优先队列 heapify 堆化 top-\\(k\\) problem Top-\\(k\\) 问题 graph 图 vertex 顶点 undirected graph 无向图 directed graph 有向图 connected graph 连通图 disconnected graph 非连通图 weighted graph 有权图 adjacency 邻接 path 路径 in-degree 入度 out-degree 出度 adjacency matrix 邻接矩阵 adjacency list 邻接表 breadth-first search 广度优先搜索 depth-first search 深度优先搜索 binary search 二分查找 searching algorithm 搜索算法 sorting algorithm 排序算法 selection sort 选择排序 bubble sort 冒泡排序 insertion sort 插入排序 quick sort 快速排序 merge sort 归并排序 heap sort 堆排序 bucket sort 桶排序 counting sort 计数排序 radix sort 基数排序 divide and conquer 分治 Tower of Hanoi problem 汉诺塔问题 backtracking algorithm 回溯算法 constraint 约束 solution 解 state 状态 pruning 剪枝 permutations problem 全排列问题 subset-sum problem 子集和问题 \\(n\\)-queens problem \\(n\\) 皇后问题 dynamic programming 动态规划 initial state 初始状态 state transition equation 状态转移方程 knapsack problem 背包问题 edit distance problem 编辑距离问题 greedy algorithm 贪心算法","path":["Chapter 16.   Appendix","16.3   Glossary"],"tags":[]},{"location":"chapter_array_and_linkedlist/","level":1,"title":"Chapter 4.   Arrays and linked lists","text":"

    Abstract

    The world of data structures is like a solid brick wall.

    In an array, the bricks line up neatly, each right beside the next. In a linked list, the bricks are scattered, with connecting vines weaving freely through the gaps.

    ","path":["Chapter 4.   Arrays and linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 4.1   Arrays
    • 4.2   Linked lists
    • 4.3   Lists
    • 4.4   Memory and cache *
    • 4.5   Summary
    • 4.6   Exercises
    ","path":["Chapter 4.   Arrays and linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/","level":1,"title":"4.1   Arrays","text":"

    An array is a linear data structure that stores elements of the same type in contiguous memory. An element's position in the array is its index. Figure 4-1 shows the main concepts and storage layout.

    Figure 4-1   Array definition and storage layout

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#411-common-array-operations","level":2,"title":"4.1.1   Common array operations","text":"","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#1-initializing-an-array","level":3,"title":"1.   Initializing an array","text":"

    We can initialize an array with or without explicit values. When values are not specified, most programming languages initialize the elements to \\(0\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    # Initialize arrays\narr: list[int] = [0] * 5  # [ 0, 0, 0, 0, 0 ]\nnums: list[int] = [1, 3, 2, 5, 4]\n
    array.cpp
    /* Initialize arrays */\n// Stored on the stack\nint arr[5] = {};\nint nums[5] = { 1, 3, 2, 5, 4 };\n// Stored on the heap (memory must be freed manually)\nint* arr1 = new int[5] {};\nint* nums1 = new int[5] { 1, 3, 2, 5, 4 };\n
    array.java
    /* Initialize arrays */\nint[] arr = new int[5]; // { 0, 0, 0, 0, 0 }\nint[] nums = { 1, 3, 2, 5, 4 };\n
    array.cs
    /* Initialize arrays */\nint[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ]\nint[] nums = [1, 3, 2, 5, 4];\n
    array.go
    /* Initialize arrays */\nvar arr [5]int\n// In Go, [5]int is an array with a specified length; []int is a slice\n// Go array lengths are determined at compile time and must be constants\n// To make extend() easier to implement, we treat slices as arrays below\nnums := []int{1, 3, 2, 5, 4}\n
    array.swift
    /* Initialize arrays */\nlet arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]\nlet nums = [1, 3, 2, 5, 4]\n
    array.js
    /* Initialize arrays */\nvar arr = new Array(5).fill(0);\nvar nums = [1, 3, 2, 5, 4];\n
    array.ts
    /* Initialize arrays */\nlet arr: number[] = new Array(5).fill(0);\nlet nums: number[] = [1, 3, 2, 5, 4];\n
    array.dart
    /* Initialize arrays */\nList<int> arr = List.filled(5, 0); // [0, 0, 0, 0, 0]\nList<int> nums = [1, 3, 2, 5, 4];\n
    array.rs
    /* Initialize arrays */\nlet arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0]\nlet slice: &[i32] = &[0; 5];\n// In Rust, [i32; 5] is an array with a specified length; &[i32] is a slice\n// Rust array lengths are determined at compile time and must be constants\n// Vector is Rust's usual dynamic array type\n// To make extend() easier to implement, we treat a vector as an array below\nlet nums: Vec<i32> = vec![1, 3, 2, 5, 4];\n
    array.c
    /* Initialize arrays */\nint arr[5] = { 0 }; // { 0, 0, 0, 0, 0 }\nint nums[5] = { 1, 3, 2, 5, 4 };\n
    array.kt
    /* Initialize arrays */\nvar arr = IntArray(5) // { 0, 0, 0, 0, 0 }\nvar nums = intArrayOf(1, 3, 2, 5, 4)\n
    array.rb
    # Initialize arrays\narr = Array.new(5, 0)\nnums = [1, 3, 2, 5, 4]\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#2-accessing-an-element","level":3,"title":"2.   Accessing an element","text":"

    Because array elements occupy contiguous memory, their addresses are easy to calculate. Given the array's address (the address of its first element) and an index, the formula in Figure 4-2 gives the element's address, allowing direct access.

    Figure 4-2   Calculating an array element's memory address

    The first element has index \\(0\\), which may seem unintuitive because counting from \\(1\\) feels more natural. However, an index essentially describes an offset from the starting memory address. The first element's offset is \\(0\\), so index \\(0\\) makes sense.

    Array access is very efficient: any element can be accessed directly in \\(O(1)\\) time.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def random_access(nums: list[int]) -> int:\n    \"\"\"Access an element at a random index\"\"\"\n    # Choose a random number in the range [0, len(nums)-1]\n    random_index = random.randint(0, len(nums) - 1)\n    # Get and return the randomly selected element\n    random_num = nums[random_index]\n    return random_num\n
    array.cpp
    /* Access an element at a random index */\nint randomAccess(int *nums, int size) {\n    // Choose a random number in [0, size)\n    int randomIndex = rand() % size;\n    // Get and return the randomly selected element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
    array.java
    /* Access an element at a random index */\nint randomAccess(int[] nums) {\n    // Choose a random number in the interval [0, nums.length)\n    int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length);\n    // Get and return the randomly selected element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
    array.cs
    /* Access an element at a random index */\nint RandomAccess(int[] nums) {\n    Random random = new();\n    // Randomly select a number from [0, nums.Length)\n    int randomIndex = random.Next(nums.Length);\n    // Get and return the randomly selected element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
    array.go
    /* Access an element at a random index */\nfunc randomAccess(nums []int) (randomNum int) {\n    // Choose a random number in the interval [0, len(nums))\n    randomIndex := rand.Intn(len(nums))\n    // Get and return the randomly selected element\n    randomNum = nums[randomIndex]\n    return\n}\n
    array.swift
    /* Access an element at a random index */\nfunc randomAccess(nums: [Int]) -> Int {\n    // Pick a random number in the interval [0, nums.count)\n    let randomIndex = nums.indices.randomElement()!\n    // Get and return the randomly selected element\n    let randomNum = nums[randomIndex]\n    return randomNum\n}\n
    array.js
    /* Access an element at a random index */\nfunction randomAccess(nums) {\n    // Choose a random index in the interval [0, nums.length)\n    const random_index = Math.floor(Math.random() * nums.length);\n    // Get and return the randomly selected element\n    const random_num = nums[random_index];\n    return random_num;\n}\n
    array.ts
    /* Access an element at a random index */\nfunction randomAccess(nums: number[]): number {\n    // Choose a random index in the interval [0, nums.length)\n    const random_index = Math.floor(Math.random() * nums.length);\n    // Get and return the randomly selected element\n    const random_num = nums[random_index];\n    return random_num;\n}\n
    array.dart
    /* Access an element at a random index */\nint randomAccess(List<int> nums) {\n  // Choose a random index in the interval [0, nums.length)\n  int randomIndex = Random().nextInt(nums.length);\n  // Get and return the randomly selected element\n  int randomNum = nums[randomIndex];\n  return randomNum;\n}\n
    array.rs
    /* Access an element at a random index */\nfn random_access(nums: &[i32]) -> i32 {\n    // Choose a random index in [0, nums.len())\n    let random_index = rand::thread_rng().gen_range(0..nums.len());\n    // Get and return the randomly selected element\n    let random_num = nums[random_index];\n    random_num\n}\n
    array.c
    /* Access an element at a random index */\nint randomAccess(int *nums, int size) {\n    // Choose a random number in [0, size)\n    int randomIndex = rand() % size;\n    // Get and return the randomly selected element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
    array.kt
    /* Access an element at a random index */\nfun randomAccess(nums: IntArray): Int {\n    // Randomly select a number in [0, nums.size)\n    val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size)\n    // Get and return the randomly selected element\n    val randomNum = nums[randomIndex]\n    return randomNum\n}\n
    array.rb
    ### Access an element at a random index ###\ndef random_access(nums)\n  # Choose a random index in the interval [0, nums.length)\n  random_index = Random.rand(0...nums.length)\n\n  # Get and return the randomly selected element\n  nums[random_index]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#3-inserting-an-element","level":3,"title":"3.   Inserting an element","text":"

    Array elements sit next to one another, with no room between them for more data. As shown in Figure 4-3, inserting into the middle requires shifting every element at or after the insertion point one position toward the end, then assigning the new element at the desired index.

    Figure 4-3   Inserting an element into an array

    Because an array's length is fixed, inserting an element causes the last element to be lost. We will discuss the solution in the section on lists.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def insert(nums: list[int], num: int, index: int):\n    \"\"\"Insert element num at index `index` in the array\"\"\"\n    # Shift all elements at and after index `index` one position to the right\n    for i in range(len(nums) - 1, index, -1):\n        nums[i] = nums[i - 1]\n    # Assign num to the element at index `index`\n    nums[index] = num\n
    array.cpp
    /* Insert element num at index `index` in the array */\nvoid insert(int *nums, int size, int num, int index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (int i = size - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.java
    /* Insert element num at index `index` in the array */\nvoid insert(int[] nums, int num, int index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (int i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.cs
    /* Insert element num at index `index` in the array */\nvoid Insert(int[] nums, int num, int index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (int i = nums.Length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.go
    /* Insert element num at index `index` in the array */\nfunc insert(nums []int, num int, index int) {\n    // Shift all elements at and after index `index` one position to the right\n    for i := len(nums) - 1; i > index; i-- {\n        nums[i] = nums[i-1]\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num\n}\n
    array.swift
    /* Insert element num at index `index` in the array */\nfunc insert(nums: inout [Int], num: Int, index: Int) {\n    // Shift all elements at and after index `index` one position to the right\n    for i in stride(from: nums.count - 1, to: index, by: -1) {\n        nums[i] = nums[i - 1]\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num\n}\n
    array.js
    /* Insert element num at index `index` in the array */\nfunction insert(nums, num, index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (let i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.ts
    /* Insert element num at index `index` in the array */\nfunction insert(nums: number[], num: number, index: number): void {\n    // Shift all elements at and after index `index` one position to the right\n    for (let i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.dart
    /* Insert element _num at index in the array */\nvoid insert(List<int> nums, int _num, int index) {\n  // Shift all elements at and after index `index` one position to the right\n  for (var i = nums.length - 1; i > index; i--) {\n    nums[i] = nums[i - 1];\n  }\n  // Assign _num to the element at index\n  nums[index] = _num;\n}\n
    array.rs
    /* Insert element num at index `index` in the array */\nfn insert(nums: &mut [i32], num: i32, index: usize) {\n    // Shift all elements at and after index `index` one position to the right\n    for i in (index + 1..nums.len()).rev() {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.c
    /* Insert element num at index `index` in the array */\nvoid insert(int *nums, int size, int num, int index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (int i = size - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.kt
    /* Insert element num at index `index` in the array */\nfun insert(nums: IntArray, num: Int, index: Int) {\n    // Shift all elements at and after index `index` one position to the right\n    for (i in nums.size - 1 downTo index + 1) {\n        nums[i] = nums[i - 1]\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num\n}\n
    array.rb
    ### Insert element num at index `index` in the array ###\ndef insert(nums, num, index)\n  # Shift all elements at and after index `index` one position to the right\n  for i in (nums.length - 1).downto(index + 1)\n    nums[i] = nums[i - 1]\n  end\n\n  # Assign num to the element at index `index`\n  nums[index] = num\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#4-removing-an-element","level":3,"title":"4.   Removing an element","text":"

    Similarly, as shown in Figure 4-4, removing the element at index \\(i\\) requires shifting every element after \\(i\\) one position toward the beginning.

    Figure 4-4   Removing an element from an array

    After removal, the old final entry is no longer meaningful, so we do not need to change it explicitly.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def remove(nums: list[int], index: int):\n    \"\"\"Remove the element at index `index`\"\"\"\n    # Shift all elements after index `index` one position to the left\n    for i in range(index, len(nums) - 1):\n        nums[i] = nums[i + 1]\n
    array.cpp
    /* Remove the element at index `index` */\nvoid remove(int *nums, int size, int index) {\n    // Shift all elements after index `index` one position to the left\n    for (int i = index; i < size - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.java
    /* Remove the element at index `index` */\nvoid remove(int[] nums, int index) {\n    // Shift all elements after index `index` one position to the left\n    for (int i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.cs
    /* Remove the element at index `index` */\nvoid Remove(int[] nums, int index) {\n    // Shift all elements after index `index` one position to the left\n    for (int i = index; i < nums.Length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.go
    /* Remove the element at index `index` */\nfunc remove(nums []int, index int) {\n    // Shift all elements after index `index` one position to the left\n    for i := index; i < len(nums)-1; i++ {\n        nums[i] = nums[i+1]\n    }\n}\n
    array.swift
    /* Remove the element at index `index` */\nfunc remove(nums: inout [Int], index: Int) {\n    // Shift all elements after index `index` one position to the left\n    for i in nums.indices.dropFirst(index).dropLast() {\n        nums[i] = nums[i + 1]\n    }\n}\n
    array.js
    /* Remove the element at index `index` */\nfunction remove(nums, index) {\n    // Shift all elements after index `index` one position to the left\n    for (let i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.ts
    /* Remove the element at index `index` */\nfunction remove(nums: number[], index: number): void {\n    // Shift all elements after index `index` one position to the left\n    for (let i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.dart
    /* Remove the element at index `index` */\nvoid remove(List<int> nums, int index) {\n  // Shift all elements after index `index` one position to the left\n  for (var i = index; i < nums.length - 1; i++) {\n    nums[i] = nums[i + 1];\n  }\n}\n
    array.rs
    /* Remove the element at index `index` */\nfn remove(nums: &mut [i32], index: usize) {\n    // Shift all elements after index `index` one position to the left\n    for i in index..nums.len() - 1 {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.c
    /* Remove the element at index `index` */\n// Note: stdio.h already declares a function named remove\nvoid removeItem(int *nums, int size, int index) {\n    // Shift all elements after index `index` one position to the left\n    for (int i = index; i < size - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.kt
    /* Remove the element at index `index` */\nfun remove(nums: IntArray, index: Int) {\n    // Shift all elements after index `index` one position to the left\n    for (i in index..<nums.size - 1) {\n        nums[i] = nums[i + 1]\n    }\n}\n
    array.rb
    ### Remove the element at index `index` ###\ndef remove(nums, index)\n  # Shift all elements after index `index` one position to the left\n  for i in index...(nums.length - 1)\n    nums[i] = nums[i + 1]\n  end\nend\n
    Visualize execution

    View full screen >

    Overall, inserting and removing array elements have these drawbacks.

    • High time complexity: Both operations take \\(O(n)\\) time on average, where \\(n\\) is the array length.
    • Lost elements: Since the length is fixed, insertion loses any element pushed beyond the array's bounds.
    • Wasted memory: We can allocate a longer array and use only its beginning, so any final entries lost during insertion are unused. However, this leaves some memory wasted.
    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#5-traversing-an-array","level":3,"title":"5.   Traversing an array","text":"

    Most programming languages let us traverse an array either by index or by visiting its elements directly:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def traverse(nums: list[int]):\n    \"\"\"Traverse the array\"\"\"\n    count = 0\n    # Traverse the array by index\n    for i in range(len(nums)):\n        count += nums[i]\n    # Iterate directly over the array elements\n    for num in nums:\n        count += num\n    # Iterate over both the indices and the elements\n    for i, num in enumerate(nums):\n        count += nums[i]\n        count += num\n
    array.cpp
    /* Traverse the array */\nvoid traverse(int *nums, int size) {\n    int count = 0;\n    // Traverse the array by index\n    for (int i = 0; i < size; i++) {\n        count += nums[i];\n    }\n}\n
    array.java
    /* Traverse the array */\nvoid traverse(int[] nums) {\n    int count = 0;\n    // Traverse the array by index\n    for (int i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Iterate directly over the array elements\n    for (int num : nums) {\n        count += num;\n    }\n}\n
    array.cs
    /* Traverse the array */\nvoid Traverse(int[] nums) {\n    int count = 0;\n    // Traverse the array by index\n    for (int i = 0; i < nums.Length; i++) {\n        count += nums[i];\n    }\n    // Iterate directly over the array elements\n    foreach (int num in nums) {\n        count += num;\n    }\n}\n
    array.go
    /* Traverse the array */\nfunc traverse(nums []int) {\n    count := 0\n    // Traverse the array by index\n    for i := 0; i < len(nums); i++ {\n        count += nums[i]\n    }\n    count = 0\n    // Iterate directly over the array elements\n    for _, num := range nums {\n        count += num\n    }\n    // Iterate over both the indices and the elements\n    for i, num := range nums {\n        count += nums[i]\n        count += num\n    }\n}\n
    array.swift
    /* Traverse the array */\nfunc traverse(nums: [Int]) {\n    var count = 0\n    // Traverse the array by index\n    for i in nums.indices {\n        count += nums[i]\n    }\n    // Iterate directly over the array elements\n    for num in nums {\n        count += num\n    }\n    // Iterate over both the indices and the elements\n    for (i, num) in nums.enumerated() {\n        count += nums[i]\n        count += num\n    }\n}\n
    array.js
    /* Traverse the array */\nfunction traverse(nums) {\n    let count = 0;\n    // Traverse the array by index\n    for (let i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Iterate directly over the array elements\n    for (const num of nums) {\n        count += num;\n    }\n}\n
    array.ts
    /* Traverse the array */\nfunction traverse(nums: number[]): void {\n    let count = 0;\n    // Traverse the array by index\n    for (let i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Iterate directly over the array elements\n    for (const num of nums) {\n        count += num;\n    }\n}\n
    array.dart
    /* Traverse the array elements */\nvoid traverse(List<int> nums) {\n  int count = 0;\n  // Traverse the array by index\n  for (var i = 0; i < nums.length; i++) {\n    count += nums[i];\n  }\n  // Iterate directly over the array elements\n  for (int _num in nums) {\n    count += _num;\n  }\n  // Traverse the array using forEach\n  nums.forEach((_num) {\n    count += _num;\n  });\n}\n
    array.rs
    /* Traverse the array */\nfn traverse(nums: &[i32]) {\n    let mut _count = 0;\n    // Traverse the array by index\n    for i in 0..nums.len() {\n        _count += nums[i];\n    }\n    // Iterate directly over the array elements\n    _count = 0;\n    for &num in nums {\n        _count += num;\n    }\n}\n
    array.c
    /* Traverse the array */\nvoid traverse(int *nums, int size) {\n    int count = 0;\n    // Traverse the array by index\n    for (int i = 0; i < size; i++) {\n        count += nums[i];\n    }\n}\n
    array.kt
    /* Traverse the array */\nfun traverse(nums: IntArray) {\n    var count = 0\n    // Traverse the array by index\n    for (i in nums.indices) {\n        count += nums[i]\n    }\n    // Iterate directly over the array elements\n    for (j in nums) {\n        count += j\n    }\n}\n
    array.rb
    ### Traverse the array ###\ndef traverse(nums)\n  count = 0\n\n  # Traverse the array by index\n  for i in 0...nums.length\n    count += nums[i]\n  end\n\n  # Iterate directly over the array elements\n  for num in nums\n    count += num\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#6-finding-an-element","level":3,"title":"6.   Finding an element","text":"

    To find a specified element, traverse the array and compare each value with the target. Return the index when a match is found.

    Because an array is a linear data structure, this is called linear search.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def find(nums: list[int], target: int) -> int:\n    \"\"\"Find a specified element in the array\"\"\"\n    for i in range(len(nums)):\n        if nums[i] == target:\n            return i\n    return -1\n
    array.cpp
    /* Find a specified element in the array */\nint find(int *nums, int size, int target) {\n    for (int i = 0; i < size; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
    array.java
    /* Find a specified element in the array */\nint find(int[] nums, int target) {\n    for (int i = 0; i < nums.length; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
    array.cs
    /* Find a specified element in the array */\nint Find(int[] nums, int target) {\n    for (int i = 0; i < nums.Length; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
    array.go
    /* Find a specified element in the array */\nfunc find(nums []int, target int) (index int) {\n    index = -1\n    for i := 0; i < len(nums); i++ {\n        if nums[i] == target {\n            index = i\n            break\n        }\n    }\n    return\n}\n
    array.swift
    /* Find a specified element in the array */\nfunc find(nums: [Int], target: Int) -> Int {\n    for i in nums.indices {\n        if nums[i] == target {\n            return i\n        }\n    }\n    return -1\n}\n
    array.js
    /* Find a specified element in the array */\nfunction find(nums, target) {\n    for (let i = 0; i < nums.length; i++) {\n        if (nums[i] === target) return i;\n    }\n    return -1;\n}\n
    array.ts
    /* Find a specified element in the array */\nfunction find(nums: number[], target: number): number {\n    for (let i = 0; i < nums.length; i++) {\n        if (nums[i] === target) {\n            return i;\n        }\n    }\n    return -1;\n}\n
    array.dart
    /* Find a specified element in the array */\nint find(List<int> nums, int target) {\n  for (var i = 0; i < nums.length; i++) {\n    if (nums[i] == target) return i;\n  }\n  return -1;\n}\n
    array.rs
    /* Find a specified element in the array */\nfn find(nums: &[i32], target: i32) -> Option<usize> {\n    for i in 0..nums.len() {\n        if nums[i] == target {\n            return Some(i);\n        }\n    }\n    None\n}\n
    array.c
    /* Find a specified element in the array */\nint find(int *nums, int size, int target) {\n    for (int i = 0; i < size; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
    array.kt
    /* Find a specified element in the array */\nfun find(nums: IntArray, target: Int): Int {\n    for (i in nums.indices) {\n        if (nums[i] == target)\n            return i\n    }\n    return -1\n}\n
    array.rb
    ### Find a specified element in the array ###\ndef find(nums, target)\n  for i in 0...nums.length\n    return i if nums[i] == target\n  end\n\n  -1\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#7-expanding-an-array","level":3,"title":"7.   Expanding an array","text":"

    In a complex system, a program cannot ensure that memory immediately after an array is available, so extending it in place is unsafe. Therefore, in most programming languages, an array's length is fixed.

    To expand an array, create a larger one and copy every element into it. This takes \\(O(n)\\) time and can be expensive for a large array. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def extend(nums: list[int], enlarge: int) -> list[int]:\n    \"\"\"Extend the array length\"\"\"\n    # Initialize an array with the extended length\n    res = [0] * (len(nums) + enlarge)\n    # Copy all elements from the original array to the new array\n    for i in range(len(nums)):\n        res[i] = nums[i]\n    # Return the new, extended array\n    return res\n
    array.cpp
    /* Extend the array length */\nint *extend(int *nums, int size, int enlarge) {\n    // Initialize an array with the extended length\n    int *res = new int[size + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < size; i++) {\n        res[i] = nums[i];\n    }\n    // Free memory\n    delete[] nums;\n    // Return the new, extended array\n    return res;\n}\n
    array.java
    /* Extend the array length */\nint[] extend(int[] nums, int enlarge) {\n    // Initialize an array with the extended length\n    int[] res = new int[nums.length + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.cs
    /* Extend the array length */\nint[] Extend(int[] nums, int enlarge) {\n    // Initialize an array with the extended length\n    int[] res = new int[nums.Length + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < nums.Length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.go
    /* Extend the array length */\nfunc extend(nums []int, enlarge int) []int {\n    // Initialize an array with the extended length\n    res := make([]int, len(nums)+enlarge)\n    // Copy all elements from the original array to the new array\n    for i, num := range nums {\n        res[i] = num\n    }\n    // Return the new, extended array\n    return res\n}\n
    array.swift
    /* Extend the array length */\nfunc extend(nums: [Int], enlarge: Int) -> [Int] {\n    // Initialize an array with the extended length\n    var res = Array(repeating: 0, count: nums.count + enlarge)\n    // Copy all elements from the original array to the new array\n    for i in nums.indices {\n        res[i] = nums[i]\n    }\n    // Return the new, extended array\n    return res\n}\n
    array.js
    /* Extend the array length */\n// JavaScript Array is a dynamic array and can be extended directly\n// For learning purposes, this function treats Array as a fixed-length array\nfunction extend(nums, enlarge) {\n    // Initialize an array with the extended length\n    const res = new Array(nums.length + enlarge).fill(0);\n    // Copy all elements from the original array to the new array\n    for (let i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.ts
    /* Extend the array length */\n// Note: TypeScript Array is a dynamic array and can grow directly\n// For learning purposes, this function treats Array as a fixed-length array\nfunction extend(nums: number[], enlarge: number): number[] {\n    // Initialize an array with the extended length\n    const res = new Array(nums.length + enlarge).fill(0);\n    // Copy all elements from the original array to the new array\n    for (let i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.dart
    /* Extend the array length */\nList<int> extend(List<int> nums, int enlarge) {\n  // Initialize an array with the extended length\n  List<int> res = List.filled(nums.length + enlarge, 0);\n  // Copy all elements from the original array to the new array\n  for (var i = 0; i < nums.length; i++) {\n    res[i] = nums[i];\n  }\n  // Return the new, extended array\n  return res;\n}\n
    array.rs
    /* Extend the array length */\nfn extend(nums: &[i32], enlarge: usize) -> Vec<i32> {\n    // Initialize an array with the extended length\n    let mut res: Vec<i32> = vec![0; nums.len() + enlarge];\n    // Copy all elements from the original array to the new one\n    res[0..nums.len()].copy_from_slice(nums);\n\n    // Return the new, extended array\n    res\n}\n
    array.c
    /* Extend the array length */\nint *extend(int *nums, int size, int enlarge) {\n    // Initialize an array with the extended length\n    int *res = (int *)malloc(sizeof(int) * (size + enlarge));\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < size; i++) {\n        res[i] = nums[i];\n    }\n    // Initialize the newly added space\n    for (int i = size; i < size + enlarge; i++) {\n        res[i] = 0;\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.kt
    /* Extend the array length */\nfun extend(nums: IntArray, enlarge: Int): IntArray {\n    // Initialize an array with the extended length\n    val res = IntArray(nums.size + enlarge)\n    // Copy all elements from the original array to the new array\n    for (i in nums.indices) {\n        res[i] = nums[i]\n    }\n    // Return the new, extended array\n    return res\n}\n
    array.rb
    ### Extend the array length ###\n# Ruby Array is a dynamic array that can grow directly\n# For learning purposes, this function treats Array as a fixed-length array\ndef extend(nums, enlarge)\n  # Initialize an array with the extended length\n  res = Array.new(nums.length + enlarge, 0)\n\n  # Copy all elements from the original array to the new array\n  for i in 0...nums.length\n    res[i] = nums[i]\n  end\n\n  # Return the new, extended array\n  res\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#412-strengths-and-limitations-of-arrays","level":2,"title":"4.1.2   Strengths and limitations of arrays","text":"

    An array stores elements of one type in contiguous memory. These known properties give the system opportunities to optimize operations.

    • Space efficiency: Data occupies a contiguous block with no extra structural overhead.
    • Random access: Any element can be accessed in \\(O(1)\\) time.
    • Cache locality: Accessing an element loads not only that element but also nearby data into the cache, speeding up later operations.

    Contiguous storage also has limitations.

    • Slow insertion and deletion: With many elements, these operations require substantial shifting.
    • Fixed length: Once initialized, an array cannot change length; expansion requires copying all data into a new array.
    • Wasted space: Any allocated space beyond actual needs is unused.
    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#413-typical-applications-of-arrays","level":2,"title":"4.1.3   Typical applications of arrays","text":"

    Arrays are fundamental, widely used data structures. They appear throughout algorithms and underpin more complex structures.

    • Random access: To draw random samples, store data in an array and generate random indices to select elements.
    • Sorting and searching: Arrays are the most common structures for these algorithms. Quick sort, merge sort, and binary search operate primarily on arrays.
    • Lookup tables: An array can provide fast access to an element or a mapping. For example, a character-to-ASCII mapping can use a character's ASCII value as an index and store its corresponding element there.
    • Machine learning: Neural networks make extensive use of linear algebra on vectors, matrices, and tensors, all built from arrays. Arrays are the most common data structure in neural network programming.
    • Implementing data structures: Arrays can implement stacks, queues, hash tables, heaps, graphs, and more. A graph's adjacency matrix, for example, is a two-dimensional array.
    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/","level":1,"title":"4.6   Exercises","text":"","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#461-check-your-understanding","level":2,"title":"4.6.1   Check your understanding","text":"","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#1-finding-elements-in-arrays-and-linked-lists","level":3,"title":"1.   Finding elements in arrays and linked lists","text":"

    An array and a singly linked list both store [A, B, C, D, E] in order. We want to read the fourth element, D:

    1. Which index can we use to access it directly in the array?
    2. Starting at the head node A, which nodes must the singly linked list visit by following next?
    3. As the requested element moves farther toward the end, how does the number of steps change for each structure? Which is better suited to repeated access by position, and why?
    Reference answer
    1. With zero-based indexing, the fourth element has index 3, so the array can read arr[3] directly.

    2. The singly linked list must start at the head and follow the path A → B → C → D, advancing along next three times.

    3. An array can locate an element directly from its starting address and the index, giving time complexity \\(O(1)\\) for access by position. To access the \\(k\\)-th node of a singly linked list, we must start at the head and follow next \\(k-1\\) times, taking \\(O(n)\\) time in the worst case.

      This comparison concerns access by position only; it does not mean that linked lists are slower for every operation.

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#2-inserting-elements-into-arrays-and-linked-lists","level":3,"title":"2.   Inserting elements into arrays and linked lists","text":"

    An array and a singly linked list both store A, B, C, D. We want to insert X after B:

    • The array has capacity 5 and currently contains [A, B, C, D, _].
    • The linked list is A → B → C → D, and we already have a reference to node B.
    1. Which elements must the array move? Write out the array after insertion.
    2. In what order should the linked list update X.next and B.next? Write out the resulting list.
    3. Why must a comparison of insertion efficiency specify that we already have a reference to node B?
    Reference answer
    1. First move D one position to the right, then move C one position to the right, and finally put X at index 2. The result is [A, B, X, C, D].

    2. Initially, B.next points to C. First set X.next = B.next, making X point to C. Then set B.next = X. The result is A → B → X → C → D. If we overwrite B.next first without saving the original link, we may lose access to C.

    3. Once the location of B is known, insertion into the linked list requires changing only two links and takes \\(O(1)\\) time. If we first need to search for B from the head, that search itself may take \\(O(n)\\) time.

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#3-how-does-a-lists-capacity-grow","level":3,"title":"3.   How does a list's capacity grow?","text":"

    An array-based list currently contains [A, B, C], with length size = 3 and capacity capacity = 4. When capacity runs out, the new array is allocated with twice the previous capacity.

    1. After appending D, what are the list's length and capacity? Does it need to grow?
    2. When E is appended next, what does the capacity become? How many existing elements must be copied?
    3. If the length of the underlying array cannot change, why does the list's capacity appear to grow?
    Reference answer
    1. D fits in the last empty position. The contents become [A, B, C, D], with size = 4 and capacity = 4. No capacity increase is needed.

    2. Appending E finds no empty position, so a new array of capacity 8 is created. The four existing elements A, B, C, D are copied into it, and then E is added. The list now has size = 5 and capacity = 8.

    3. The original array itself does not become longer. The list creates a larger array, copies the existing elements, and switches to using the new array for storage. From the user's perspective, its capacity has grown.

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#462-programming-exercises","level":2,"title":"4.6.2   Programming exercises","text":"","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#1-adding-one-to-a-large-integer-stored-in-an-array","level":3,"title":"1.   Adding one to a large integer stored in an array","text":"

    The array digits stores the digits of a nonnegative integer from left to right. For example, [3, 0, 8] represents 308. Zero is represented by [0]; all other inputs have a nonzero first digit.

    Simulate the usual digit-by-digit method for decimal addition to add 1 to this integer, and return the result in the same array format. You may modify digits directly. If a carry creates a new leading digit, you may return a longer array.

    Hints
    1. Start at the last digit of the array, just as you would when adding by hand.
    2. If the current digit is less than 9, increment it and return immediately.
    3. If the current digit is 9, change it to 0. If all digits are 9, add a leading 1.

    LeetCode

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#2-reversing-a-singly-linked-list","level":3,"title":"2.   Reversing a singly linked list","text":"

    You are given the head node head of a singly linked list. Each node contains a value and a next reference to the following node.

    Use iteration to reverse every link between nodes, and return the new head. Do not create any new linked-list nodes.

    Hints
    1. First draw three connected nodes and the two pointers prev and cur on paper.
    2. Before changing cur.next, save the original next node in nxt.
    3. After reversing cur.next, set prev = cur and then cur = nxt to continue with the next node in the original list.

    LeetCode

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/","level":1,"title":"4.2   Linked lists","text":"

    Memory is a resource used by all programs, and in a complex system, free space may be scattered throughout it. An array requires contiguous storage, which may be unavailable for a very large array. This is where a linked list's flexibility helps.

    A linked list is a linear data structure whose elements are node objects connected by references. Each reference stores the next node's memory address, allowing us to reach it from the current node.

    This design lets nodes occupy scattered memory locations; their addresses need not be contiguous.

    Figure 4-5   Linked list definition and storage layout

    As shown above, the basic unit of a linked list is a node object. Each node holds a value and a reference to the next node.

    • The first node is called the head, and the last is called the tail.
    • The tail points to a null value: null in Java, nullptr in C++, and None in Python.
    • In languages supporting pointers, such as C, C++, Go, and Rust, these references are expressed as pointers.

    As the code below shows, each ListNode stores a reference (pointer) in addition to its value. Thus, a linked list uses more memory than an array for the same amount of data.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class ListNode:\n    \"\"\"Linked list node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val               # Node value\n        self.next: ListNode | None = None # Reference to the next node\n
    /* Linked list node structure */\nstruct ListNode {\n    int val;         // Node value\n    ListNode *next;  // Pointer to the next node\n    ListNode(int x) : val(x), next(nullptr) {}  // Constructor\n};\n
    /* Linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode next;  // Reference to the next node\n    ListNode(int x) { val = x; }  // Constructor\n}\n
    /* Linked list node class */\nclass ListNode(int x) {  // Constructor\n    int val = x;         // Node value\n    ListNode? next;      // Reference to the next node\n}\n
    /* Linked list node structure */\ntype ListNode struct {\n    Val  int       // Node value\n    Next *ListNode // Pointer to the next node\n}\n\n// NewListNode constructor: create a new linked list\nfunc NewListNode(val int) *ListNode {\n    return &ListNode{\n        Val:  val,\n        Next: nil,\n    }\n}\n
    /* Linked list node class */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the next node\n\n    init(x: Int) { // Constructor\n        val = x\n    }\n}\n
    /* Linked list node class */\nclass ListNode {\n    constructor(val, next) {\n        this.val = (val === undefined ? 0 : val);       // Node value\n        this.next = (next === undefined ? null : next); // Reference to the next node\n    }\n}\n
    /* Linked list node class */\nclass ListNode {\n    val: number;\n    next: ListNode | null;\n    constructor(val?: number, next?: ListNode | null) {\n        this.val = val === undefined ? 0 : val;        // Node value\n        this.next = next === undefined ? null : next;  // Reference to the next node\n    }\n}\n
    /* Linked list node class */\nclass ListNode {\n  int val; // Node value\n  ListNode? next; // Reference to the next node\n  ListNode(this.val, [this.next]); // Constructor\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n/* Linked list node class */\n#[derive(Debug)]\nstruct ListNode {\n    val: i32, // Node value\n    next: Option<Rc<RefCell<ListNode>>>, // Pointer to the next node\n}\n
    /* Linked list node structure */\ntypedef struct ListNode {\n    int val;               // Node value\n    struct ListNode *next; // Pointer to the next node\n} ListNode;\n\n/* Constructor */\nListNode *newListNode(int val) {\n    ListNode *node;\n    node = (ListNode *) malloc(sizeof(ListNode));\n    node->val = val;\n    node->next = NULL;\n    return node;\n}\n
    /* Linked list node class */\n// Constructor\nclass ListNode(x: Int) {\n    val _val: Int = x          // Node value\n    var next: ListNode? = null // Reference to the next node\n}\n
    # Linked list node class\nclass ListNode\n  attr_accessor :val  # Node value\n  attr_accessor :next # Reference to the next node\n\n  def initialize(val=0, next_node=nil)\n    @val = val\n    @next = next_node\n  end\nend\n
    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#421-common-linked-list-operations","level":2,"title":"4.2.1   Common linked list operations","text":"","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#1-initializing-a-linked-list","level":3,"title":"1.   Initializing a linked list","text":"

    Building a linked list takes two steps: initialize the node objects, then connect them with references. Afterward, start at the head and follow next references to visit every node in order.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    # Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4\n# Initialize the nodes\nn0 = ListNode(1)\nn1 = ListNode(3)\nn2 = ListNode(2)\nn3 = ListNode(5)\nn4 = ListNode(4)\n# Link the nodes with references\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
    linked_list.cpp
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nListNode* n0 = new ListNode(1);\nListNode* n1 = new ListNode(3);\nListNode* n2 = new ListNode(2);\nListNode* n3 = new ListNode(5);\nListNode* n4 = new ListNode(4);\n// Link the nodes with references\nn0->next = n1;\nn1->next = n2;\nn2->next = n3;\nn3->next = n4;\n
    linked_list.java
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nListNode n0 = new ListNode(1);\nListNode n1 = new ListNode(3);\nListNode n2 = new ListNode(2);\nListNode n3 = new ListNode(5);\nListNode n4 = new ListNode(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.cs
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nListNode n0 = new(1);\nListNode n1 = new(3);\nListNode n2 = new(2);\nListNode n3 = new(5);\nListNode n4 = new(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.go
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nn0 := NewListNode(1)\nn1 := NewListNode(3)\nn2 := NewListNode(2)\nn3 := NewListNode(5)\nn4 := NewListNode(4)\n// Link the nodes with references\nn0.Next = n1\nn1.Next = n2\nn2.Next = n3\nn3.Next = n4\n
    linked_list.swift
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nlet n0 = ListNode(x: 1)\nlet n1 = ListNode(x: 3)\nlet n2 = ListNode(x: 2)\nlet n3 = ListNode(x: 5)\nlet n4 = ListNode(x: 4)\n// Link the nodes with references\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
    linked_list.js
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.ts
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.dart
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\\\n// Initialize the nodes\nListNode n0 = ListNode(1);\nListNode n1 = ListNode(3);\nListNode n2 = ListNode(2);\nListNode n3 = ListNode(5);\nListNode n4 = ListNode(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.rs
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nlet n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None }));\nlet n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None }));\nlet n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None }));\nlet n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None }));\nlet n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None }));\n\n// Link the nodes with references\nn0.borrow_mut().next = Some(n1.clone());\nn1.borrow_mut().next = Some(n2.clone());\nn2.borrow_mut().next = Some(n3.clone());\nn3.borrow_mut().next = Some(n4.clone());\n
    linked_list.c
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nListNode* n0 = newListNode(1);\nListNode* n1 = newListNode(3);\nListNode* n2 = newListNode(2);\nListNode* n3 = newListNode(5);\nListNode* n4 = newListNode(4);\n// Link the nodes with references\nn0->next = n1;\nn1->next = n2;\nn2->next = n3;\nn3->next = n4;\n
    linked_list.kt
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nval n0 = ListNode(1)\nval n1 = ListNode(3)\nval n2 = ListNode(2)\nval n3 = ListNode(5)\nval n4 = ListNode(4)\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.rb
    # Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4\n# Initialize the nodes\nn0 = ListNode.new(1)\nn1 = ListNode.new(3)\nn2 = ListNode.new(2)\nn3 = ListNode.new(5)\nn4 = ListNode.new(4)\n# Link the nodes with references\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
    Visualize execution

    View full screen >

    An array is a single variable, such as nums, containing entries nums[0], nums[1], and so on. A linked list consists of separate node objects. We usually refer to the list by its head node; for example, the list above can be called n0.

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#2-inserting-a-node","level":3,"title":"2.   Inserting a node","text":"

    Inserting a node is straightforward. As shown in Figure 4-6, inserting a new node P between adjacent nodes n0 and n1 requires changing only two references (pointers), taking \\(O(1)\\) time.

    In contrast, inserting an array element takes \\(O(n)\\) time and is slower for large datasets.

    Figure 4-6   Inserting a node into a linked list

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    def insert(n0: ListNode, P: ListNode):\n    \"\"\"Insert node P after node n0 in the linked list\"\"\"\n    n1 = n0.next\n    P.next = n1\n    n0.next = P\n
    linked_list.cpp
    /* Insert node P after node n0 in the linked list */\nvoid insert(ListNode *n0, ListNode *P) {\n    ListNode *n1 = n0->next;\n    P->next = n1;\n    n0->next = P;\n}\n
    linked_list.java
    /* Insert node P after node n0 in the linked list */\nvoid insert(ListNode n0, ListNode P) {\n    ListNode n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
    linked_list.cs
    /* Insert node P after node n0 in the linked list */\nvoid Insert(ListNode n0, ListNode P) {\n    ListNode? n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
    linked_list.go
    /* Insert node P after node n0 in the linked list */\nfunc insertNode(n0 *ListNode, P *ListNode) {\n    n1 := n0.Next\n    P.Next = n1\n    n0.Next = P\n}\n
    linked_list.swift
    /* Insert node P after node n0 in the linked list */\nfunc insert(n0: ListNode, P: ListNode) {\n    let n1 = n0.next\n    P.next = n1\n    n0.next = P\n}\n
    linked_list.js
    /* Insert node P after node n0 in the linked list */\nfunction insert(n0, P) {\n    const n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
    linked_list.ts
    /* Insert node P after node n0 in the linked list */\nfunction insert(n0: ListNode, P: ListNode): void {\n    const n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
    linked_list.dart
    /* Insert node P after node n0 in the linked list */\nvoid insert(ListNode n0, ListNode P) {\n  ListNode? n1 = n0.next;\n  P.next = n1;\n  n0.next = P;\n}\n
    linked_list.rs
    /* Insert node P after node n0 in the linked list */\n#[allow(non_snake_case)]\npub fn insert<T>(n0: &Rc<RefCell<ListNode<T>>>, P: Rc<RefCell<ListNode<T>>>) {\n    let n1 = n0.borrow_mut().next.take();\n    P.borrow_mut().next = n1;\n    n0.borrow_mut().next = Some(P);\n}\n
    linked_list.c
    /* Insert node P after node n0 in the linked list */\nvoid insert(ListNode *n0, ListNode *P) {\n    ListNode *n1 = n0->next;\n    P->next = n1;\n    n0->next = P;\n}\n
    linked_list.kt
    /* Insert node p after node n0 in the linked list */\nfun insert(n0: ListNode?, p: ListNode?) {\n    val n1 = n0?.next\n    p?.next = n1\n    n0?.next = p\n}\n
    linked_list.rb
    ### Insert node _p after node n0 in the linked list ###\n# In Ruby, `p` is a built-in method and `P` denotes a constant, so use `_p` instead\ndef insert(n0, _p)\n  n1 = n0.next\n  _p.next = n1\n  n0.next = _p\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#3-removing-a-node","level":3,"title":"3.   Removing a node","text":"

    Removing a node is also straightforward: as shown in Figure 4-7, only one reference (pointer) needs to change.

    Although P still points to n1 after removal, traversing the list can no longer reach P. It therefore no longer belongs to the list.

    Figure 4-7   Removing a linked list node

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    def remove(n0: ListNode):\n    \"\"\"Remove the first node after node n0 in the linked list\"\"\"\n    if not n0.next:\n        return\n    # n0 -> P -> n1\n    P = n0.next\n    n1 = P.next\n    n0.next = n1\n
    linked_list.cpp
    /* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode *n0) {\n    if (n0->next == nullptr)\n        return;\n    // n0 -> P -> n1\n    ListNode *P = n0->next;\n    ListNode *n1 = P->next;\n    n0->next = n1;\n    // Free memory\n    delete P;\n}\n
    linked_list.java
    /* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode n0) {\n    if (n0.next == null)\n        return;\n    // n0 -> P -> n1\n    ListNode P = n0.next;\n    ListNode n1 = P.next;\n    n0.next = n1;\n}\n
    linked_list.cs
    /* Remove the first node after node n0 in the linked list */\nvoid Remove(ListNode n0) {\n    if (n0.next == null)\n        return;\n    // n0 -> P -> n1\n    ListNode P = n0.next;\n    ListNode? n1 = P.next;\n    n0.next = n1;\n}\n
    linked_list.go
    /* Remove the first node after node n0 in the linked list */\nfunc removeItem(n0 *ListNode) {\n    if n0.Next == nil {\n        return\n    }\n    // n0 -> P -> n1\n    P := n0.Next\n    n1 := P.Next\n    n0.Next = n1\n}\n
    linked_list.swift
    /* Remove the first node after node n0 in the linked list */\nfunc remove(n0: ListNode) {\n    if n0.next == nil {\n        return\n    }\n    // n0 -> P -> n1\n    let P = n0.next\n    let n1 = P?.next\n    n0.next = n1\n}\n
    linked_list.js
    /* Remove the first node after node n0 in the linked list */\nfunction remove(n0) {\n    if (!n0.next) return;\n    // n0 -> P -> n1\n    const P = n0.next;\n    const n1 = P.next;\n    n0.next = n1;\n}\n
    linked_list.ts
    /* Remove the first node after node n0 in the linked list */\nfunction remove(n0: ListNode): void {\n    if (!n0.next) {\n        return;\n    }\n    // n0 -> P -> n1\n    const P = n0.next;\n    const n1 = P.next;\n    n0.next = n1;\n}\n
    linked_list.dart
    /* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode n0) {\n  if (n0.next == null) return;\n  // n0 -> P -> n1\n  ListNode P = n0.next!;\n  ListNode? n1 = P.next;\n  n0.next = n1;\n}\n
    linked_list.rs
    /* Remove the first node after node n0 in the linked list */\n#[allow(non_snake_case)]\npub fn remove<T>(n0: &Rc<RefCell<ListNode<T>>>) {\n    // n0 -> P -> n1\n    let P = n0.borrow_mut().next.take();\n    if let Some(node) = P {\n        let n1 = node.borrow_mut().next.take();\n        n0.borrow_mut().next = n1;\n    }\n}\n
    linked_list.c
    /* Remove the first node after node n0 in the linked list */\n// Note: stdio.h already declares a function named remove\nvoid removeItem(ListNode *n0) {\n    if (!n0->next)\n        return;\n    // n0 -> P -> n1\n    ListNode *P = n0->next;\n    ListNode *n1 = P->next;\n    n0->next = n1;\n    // Free memory\n    free(P);\n}\n
    linked_list.kt
    /* Remove the first node after node n0 in the linked list */\nfun remove(n0: ListNode?) {\n    if (n0?.next == null)\n        return\n    // n0 -> P -> n1\n    val p = n0.next\n    val n1 = p?.next\n    n0.next = n1\n}\n
    linked_list.rb
    ### Remove the first node after node n0 in the linked list ###\ndef remove(n0)\n  return if n0.next.nil?\n\n  # n0 -> remove_node -> n1\n  remove_node = n0.next\n  n1 = remove_node.next\n  n0.next = n1\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#4-accessing-a-node","level":3,"title":"4.   Accessing a node","text":"

    Accessing a node in a linked list is relatively slow. An array provides access to any element in \\(O(1)\\) time, but a linked list must be traversed from its head until the desired node is reached. Accessing the \\(i\\)-th node requires \\(i - 1\\) iterations, giving \\(O(n)\\) time complexity.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    def access(head: ListNode, index: int) -> ListNode | None:\n    \"\"\"Access the node at index `index` in the linked list\"\"\"\n    for _ in range(index):\n        if not head:\n            return None\n        head = head.next\n    return head\n
    linked_list.cpp
    /* Access the node at index `index` in the linked list */\nListNode *access(ListNode *head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == nullptr)\n            return nullptr;\n        head = head->next;\n    }\n    return head;\n}\n
    linked_list.java
    /* Access the node at index `index` in the linked list */\nListNode access(ListNode head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == null)\n            return null;\n        head = head.next;\n    }\n    return head;\n}\n
    linked_list.cs
    /* Access the node at index `index` in the linked list */\nListNode? Access(ListNode? head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == null)\n            return null;\n        head = head.next;\n    }\n    return head;\n}\n
    linked_list.go
    /* Access the node at index `index` in the linked list */\nfunc access(head *ListNode, index int) *ListNode {\n    for i := 0; i < index; i++ {\n        if head == nil {\n            return nil\n        }\n        head = head.Next\n    }\n    return head\n}\n
    linked_list.swift
    /* Access the node at index `index` in the linked list */\nfunc access(head: ListNode, index: Int) -> ListNode? {\n    var head: ListNode? = head\n    for _ in 0 ..< index {\n        if head == nil {\n            return nil\n        }\n        head = head?.next\n    }\n    return head\n}\n
    linked_list.js
    /* Access the node at index `index` in the linked list */\nfunction access(head, index) {\n    for (let i = 0; i < index; i++) {\n        if (!head) {\n            return null;\n        }\n        head = head.next;\n    }\n    return head;\n}\n
    linked_list.ts
    /* Access the node at index `index` in the linked list */\nfunction access(head: ListNode | null, index: number): ListNode | null {\n    for (let i = 0; i < index; i++) {\n        if (!head) {\n            return null;\n        }\n        head = head.next;\n    }\n    return head;\n}\n
    linked_list.dart
    /* Access the node at index `index` in the linked list */\nListNode? access(ListNode? head, int index) {\n  for (var i = 0; i < index; i++) {\n    if (head == null) return null;\n    head = head.next;\n  }\n  return head;\n}\n
    linked_list.rs
    /* Access the node at index `index` in the linked list */\npub fn access<T>(head: Rc<RefCell<ListNode<T>>>, index: i32) -> Option<Rc<RefCell<ListNode<T>>>> {\n    fn dfs<T>(\n        head: Option<&Rc<RefCell<ListNode<T>>>>,\n        index: i32,\n    ) -> Option<Rc<RefCell<ListNode<T>>>> {\n        if index <= 0 {\n            return head.cloned();\n        }\n\n        if let Some(node) = head {\n            dfs(node.borrow().next.as_ref(), index - 1)\n        } else {\n            None\n        }\n    }\n\n    dfs(Some(head).as_ref(), index)\n}\n
    linked_list.c
    /* Access the node at index `index` in the linked list */\nListNode *access(ListNode *head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == NULL)\n            return NULL;\n        head = head->next;\n    }\n    return head;\n}\n
    linked_list.kt
    /* Access the node at index `index` in the linked list */\nfun access(head: ListNode?, index: Int): ListNode? {\n    var h = head\n    for (i in 0..<index) {\n        if (h == null)\n            return null\n        h = h.next\n    }\n    return h\n}\n
    linked_list.rb
    ### Access the node at index `index` in the linked list ###\ndef access(head, index)\n  for i in 0...index\n    return nil if head.nil?\n    head = head.next\n  end\n\n  head\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#5-finding-a-node","level":3,"title":"5.   Finding a node","text":"

    Traverse the list to find a node with value target, then return its index. This is also linear search. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    def find(head: ListNode, target: int) -> int:\n    \"\"\"Find the first node with value target in the linked list\"\"\"\n    index = 0\n    while head:\n        if head.val == target:\n            return index\n        head = head.next\n        index += 1\n    return -1\n
    linked_list.cpp
    /* Find the first node with value target in the linked list */\nint find(ListNode *head, int target) {\n    int index = 0;\n    while (head != nullptr) {\n        if (head->val == target)\n            return index;\n        head = head->next;\n        index++;\n    }\n    return -1;\n}\n
    linked_list.java
    /* Find the first node with value target in the linked list */\nint find(ListNode head, int target) {\n    int index = 0;\n    while (head != null) {\n        if (head.val == target)\n            return index;\n        head = head.next;\n        index++;\n    }\n    return -1;\n}\n
    linked_list.cs
    /* Find the first node with value target in the linked list */\nint Find(ListNode? head, int target) {\n    int index = 0;\n    while (head != null) {\n        if (head.val == target)\n            return index;\n        head = head.next;\n        index++;\n    }\n    return -1;\n}\n
    linked_list.go
    /* Find the first node with value target in the linked list */\nfunc findNode(head *ListNode, target int) int {\n    index := 0\n    for head != nil {\n        if head.Val == target {\n            return index\n        }\n        head = head.Next\n        index++\n    }\n    return -1\n}\n
    linked_list.swift
    /* Find the first node with value target in the linked list */\nfunc find(head: ListNode, target: Int) -> Int {\n    var head: ListNode? = head\n    var index = 0\n    while head != nil {\n        if head?.val == target {\n            return index\n        }\n        head = head?.next\n        index += 1\n    }\n    return -1\n}\n
    linked_list.js
    /* Find the first node with value target in the linked list */\nfunction find(head, target) {\n    let index = 0;\n    while (head !== null) {\n        if (head.val === target) {\n            return index;\n        }\n        head = head.next;\n        index += 1;\n    }\n    return -1;\n}\n
    linked_list.ts
    /* Find the first node with value target in the linked list */\nfunction find(head: ListNode | null, target: number): number {\n    let index = 0;\n    while (head !== null) {\n        if (head.val === target) {\n            return index;\n        }\n        head = head.next;\n        index += 1;\n    }\n    return -1;\n}\n
    linked_list.dart
    /* Find the first node with value target in the linked list */\nint find(ListNode? head, int target) {\n  int index = 0;\n  while (head != null) {\n    if (head.val == target) {\n      return index;\n    }\n    head = head.next;\n    index++;\n  }\n  return -1;\n}\n
    linked_list.rs
    /* Find the first node with value target in the linked list */\npub fn find<T: PartialEq>(head: Rc<RefCell<ListNode<T>>>, target: T) -> i32 {\n    fn find<T: PartialEq>(head: Option<&Rc<RefCell<ListNode<T>>>>, target: T, idx: i32) -> i32 {\n        if let Some(node) = head {\n            if node.borrow().val == target {\n                return idx;\n            }\n            return find(node.borrow().next.as_ref(), target, idx + 1);\n        } else {\n            -1\n        }\n    }\n\n    find(Some(head).as_ref(), target, 0)\n}\n
    linked_list.c
    /* Find the first node with value target in the linked list */\nint find(ListNode *head, int target) {\n    int index = 0;\n    while (head) {\n        if (head->val == target)\n            return index;\n        head = head->next;\n        index++;\n    }\n    return -1;\n}\n
    linked_list.kt
    /* Find the first node with value target in the linked list */\nfun find(head: ListNode?, target: Int): Int {\n    var index = 0\n    var h = head\n    while (h != null) {\n        if (h._val == target)\n            return index\n        h = h.next\n        index++\n    }\n    return -1\n}\n
    linked_list.rb
    ### Find the first node with value target in the linked list ###\ndef find(head, target)\n  index = 0\n  while head\n    return index if head.val == target\n    head = head.next\n    index += 1\n  end\n\n  -1\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#422-arrays-versus-linked-lists","level":2,"title":"4.2.2   Arrays versus linked lists","text":"

    Table 4-1 compares their properties and operation costs. Their contrasting storage strategies give them contrasting strengths.

    Table 4-1   Comparing array and linked list efficiency

    Array Linked list Storage Contiguous memory Scattered memory Expansion Fixed length Flexible expansion Memory efficiency Less memory per element, but may waste space More memory per element Access an element \\(O(1)\\) \\(O(n)\\) Add an element \\(O(n)\\) \\(O(1)\\) Remove an element \\(O(n)\\) \\(O(1)\\)","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#423-common-types-of-linked-lists","level":2,"title":"4.2.3   Common types of linked lists","text":"

    Figure 4-8 shows three common types.

    • Singly linked list: The ordinary linked list introduced above. Each node stores a value and a reference to the next node. The first and last nodes are the head and tail, and the tail points to None.
    • Circular linked list: Connecting a singly linked list's tail to its head forms a circular list. Any node can then be treated as the head.
    • Doubly linked list: Each node stores references (pointers) in both directions, to its successor and predecessor. This makes it more flexible and allows traversal in either direction, at the cost of additional memory.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class ListNode:\n    \"\"\"Doubly linked list node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                # Node value\n        self.next: ListNode | None = None  # Reference to the successor\n        self.prev: ListNode | None = None  # Reference to the predecessor\n
    /* Doubly linked list node structure */\nstruct ListNode {\n    int val;         // Node value\n    ListNode *next;  // Pointer to the successor\n    ListNode *prev;  // Pointer to the predecessor\n    ListNode(int x) : val(x), next(nullptr), prev(nullptr) {}  // Constructor\n};\n
    /* Doubly linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode next;  // Reference to the successor\n    ListNode prev;  // Reference to the predecessor\n    ListNode(int x) { val = x; }  // Constructor\n}\n
    /* Doubly linked list node class */\nclass ListNode(int x) {  // Constructor\n    int val = x;    // Node value\n    ListNode next;  // Reference to the successor\n    ListNode prev;  // Reference to the predecessor\n}\n
    /* Doubly linked list node structure */\ntype DoublyListNode struct {\n    Val  int             // Node value\n    Next *DoublyListNode // Pointer to the successor\n    Prev *DoublyListNode // Pointer to the predecessor\n}\n\n// Initialize a node with NewDoublyListNode\nfunc NewDoublyListNode(val int) *DoublyListNode {\n    return &DoublyListNode{\n        Val:  val,\n        Next: nil,\n        Prev: nil,\n    }\n}\n
    /* Doubly linked list node class */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the successor\n    var prev: ListNode? // Reference to the predecessor\n\n    init(x: Int) { // Constructor\n        val = x\n    }\n}\n
    /* Doubly linked list node class */\nclass ListNode {\n    constructor(val, next, prev) {\n        this.val = val  ===  undefined ? 0 : val;        // Node value\n        this.next = next  ===  undefined ? null : next;  // Reference to the successor\n        this.prev = prev  ===  undefined ? null : prev;  // Reference to the predecessor\n    }\n}\n
    /* Doubly linked list node class */\nclass ListNode {\n    val: number;\n    next: ListNode | null;\n    prev: ListNode | null;\n    constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {\n        this.val = val  ===  undefined ? 0 : val;        // Node value\n        this.next = next  ===  undefined ? null : next;  // Reference to the successor\n        this.prev = prev  ===  undefined ? null : prev;  // Reference to the predecessor\n    }\n}\n
    /* Doubly linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode? next;  // Reference to the successor\n    ListNode? prev;  // Reference to the predecessor\n    ListNode(this.val, [this.next, this.prev]);  // Constructor\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Doubly linked list node type */\n#[derive(Debug)]\nstruct ListNode {\n    val: i32, // Node value\n    next: Option<Rc<RefCell<ListNode>>>, // Pointer to the successor\n    prev: Option<Rc<RefCell<ListNode>>>, // Pointer to the predecessor\n}\n\n/* Constructor */\nimpl ListNode {\n    fn new(val: i32) -> Self {\n        ListNode {\n            val,\n            next: None,\n            prev: None,\n        }\n    }\n}\n
    /* Doubly linked list node structure */\ntypedef struct ListNode {\n    int val;               // Node value\n    struct ListNode *next; // Pointer to the successor\n    struct ListNode *prev; // Pointer to the predecessor\n} ListNode;\n\n/* Constructor */\nListNode *newListNode(int val) {\n    ListNode *node;\n    node = (ListNode *) malloc(sizeof(ListNode));\n    node->val = val;\n    node->next = NULL;\n    node->prev = NULL;\n    return node;\n}\n
    /* Doubly linked list node class */\n// Constructor\nclass ListNode(x: Int) {\n    val _val: Int = x           // Node value\n    var next: ListNode? = null  // Reference to the successor\n    var prev: ListNode? = null  // Reference to the predecessor\n}\n
    # Doubly linked list node class\nclass ListNode\n  attr_accessor :val    # Node value\n  attr_accessor :next   # Reference to the successor\n  attr_accessor :prev   # Reference to the predecessor\n\n  def initialize(val=0, next_node=nil, prev_node=nil)\n    @val = val\n    @next = next_node\n    @prev = prev_node\n  end\nend\n

    Figure 4-8   Common types of linked lists

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#424-typical-applications-of-linked-lists","level":2,"title":"4.2.4   Typical applications of linked lists","text":"

    Singly linked lists commonly implement stacks, queues, hash tables, and graphs.

    • Stacks and queues: Inserting and removing at the same end gives last-in, first-out behavior, forming a stack. Inserting at one end and removing at the other gives first-in, first-out behavior, forming a queue.
    • Hash tables: Separate chaining is a common collision-handling strategy that places colliding elements in a linked list.
    • Graphs: An adjacency list associates each vertex with a linked list whose elements represent the other vertices connected to it.

    Doubly linked lists are useful when both the previous and next elements must be found quickly.

    • Advanced data structures: In structures such as red-black trees and B-trees, a node may need access to its parent. Storing a parent reference is similar to the backward link in a doubly linked list.
    • Browser history: The forward and back buttons require access to the next and previous visited pages. A doubly linked list makes this straightforward.
    • LRU caching: Least recently used eviction needs fast access to the least recently used data and efficient insertion and removal, making doubly linked lists a good fit.

    Circular linked lists suit cyclic operations, such as operating-system resource scheduling.

    • Round-robin scheduling: This common CPU scheduling algorithm cycles through processes, giving each a time slice before moving to the next. A circular linked list can implement this cycle.
    • Data buffers: Some buffers use circular linked lists. Audio and video players, for example, may divide a stream into buffer blocks arranged in a circular list for seamless playback.
    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/","level":1,"title":"4.3   Lists","text":"

    A list is an abstract data structure representing an ordered collection. It supports access, modification, insertion, removal, and traversal without requiring users to manage capacity limits. Lists can be implemented with linked lists or arrays.

    • A linked list naturally serves as a list, supporting these operations and flexible dynamic growth.
    • An array also supports these operations, but its fixed length makes it a list with a length limit.

    When an array implements a list, its fixed length limits the list's usefulness. We often cannot predict how much data we will store. Too small an allocation may not meet our needs; too large an allocation wastes memory.

    A dynamic array solves this problem. It retains the advantages of an array while allowing capacity to grow during execution.

    Many standard-library lists use dynamic arrays, including Python's list, Java's ArrayList, C++'s vector, and C#'s List. In the discussion that follows, we treat lists and dynamic arrays as equivalent concepts.

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#431-common-list-operations","level":2,"title":"4.3.1   Common list operations","text":"","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#1-initializing-a-list","level":3,"title":"1.   Initializing a list","text":"

    We usually initialize a list either without elements or with explicit initial values:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Initialize lists\n# Without initial values\nnums1: list[int] = []\n# With initial values\nnums: list[int] = [1, 3, 2, 5, 4]\n
    list.cpp
    /* Initialize lists */\n// In C++, vector is the list type used for nums here\n// Without initial values\nvector<int> nums1;\n// With initial values\nvector<int> nums = { 1, 3, 2, 5, 4 };\n
    list.java
    /* Initialize lists */\n// Without initial values\nList<Integer> nums1 = new ArrayList<>();\n// With initial values (use Integer[], the boxed counterpart of int[])\nInteger[] numbers = new Integer[] { 1, 3, 2, 5, 4 };\nList<Integer> nums = new ArrayList<>(Arrays.asList(numbers));\n
    list.cs
    /* Initialize lists */\n// Without initial values\nList<int> nums1 = [];\n// With initial values\nint[] numbers = [1, 3, 2, 5, 4];\nList<int> nums = [.. numbers];\n
    list_test.go
    /* Initialize lists */\n// Without initial values\nnums1 := []int{}\n// With initial values\nnums := []int{1, 3, 2, 5, 4}\n
    list.swift
    /* Initialize lists */\n// Without initial values\nlet nums1: [Int] = []\n// With initial values\nvar nums = [1, 3, 2, 5, 4]\n
    list.js
    /* Initialize lists */\n// Without initial values\nconst nums1 = [];\n// With initial values\nconst nums = [1, 3, 2, 5, 4];\n
    list.ts
    /* Initialize lists */\n// Without initial values\nconst nums1: number[] = [];\n// With initial values\nconst nums: number[] = [1, 3, 2, 5, 4];\n
    list.dart
    /* Initialize lists */\n// Without initial values\nList<int> nums1 = [];\n// With initial values\nList<int> nums = [1, 3, 2, 5, 4];\n
    list.rs
    /* Initialize lists */\n// Without initial values\nlet nums1: Vec<i32> = Vec::new();\n// With initial values\nlet nums: Vec<i32> = vec![1, 3, 2, 5, 4];\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Initialize lists */\n// Without initial values\nvar nums1 = listOf<Int>()\n// With initial values\nvar numbers = arrayOf(1, 3, 2, 5, 4)\nvar nums = numbers.toMutableList()\n
    list.rb
    # Initialize lists\n# Without initial values\nnums1 = []\n# With initial values\nnums = [1, 3, 2, 5, 4]\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#2-accessing-an-element","level":3,"title":"2.   Accessing an element","text":"

    Since the list is based on an array, accessing and updating an element are efficient \\(O(1)\\) operations.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Access an element\nnum: int = nums[1]  # Access the element at index 1\n\n# Update an element\nnums[1] = 0    # Set the element at index 1 to 0\n
    list.cpp
    /* Access an element */\nint num = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list.java
    /* Access an element */\nint num = nums.get(1);  // Access the element at index 1\n\n/* Update an element */\nnums.set(1, 0);  // Set the element at index 1 to 0\n
    list.cs
    /* Access an element */\nint num = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list_test.go
    /* Access an element */\nnum := nums[1]  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0     // Set the element at index 1 to 0\n
    list.swift
    /* Access an element */\nlet num = nums[1] // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0 // Set the element at index 1 to 0\n
    list.js
    /* Access an element */\nconst num = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list.ts
    /* Access an element */\nconst num: number = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list.dart
    /* Access an element */\nint num = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list.rs
    /* Access an element */\nlet num: i32 = nums[1];  // Access the element at index 1\n/* Update an element */\nnums[1] = 0;             // Set the element at index 1 to 0\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Access an element */\nval num = nums[1]       // Access the element at index 1\n/* Update an element */\nnums[1] = 0             // Set the element at index 1 to 0\n
    list.rb
    # Access an element\nnum = nums[1] # Access the element at index 1\n# Update an element\nnums[1] = 0 # Set the element at index 1 to 0\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#3-inserting-and-removing-elements","level":3,"title":"3.   Inserting and removing elements","text":"

    Unlike a fixed array, a list can freely add and remove elements. Appending takes \\(O(1)\\) time, while insertion and removal retain the array's \\(O(n)\\) time complexity.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Clear the list\nnums.clear()\n\n# Append elements\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n# Insert an element in the middle\nnums.insert(3, 6)  # Insert 6 at index 3\n\n# Remove an element\nnums.pop(3)        # Remove the element at index 3\n
    list.cpp
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.push_back(1);\nnums.push_back(3);\nnums.push_back(2);\nnums.push_back(5);\nnums.push_back(4);\n\n/* Insert an element in the middle */\nnums.insert(nums.begin() + 3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.erase(nums.begin() + 3);      // Remove the element at index 3\n
    list.java
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.add(3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.remove(3);  // Remove the element at index 3\n
    list.cs
    /* Clear the list */\nnums.Clear();\n\n/* Append elements */\nnums.Add(1);\nnums.Add(3);\nnums.Add(2);\nnums.Add(5);\nnums.Add(4);\n\n/* Insert an element in the middle */\nnums.Insert(3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.RemoveAt(3);  // Remove the element at index 3\n
    list_test.go
    /* Clear the list */\nnums = nil\n\n/* Append elements */\nnums = append(nums, 1)\nnums = append(nums, 3)\nnums = append(nums, 2)\nnums = append(nums, 5)\nnums = append(nums, 4)\n\n/* Insert an element in the middle */\nnums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Insert 6 at index 3\n\n/* Remove an element */\nnums = append(nums[:3], nums[4:]...) // Remove the element at index 3\n
    list.swift
    /* Clear the list */\nnums.removeAll()\n\n/* Append elements */\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n/* Insert an element in the middle */\nnums.insert(6, at: 3) // Insert 6 at index 3\n\n/* Remove an element */\nnums.remove(at: 3) // Remove the element at index 3\n
    list.js
    /* Clear the list */\nnums.length = 0;\n\n/* Append elements */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.splice(3, 0, 6); // Insert 6 at index 3\n\n/* Remove an element */\nnums.splice(3, 1);  // Remove the element at index 3\n
    list.ts
    /* Clear the list */\nnums.length = 0;\n\n/* Append elements */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.splice(3, 0, 6); // Insert 6 at index 3\n\n/* Remove an element */\nnums.splice(3, 1);  // Remove the element at index 3\n
    list.dart
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.insert(3, 6); // Insert 6 at index 3\n\n/* Remove an element */\nnums.removeAt(3); // Remove the element at index 3\n
    list.rs
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.insert(3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.remove(3);    // Remove the element at index 3\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.add(3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.remove(3);  // Remove the element at index 3\n
    list.rb
    # Clear the list\nnums.clear\n\n# Append elements\nnums << 1\nnums << 3\nnums << 2\nnums << 5\nnums << 4\n\n# Insert an element in the middle\nnums.insert(3, 6) # Insert 6 at index 3\n\n# Remove an element\nnums.delete_at(3) # Remove the element at index 3\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#4-traversing-a-list","level":3,"title":"4.   Traversing a list","text":"

    Like an array, a list can be traversed by index or by visiting its elements directly.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Traverse the list by index\ncount = 0\nfor i in range(len(nums)):\n    count += nums[i]\n\n# Traverse list elements directly\nfor num in nums:\n    count += num\n
    list.cpp
    /* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.size(); i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (int num : nums) {\n    count += num;\n}\n
    list.java
    /* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.size(); i++) {\n    count += nums.get(i);\n}\n\n/* Traverse list elements directly */\nfor (int num : nums) {\n    count += num;\n}\n
    list.cs
    /* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.Count; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nforeach (int num in nums) {\n    count += num;\n}\n
    list_test.go
    /* Traverse the list by index */\ncount := 0\nfor i := 0; i < len(nums); i++ {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\ncount = 0\nfor _, num := range nums {\n    count += num\n}\n
    list.swift
    /* Traverse the list by index */\nvar count = 0\nfor i in nums.indices {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\ncount = 0\nfor num in nums {\n    count += num\n}\n
    list.js
    /* Traverse the list by index */\nlet count = 0;\nfor (let i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (const num of nums) {\n    count += num;\n}\n
    list.ts
    /* Traverse the list by index */\nlet count = 0;\nfor (let i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (const num of nums) {\n    count += num;\n}\n
    list.dart
    /* Traverse the list by index */\nint count = 0;\nfor (var i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (var num in nums) {\n    count += num;\n}\n
    list.rs
    // Traverse the list by index\nlet mut _count = 0;\nfor i in 0..nums.len() {\n    _count += nums[i];\n}\n\n// Traverse list elements directly\n_count = 0;\nfor num in &nums {\n    _count += num;\n}\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Traverse the list by index */\nvar count = 0\nfor (i in nums.indices) {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\nfor (num in nums) {\n    count += num\n}\n
    list.rb
    # Traverse the list by index\ncount = 0\nfor i in 0...nums.length\n    count += nums[i]\nend\n\n# Traverse list elements directly\ncount = 0\nfor num in nums\n    count += num\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#5-concatenating-lists","level":3,"title":"5.   Concatenating lists","text":"

    Given another list nums1, we can append its elements to the end of the original list.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Concatenate two lists\nnums1: list[int] = [6, 8, 7, 10, 9]\nnums += nums1  # Append nums1 to nums\n
    list.cpp
    /* Concatenate two lists */\nvector<int> nums1 = { 6, 8, 7, 10, 9 };\n// Append nums1 to nums\nnums.insert(nums.end(), nums1.begin(), nums1.end());\n
    list.java
    /* Concatenate two lists */\nList<Integer> nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));\nnums.addAll(nums1);  // Append nums1 to nums\n
    list.cs
    /* Concatenate two lists */\nList<int> nums1 = [6, 8, 7, 10, 9];\nnums.AddRange(nums1);  // Append nums1 to nums\n
    list_test.go
    /* Concatenate two lists */\nnums1 := []int{6, 8, 7, 10, 9}\nnums = append(nums, nums1...)  // Append nums1 to nums\n
    list.swift
    /* Concatenate two lists */\nlet nums1 = [6, 8, 7, 10, 9]\nnums.append(contentsOf: nums1) // Append nums1 to nums\n
    list.js
    /* Concatenate two lists */\nconst nums1 = [6, 8, 7, 10, 9];\nnums.push(...nums1);  // Append nums1 to nums\n
    list.ts
    /* Concatenate two lists */\nconst nums1: number[] = [6, 8, 7, 10, 9];\nnums.push(...nums1);  // Append nums1 to nums\n
    list.dart
    /* Concatenate two lists */\nList<int> nums1 = [6, 8, 7, 10, 9];\nnums.addAll(nums1);  // Append nums1 to nums\n
    list.rs
    /* Concatenate two lists */\nlet nums1: Vec<i32> = vec![6, 8, 7, 10, 9];\nnums.extend(nums1);\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Concatenate two lists */\nval nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList()\nnums.addAll(nums1)  // Append nums1 to nums\n
    list.rb
    # Concatenate two lists\nnums1 = [6, 8, 7, 10, 9]\nnums += nums1\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#6-sorting-a-list","level":3,"title":"6.   Sorting a list","text":"

    Once the list is sorted, we can apply binary search and two-pointer algorithms, which often appear in array problems.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Sort the list\nnums.sort()  # After sorting, elements are in ascending order\n
    list.cpp
    /* Sort the list */\nsort(nums.begin(), nums.end());  // After sorting, elements are in ascending order\n
    list.java
    /* Sort the list */\nCollections.sort(nums);  // After sorting, elements are in ascending order\n
    list.cs
    /* Sort the list */\nnums.Sort(); // After sorting, elements are in ascending order\n
    list_test.go
    /* Sort the list */\nsort.Ints(nums)  // After sorting, elements are in ascending order\n
    list.swift
    /* Sort the list */\nnums.sort() // After sorting, elements are in ascending order\n
    list.js
    /* Sort the list */\nnums.sort((a, b) => a - b);  // After sorting, elements are in ascending order\n
    list.ts
    /* Sort the list */\nnums.sort((a, b) => a - b);  // After sorting, elements are in ascending order\n
    list.dart
    /* Sort the list */\nnums.sort(); // After sorting, elements are in ascending order\n
    list.rs
    /* Sort the list */\nnums.sort(); // After sorting, elements are in ascending order\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Sort the list */\nnums.sort() // After sorting, elements are in ascending order\n
    list.rb
    # Sort the list\nnums = nums.sort { |a, b| a <=> b } # After sorting, elements are in ascending order\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#432-implementing-a-list","level":2,"title":"4.3.2   Implementing a list","text":"

    Many languages, including Java, C++, and Python, provide lists. Their implementations are sophisticated, with carefully chosen parameters such as initial capacity and growth factor. Interested readers can study their source code.

    To understand how a list works, let's implement a simplified version with three key design choices.

    • Initial capacity: Choose a reasonable starting array capacity. We use 10 here.
    • Element count: Maintain size, the number of elements, updating it on insertion and removal. It identifies the list's end and tells us when expansion is needed.
    • Growth mechanism: If insertion finds the capacity full, allocate a larger array using a growth factor and copy all current elements into it. Here, capacity doubles each time.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_list.py
    class MyList:\n    \"\"\"List class\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._capacity: int = 10  # List capacity\n        self._arr: list[int] = [0] * self._capacity  # Array (stores the list elements)\n        self._size: int = 0  # List length (current number of elements)\n        self._extend_ratio: int = 2  # Factor by which the list capacity grows each time\n\n    def size(self) -> int:\n        \"\"\"Get the list length (current number of elements)\"\"\"\n        return self._size\n\n    def capacity(self) -> int:\n        \"\"\"Get the list capacity\"\"\"\n        return self._capacity\n\n    def get(self, index: int) -> int:\n        \"\"\"Access an element\"\"\"\n        # Raise an exception if the index is out of bounds; the same applies below\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        return self._arr[index]\n\n    def set(self, num: int, index: int):\n        \"\"\"Update an element\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        self._arr[index] = num\n\n    def add(self, num: int):\n        \"\"\"Append an element at the end\"\"\"\n        # Grow the capacity when adding an element would exceed it\n        if self.size() == self.capacity():\n            self.extend_capacity()\n        self._arr[self._size] = num\n        self._size += 1\n\n    def insert(self, num: int, index: int):\n        \"\"\"Insert an element in the middle\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        # Grow the capacity when adding an element would exceed it\n        if self._size == self.capacity():\n            self.extend_capacity()\n        # Shift all elements at and after index `index` one position to the right\n        for j in range(self._size - 1, index - 1, -1):\n            self._arr[j + 1] = self._arr[j]\n        self._arr[index] = num\n        # Update the number of elements\n        self._size += 1\n\n    def remove(self, index: int) -> int:\n        \"\"\"Remove an element\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        num = self._arr[index]\n        # Shift all elements after index `index` one position to the left\n        for j in range(index, self._size - 1):\n            self._arr[j] = self._arr[j + 1]\n        # Update the number of elements\n        self._size -= 1\n        # Return the removed element\n        return num\n\n    def extend_capacity(self):\n        \"\"\"Grow the list capacity\"\"\"\n        # Create a new array with _extend_ratio times the original length and copy the original array into it\n        self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)\n        # Update the list capacity\n        self._capacity = len(self._arr)\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return the portion of the list that contains elements\"\"\"\n        return self._arr[: self._size]\n
    my_list.cpp
    /* List class */\nclass MyList {\n  private:\n    int *arr;             // Array (stores the list elements)\n    int arrCapacity = 10; // List capacity\n    int arrSize = 0;      // List length (current number of elements)\n    int extendRatio = 2;   // Factor by which the list capacity grows each time\n\n  public:\n    /* Constructor */\n    MyList() {\n        arr = new int[arrCapacity];\n    }\n\n    /* Destructor */\n    ~MyList() {\n        delete[] arr;\n    }\n\n    /* Get the list length (current number of elements)*/\n    int size() {\n        return arrSize;\n    }\n\n    /* Get the list capacity */\n    int capacity() {\n        return arrCapacity;\n    }\n\n    /* Access an element */\n    int get(int index) {\n        // Raise an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Update an element */\n    void set(int index, int num) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Append an element at the end */\n    void add(int num) {\n        // Grow the capacity when adding an element would exceed it\n        if (size() == capacity())\n            extendCapacity();\n        arr[size()] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Insert an element in the middle */\n    void insert(int index, int num) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        // Grow the capacity when adding an element would exceed it\n        if (size() == capacity())\n            extendCapacity();\n        // Shift all elements at and after index `index` one position to the right\n        for (int j = size() - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Remove an element */\n    int remove(int index) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        int num = arr[index];\n        // Shift all elements after index `index` one position to the left\n        for (int j = index; j < size() - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        arrSize--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    void extendCapacity() {\n        // Create a new array with extendRatio times the original array's length\n        int newCapacity = capacity() * extendRatio;\n        int *tmp = arr;\n        arr = new int[newCapacity];\n        // Copy all elements from the original array to the new array\n        for (int i = 0; i < size(); i++) {\n            arr[i] = tmp[i];\n        }\n        // Free memory\n        delete[] tmp;\n        arrCapacity = newCapacity;\n    }\n\n    /* Convert the list to a vector for printing */\n    vector<int> toVector() {\n        // Convert only the list elements within the occupied range\n        vector<int> vec(size());\n        for (int i = 0; i < size(); i++) {\n            vec[i] = arr[i];\n        }\n        return vec;\n    }\n};\n
    my_list.java
    /* List class */\nclass MyList {\n    private int[] arr; // Array (stores the list elements)\n    private int capacity = 10; // List capacity\n    private int size = 0; // List length (current number of elements)\n    private int extendRatio = 2; // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    public MyList() {\n        arr = new int[capacity];\n    }\n\n    /* Get the list length (current number of elements) */\n    public int size() {\n        return size;\n    }\n\n    /* Get the list capacity */\n    public int capacity() {\n        return capacity;\n    }\n\n    /* Access an element */\n    public int get(int index) {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Update an element */\n    public void set(int index, int num) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Append at the end */\n    public void add(int num) {\n        // Grow the capacity when adding an element would exceed it\n        if (size == capacity())\n            extendCapacity();\n        arr[size] = num;\n        // Update the number of elements\n        size++;\n    }\n\n    /* Insert an element in the middle */\n    public void insert(int index, int num) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        // Grow the capacity when adding an element would exceed it\n        if (size == capacity())\n            extendCapacity();\n        // Shift all elements at and after index `index` one position to the right\n        for (int j = size - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        size++;\n    }\n\n    /* Remove an element */\n    public int remove(int index) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        int num = arr[index];\n        // Shift all elements after index `index` one position to the left\n        for (int j = index; j < size - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    public void extendCapacity() {\n        // Create an array extendRatio times as long as the original and copy the original array into it\n        arr = Arrays.copyOf(arr, capacity() * extendRatio);\n        // Update the list capacity\n        capacity = arr.length;\n    }\n\n    /* Convert the list to an array */\n    public int[] toArray() {\n        int size = size();\n        // Convert only the list elements within the occupied range\n        int[] arr = new int[size];\n        for (int i = 0; i < size; i++) {\n            arr[i] = get(i);\n        }\n        return arr;\n    }\n}\n
    my_list.cs
    /* List class */\nclass MyList {\n    private int[] arr;           // Array (stores the list elements)\n    private int arrCapacity = 10;    // List capacity\n    private int arrSize = 0;         // List length (current number of elements)\n    private readonly int extendRatio = 2;  // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    public MyList() {\n        arr = new int[arrCapacity];\n    }\n\n    /* Get the list length (current number of elements)*/\n    public int Size() {\n        return arrSize;\n    }\n\n    /* Get the list capacity */\n    public int Capacity() {\n        return arrCapacity;\n    }\n\n    /* Access an element */\n    public int Get(int index) {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Update an element */\n    public void Set(int index, int num) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Append at the end */\n    public void Add(int num) {\n        // Grow the capacity when adding an element would exceed it\n        if (arrSize == arrCapacity)\n            ExtendCapacity();\n        arr[arrSize] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Insert an element in the middle */\n    public void Insert(int index, int num) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        // Grow the capacity when adding an element would exceed it\n        if (arrSize == arrCapacity)\n            ExtendCapacity();\n        // Shift all elements at and after index `index` one position to the right\n        for (int j = arrSize - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Remove an element */\n    public int Remove(int index) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        int num = arr[index];\n        // Shift all elements after index `index` one position to the left\n        for (int j = index; j < arrSize - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        arrSize--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    public void ExtendCapacity() {\n        // Create an array of length arrCapacity * extendRatio and copy the original array into it\n        Array.Resize(ref arr, arrCapacity * extendRatio);\n        // Update the list capacity\n        arrCapacity = arr.Length;\n    }\n\n    /* Convert the list to an array */\n    public int[] ToArray() {\n        // Convert only the list elements within the occupied range\n        int[] arr = new int[arrSize];\n        for (int i = 0; i < arrSize; i++) {\n            arr[i] = Get(i);\n        }\n        return arr;\n    }\n}\n
    my_list.go
    /* List type */\ntype myList struct {\n    arrCapacity int\n    arr         []int\n    arrSize     int\n    extendRatio int\n}\n\n/* Constructor */\nfunc newMyList() *myList {\n    return &myList{\n        arrCapacity: 10,              // List capacity\n        arr:         make([]int, 10), // Array (stores the list elements)\n        arrSize:     0,               // List length (current number of elements)\n        extendRatio: 2,               // Factor by which the list capacity grows each time\n    }\n}\n\n/* Get the list length (current number of elements) */\nfunc (l *myList) size() int {\n    return l.arrSize\n}\n\n/*  Get the list capacity */\nfunc (l *myList) capacity() int {\n    return l.arrCapacity\n}\n\n/* Access an element */\nfunc (l *myList) get(index int) int {\n    // Panic if the index is out of bounds; the same applies below\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    return l.arr[index]\n}\n\n/* Update an element */\nfunc (l *myList) set(num, index int) {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    l.arr[index] = num\n}\n\n/* Append at the end */\nfunc (l *myList) add(num int) {\n    // Grow the capacity when adding an element would exceed it\n    if l.arrSize == l.arrCapacity {\n        l.extendCapacity()\n    }\n    l.arr[l.arrSize] = num\n    // Update the number of elements\n    l.arrSize++\n}\n\n/* Insert an element in the middle */\nfunc (l *myList) insert(num, index int) {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    // Grow the capacity when adding an element would exceed it\n    if l.arrSize == l.arrCapacity {\n        l.extendCapacity()\n    }\n    // Shift all elements at and after index `index` one position to the right\n    for j := l.arrSize - 1; j >= index; j-- {\n        l.arr[j+1] = l.arr[j]\n    }\n    l.arr[index] = num\n    // Update the number of elements\n    l.arrSize++\n}\n\n/* Remove an element */\nfunc (l *myList) remove(index int) int {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    num := l.arr[index]\n    // Shift all elements after index `index` one position to the left\n    for j := index; j < l.arrSize-1; j++ {\n        l.arr[j] = l.arr[j+1]\n    }\n    // Update the number of elements\n    l.arrSize--\n    // Return the removed element\n    return num\n}\n\n/* Grow the list capacity */\nfunc (l *myList) extendCapacity() {\n    // Extend the backing slice to extendRatio times its original length, preserving the existing elements\n    l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...)\n    // Update the list capacity\n    l.arrCapacity = len(l.arr)\n}\n\n/* Return the portion of the list that contains elements */\nfunc (l *myList) toArray() []int {\n    // Return a slice containing only the occupied elements\n    return l.arr[:l.arrSize]\n}\n
    my_list.swift
    /* List class */\nclass MyList {\n    private var arr: [Int] // Array (stores the list elements)\n    private var _capacity: Int // List capacity\n    private var _size: Int // List length (current number of elements)\n    private let extendRatio: Int // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    init() {\n        _capacity = 10\n        _size = 0\n        extendRatio = 2\n        arr = Array(repeating: 0, count: _capacity)\n    }\n\n    /* Get the list length (current number of elements)*/\n    func size() -> Int {\n        _size\n    }\n\n    /* Get the list capacity */\n    func capacity() -> Int {\n        _capacity\n    }\n\n    /* Access an element */\n    func get(index: Int) -> Int {\n        // An out-of-bounds index triggers a fatal error; the same check is used below\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        return arr[index]\n    }\n\n    /* Update an element */\n    func set(index: Int, num: Int) {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        arr[index] = num\n    }\n\n    /* Append at the end */\n    func add(num: Int) {\n        // Grow the capacity when adding an element would exceed it\n        if size() == capacity() {\n            extendCapacity()\n        }\n        arr[size()] = num\n        // Update the number of elements\n        _size += 1\n    }\n\n    /* Insert an element in the middle */\n    func insert(index: Int, num: Int) {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        // Grow the capacity when adding an element would exceed it\n        if size() == capacity() {\n            extendCapacity()\n        }\n        // Shift all elements at and after index `index` one position to the right\n        for j in (index ..< size()).reversed() {\n            arr[j + 1] = arr[j]\n        }\n        arr[index] = num\n        // Update the number of elements\n        _size += 1\n    }\n\n    /* Remove an element */\n    @discardableResult\n    func remove(index: Int) -> Int {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        let num = arr[index]\n        // Shift all elements after index `index` one position to the left\n        for j in index ..< (size() - 1) {\n            arr[j] = arr[j + 1]\n        }\n        // Update the number of elements\n        _size -= 1\n        // Return the removed element\n        return num\n    }\n\n    /* Grow the list capacity */\n    func extendCapacity() {\n        // Create an array extendRatio times as long as the original and copy the original array into it\n        arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1))\n        // Update the list capacity\n        _capacity = arr.count\n    }\n\n    /* Convert the list to an array */\n    func toArray() -> [Int] {\n        Array(arr.prefix(size()))\n    }\n}\n
    my_list.js
    /* List class */\nclass MyList {\n    #arr = new Array(); // Array (stores the list elements)\n    #capacity = 10; // List capacity\n    #size = 0; // List length (current number of elements)\n    #extendRatio = 2; // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    constructor() {\n        this.#arr = new Array(this.#capacity);\n    }\n\n    /* Get the list length (current number of elements)*/\n    size() {\n        return this.#size;\n    }\n\n    /* Get the list capacity */\n    capacity() {\n        return this.#capacity;\n    }\n\n    /* Access an element */\n    get(index) {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        return this.#arr[index];\n    }\n\n    /* Update an element */\n    set(index, num) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        this.#arr[index] = num;\n    }\n\n    /* Append an element at the end */\n    add(num) {\n        // Grow the capacity if the length equals the capacity\n        if (this.#size === this.#capacity) {\n            this.extendCapacity();\n        }\n        // Append the new element to the end of the list\n        this.#arr[this.#size] = num;\n        this.#size++;\n    }\n\n    /* Insert an element in the middle */\n    insert(index, num) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        // Grow the capacity when adding an element would exceed it\n        if (this.#size === this.#capacity) {\n            this.extendCapacity();\n        }\n        // Shift all elements at and after index `index` one position to the right\n        for (let j = this.#size - 1; j >= index; j--) {\n            this.#arr[j + 1] = this.#arr[j];\n        }\n        // Update the number of elements\n        this.#arr[index] = num;\n        this.#size++;\n    }\n\n    /* Remove an element */\n    remove(index) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        let num = this.#arr[index];\n        // Shift all elements after index `index` one position to the left\n        for (let j = index; j < this.#size - 1; j++) {\n            this.#arr[j] = this.#arr[j + 1];\n        }\n        // Update the number of elements\n        this.#size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    extendCapacity() {\n        // Create an array extendRatio times the original length and copy the original array into it\n        this.#arr = this.#arr.concat(\n            new Array(this.capacity() * (this.#extendRatio - 1))\n        );\n        // Update the list capacity\n        this.#capacity = this.#arr.length;\n    }\n\n    /* Convert the list to an array */\n    toArray() {\n        let size = this.size();\n        // Convert only the list elements within the occupied range\n        const arr = new Array(size);\n        for (let i = 0; i < size; i++) {\n            arr[i] = this.get(i);\n        }\n        return arr;\n    }\n}\n
    my_list.ts
    /* List class */\nclass MyList {\n    private arr: Array<number>; // Array (stores the list elements)\n    private _capacity: number = 10; // List capacity\n    private _size: number = 0; // List length (current number of elements)\n    private extendRatio: number = 2; // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    constructor() {\n        this.arr = new Array(this._capacity);\n    }\n\n    /* Get the list length (current number of elements)*/\n    public size(): number {\n        return this._size;\n    }\n\n    /* Get the list capacity */\n    public capacity(): number {\n        return this._capacity;\n    }\n\n    /* Access an element */\n    public get(index: number): number {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        return this.arr[index];\n    }\n\n    /* Update an element */\n    public set(index: number, num: number): void {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        this.arr[index] = num;\n    }\n\n    /* Append an element at the end */\n    public add(num: number): void {\n        // Grow the capacity if the length equals the capacity\n        if (this._size === this._capacity) this.extendCapacity();\n        // Append the new element to the end of the list\n        this.arr[this._size] = num;\n        this._size++;\n    }\n\n    /* Insert an element in the middle */\n    public insert(index: number, num: number): void {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        // Grow the capacity when adding an element would exceed it\n        if (this._size === this._capacity) {\n            this.extendCapacity();\n        }\n        // Shift all elements at and after index `index` one position to the right\n        for (let j = this._size - 1; j >= index; j--) {\n            this.arr[j + 1] = this.arr[j];\n        }\n        // Update the number of elements\n        this.arr[index] = num;\n        this._size++;\n    }\n\n    /* Remove an element */\n    public remove(index: number): number {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        let num = this.arr[index];\n        // Shift all elements after index one position to the left\n        for (let j = index; j < this._size - 1; j++) {\n            this.arr[j] = this.arr[j + 1];\n        }\n        // Update the number of elements\n        this._size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    public extendCapacity(): void {\n        // Create a larger array by appending empty slots to a copy of the original array\n        this.arr = this.arr.concat(\n            new Array(this.capacity() * (this.extendRatio - 1))\n        );\n        // Update the list capacity\n        this._capacity = this.arr.length;\n    }\n\n    /* Convert the list to an array */\n    public toArray(): number[] {\n        let size = this.size();\n        // Convert only the list elements within the occupied range\n        const arr = new Array(size);\n        for (let i = 0; i < size; i++) {\n            arr[i] = this.get(i);\n        }\n        return arr;\n    }\n}\n
    my_list.dart
    /* List class */\nclass MyList {\n  late List<int> _arr; // Array (stores the list elements)\n  int _capacity = 10; // List capacity\n  int _size = 0; // List length (current number of elements)\n  int _extendRatio = 2; // Factor by which the list capacity grows each time\n\n  /* Constructor */\n  MyList() {\n    _arr = List.filled(_capacity, 0);\n  }\n\n  /* Get the list length (current number of elements)*/\n  int size() => _size;\n\n  /* Get the list capacity */\n  int capacity() => _capacity;\n\n  /* Access an element */\n  int get(int index) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    return _arr[index];\n  }\n\n  /* Update an element */\n  void set(int index, int _num) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    _arr[index] = _num;\n  }\n\n  /* Append an element at the end */\n  void add(int _num) {\n    // Grow the capacity when adding an element would exceed it\n    if (_size == _capacity) extendCapacity();\n    _arr[_size] = _num;\n    // Update the number of elements\n    _size++;\n  }\n\n  /* Insert an element in the middle */\n  void insert(int index, int _num) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    // Grow the capacity when adding an element would exceed it\n    if (_size == _capacity) extendCapacity();\n    // Shift all elements at and after index `index` one position to the right\n    for (var j = _size - 1; j >= index; j--) {\n      _arr[j + 1] = _arr[j];\n    }\n    _arr[index] = _num;\n    // Update the number of elements\n    _size++;\n  }\n\n  /* Remove an element */\n  int remove(int index) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    int _num = _arr[index];\n    // Shift all elements after index one position to the left\n    for (var j = index; j < _size - 1; j++) {\n      _arr[j] = _arr[j + 1];\n    }\n    // Update the number of elements\n    _size--;\n    // Return the removed element\n    return _num;\n  }\n\n  /* Grow the list capacity */\n  void extendCapacity() {\n    // Create a new array with _extendRatio times the length of the original array\n    final _newNums = List.filled(_capacity * _extendRatio, 0);\n    // Copy the original array into the new array\n    List.copyRange(_newNums, 0, _arr);\n    // Update the reference _arr\n    _arr = _newNums;\n    // Update the list capacity\n    _capacity = _arr.length;\n  }\n\n  /* Convert the list to an array */\n  List<int> toArray() {\n    List<int> arr = [];\n    for (var i = 0; i < _size; i++) {\n      arr.add(get(i));\n    }\n    return arr;\n  }\n}\n
    my_list.rs
    /* List type */\n#[allow(dead_code)]\nstruct MyList {\n    arr: Vec<i32>,       // Array (stores the list elements)\n    capacity: usize,     // List capacity\n    size: usize,         // List length (current number of elements)\n    extend_ratio: usize, // Factor by which the list capacity grows each time\n}\n\n#[allow(unused, unused_comparisons)]\nimpl MyList {\n    /* Constructor */\n    pub fn new(capacity: usize) -> Self {\n        let mut vec = vec![0; capacity];\n        Self {\n            arr: vec,\n            capacity,\n            size: 0,\n            extend_ratio: 2,\n        }\n    }\n\n    /* Get the list length (current number of elements)*/\n    pub fn size(&self) -> usize {\n        return self.size;\n    }\n\n    /* Get the list capacity */\n    pub fn capacity(&self) -> usize {\n        return self.capacity;\n    }\n\n    /* Access an element */\n    pub fn get(&self, index: usize) -> i32 {\n        // Panic if the index is out of bounds; the same applies below\n        if index >= self.size {\n            panic!(\"Index out of bounds\")\n        };\n        return self.arr[index];\n    }\n\n    /* Update an element */\n    pub fn set(&mut self, index: usize, num: i32) {\n        if index >= self.size {\n            panic!(\"Index out of bounds\")\n        };\n        self.arr[index] = num;\n    }\n\n    /* Append an element at the end */\n    pub fn add(&mut self, num: i32) {\n        // Grow the capacity when adding an element would exceed it\n        if self.size == self.capacity() {\n            self.extend_capacity();\n        }\n        self.arr[self.size] = num;\n        // Update the number of elements\n        self.size += 1;\n    }\n\n    /* Insert an element in the middle */\n    pub fn insert(&mut self, index: usize, num: i32) {\n        if index >= self.size() {\n            panic!(\"Index out of bounds\")\n        };\n        // Grow the capacity when adding an element would exceed it\n        if self.size == self.capacity() {\n            self.extend_capacity();\n        }\n        // Shift all elements at and after index `index` one position to the right\n        for j in (index..self.size).rev() {\n            self.arr[j + 1] = self.arr[j];\n        }\n        self.arr[index] = num;\n        // Update the number of elements\n        self.size += 1;\n    }\n\n    /* Remove an element */\n    pub fn remove(&mut self, index: usize) -> i32 {\n        if index >= self.size() {\n            panic!(\"Index out of bounds\")\n        };\n        let num = self.arr[index];\n        // Shift all elements after index `index` one position to the left\n        for j in index..self.size - 1 {\n            self.arr[j] = self.arr[j + 1];\n        }\n        // Update the number of elements\n        self.size -= 1;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    pub fn extend_capacity(&mut self) {\n        // Grow the backing vector to extend_ratio times its original length, preserving its elements\n        let new_capacity = self.capacity * self.extend_ratio;\n        self.arr.resize(new_capacity, 0);\n        // Update the list capacity\n        self.capacity = new_capacity;\n    }\n\n    /* Copy the list elements into a vector */\n    pub fn to_array(&self) -> Vec<i32> {\n        // Convert only the list elements within the occupied range\n        let mut arr = Vec::new();\n        for i in 0..self.size {\n            arr.push(self.get(i));\n        }\n        arr\n    }\n}\n
    my_list.c
    /* List structure */\ntypedef struct {\n    int *arr;        // Array (stores the list elements)\n    int capacity;    // List capacity\n    int size;        // List size\n    int extendRatio; // List capacity growth factor\n} MyList;\n\n/* Constructor */\nMyList *newMyList() {\n    MyList *nums = malloc(sizeof(MyList));\n    nums->capacity = 10;\n    nums->arr = malloc(sizeof(int) * nums->capacity);\n    nums->size = 0;\n    nums->extendRatio = 2;\n    return nums;\n}\n\n/* Destructor */\nvoid delMyList(MyList *nums) {\n    free(nums->arr);\n    free(nums);\n}\n\n/* Get the list size */\nint size(MyList *nums) {\n    return nums->size;\n}\n\n/* Get the list capacity */\nint capacity(MyList *nums) {\n    return nums->capacity;\n}\n\n/* Access an element */\nint get(MyList *nums, int index) {\n    assert(index >= 0 && index < nums->size);\n    return nums->arr[index];\n}\n\n/* Update an element */\nvoid set(MyList *nums, int index, int num) {\n    assert(index >= 0 && index < nums->size);\n    nums->arr[index] = num;\n}\n\n/* Append an element at the end */\nvoid add(MyList *nums, int num) {\n    if (size(nums) == capacity(nums)) {\n        extendCapacity(nums); // Grow the capacity\n    }\n    nums->arr[size(nums)] = num;\n    nums->size++;\n}\n\n/* Insert an element in the middle */\nvoid insert(MyList *nums, int index, int num) {\n    assert(index >= 0 && index < size(nums));\n    // Grow the capacity when adding an element would exceed it\n    if (size(nums) == capacity(nums)) {\n        extendCapacity(nums); // Grow the capacity\n    }\n    for (int i = size(nums); i > index; --i) {\n        nums->arr[i] = nums->arr[i - 1];\n    }\n    nums->arr[index] = num;\n    nums->size++;\n}\n\n/* Remove an element */\n// Note: stdio.h already declares a function named remove\nint removeItem(MyList *nums, int index) {\n    assert(index >= 0 && index < size(nums));\n    int num = nums->arr[index];\n    for (int i = index; i < size(nums) - 1; i++) {\n        nums->arr[i] = nums->arr[i + 1];\n    }\n    nums->size--;\n    return num;\n}\n\n/* Grow the list capacity */\nvoid extendCapacity(MyList *nums) {\n    // Allocate new space first\n    int newCapacity = capacity(nums) * nums->extendRatio;\n    int *extend = (int *)malloc(sizeof(int) * newCapacity);\n    int *temp = nums->arr;\n\n    // Copy the old data into the new array\n    for (int i = 0; i < size(nums); i++)\n        extend[i] = nums->arr[i];\n\n    // Free the old array\n    free(temp);\n\n    // Update the array pointer and capacity\n    nums->arr = extend;\n    nums->capacity = newCapacity;\n}\n\n/* Convert the list to an array for printing */\nint *toArray(MyList *nums) {\n    return nums->arr;\n}\n
    my_list.kt
    /* List class */\nclass MyList {\n    private var arr: IntArray = intArrayOf() // Array (stores the list elements)\n    private var capacity: Int = 10 // List capacity\n    private var size: Int = 0 // List length (current number of elements)\n    private var extendRatio: Int = 2 // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    init {\n        arr = IntArray(capacity)\n    }\n\n    /* Get the list length (current number of elements) */\n    fun size(): Int {\n        return size\n    }\n\n    /* Get the list capacity */\n    fun capacity(): Int {\n        return capacity\n    }\n\n    /* Access an element */\n    fun get(index: Int): Int {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        return arr[index]\n    }\n\n    /* Update an element */\n    fun set(index: Int, num: Int) {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        arr[index] = num\n    }\n\n    /* Append at the end */\n    fun add(num: Int) {\n        // Grow the capacity when adding an element would exceed it\n        if (size == capacity())\n            extendCapacity()\n        arr[size] = num\n        // Update the number of elements\n        size++\n    }\n\n    /* Insert an element in the middle */\n    fun insert(index: Int, num: Int) {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        // Grow the capacity when adding an element would exceed it\n        if (size == capacity())\n            extendCapacity()\n        // Shift all elements at and after index `index` one position to the right\n        for (j in size - 1 downTo index)\n            arr[j + 1] = arr[j]\n        arr[index] = num\n        // Update the number of elements\n        size++\n    }\n\n    /* Remove an element */\n    fun remove(index: Int): Int {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        val num = arr[index]\n        // Shift all elements after index `index` one position to the left\n        for (j in index..<size - 1)\n            arr[j] = arr[j + 1]\n        // Update the number of elements\n        size--\n        // Return the removed element\n        return num\n    }\n\n    /* Grow the list capacity */\n    fun extendCapacity() {\n        // Create an array extendRatio times as long as the original and copy the original array into it\n        arr = arr.copyOf(capacity() * extendRatio)\n        // Update the list capacity\n        capacity = arr.size\n    }\n\n    /* Convert the list to an array */\n    fun toArray(): IntArray {\n        val size = size()\n        // Convert only the list elements within the occupied range\n        val arr = IntArray(size)\n        for (i in 0..<size) {\n            arr[i] = get(i)\n        }\n        return arr\n    }\n}\n
    my_list.rb
    ### List class ###\nclass MyList\n  attr_reader :size       # Get the list length (current number of elements)\n  attr_reader :capacity   # Get the list capacity\n\n  ### Constructor ###\n  def initialize\n    @capacity = 10\n    @size = 0\n    @extend_ratio = 2\n    @arr = Array.new(capacity)\n  end\n\n  ### Access an element ###\n  def get(index)\n    # Raise an exception if the index is out of bounds; the same applies below\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    @arr[index]\n  end\n\n  ### Update an element ###\n  def set(index, num)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    @arr[index] = num\n  end\n\n  ### Append an element at the end ###\n  def add(num)\n    # Grow the capacity when adding an element would exceed it\n    extend_capacity if size == capacity\n    @arr[size] = num\n\n    # Update the number of elements\n    @size += 1\n  end\n\n  ### Insert an element in the middle ###\n  def insert(index, num)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n\n    # Grow the capacity when adding an element would exceed it\n    extend_capacity if size == capacity\n\n    # Shift all elements at and after index `index` one position to the right\n    for j in (size - 1).downto(index)\n      @arr[j + 1] = @arr[j]\n    end\n    @arr[index] = num\n\n    # Update the number of elements\n    @size += 1\n  end\n\n  ### Remove an element ###\n  def remove(index)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    num = @arr[index]\n\n    # Shift all elements after index one position to the left\n    for j in index...size\n      @arr[j] = @arr[j + 1]\n    end\n\n    # Update the number of elements\n    @size -= 1\n\n    # Return the removed element\n    num\n  end\n\n  ### Grow the list capacity ###\n  def extend_capacity\n    # Duplicate the original array and append capacity * (extend_ratio - 1) empty slots\n    arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1))\n    # Update the list capacity\n    @capacity = arr.length\n  end\n\n  ### Convert the list to an array ###\n  def to_array\n    sz = size\n    # Convert only the list elements within the occupied range\n    arr = Array.new(sz)\n    for i in 0...sz\n      arr[i] = get(i)\n    end\n    arr\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/","level":1,"title":"4.4   Memory and cache *","text":"

    The first two sections of this chapter introduced arrays and linked lists, two fundamental structures representing contiguous and scattered storage, respectively.

    Physical structure largely determines how efficiently a program uses memory and cache, affecting its overall performance.

    ","path":["Chapter 4.   Arrays and linked lists","4.4   Memory and cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#441-computer-storage-devices","level":2,"title":"4.4.1   Computer storage devices","text":"

    Computers have three types of storage: disk storage, random-access memory (RAM), and cache memory. Table 4-2 compares their roles and performance characteristics.

    Table 4-2   Computer storage devices

    Disk storage RAM Cache Purpose Long-term storage of the operating system, programs, files, and other data Temporary storage of running programs and data being processed Stores frequently accessed data and instructions to reduce CPU accesses to RAM Volatility Retains data without power Loses data without power Loses data without power Capacity Large, on the TB scale Smaller, on the GB scale Very small, on the MB scale Speed Slower, hundreds to thousands of MB/s Faster, tens of GB/s Very fast, tens to hundreds of GB/s Price (Chinese yuan) Inexpensive, fractions of a yuan to several yuan per GB More expensive, tens to hundreds of yuan per GB Very expensive, priced as part of the CPU

    We can picture computer storage as the pyramid in Figure 4-9. Nearer the top, storage is faster, smaller, and more expensive. This hierarchy is a deliberate design by computer scientists and engineers.

    • RAM cannot readily replace disk storage. It loses data when power is removed, making it unsuitable for long-term storage. It also costs tens of times more than disk storage, making such a replacement impractical for consumers.
    • Cache capacity and speed are difficult to maximize together. From L1 to L2 to L3, increasing capacity means greater physical size and distance from the CPU core, increasing transfer time and access latency. With current technology, a multilevel cache offers the best balance of capacity, speed, and cost.

    Figure 4-9   Computer storage hierarchy

    Tip

    The storage hierarchy balances speed, capacity, and cost. Such tradeoffs occur throughout industry, requiring us to find the best balance among competing strengths and limitations.

    In general, disks store large amounts of data long term, RAM temporarily holds data being processed, and caches hold frequently accessed data and instructions to speed up execution. The three work together to keep the computer efficient.

    As shown in Figure 4-10, a running program reads data from disk into RAM for the CPU to process. The cache can be viewed as part of the CPU: it intelligently loads data from RAM and provides faster access, improving execution speed and reducing dependence on slower memory.

    Figure 4-10   Data flow between disk, RAM, and cache

    ","path":["Chapter 4.   Arrays and linked lists","4.4   Memory and cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#442-memory-efficiency-of-data-structures","level":2,"title":"4.4.2   Memory efficiency of data structures","text":"

    Arrays and linked lists each have strengths and limitations in memory use.

    On one hand, memory is finite, and the same region cannot be shared by multiple programs, so data structures should use it efficiently. Arrays pack elements tightly without extra references (pointers) between nodes, making them more space-efficient. However, they require a sufficiently large contiguous allocation, which can waste memory, and expansion costs extra time and space. Linked lists instead allocate and reclaim memory node by node, providing greater flexibility.

    On the other hand, repeated allocation and deallocation increasingly fragment free memory, reducing its usability. Arrays' contiguous storage is less prone to causing fragmentation. Linked lists store elements in scattered locations, so frequent insertions and deletions are more likely to fragment memory.

    ","path":["Chapter 4.   Arrays and linked lists","4.4   Memory and cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#443-cache-efficiency-of-data-structures","level":2,"title":"4.4.3   Cache efficiency of data structures","text":"

    A cache is much smaller than RAM but far faster, making it crucial to execution speed. Its limited capacity holds only a small portion of frequently accessed data. If the CPU requests data not in the cache, a cache miss occurs, and the data must be fetched from slower RAM.

    Clearly, fewer cache misses mean more efficient CPU reads and writes and better program performance. The fraction of accesses successfully served by the cache is the cache hit rate, a common measure of cache efficiency.

    Caches use the following mechanisms to improve efficiency.

    • Cache lines: Data is stored and loaded in cache-line units rather than one byte at a time, making transfers more efficient.
    • Prefetching: The processor tries to predict access patterns, such as sequential access or fixed strides, and loads matching data into the cache to improve the hit rate.
    • Spatial locality: Accessing data suggests that nearby data may soon be needed, so the cache loads neighboring data as well.
    • Temporal locality: Recently accessed data is likely to be accessed again soon, so keeping it in the cache improves the hit rate.

    Arrays and linked lists use the cache differently, mainly in the following ways.

    • Storage size: Linked list elements require more space, leaving less room for useful data in the cache.
    • Cache lines: Linked list data is scattered, so loading a whole cache line brings in a larger proportion of unneeded data.
    • Prefetching: Array access patterns are more predictable, making it easier to anticipate the next data needed.
    • Spatial locality: Array elements are stored together, so data near a loaded element is more likely to be accessed soon.

    Overall, arrays have higher cache hit rates and usually faster operations than linked lists. This makes array-based structures popular for algorithmic problems.

    However, better cache efficiency does not make arrays superior in every situation. Choose a structure based on the requirements. Both arrays and linked lists can implement a stack, discussed in the next chapter, but suit different situations.

    • For algorithm exercises, an array-based stack is often preferable because of faster operations and random access, at the cost of allocating some memory in advance.
    • If the dataset is very large, changes frequently, and the stack's size is hard to predict, a linked-list-based stack may be more suitable. It can distribute data throughout memory and avoid the extra cost of array expansion.
    ","path":["Chapter 4.   Arrays and linked lists","4.4   Memory and cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/","level":1,"title":"4.5   Summary","text":"","path":["Chapter 4.   Arrays and linked lists","4.5   Summary"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Arrays and linked lists are fundamental structures representing contiguous and scattered memory storage. Their strengths are complementary.
    • Arrays support random access and use less memory, but insertion and deletion are slow, and their length is fixed after initialization.
    • Linked lists efficiently insert and remove nodes by changing references (pointers), and their length is flexible. However, accessing nodes is slow and they use more memory. Common forms include singly linked, circular, and doubly linked lists.
    • A list is an ordered collection supporting insertion, removal, access, and modification. It is usually implemented as a dynamic array, retaining array advantages while allowing its length to change.
    • Lists make arrays much more practical, though they may waste some memory.
    • During execution, data is mainly stored in RAM. Arrays use memory more efficiently, while linked lists offer more flexible allocation.
    • Cache lines, prefetching, spatial locality, and temporal locality provide fast CPU access to data, substantially improving execution speed.
    • Arrays usually outperform linked lists because of higher cache hit rates. Choose a structure according to the actual requirements and setting.
    ","path":["Chapter 4.   Arrays and linked lists","4.5   Summary"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Does storing an array on the stack or heap affect time and space efficiency?

    Both use contiguous memory, so data operations have essentially the same efficiency. However, the stack and heap differ in these ways.

    1. Allocation and deallocation: Stack memory is relatively small and allocated automatically by the compiler. The heap is larger, supports dynamic allocation in code, and is more prone to fragmentation. Heap allocation and deallocation are therefore usually slower.
    2. Size limits: Stack space is relatively small, while the heap is generally limited by available memory, making it better suited to large arrays.
    3. Flexibility: Stack array sizes must be known at compile time, while heap array sizes can be determined at runtime.

    Q: Why must array elements have the same type, while this requirement is not emphasized for linked lists?

    Linked lists consist of nodes connected by references (pointers). Different nodes can hold different types, such as int, double, string, and object.

    Array elements must have the same type so that their positions can be calculated from offsets. If an array contained both int and long, occupying 4 and 8 bytes respectively, the following formula would not work because there would be two element sizes.

    # Element address = array address (first element's address) + element size * element index\n

    Q: After removing node P, must we set P.next to None?

    Leaving P.next unchanged is acceptable. A traversal from the list's head to its tail no longer encounters P, so it has been removed. Whatever P points to no longer affects the list.

    For algorithm exercises, leaving it connected is fine as long as the logic is correct. For a standard library, disconnecting it is safer and clearer: if the removed node is not reclaimed properly, its reference may prevent its successor from being reclaimed.

    Q: Insertion and deletion in a linked list take \\(O(1)\\) time, but locating the element first takes \\(O(n)\\). Why is the overall complexity not \\(O(n)\\)?

    Finding an element and then deleting it does take \\(O(n)\\) time. However, \\(O(1)\\) insertion and deletion are useful in other settings. A deque, for example, can use a linked list with pointers maintained at the head and tail, giving \\(O(1)\\) operations.

    Q: In the linked list storage diagram, does the light-blue pointer occupy a separate memory location, or share half the node's space with its value?

    The diagram is qualitative. Exact sizes depend on the circumstances.

    • Node values of different types, such as int, long, double, and objects, occupy different amounts of space.
    • A pointer's size depends on the operating system and compilation environment, and is usually 8 or 4 bytes.

    Q: Does appending to a list always take \\(O(1)\\) time?

    If appending exceeds the available capacity, the list must first expand. The system allocates new memory and copies all existing elements, making that operation \\(O(n)\\).

    Q: When lists waste memory, does this mean the extra variables for capacity, length, and growth factor?

    The main waste has two sources. First, the initial allocation may exceed what we need. Second, to avoid frequent resizing, capacity usually grows by a factor such as \\(\\times 1.5\\), leaving empty slots that may never all be filled.

    Q: In Python, after n = [1, 2, 3], the three elements seem to have consecutive addresses. But in m = [2, 1, 3], their IDs are not consecutive and match those in n. Is m still an array?

    Consider storing linked list nodes instead: n = [n1, n2, n3, n4, n5]. These five objects are usually scattered in memory, yet an index still gives a node's address in \\(O(1)\\) time. The array stores references to the nodes, not the nodes themselves.

    Unlike many languages, Python also wraps numbers in objects. A list stores references to those objects rather than the numbers themselves. Thus, the same numbers in the two arrays can have the same IDs, and their addresses need not be contiguous.

    Q: C++ STL's std::list already implements a doubly linked list. Why do algorithm books rarely use it directly? Does it have limitations?

    First, we often favor arrays and use linked lists only when needed, mainly for two reasons.

    • Space overhead: Each element needs two extra pointers, to its predecessor and successor, so std::list usually consumes more space than std::vector.
    • Poor cache locality: Its data is not contiguous, making cache use less efficient. In general, std::vector performs better.

    Second, linked structures are mainly needed for binary trees and graphs. For stacks and queues, we usually use the language's provided stack and queue rather than a linked list directly.

    Q: Does res = [[0]] * n create a two-dimensional list with independent [0] entries?

    No. Every [0] is a reference to the same object. Modifying an element through one reference changes the corresponding element seen through all the others.

    To create independent [0] lists, use res = [[0] for _ in range(n)]. This initializes \\(n\\) separate list objects.

    Q: Does res = [0] * n create independent integer zeros?

    All zeros in the list refer to the same object. Python caches small integers, usually -5 through 256, to maximize reuse and improve performance.

    Even so, each list entry can be changed independently because Python integers are immutable. Assigning a new value switches the entry to a different object reference instead of modifying the original object.

    When an element is mutable, such as a list, dictionary, or class instance, modifying the object itself is visible through every reference to it.

    ","path":["Chapter 4.   Arrays and linked lists","4.5   Summary"],"tags":[]},{"location":"chapter_backtracking/","level":1,"title":"Chapter 13.   Backtracking","text":"

    Abstract

    Like explorers in a maze, we may encounter obstacles as we move forward.

    Backtracking lets us start again, try new paths, and eventually find our way out into the light.

    ","path":["Chapter 13.   Backtracking"],"tags":[]},{"location":"chapter_backtracking/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 13.1   Backtracking algorithms
    • 13.2   Permutations problem
    • 13.3   Subset sum problem
    • 13.4   N-queens problem
    • 13.5   Summary
    • 13.6   Exercises
    ","path":["Chapter 13.   Backtracking"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/","level":1,"title":"13.1   Backtracking algorithms","text":"

    A backtracking algorithm solves a problem through exhaustive search. Starting from an initial state, it explores possible solutions by brute force, recording valid solutions as it finds them, until a solution is found or every possible choice has been tried without success.

    Backtracking usually traverses the solution space through depth-first search. In the binary tree chapter, we learned that preorder, inorder, and postorder traversal are all forms of depth-first search. We will use preorder traversal to build a backtracking problem and explore the approach step by step.

    Example 1

    Given a binary tree, find and record every node with value \\(7\\). Return a list of these nodes.

    Traverse the tree in preorder and check whether the current node's value is \\(7\\). If so, add that node's value to the result list res. The code and figure below show the process:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_i_compact.py
    def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: example 1\"\"\"\n    if root is None:\n        return\n    if root.val == 7:\n        # Record the solution\n        res.append(root)\n    pre_order(root.left)\n    pre_order(root.right)\n
    preorder_traversal_i_compact.cpp
    /* Preorder traversal: example 1 */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr) {\n        return;\n    }\n    if (root->val == 7) {\n        // Record the solution\n        res.push_back(root);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n}\n
    preorder_traversal_i_compact.java
    /* Preorder traversal: example 1 */\nvoid preOrder(TreeNode root) {\n    if (root == null) {\n        return;\n    }\n    if (root.val == 7) {\n        // Record the solution\n        res.add(root);\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n}\n
    preorder_traversal_i_compact.cs
    /* Preorder traversal: example 1 */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) {\n        return;\n    }\n    if (root.val == 7) {\n        // Record the solution\n        res.Add(root);\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n}\n
    preorder_traversal_i_compact.go
    /* Preorder traversal: example 1 */\nfunc preOrderI(root *TreeNode, res *[]*TreeNode) {\n    if root == nil {\n        return\n    }\n    if (root.Val).(int) == 7 {\n        // Record the solution\n        *res = append(*res, root)\n    }\n    preOrderI(root.Left, res)\n    preOrderI(root.Right, res)\n}\n
    preorder_traversal_i_compact.swift
    /* Preorder traversal: example 1 */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    if root.val == 7 {\n        // Record the solution\n        res.append(root)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n}\n
    preorder_traversal_i_compact.js
    /* Preorder traversal: example 1 */\nfunction preOrder(root, res) {\n    if (root === null) {\n        return;\n    }\n    if (root.val === 7) {\n        // Record the solution\n        res.push(root);\n    }\n    preOrder(root.left, res);\n    preOrder(root.right, res);\n}\n
    preorder_traversal_i_compact.ts
    /* Preorder traversal: example 1 */\nfunction preOrder(root: TreeNode | null, res: TreeNode[]): void {\n    if (root === null) {\n        return;\n    }\n    if (root.val === 7) {\n        // Record the solution\n        res.push(root);\n    }\n    preOrder(root.left, res);\n    preOrder(root.right, res);\n}\n
    preorder_traversal_i_compact.dart
    /* Preorder traversal: example 1 */\nvoid preOrder(TreeNode? root, List<TreeNode> res) {\n  if (root == null) {\n    return;\n  }\n  if (root.val == 7) {\n    // Record the solution\n    res.add(root);\n  }\n  preOrder(root.left, res);\n  preOrder(root.right, res);\n}\n
    preorder_traversal_i_compact.rs
    /* Preorder traversal: example 1 */\nfn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<&Rc<RefCell<TreeNode>>>) {\n    if root.is_none() {\n        return;\n    }\n    if let Some(node) = root {\n        if node.borrow().val == 7 {\n            // Record the solution\n            res.push(node.clone());\n        }\n        pre_order(res, node.borrow().left.as_ref());\n        pre_order(res, node.borrow().right.as_ref());\n    }\n}\n
    preorder_traversal_i_compact.c
    /* Preorder traversal: example 1 */\nvoid preOrder(TreeNode *root) {\n    if (root == NULL) {\n        return;\n    }\n    if (root->val == 7) {\n        // Record the solution\n        res[resSize++] = root;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n}\n
    preorder_traversal_i_compact.kt
    /* Preorder traversal: example 1 */\nfun preOrder(root: TreeNode?) {\n    if (root == null) {\n        return\n    }\n    if (root._val == 7) {\n        // Record the solution\n        res!!.add(root)\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n}\n
    preorder_traversal_i_compact.rb
    ### Preorder traversal: example 1 ###\ndef pre_order(root)\n  return unless root\n\n  # Record the solution\n  $res << root if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\nend\n
    Visualize execution

    View full screen >

    Figure 13-1   Finding nodes during preorder traversal

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1311-trying-choices-and-backtracking","level":2,"title":"13.1.1   Trying choices and backtracking","text":"

    Backtracking gets its name from its pattern of trying choices and undoing them while searching the solution space. When the search reaches a state that cannot advance or lead to a valid solution, it undoes the last choice, returns to the previous state, and tries other possibilities.

    In Example 1, visiting a node is a trial, while a return after passing a leaf or returning to a parent is backtracking.

    Backtracking involves more than returning from a function. To see why, let's extend Example 1.

    Example 2

    Find every node with value \\(7\\) in a binary tree, and return the paths from the root to those nodes.

    Extend the first solution with a list path recording the current path of visited nodes. When a node with value \\(7\\) is reached, append a copy of path to the result list res. After traversal, res contains all solutions. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_ii_compact.py
    def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: example 2\"\"\"\n    if root is None:\n        return\n    # Try\n    path.append(root)\n    if root.val == 7:\n        # Record the solution\n        res.append(list(path))\n    pre_order(root.left)\n    pre_order(root.right)\n    # Backtrack\n    path.pop()\n
    preorder_traversal_ii_compact.cpp
    /* Preorder traversal: example 2 */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr) {\n        return;\n    }\n    // Try\n    path.push_back(root);\n    if (root->val == 7) {\n        // Record the solution\n        res.push_back(path);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    path.pop_back();\n}\n
    preorder_traversal_ii_compact.java
    /* Preorder traversal: example 2 */\nvoid preOrder(TreeNode root) {\n    if (root == null) {\n        return;\n    }\n    // Try\n    path.add(root);\n    if (root.val == 7) {\n        // Record the solution\n        res.add(new ArrayList<>(path));\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n    // Backtrack\n    path.remove(path.size() - 1);\n}\n
    preorder_traversal_ii_compact.cs
    /* Preorder traversal: example 2 */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) {\n        return;\n    }\n    // Try\n    path.Add(root);\n    if (root.val == 7) {\n        // Record the solution\n        res.Add(new List<TreeNode>(path));\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n    // Backtrack\n    path.RemoveAt(path.Count - 1);\n}\n
    preorder_traversal_ii_compact.go
    /* Preorder traversal: example 2 */\nfunc preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n    if root == nil {\n        return\n    }\n    // Try\n    *path = append(*path, root)\n    if root.Val.(int) == 7 {\n        // Record the solution\n        *res = append(*res, append([]*TreeNode{}, *path...))\n    }\n    preOrderII(root.Left, res, path)\n    preOrderII(root.Right, res, path)\n    // Backtrack\n    *path = (*path)[:len(*path)-1]\n}\n
    preorder_traversal_ii_compact.swift
    /* Preorder traversal: example 2 */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Try\n    path.append(root)\n    if root.val == 7 {\n        // Record the solution\n        res.append(path)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n    // Backtrack\n    path.removeLast()\n}\n
    preorder_traversal_ii_compact.js
    /* Preorder traversal: example 2 */\nfunction preOrder(root, path, res) {\n    if (root === null) {\n        return;\n    }\n    // Try\n    path.push(root);\n    if (root.val === 7) {\n        // Record the solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
    preorder_traversal_ii_compact.ts
    /* Preorder traversal: example 2 */\nfunction preOrder(\n    root: TreeNode | null,\n    path: TreeNode[],\n    res: TreeNode[][]\n): void {\n    if (root === null) {\n        return;\n    }\n    // Try\n    path.push(root);\n    if (root.val === 7) {\n        // Record the solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
    preorder_traversal_ii_compact.dart
    /* Preorder traversal: example 2 */\nvoid preOrder(\n  TreeNode? root,\n  List<TreeNode> path,\n  List<List<TreeNode>> res,\n) {\n  if (root == null) {\n    return;\n  }\n\n  // Try\n  path.add(root);\n  if (root.val == 7) {\n    // Record the solution\n    res.add(List.from(path));\n  }\n  preOrder(root.left, path, res);\n  preOrder(root.right, path, res);\n  // Backtrack\n  path.removeLast();\n}\n
    preorder_traversal_ii_compact.rs
    /* Preorder traversal: example 2 */\nfn pre_order(\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n    path: &mut Vec<Rc<RefCell<TreeNode>>>,\n    root: Option<&Rc<RefCell<TreeNode>>>,\n) {\n    if root.is_none() {\n        return;\n    }\n    if let Some(node) = root {\n        // Try\n        path.push(node.clone());\n        if node.borrow().val == 7 {\n            // Record the solution\n            res.push(path.clone());\n        }\n        pre_order(res, path, node.borrow().left.as_ref());\n        pre_order(res, path, node.borrow().right.as_ref());\n        // Backtrack\n        path.pop();\n    }\n}\n
    preorder_traversal_ii_compact.c
    /* Preorder traversal: example 2 */\nvoid preOrder(TreeNode *root) {\n    if (root == NULL) {\n        return;\n    }\n    // Try\n    path[pathSize++] = root;\n    if (root->val == 7) {\n        // Record the solution\n        for (int i = 0; i < pathSize; ++i) {\n            res[resSize][i] = path[i];\n        }\n        resSize++;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    pathSize--;\n}\n
    preorder_traversal_ii_compact.kt
    /* Preorder traversal: example 2 */\nfun preOrder(root: TreeNode?) {\n    if (root == null) {\n        return\n    }\n    // Try\n    path!!.add(root)\n    if (root._val == 7) {\n        // Record the solution\n        res!!.add(path!!.toMutableList())\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n    // Backtrack\n    path!!.removeAt(path!!.size - 1)\n}\n
    preorder_traversal_ii_compact.rb
    ### Preorder traversal: example 2 ###\ndef pre_order(root)\n  return unless root\n\n  # Try\n  $path << root\n\n  # Record the solution\n  $res << $path.dup if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\n\n  # Backtrack\n  $path.pop\nend\n
    Visualize execution

    View full screen >

    Each time we try a node, append it to path. Before backtracking, pop that node from path to restore the state from before the trial.

    As Figure 13-2 illustrates, trying a choice and backtracking are like moving forward and undoing that move: they are inverse operations.

    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 13-2   Trying choices and backtracking

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1312-pruning","level":2,"title":"13.1.2   Pruning","text":"

    Complex backtracking problems usually have one or more constraints, which can often be used for pruning.

    Example 3

    Find every node with value \\(7\\) in a binary tree and return the paths from the root to those nodes, requiring that no path contain a node with value \\(3\\).

    To enforce this constraint, add a pruning step: when a node with value \\(3\\) is encountered, return immediately without searching farther. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_compact.py
    def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: example 3\"\"\"\n    # Prune\n    if root is None or root.val == 3:\n        return\n    # Try\n    path.append(root)\n    if root.val == 7:\n        # Record the solution\n        res.append(list(path))\n    pre_order(root.left)\n    pre_order(root.right)\n    # Backtrack\n    path.pop()\n
    preorder_traversal_iii_compact.cpp
    /* Preorder traversal: example 3 */\nvoid preOrder(TreeNode *root) {\n    // Prune\n    if (root == nullptr || root->val == 3) {\n        return;\n    }\n    // Try\n    path.push_back(root);\n    if (root->val == 7) {\n        // Record the solution\n        res.push_back(path);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    path.pop_back();\n}\n
    preorder_traversal_iii_compact.java
    /* Preorder traversal: example 3 */\nvoid preOrder(TreeNode root) {\n    // Prune\n    if (root == null || root.val == 3) {\n        return;\n    }\n    // Try\n    path.add(root);\n    if (root.val == 7) {\n        // Record the solution\n        res.add(new ArrayList<>(path));\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n    // Backtrack\n    path.remove(path.size() - 1);\n}\n
    preorder_traversal_iii_compact.cs
    /* Preorder traversal: example 3 */\nvoid PreOrder(TreeNode? root) {\n    // Prune\n    if (root == null || root.val == 3) {\n        return;\n    }\n    // Try\n    path.Add(root);\n    if (root.val == 7) {\n        // Record the solution\n        res.Add(new List<TreeNode>(path));\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n    // Backtrack\n    path.RemoveAt(path.Count - 1);\n}\n
    preorder_traversal_iii_compact.go
    /* Preorder traversal: example 3 */\nfunc preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n    // Prune\n    if root == nil || root.Val == 3 {\n        return\n    }\n    // Try\n    *path = append(*path, root)\n    if root.Val.(int) == 7 {\n        // Record the solution\n        *res = append(*res, append([]*TreeNode{}, *path...))\n    }\n    preOrderIII(root.Left, res, path)\n    preOrderIII(root.Right, res, path)\n    // Backtrack\n    *path = (*path)[:len(*path)-1]\n}\n
    preorder_traversal_iii_compact.swift
    /* Preorder traversal: example 3 */\nfunc preOrder(root: TreeNode?) {\n    // Prune\n    guard let root = root, root.val != 3 else {\n        return\n    }\n    // Try\n    path.append(root)\n    if root.val == 7 {\n        // Record the solution\n        res.append(path)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n    // Backtrack\n    path.removeLast()\n}\n
    preorder_traversal_iii_compact.js
    /* Preorder traversal: example 3 */\nfunction preOrder(root, path, res) {\n    // Prune\n    if (root === null || root.val === 3) {\n        return;\n    }\n    // Try\n    path.push(root);\n    if (root.val === 7) {\n        // Record the solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
    preorder_traversal_iii_compact.ts
    /* Preorder traversal: example 3 */\nfunction preOrder(\n    root: TreeNode | null,\n    path: TreeNode[],\n    res: TreeNode[][]\n): void {\n    // Prune\n    if (root === null || root.val === 3) {\n        return;\n    }\n    // Try\n    path.push(root);\n    if (root.val === 7) {\n        // Record the solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
    preorder_traversal_iii_compact.dart
    /* Preorder traversal: example 3 */\nvoid preOrder(\n  TreeNode? root,\n  List<TreeNode> path,\n  List<List<TreeNode>> res,\n) {\n  if (root == null || root.val == 3) {\n    return;\n  }\n\n  // Try\n  path.add(root);\n  if (root.val == 7) {\n    // Record the solution\n    res.add(List.from(path));\n  }\n  preOrder(root.left, path, res);\n  preOrder(root.right, path, res);\n  // Backtrack\n  path.removeLast();\n}\n
    preorder_traversal_iii_compact.rs
    /* Preorder traversal: example 3 */\nfn pre_order(\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n    path: &mut Vec<Rc<RefCell<TreeNode>>>,\n    root: Option<&Rc<RefCell<TreeNode>>>,\n) {\n    // Prune\n    if root.is_none() || root.as_ref().unwrap().borrow().val == 3 {\n        return;\n    }\n    if let Some(node) = root {\n        // Try\n        path.push(node.clone());\n        if node.borrow().val == 7 {\n            // Record the solution\n            res.push(path.clone());\n        }\n        pre_order(res, path, node.borrow().left.as_ref());\n        pre_order(res, path, node.borrow().right.as_ref());\n        // Backtrack\n        path.pop();\n    }\n}\n
    preorder_traversal_iii_compact.c
    /* Preorder traversal: example 3 */\nvoid preOrder(TreeNode *root) {\n    // Prune\n    if (root == NULL || root->val == 3) {\n        return;\n    }\n    // Try\n    path[pathSize++] = root;\n    if (root->val == 7) {\n        // Record the solution\n        for (int i = 0; i < pathSize; i++) {\n            res[resSize][i] = path[i];\n        }\n        resSize++;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    pathSize--;\n}\n
    preorder_traversal_iii_compact.kt
    /* Preorder traversal: example 3 */\nfun preOrder(root: TreeNode?) {\n    // Prune\n    if (root == null || root._val == 3) {\n        return\n    }\n    // Try\n    path!!.add(root)\n    if (root._val == 7) {\n        // Record the solution\n        res!!.add(path!!.toMutableList())\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n    // Backtrack\n    path!!.removeAt(path!!.size - 1)\n}\n
    preorder_traversal_iii_compact.rb
    ### Preorder traversal: example 3 ###\ndef pre_order(root)\n  # Prune\n  return if !root || root.val == 3\n\n  # Try\n  $path.append(root)\n\n  # Record the solution\n  $res << $path.dup if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\n\n  # Backtrack\n  $path.pop\nend\n
    Visualize execution

    View full screen >

    Pruning is an apt name. As shown in Figure 13-3, we cut off search branches that violate the constraints, avoiding pointless trials and improving efficiency.

    Figure 13-3   Pruning according to constraints

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1313-a-code-template","level":2,"title":"13.1.3   A code template","text":"

    We will now extract the core pattern of trying, undoing, and pruning into a more general backtracking template.

    In the following template, state is the current problem state, and choices contains the choices available from that state:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def backtrack(state: State, choices: list[choice], res: list[state]):\n    \"\"\"Backtracking template\"\"\"\n    # Check whether the state is a solution\n    if is_solution(state):\n        # Record the solution\n        record_solution(state, res)\n        # Stop searching this branch\n        return\n    # Try every choice\n    for choice in choices:\n        # Prune: check whether the choice is valid\n        if is_valid(state, choice):\n            # Try: make the choice and update the state\n            make_choice(state, choice)\n            backtrack(state, choices, res)\n            # Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice)\n
    /* Backtracking template */\nvoid backtrack(State *state, vector<Choice *> &choices, vector<State *> &res) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (Choice choice : choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nvoid backtrack(State state, List<Choice> choices, List<State> res) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (Choice choice : choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nvoid Backtrack(State state, List<Choice> choices, List<State> res) {\n    // Check whether the state is a solution\n    if (IsSolution(state)) {\n        // Record the solution\n        RecordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    foreach (Choice choice in choices) {\n        // Prune: check whether the choice is valid\n        if (IsValid(state, choice)) {\n            // Try: make the choice and update the state\n            MakeChoice(state, choice);\n            Backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            UndoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nfunc backtrack(state *State, choices []Choice, res *[]State) {\n    // Check whether the state is a solution\n    if isSolution(state) {\n        // Record the solution\n        recordSolution(state, res)\n        // Stop searching this branch\n        return\n    }\n    // Try every choice\n    for _, choice := range choices {\n        // Prune: check whether the choice is valid\n        if isValid(state, choice) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice)\n            backtrack(state, choices, res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
    /* Backtracking template */\nfunc backtrack(state: inout State, choices: [Choice], res: inout [State]) {\n    // Check whether the state is a solution\n    if isSolution(state: state) {\n        // Record the solution\n        recordSolution(state: state, res: &res)\n        // Stop searching this branch\n        return\n    }\n    // Try every choice\n    for choice in choices {\n        // Prune: check whether the choice is valid\n        if isValid(state: state, choice: choice) {\n            // Try: make the choice and update the state\n            makeChoice(state: &state, choice: choice)\n            backtrack(state: &state, choices: choices, res: &res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state: &state, choice: choice)\n        }\n    }\n}\n
    /* Backtracking template */\nfunction backtrack(state, choices, res) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (let choice of choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nfunction backtrack(state: State, choices: Choice[], res: State[]): void {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (let choice of choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nvoid backtrack(State state, List<Choice>, List<State> res) {\n  // Check whether the state is a solution\n  if (isSolution(state)) {\n    // Record the solution\n    recordSolution(state, res);\n    // Stop searching this branch\n    return;\n  }\n  // Try every choice\n  for (Choice choice in choices) {\n    // Prune: check whether the choice is valid\n    if (isValid(state, choice)) {\n      // Try: make the choice and update the state\n      makeChoice(state, choice);\n      backtrack(state, choices, res);\n      // Backtrack: undo the choice and restore the previous state\n      undoChoice(state, choice);\n    }\n  }\n}\n
    /* Backtracking template */\nfn backtrack(state: &mut State, choices: &Vec<Choice>, res: &mut Vec<State>) {\n    // Check whether the state is a solution\n    if is_solution(state) {\n        // Record the solution\n        record_solution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for choice in choices {\n        // Prune: check whether the choice is valid\n        if is_valid(state, choice) {\n            // Try: make the choice and update the state\n            make_choice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nvoid backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res, numRes);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (int i = 0; i < numChoices; i++) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, &choices[i])) {\n            // Try: make the choice and update the state\n            makeChoice(state, &choices[i]);\n            backtrack(state, choices, numChoices, res, numRes);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, &choices[i]);\n        }\n    }\n}\n
    /* Backtracking template */\nfun backtrack(state: State?, choices: List<Choice?>, res: List<State?>?) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res)\n        // Stop searching this branch\n        return\n    }\n    // Try every choice\n    for (choice in choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice)\n            backtrack(state, choices, res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
    ### Backtracking template ###\ndef backtrack(state, choices, res)\n    # Check whether the state is a solution\n    if is_solution?(state)\n        # Record the solution\n        record_solution(state, res)\n        return\n    end\n\n    # Try every choice\n    for choice in choices\n        # Prune: check whether the choice is valid\n        if is_valid?(state, choice)\n            # Try: make the choice and update the state\n            make_choice(state, choice)\n            backtrack(state, choices, res)\n            # Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice)\n        end\n    end\nend\n

    Now apply this template to Example 3. The state is the current path of visited nodes, choices consists of the current node's left and right children, and res is the list of paths:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_template.py
    def is_solution(state: list[TreeNode]) -> bool:\n    \"\"\"Check whether the current state is a solution\"\"\"\n    return state and state[-1].val == 7\n\ndef record_solution(state: list[TreeNode], res: list[list[TreeNode]]):\n    \"\"\"Record the solution\"\"\"\n    res.append(list(state))\n\ndef is_valid(state: list[TreeNode], choice: TreeNode) -> bool:\n    \"\"\"Check whether the choice is valid in the current state\"\"\"\n    return choice is not None and choice.val != 3\n\ndef make_choice(state: list[TreeNode], choice: TreeNode):\n    \"\"\"Update the state\"\"\"\n    state.append(choice)\n\ndef undo_choice(state: list[TreeNode], choice: TreeNode):\n    \"\"\"Restore the state\"\"\"\n    state.pop()\n\ndef backtrack(\n    state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]\n):\n    \"\"\"Backtracking: example 3\"\"\"\n    # Check whether this is a solution\n    if is_solution(state):\n        # Record the solution\n        record_solution(state, res)\n    # Iterate over all choices\n    for choice in choices:\n        # Prune: check whether the choice is valid\n        if is_valid(state, choice):\n            # Try: make a choice and update the state\n            make_choice(state, choice)\n            # Proceed to the next choice\n            backtrack(state, [choice.left, choice.right], res)\n            # Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice)\n
    preorder_traversal_iii_template.cpp
    /* Check whether the current state is a solution */\nbool isSolution(vector<TreeNode *> &state) {\n    return !state.empty() && state.back()->val == 7;\n}\n\n/* Record the solution */\nvoid recordSolution(vector<TreeNode *> &state, vector<vector<TreeNode *>> &res) {\n    res.push_back(state);\n}\n\n/* Check whether the choice is valid in the current state */\nbool isValid(vector<TreeNode *> &state, TreeNode *choice) {\n    return choice != nullptr && choice->val != 3;\n}\n\n/* Update the state */\nvoid makeChoice(vector<TreeNode *> &state, TreeNode *choice) {\n    state.push_back(choice);\n}\n\n/* Restore the state */\nvoid undoChoice(vector<TreeNode *> &state, TreeNode *choice) {\n    state.pop_back();\n}\n\n/* Backtracking: example 3 */\nvoid backtrack(vector<TreeNode *> &state, vector<TreeNode *> &choices, vector<vector<TreeNode *>> &res) {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n    }\n    // Iterate over all choices\n    for (TreeNode *choice : choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice);\n            // Proceed to the next choice\n            vector<TreeNode *> nextChoices{choice->left, choice->right};\n            backtrack(state, nextChoices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    preorder_traversal_iii_template.java
    /* Check whether the current state is a solution */\nboolean isSolution(List<TreeNode> state) {\n    return !state.isEmpty() && state.get(state.size() - 1).val == 7;\n}\n\n/* Record the solution */\nvoid recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n    res.add(new ArrayList<>(state));\n}\n\n/* Check whether the choice is valid in the current state */\nboolean isValid(List<TreeNode> state, TreeNode choice) {\n    return choice != null && choice.val != 3;\n}\n\n/* Update the state */\nvoid makeChoice(List<TreeNode> state, TreeNode choice) {\n    state.add(choice);\n}\n\n/* Restore the state */\nvoid undoChoice(List<TreeNode> state, TreeNode choice) {\n    state.remove(state.size() - 1);\n}\n\n/* Backtracking: example 3 */\nvoid backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n    }\n    // Iterate over all choices\n    for (TreeNode choice : choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice);\n            // Proceed to the next choice\n            backtrack(state, Arrays.asList(choice.left, choice.right), res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    preorder_traversal_iii_template.cs
    /* Check whether the current state is a solution */\nbool IsSolution(List<TreeNode> state) {\n    return state.Count != 0 && state[^1].val == 7;\n}\n\n/* Record the solution */\nvoid RecordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n    res.Add(new List<TreeNode>(state));\n}\n\n/* Check whether the choice is valid in the current state */\nbool IsValid(List<TreeNode> state, TreeNode choice) {\n    return choice != null && choice.val != 3;\n}\n\n/* Update the state */\nvoid MakeChoice(List<TreeNode> state, TreeNode choice) {\n    state.Add(choice);\n}\n\n/* Restore the state */\nvoid UndoChoice(List<TreeNode> state, TreeNode choice) {\n    state.RemoveAt(state.Count - 1);\n}\n\n/* Backtracking: example 3 */\nvoid Backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {\n    // Check whether this is a solution\n    if (IsSolution(state)) {\n        // Record the solution\n        RecordSolution(state, res);\n    }\n    // Iterate over all choices\n    foreach (TreeNode choice in choices) {\n        // Prune: check whether the choice is valid\n        if (IsValid(state, choice)) {\n            // Try: make a choice and update the state\n            MakeChoice(state, choice);\n            // Proceed to the next choice\n            Backtrack(state, [choice.left!, choice.right!], res);\n            // Backtrack: undo the choice and restore the previous state\n            UndoChoice(state, choice);\n        }\n    }\n}\n
    preorder_traversal_iii_template.go
    /* Check whether the current state is a solution */\nfunc isSolution(state *[]*TreeNode) bool {\n    return len(*state) != 0 && (*state)[len(*state)-1].Val == 7\n}\n\n/* Record the solution */\nfunc recordSolution(state *[]*TreeNode, res *[][]*TreeNode) {\n    *res = append(*res, append([]*TreeNode{}, *state...))\n}\n\n/* Check whether the choice is valid in the current state */\nfunc isValid(state *[]*TreeNode, choice *TreeNode) bool {\n    return choice != nil && choice.Val != 3\n}\n\n/* Update the state */\nfunc makeChoice(state *[]*TreeNode, choice *TreeNode) {\n    *state = append(*state, choice)\n}\n\n/* Restore the state */\nfunc undoChoice(state *[]*TreeNode, choice *TreeNode) {\n    *state = (*state)[:len(*state)-1]\n}\n\n/* Backtracking: example 3 */\nfunc backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) {\n    // Check whether this is a solution\n    if isSolution(state) {\n        // Record the solution\n        recordSolution(state, res)\n    }\n    // Iterate over all choices\n    for _, choice := range *choices {\n        // Prune: check whether the choice is valid\n        if isValid(state, choice) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice)\n            // Proceed to the next choice\n            temp := make([]*TreeNode, 0)\n            temp = append(temp, choice.Left, choice.Right)\n            backtrackIII(state, &temp, res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
    preorder_traversal_iii_template.swift
    /* Check whether the current state is a solution */\nfunc isSolution(state: [TreeNode]) -> Bool {\n    !state.isEmpty && state.last!.val == 7\n}\n\n/* Record the solution */\nfunc recordSolution(state: [TreeNode], res: inout [[TreeNode]]) {\n    res.append(state)\n}\n\n/* Check whether the choice is valid in the current state */\nfunc isValid(state: [TreeNode], choice: TreeNode?) -> Bool {\n    choice != nil && choice!.val != 3\n}\n\n/* Update the state */\nfunc makeChoice(state: inout [TreeNode], choice: TreeNode) {\n    state.append(choice)\n}\n\n/* Restore the state */\nfunc undoChoice(state: inout [TreeNode], choice: TreeNode) {\n    state.removeLast()\n}\n\n/* Backtracking: example 3 */\nfunc backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) {\n    // Check whether this is a solution\n    if isSolution(state: state) {\n        recordSolution(state: state, res: &res)\n    }\n    // Iterate over all choices\n    for choice in choices {\n        // Prune: check whether the choice is valid\n        if isValid(state: state, choice: choice) {\n            // Try: make a choice and update the state\n            makeChoice(state: &state, choice: choice)\n            // Proceed to the next choice\n            backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state: &state, choice: choice)\n        }\n    }\n}\n
    preorder_traversal_iii_template.js
    /* Check whether the current state is a solution */\nfunction isSolution(state) {\n    return state && state[state.length - 1]?.val === 7;\n}\n\n/* Record the solution */\nfunction recordSolution(state, res) {\n    res.push([...state]);\n}\n\n/* Check whether the choice is valid in the current state */\nfunction isValid(state, choice) {\n    return choice !== null && choice.val !== 3;\n}\n\n/* Update the state */\nfunction makeChoice(state, choice) {\n    state.push(choice);\n}\n\n/* Restore the state */\nfunction undoChoice(state) {\n    state.pop();\n}\n\n/* Backtracking: example 3 */\nfunction backtrack(state, choices, res) {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n    }\n    // Iterate over all choices\n    for (const choice of choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice);\n            // Proceed to the next choice\n            backtrack(state, [choice.left, choice.right], res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state);\n        }\n    }\n}\n
    preorder_traversal_iii_template.ts
    /* Check whether the current state is a solution */\nfunction isSolution(state: TreeNode[]): boolean {\n    return state && state[state.length - 1]?.val === 7;\n}\n\n/* Record the solution */\nfunction recordSolution(state: TreeNode[], res: TreeNode[][]): void {\n    res.push([...state]);\n}\n\n/* Check whether the choice is valid in the current state */\nfunction isValid(state: TreeNode[], choice: TreeNode): boolean {\n    return choice !== null && choice.val !== 3;\n}\n\n/* Update the state */\nfunction makeChoice(state: TreeNode[], choice: TreeNode): void {\n    state.push(choice);\n}\n\n/* Restore the state */\nfunction undoChoice(state: TreeNode[]): void {\n    state.pop();\n}\n\n/* Backtracking: example 3 */\nfunction backtrack(\n    state: TreeNode[],\n    choices: TreeNode[],\n    res: TreeNode[][]\n): void {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n    }\n    // Iterate over all choices\n    for (const choice of choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice);\n            // Proceed to the next choice\n            backtrack(state, [choice.left, choice.right], res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state);\n        }\n    }\n}\n
    preorder_traversal_iii_template.dart
    /* Check whether the current state is a solution */\nbool isSolution(List<TreeNode> state) {\n  return state.isNotEmpty && state.last.val == 7;\n}\n\n/* Record the solution */\nvoid recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n  res.add(List.from(state));\n}\n\n/* Check whether the choice is valid in the current state */\nbool isValid(List<TreeNode> state, TreeNode? choice) {\n  return choice != null && choice.val != 3;\n}\n\n/* Update the state */\nvoid makeChoice(List<TreeNode> state, TreeNode? choice) {\n  state.add(choice!);\n}\n\n/* Restore the state */\nvoid undoChoice(List<TreeNode> state, TreeNode? choice) {\n  state.removeLast();\n}\n\n/* Backtracking: example 3 */\nvoid backtrack(\n  List<TreeNode> state,\n  List<TreeNode?> choices,\n  List<List<TreeNode>> res,\n) {\n  // Check whether this is a solution\n  if (isSolution(state)) {\n    // Record the solution\n    recordSolution(state, res);\n  }\n  // Iterate over all choices\n  for (TreeNode? choice in choices) {\n    // Prune: check whether the choice is valid\n    if (isValid(state, choice)) {\n      // Try: make a choice and update the state\n      makeChoice(state, choice);\n      // Proceed to the next choice\n      backtrack(state, [choice!.left, choice.right], res);\n      // Backtrack: undo the choice and restore the previous state\n      undoChoice(state, choice);\n    }\n  }\n}\n
    preorder_traversal_iii_template.rs
    /* Check whether the current state is a solution */\nfn is_solution(state: &mut Vec<Rc<RefCell<TreeNode>>>) -> bool {\n    return !state.is_empty() && state.last().unwrap().borrow().val == 7;\n}\n\n/* Record the solution */\nfn record_solution(\n    state: &mut Vec<Rc<RefCell<TreeNode>>>,\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n) {\n    res.push(state.clone());\n}\n\n/* Check whether the choice is valid in the current state */\nfn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Option<&Rc<RefCell<TreeNode>>>) -> bool {\n    return choice.is_some() && choice.unwrap().borrow().val != 3;\n}\n\n/* Update the state */\nfn make_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Rc<RefCell<TreeNode>>) {\n    state.push(choice);\n}\n\n/* Restore the state */\nfn undo_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, _: Rc<RefCell<TreeNode>>) {\n    state.pop();\n}\n\n/* Backtracking: example 3 */\nfn backtrack(\n    state: &mut Vec<Rc<RefCell<TreeNode>>>,\n    choices: &Vec<Option<&Rc<RefCell<TreeNode>>>>,\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n) {\n    // Check whether this is a solution\n    if is_solution(state) {\n        // Record the solution\n        record_solution(state, res);\n    }\n    // Iterate over all choices\n    for &choice in choices.iter() {\n        // Prune: check whether the choice is valid\n        if is_valid(state, choice) {\n            // Try: make a choice and update the state\n            make_choice(state, choice.unwrap().clone());\n            // Proceed to the next choice\n            backtrack(\n                state,\n                &vec![\n                    choice.unwrap().borrow().left.as_ref(),\n                    choice.unwrap().borrow().right.as_ref(),\n                ],\n                res,\n            );\n            // Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice.unwrap().clone());\n        }\n    }\n}\n
    preorder_traversal_iii_template.c
    /* Check whether the current state is a solution */\nbool isSolution(void) {\n    return pathSize > 0 && path[pathSize - 1]->val == 7;\n}\n\n/* Record the solution */\nvoid recordSolution(void) {\n    for (int i = 0; i < pathSize; i++) {\n        res[resSize][i] = path[i];\n    }\n    resSize++;\n}\n\n/* Check whether the choice is valid in the current state */\nbool isValid(TreeNode *choice) {\n    return choice != NULL && choice->val != 3;\n}\n\n/* Update the state */\nvoid makeChoice(TreeNode *choice) {\n    path[pathSize++] = choice;\n}\n\n/* Restore the state */\nvoid undoChoice(void) {\n    pathSize--;\n}\n\n/* Backtracking: example 3 */\nvoid backtrack(TreeNode *choices[2]) {\n    // Check whether this is a solution\n    if (isSolution()) {\n        // Record the solution\n        recordSolution();\n    }\n    // Iterate over all choices\n    for (int i = 0; i < 2; i++) {\n        TreeNode *choice = choices[i];\n        // Prune: check whether the choice is valid\n        if (isValid(choice)) {\n            // Try: make a choice and update the state\n            makeChoice(choice);\n            // Proceed to the next choice\n            TreeNode *nextChoices[2] = {choice->left, choice->right};\n            backtrack(nextChoices);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice();\n        }\n    }\n}\n
    preorder_traversal_iii_template.kt
    /* Check whether the current state is a solution */\nfun isSolution(state: MutableList<TreeNode?>): Boolean {\n    return state.isNotEmpty() && state[state.size - 1]?._val == 7\n}\n\n/* Record the solution */\nfun recordSolution(state: MutableList<TreeNode?>?, res: MutableList<MutableList<TreeNode?>?>) {\n    res.add(state!!.toMutableList())\n}\n\n/* Check whether the choice is valid in the current state */\nfun isValid(state: MutableList<TreeNode?>?, choice: TreeNode?): Boolean {\n    return choice != null && choice._val != 3\n}\n\n/* Update the state */\nfun makeChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {\n    state.add(choice)\n}\n\n/* Restore the state */\nfun undoChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {\n    state.removeLast()\n}\n\n/* Backtracking: example 3 */\nfun backtrack(\n    state: MutableList<TreeNode?>,\n    choices: MutableList<TreeNode?>,\n    res: MutableList<MutableList<TreeNode?>?>\n) {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res)\n    }\n    // Iterate over all choices\n    for (choice in choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice)\n            // Proceed to the next choice\n            backtrack(state, mutableListOf(choice!!.left, choice.right), res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
    preorder_traversal_iii_template.rb
    ### Check whether the current state is a solution ###\ndef is_solution?(state)\n  !state.empty? && state.last.val == 7\nend\n\n### Record the solution ###\ndef record_solution(state, res)\n  res << state.dup\nend\n\n### Check whether the choice is valid in the current state ###\ndef is_valid?(state, choice)\n  choice && choice.val != 3\nend\n\n### Update the state ###\ndef make_choice(state, choice)\n  state << choice\nend\n\n### Restore the state ###\ndef undo_choice(state, choice)\n  state.pop\nend\n\n### Backtracking: example 3 ###\ndef backtrack(state, choices, res)\n  # Check whether this is a solution\n  record_solution(state, res) if is_solution?(state)\n\n  # Iterate over all choices\n  for choice in choices\n    # Prune: check whether the choice is valid\n    if is_valid?(state, choice)\n      # Try: make a choice and update the state\n      make_choice(state, choice)\n      # Proceed to the next choice\n      backtrack(state, [choice.left, choice.right], res)\n      # Backtrack: undo the choice and restore the previous state\n      undo_choice(state, choice)\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    The problem requires us to keep searching after finding a node with value \\(7\\), so remove the return after recording a solution. Figure 13-4 compares the searches with and without that return.

    Figure 13-4   Comparing searches with and without return

    The template-based implementation is more verbose than the preorder version, but more general. Many backtracking problems can be solved within this template: define state and choices for the problem, then implement each helper method.

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1314-common-terminology","level":2,"title":"13.1.4   Common terminology","text":"

    Table 13-1 summarizes common backtracking terms and illustrates them with Example 3, helping us analyze problems more clearly.

    Table 13-1   Common backtracking terminology

    Term Definition Example 3 Solution An answer satisfying the problem's conditions; there may be one or more All paths from the root to a node valued \\(7\\) that satisfy the constraint Constraint A condition restricting which solutions are feasible, often used for pruning The path contains no node valued \\(3\\) State The situation at a particular point, including the choices already made The current path of visited nodes, stored in path Trial Exploring the solution space by making a choice, updating the state, and checking for a solution Recursively visit a left or right child, append it to path, and check whether its value is \\(7\\) Backtracking Undoing an earlier choice and returning to the previous state when a constraint cannot be met Stop searching and return after passing a leaf, finishing a node, or encountering value \\(3\\) Pruning Using problem properties and constraints to avoid pointless search paths and improve efficiency Stop searching when a node valued \\(3\\) is encountered

    Tip

    Concepts such as problems, solutions, and states are shared by divide and conquer, backtracking, dynamic programming, greedy algorithms, and other approaches.

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1315-strengths-and-limitations","level":2,"title":"13.1.5   Strengths and limitations","text":"

    Backtracking is fundamentally a depth-first search that tries possible solutions until it finds ones meeting the conditions. It can find all possible solutions and can be highly efficient with effective pruning.

    However, for large or complex problems, backtracking may be unacceptably slow.

    • Time: Backtracking often explores every possibility in the state space, leading to exponential or factorial time complexity.
    • Space: Recursive calls must preserve state, such as the path and auxiliary pruning variables. Deep recursion can therefore require substantial space.

    Even so, backtracking remains the best approach for some search and constraint satisfaction problems. When we cannot predict which choices lead to valid solutions, we must explore every possibility. The key is then to improve efficiency, commonly through two techniques.

    • Pruning: Avoid paths that cannot produce a solution, saving time and space.
    • Heuristic search: Use strategies or estimates to explore paths most likely to yield valid solutions first.
    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1316-typical-backtracking-problems","level":2,"title":"13.1.6   Typical backtracking problems","text":"

    Backtracking can solve many search, constraint satisfaction, and combinatorial optimization problems.

    Search problems seek solutions satisfying specified conditions.

    • Permutations: Given a collection, find every possible ordering of its elements.
    • Subset sum: Given a collection and a target sum, find every subset whose sum equals the target.
    • Tower of Hanoi: Given three rods and disks of different sizes, move all disks from one rod to another, moving only one at a time and never placing a larger disk on a smaller one.

    Constraint satisfaction problems seek solutions meeting all constraints.

    • \\(n\\)-queens: Place \\(n\\) queens on an \\(n \\times n\\) chessboard so that no two attack each other.
    • Sudoku: Fill a \\(9 \\times 9\\) grid with digits \\(1\\) ~ \\(9\\) so that no digit repeats in any row, column, or \\(3 \\times 3\\) subgrid.
    • Graph coloring: Color an undirected graph's vertices with as few colors as possible, ensuring adjacent vertices have different colors.

    Combinatorial optimization problems seek an optimal solution satisfying conditions within a space of combinations.

    • 0-1 knapsack: Given items with values and weights and a knapsack, choose items to maximize total value within the capacity limit.
    • Traveling salesperson: Starting at a vertex in a graph, visit every other vertex exactly once and return to the start along the shortest possible route.
    • Maximum clique: Given an undirected graph, find its largest complete subgraph, in which every pair of vertices is connected by an edge.

    For many combinatorial optimization problems, backtracking is not the best approach.

    • 0-1 knapsack is usually solved more efficiently with dynamic programming.
    • The traveling salesperson problem is a well-known NP-hard problem. Common approaches include genetic algorithms and ant colony algorithms.
    • Maximum clique is a classic graph theory problem that can be approached with heuristics such as greedy algorithms.
    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/exercises/","level":1,"title":"13.6   Exercises","text":"","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#1361-check-your-understanding","level":2,"title":"13.6.1   Check your understanding","text":"","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#1-will-this-permutation-algorithm-miss-results","level":3,"title":"1.   Will this permutation algorithm miss results?","text":"

    A backtracking algorithm tries the numbers 1, 2, 3 in order to generate all permutations. Whenever it chooses a number x, it:

    1. Appends x to the current path.
    2. Marks x as used.
    3. Recursively fills the next position.

    After the recursive call returns, the student removes only x from the end of the path, then continues trying the next number.

    1. Which permutation does the algorithm produce first? Can it still produce all six permutations?
    2. Before returning to the previous recursion level, is removing the last number from the path enough? If not, what else is needed? Explain.
    Reference answer
    1. It first produces [1, 2, 3], but cannot generate every permutation. Although the path becomes shorter as calls return, the numbers 1, 2, and 3 remain marked as used, leaving later branches with no available numbers.

    2. No. After removing x from the end of the path, we must also mark x as unused again. The current path and the used markers together describe the search state. Choosing a number changes both, so backtracking must restore both to allow other branches to choose x again.

    ","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#2-does-the-order-of-choosing-numbers-matter","level":3,"title":"2.   Does the order of choosing numbers matter?","text":"

    Given the sorted array [2, 3, 5] and target value 5, each number may be chosen repeatedly. The algorithm requires the numbers along each search path to appear in nondecreasing order.

    1. Which distinct combinations can be obtained?
    2. Why is it unnecessary to search the same set of numbers in different orders? What does the nondecreasing-order restriction achieve?
    3. The current path is [3], the remaining sum is 2, and the next candidate is 3. Why can we stop checking all further candidates at this level?
    Reference answer
    1. The distinct combinations are [2, 3] and [5].

    2. This problem treats [2, 3] and [3, 2] as the same combination: selection order does not distinguish answers. Requiring the path's numbers to appear in nondecreasing order lets the search skip duplicate combinations such as [3, 2] directly.

    3. The remaining sum is 2, and candidate 3 already exceeds it. Since the array is sorted, all candidates after 3 are larger still and cannot join the current combination. We can therefore stop checking this level.

    ","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#3-where-can-the-next-queen-be-placed","level":3,"title":"3.   Where can the next queen be placed?","text":"

    Place queens row by row on a 4 × 4 board, with both row and column indices starting at 0. Queens are already at (0, 1) and (1, 3). The next queen must be placed in row 2.

    1. Which columns are ruled out because they already contain a queen?
    2. Among the remaining columns, which positions are ruled out by sharing a diagonal with a queen?
    3. Which positions in row 2 remain available to try?
    Reference answer
    1. Columns 1 and 3 already contain queens, ruling out positions (2, 1) and (2, 3).

    2. Of the remaining positions, (2, 2) shares a diagonal with (1, 3), so it is also ruled out. Position (2, 0) shares neither a column nor a diagonal with either existing queen.

    3. The only position to try in row 2 is (2, 0).

      This step establishes only that the current placement is valid. If the board cannot be completed later, we must still backtrack and try other earlier choices.

    ","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#1362-programming-exercises","level":2,"title":"13.6.2   Programming exercises","text":"","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#1-all-permutations-of-distinct-elements","level":3,"title":"1.   All permutations of distinct elements","text":"

    The integer array nums contains at least one element, and all its elements are distinct. List every possible ordering that uses each element exactly once, returning each ordering as an array. The permutations may appear in any order in the result. Use backtracking and a Boolean array to record whether the element at each position is already included in the current permutation.

    Hints
    1. The recursion depth indicates which position in the permutation is being filled.
    2. At each level, try only unused elements.
    3. When the path length reaches the length of nums, add a copy of the path to the answer.

    LeetCode

    ","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/","level":1,"title":"13.4   N-queens problem","text":"

    Question

    In chess, a queen can attack any piece in the same row, column, or diagonal. Given \\(n\\) queens and an \\(n \\times n\\) chessboard, find all arrangements in which no two queens can attack each other.

    As shown in Figure 13-15, there are two solutions when \\(n = 4\\). From a backtracking perspective, the \\(n \\times n\\) board has \\(n^2\\) squares, which provide all possible choices. As we place queens one by one, the board changes; its arrangement at any moment is the current state.

    Figure 13-15   Solutions to the 4-queens problem

    Figure 13-16 shows the three constraints: no two queens may share a row, column, or diagonal. Diagonals run in two directions: the main-diagonal direction \\ and the antidiagonal direction /.

    Figure 13-16   Constraints of the n-queens problem

    ","path":["Chapter 13.   Backtracking","13.4   N-queens problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#1-placing-queens-row-by-row","level":3,"title":"1.   Placing queens row by row","text":"

    There are as many queens as rows, namely \\(n\\), so we can immediately conclude that each row must contain exactly one queen.

    We can therefore place queens row by row: start at the first row and place one queen in each row through the last.

    Figure 13-17 illustrates this process for 4 queens. To fit the illustration, only one branch from the first row is expanded, and all arrangements violating the column or diagonal constraints are pruned.

    Figure 13-17   Placing queens row by row

    In essence, placing queens row by row is itself a form of pruning: it eliminates every search branch that places multiple queens in the same row.

    ","path":["Chapter 13.   Backtracking","13.4   N-queens problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#2-pruning-by-columns-and-diagonals","level":3,"title":"2.   Pruning by columns and diagonals","text":"

    To enforce the column constraint, use a Boolean array cols of length \\(n\\) to record whether each column contains a queen. Before placing a queen, consult cols to prune columns already occupied, and update cols as the search proceeds and backtracks.

    Tip

    The matrix starts at its top-left corner. Row indices increase downward, and column indices increase to the right.

    How do we enforce the diagonal constraints? Let \\((row, col)\\) be the row and column indices of a square. Along any diagonal in the main-diagonal direction, the row index minus the column index is the same: \\(row - col\\) is constant along that diagonal.

    Thus, if two squares satisfy \\(row_1 - col_1 = row_2 - col_2\\), they lie on the same main-direction diagonal. Using this property, the array diags1 in Figure 13-18 records whether each such diagonal contains a queen.

    Similarly, \\(row + col\\) is constant along each antidiagonal. We can use an array diags2 to enforce the antidiagonal constraint.

    Figure 13-18   Enforcing column and diagonal constraints

    ","path":["Chapter 13.   Backtracking","13.4   N-queens problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#3-code-implementation","level":3,"title":"3.   Code implementation","text":"

    In a square matrix of order \\(n\\), \\(row - col\\) ranges over \\([-n + 1, n - 1]\\), and \\(row + col\\) ranges over \\([0, 2n - 2]\\). There are therefore \\(2n - 1\\) diagonals in each direction, so both diags1 and diags2 have length \\(2n - 1\\).

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby n_queens.py
    def backtrack(\n    row: int,\n    n: int,\n    state: list[list[str]],\n    res: list[list[list[str]]],\n    cols: list[bool],\n    diags1: list[bool],\n    diags2: list[bool],\n):\n    \"\"\"Backtracking: n-queens\"\"\"\n    # Record a solution when a queen has been placed in every row\n    if row == n:\n        res.append([list(row) for row in state])\n        return\n    # Iterate over all columns\n    for col in range(n):\n        # Calculate the main-diagonal and anti-diagonal indices for this square\n        diag1 = row - col + n - 1\n        diag2 = row + col\n        # Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if not cols[col] and not diags1[diag1] and not diags2[diag2]:\n            # Try: place a queen on this square\n            state[row][col] = \"Q\"\n            cols[col] = diags1[diag1] = diags2[diag2] = True\n            # Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2)\n            # Backtrack: restore this square to empty\n            state[row][col] = \"#\"\n            cols[col] = diags1[diag1] = diags2[diag2] = False\n\ndef n_queens(n: int) -> list[list[list[str]]]:\n    \"\"\"Solve the n-queens problem\"\"\"\n    # Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    state = [[\"#\" for _ in range(n)] for _ in range(n)]\n    cols = [False] * n  # Track whether a column contains a queen\n    diags1 = [False] * (2 * n - 1)  # Track whether a main diagonal contains a queen\n    diags2 = [False] * (2 * n - 1)  # Track whether an anti-diagonal contains a queen\n    res = []\n    backtrack(0, n, state, res, cols, diags1, diags2)\n\n    return res\n
    n_queens.cpp
    /* Backtracking: n-queens */\nvoid backtrack(int row, int n, vector<vector<string>> &state, vector<vector<vector<string>>> &res, vector<bool> &cols,\n               vector<bool> &diags1, vector<bool> &diags2) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\";\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\";\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nvector<vector<vector<string>>> nQueens(int n) {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    vector<vector<string>> state(n, vector<string>(n, \"#\"));\n    vector<bool> cols(n, false);           // Track whether a column contains a queen\n    vector<bool> diags1(2 * n - 1, false); // Track whether a main diagonal contains a queen\n    vector<bool> diags2(2 * n - 1, false); // Track whether an anti-diagonal contains a queen\n    vector<vector<vector<string>>> res;\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
    n_queens.java
    /* Backtracking: n-queens */\nvoid backtrack(int row, int n, List<List<String>> state, List<List<List<String>>> res,\n        boolean[] cols, boolean[] diags1, boolean[] diags2) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        List<List<String>> copyState = new ArrayList<>();\n        for (List<String> sRow : state) {\n            copyState.add(new ArrayList<>(sRow));\n        }\n        res.add(copyState);\n        return;\n    }\n    // Iterate over all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state.get(row).set(col, \"Q\");\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state.get(row).set(col, \"#\");\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nList<List<List<String>>> nQueens(int n) {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    List<List<String>> state = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        List<String> row = new ArrayList<>();\n        for (int j = 0; j < n; j++) {\n            row.add(\"#\");\n        }\n        state.add(row);\n    }\n    boolean[] cols = new boolean[n]; // Track whether a column contains a queen\n    boolean[] diags1 = new boolean[2 * n - 1]; // Track whether a main diagonal contains a queen\n    boolean[] diags2 = new boolean[2 * n - 1]; // Track whether an anti-diagonal contains a queen\n    List<List<List<String>>> res = new ArrayList<>();\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
    n_queens.cs
    /* Backtracking: n-queens */\nvoid Backtrack(int row, int n, List<List<string>> state, List<List<List<string>>> res,\n        bool[] cols, bool[] diags1, bool[] diags2) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        List<List<string>> copyState = [];\n        foreach (List<string> sRow in state) {\n            copyState.Add(new List<string>(sRow));\n        }\n        res.Add(copyState);\n        return;\n    }\n    // Iterate over all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\";\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            Backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\";\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nList<List<List<string>>> NQueens(int n) {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    List<List<string>> state = [];\n    for (int i = 0; i < n; i++) {\n        List<string> row = [];\n        for (int j = 0; j < n; j++) {\n            row.Add(\"#\");\n        }\n        state.Add(row);\n    }\n    bool[] cols = new bool[n]; // Track whether a column contains a queen\n    bool[] diags1 = new bool[2 * n - 1]; // Track whether a main diagonal contains a queen\n    bool[] diags2 = new bool[2 * n - 1]; // Track whether an anti-diagonal contains a queen\n    List<List<List<string>>> res = [];\n\n    Backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
    n_queens.go
    /* Backtracking: n-queens */\nfunc backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) {\n    // Record a solution when a queen has been placed in every row\n    if row == n {\n        newState := make([][]string, len(*state))\n        for i, _ := range newState {\n            newState[i] = make([]string, len((*state)[0]))\n            copy(newState[i], (*state)[i])\n\n        }\n        *res = append(*res, newState)\n        return\n    }\n    // Iterate over all columns\n    for col := 0; col < n; col++ {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        diag1 := row - col + n - 1\n        diag2 := row + col\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] {\n            // Try: place a queen on this square\n            (*state)[row][col] = \"Q\"\n            (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true\n            // Place a queen in the next row\n            backtrack(row+1, n, state, res, cols, diags1, diags2)\n            // Backtrack: restore this square to empty\n            (*state)[row][col] = \"#\"\n            (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfunc nQueens(n int) [][][]string {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    state := make([][]string, n)\n    for i := 0; i < n; i++ {\n        row := make([]string, n)\n        for i := 0; i < n; i++ {\n            row[i] = \"#\"\n        }\n        state[i] = row\n    }\n    // Track whether a column contains a queen\n    cols := make([]bool, n)\n    diags1 := make([]bool, 2*n-1)\n    diags2 := make([]bool, 2*n-1)\n    res := make([][][]string, 0)\n    backtrack(0, n, &state, &res, &cols, &diags1, &diags2)\n    return res\n}\n
    n_queens.swift
    /* Backtracking: n-queens */\nfunc backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) {\n    // Record a solution when a queen has been placed in every row\n    if row == n {\n        res.append(state)\n        return\n    }\n    // Iterate over all columns\n    for col in 0 ..< n {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        let diag1 = row - col + n - 1\n        let diag2 = row + col\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if !cols[col] && !diags1[diag1] && !diags2[diag2] {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\"\n            cols[col] = true\n            diags1[diag1] = true\n            diags2[diag2] = true\n            // Place a queen in the next row\n            backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\"\n            cols[col] = false\n            diags1[diag1] = false\n            diags2[diag2] = false\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfunc nQueens(n: Int) -> [[[String]]] {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    var state = Array(repeating: Array(repeating: \"#\", count: n), count: n)\n    var cols = Array(repeating: false, count: n) // Track whether a column contains a queen\n    var diags1 = Array(repeating: false, count: 2 * n - 1) // Track whether a main diagonal contains a queen\n    var diags2 = Array(repeating: false, count: 2 * n - 1) // Track whether an anti-diagonal contains a queen\n    var res: [[[String]]] = []\n\n    backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)\n\n    return res\n}\n
    n_queens.js
    /* Backtracking: n-queens */\nfunction backtrack(row, n, state, res, cols, diags1, diags2) {\n    // Record a solution when a queen has been placed in every row\n    if (row === n) {\n        res.push(state.map((row) => row.slice()));\n        return;\n    }\n    // Iterate over all columns\n    for (let col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        const diag1 = row - col + n - 1;\n        const diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfunction nQueens(n) {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    const state = Array.from({ length: n }, () => Array(n).fill('#'));\n    const cols = Array(n).fill(false); // Track whether a column contains a queen\n    const diags1 = Array(2 * n - 1).fill(false); // Track whether a main diagonal contains a queen\n    const diags2 = Array(2 * n - 1).fill(false); // Track whether an anti-diagonal contains a queen\n    const res = [];\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n    return res;\n}\n
    n_queens.ts
    /* Backtracking: n-queens */\nfunction backtrack(\n    row: number,\n    n: number,\n    state: string[][],\n    res: string[][][],\n    cols: boolean[],\n    diags1: boolean[],\n    diags2: boolean[]\n): void {\n    // Record a solution when a queen has been placed in every row\n    if (row === n) {\n        res.push(state.map((row) => row.slice()));\n        return;\n    }\n    // Iterate over all columns\n    for (let col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        const diag1 = row - col + n - 1;\n        const diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfunction nQueens(n: number): string[][][] {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    const state = Array.from({ length: n }, () => Array(n).fill('#'));\n    const cols = Array(n).fill(false); // Track whether a column contains a queen\n    const diags1 = Array(2 * n - 1).fill(false); // Track whether a main diagonal contains a queen\n    const diags2 = Array(2 * n - 1).fill(false); // Track whether an anti-diagonal contains a queen\n    const res: string[][][] = [];\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n    return res;\n}\n
    n_queens.dart
    /* Backtracking: n-queens */\nvoid backtrack(\n  int row,\n  int n,\n  List<List<String>> state,\n  List<List<List<String>>> res,\n  List<bool> cols,\n  List<bool> diags1,\n  List<bool> diags2,\n) {\n  // Record a solution when a queen has been placed in every row\n  if (row == n) {\n    List<List<String>> copyState = [];\n    for (List<String> sRow in state) {\n      copyState.add(List.from(sRow));\n    }\n    res.add(copyState);\n    return;\n  }\n  // Iterate over all columns\n  for (int col = 0; col < n; col++) {\n    // Calculate the main-diagonal and anti-diagonal indices for this square\n    int diag1 = row - col + n - 1;\n    int diag2 = row + col;\n    // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n    if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n      // Try: place a queen on this square\n      state[row][col] = \"Q\";\n      cols[col] = true;\n      diags1[diag1] = true;\n      diags2[diag2] = true;\n      // Place a queen in the next row\n      backtrack(row + 1, n, state, res, cols, diags1, diags2);\n      // Backtrack: restore this square to empty\n      state[row][col] = \"#\";\n      cols[col] = false;\n      diags1[diag1] = false;\n      diags2[diag2] = false;\n    }\n  }\n}\n\n/* Solve the n-queens problem */\nList<List<List<String>>> nQueens(int n) {\n  // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n  List<List<String>> state = List.generate(n, (index) => List.filled(n, \"#\"));\n  List<bool> cols = List.filled(n, false); // Track whether a column contains a queen\n  List<bool> diags1 = List.filled(2 * n - 1, false); // Track whether a main diagonal contains a queen\n  List<bool> diags2 = List.filled(2 * n - 1, false); // Track whether an anti-diagonal contains a queen\n  List<List<List<String>>> res = [];\n\n  backtrack(0, n, state, res, cols, diags1, diags2);\n\n  return res;\n}\n
    n_queens.rs
    /* Backtracking: n-queens */\nfn backtrack(\n    row: usize,\n    n: usize,\n    state: &mut Vec<Vec<String>>,\n    res: &mut Vec<Vec<Vec<String>>>,\n    cols: &mut [bool],\n    diags1: &mut [bool],\n    diags2: &mut [bool],\n) {\n    // Record a solution when a queen has been placed in every row\n    if row == n {\n        res.push(state.clone());\n        return;\n    }\n    // Iterate over all columns\n    for col in 0..n {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        let diag1 = row + n - 1 - col;\n        let diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if !cols[col] && !diags1[diag1] && !diags2[diag2] {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\".into();\n            (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true);\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\".into();\n            (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false);\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfn n_queens(n: usize) -> Vec<Vec<Vec<String>>> {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    let mut state: Vec<Vec<String>> = vec![vec![\"#\".to_string(); n]; n];\n    let mut cols = vec![false; n]; // Track whether a column contains a queen\n    let mut diags1 = vec![false; 2 * n - 1]; // Track whether a main diagonal contains a queen\n    let mut diags2 = vec![false; 2 * n - 1]; // Track whether an anti-diagonal contains a queen\n    let mut res: Vec<Vec<Vec<String>>> = Vec::new();\n\n    backtrack(\n        0,\n        n,\n        &mut state,\n        &mut res,\n        &mut cols,\n        &mut diags1,\n        &mut diags2,\n    );\n\n    res\n}\n
    n_queens.c
    /* Backtracking: n-queens */\nvoid backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE],\n               bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        res[*resSize] = (char **)malloc(sizeof(char *) * n);\n        for (int i = 0; i < n; ++i) {\n            res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1));\n            strcpy(res[*resSize][i], state[i]);\n        }\n        (*resSize)++;\n        return;\n    }\n    // Iterate over all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nchar ***nQueens(int n, int *returnSize) {\n    char state[MAX_SIZE][MAX_SIZE];\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    for (int i = 0; i < n; ++i) {\n        for (int j = 0; j < n; ++j) {\n            state[i][j] = '#';\n        }\n        state[i][n] = '\\0';\n    }\n    bool cols[MAX_SIZE] = {false};           // Track whether a column contains a queen\n    bool diags1[2 * MAX_SIZE - 1] = {false}; // Track whether a main diagonal contains a queen\n    bool diags2[2 * MAX_SIZE - 1] = {false}; // Track whether an anti-diagonal contains a queen\n\n    char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE);\n    *returnSize = 0;\n    backtrack(0, n, state, res, returnSize, cols, diags1, diags2);\n    return res;\n}\n
    n_queens.kt
    /* Backtracking: n-queens */\nfun backtrack(\n    row: Int,\n    n: Int,\n    state: MutableList<MutableList<String>>,\n    res: MutableList<MutableList<MutableList<String>>?>,\n    cols: BooleanArray,\n    diags1: BooleanArray,\n    diags2: BooleanArray\n) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        val copyState = mutableListOf<MutableList<String>>()\n        for (sRow in state) {\n            copyState.add(sRow.toMutableList())\n        }\n        res.add(copyState)\n        return\n    }\n    // Iterate over all columns\n    for (col in 0..<n) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        val diag1 = row - col + n - 1\n        val diag2 = row + col\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\"\n            diags2[diag2] = true\n            diags1[diag1] = diags2[diag2]\n            cols[col] = diags1[diag1]\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2)\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\"\n            diags2[diag2] = false\n            diags1[diag1] = diags2[diag2]\n            cols[col] = diags1[diag1]\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfun nQueens(n: Int): MutableList<MutableList<MutableList<String>>?> {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    val state = mutableListOf<MutableList<String>>()\n    for (i in 0..<n) {\n        val row = mutableListOf<String>()\n        for (j in 0..<n) {\n            row.add(\"#\")\n        }\n        state.add(row)\n    }\n    val cols = BooleanArray(n) // Track whether a column contains a queen\n    val diags1 = BooleanArray(2 * n - 1) // Track whether a main diagonal contains a queen\n    val diags2 = BooleanArray(2 * n - 1) // Track whether an anti-diagonal contains a queen\n    val res = mutableListOf<MutableList<MutableList<String>>?>()\n\n    backtrack(0, n, state, res, cols, diags1, diags2)\n\n    return res\n}\n
    n_queens.rb
    ### Backtracking: n-queens ###\ndef backtrack(row, n, state, res, cols, diags1, diags2)\n  # Record a solution when a queen has been placed in every row\n  if row == n\n    res << state.map { |row| row.dup }\n    return\n  end\n\n  # Iterate over all columns\n  for col in 0...n\n    # Calculate the main-diagonal and anti-diagonal indices for this square\n    diag1 = row - col + n - 1\n    diag2 = row + col\n    # Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n    if !cols[col] && !diags1[diag1] && !diags2[diag2]\n      # Try: place a queen on this square\n      state[row][col] = \"Q\"\n      cols[col] = diags1[diag1] = diags2[diag2] = true\n      # Place a queen in the next row\n      backtrack(row + 1, n, state, res, cols, diags1, diags2)\n      # Backtrack: restore this square to empty\n      state[row][col] = \"#\"\n      cols[col] = diags1[diag1] = diags2[diag2] = false\n    end\n  end\nend\n\n### Solve the n-queens problem ###\ndef n_queens(n)\n  # Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n  state = Array.new(n) { Array.new(n, \"#\") }\n  cols = Array.new(n, false) # Track whether a column contains a queen\n  diags1 = Array.new(2 * n - 1, false) # Track whether a main diagonal contains a queen\n  diags2 = Array.new(2 * n - 1, false) # Track whether an anti-diagonal contains a queen\n  res = []\n  backtrack(0, n, state, res, cols, diags1, diags2)\n\n  res\nend\n
    Visualize execution

    View full screen >

    We place a queen in each of \\(n\\) rows. Considering the column constraint, the successive rows have \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), and \\(1\\) choices, giving \\(O(n!)\\) time. Recording a solution requires copying the matrix state and appending the copy to res, which takes \\(O(n^2)\\) time. The overall time complexity is therefore \\(O(n! \\cdot n^2)\\). In practice, diagonal pruning greatly reduces the search space, so the search is often faster than this bound suggests.

    The array state uses \\(O(n^2)\\) space, while cols, diags1, and diags2 each use \\(O(n)\\) space. The maximum recursion depth is \\(n\\), requiring \\(O(n)\\) space for stack frames. The space complexity is therefore \\(O(n^2)\\).

    ","path":["Chapter 13.   Backtracking","13.4   N-queens problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/","level":1,"title":"13.2   Permutations problem","text":"

    Generating permutations is a classic application of backtracking. Given a collection, such as an array or a string, the task is to find every possible ordering of its elements.

    Table 13-2 gives several input arrays and all their permutations.

    Table 13-2   Examples of permutations

    Input array All permutations \\([1]\\) \\([1]\\) \\([1, 2]\\) \\([1, 2], [2, 1]\\) \\([1, 2, 3]\\) \\([1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]\\)","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1321-distinct-elements","level":2,"title":"13.2.1   Distinct elements","text":"

    Question

    Given an integer array with no duplicate elements, return all possible permutations.

    From a backtracking perspective, we can view a permutation as the result of a sequence of choices. For input \\([1, 2, 3]\\), choosing \\(1\\), then \\(3\\), then \\(2\\) produces \\([1, 3, 2]\\). Backtracking undoes a choice so that other choices can be tried.

    In the code, choices contains all the input elements, and state contains the elements chosen so far. Each element may be chosen only once, so every element in state must be unique.

    As shown in Figure 13-5, we can expand the search into a recursion tree, where each node represents the current state. Starting from the root, three rounds of choices lead to a leaf, and each leaf corresponds to one permutation.

    Figure 13-5   Recursion tree for permutations

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1-pruning-repeated-selections","level":3,"title":"1.   Pruning repeated selections","text":"

    To ensure that each element is chosen only once, introduce a Boolean array selected, where selected[i] indicates whether choices[i] has been chosen. Use it for the following pruning steps.

    • After choosing choices[i], set selected[i] to \\(\\text{True}\\) to mark it as selected.
    • When iterating over choices, skip all selected elements, pruning their branches.

    As shown in Figure 13-6, if we choose 1 first, 3 second, and 2 third, we must prune the branch for 1 in the second round and the branches for 1 and 3 in the third round.

    Figure 13-6   Example of pruning permutations

    This pruning reduces the search space from \\(O(n^n)\\) to \\(O(n!)\\).

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    With these details established, we can fill in the backtracking template. To keep the code short, we inline the template's helper functions into backtrack():

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_i.py
    def backtrack(\n    state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n    \"\"\"Backtracking: permutations I\"\"\"\n    # Record a solution when the state length equals the number of elements\n    if len(state) == len(choices):\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    for i, choice in enumerate(choices):\n        # Prune: do not choose an element more than once\n        if not selected[i]:\n            # Try: make a choice and update the state\n            selected[i] = True\n            state.append(choice)\n            # Proceed to the next choice\n            backtrack(state, choices, selected, res)\n            # Backtrack: undo the choice and restore the previous state\n            selected[i] = False\n            state.pop()\n\ndef permutations_i(nums: list[int]) -> list[list[int]]:\n    \"\"\"Permutations I\"\"\"\n    res = []\n    backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n    return res\n
    permutations_i.cpp
    /* Backtracking: permutations I */\nvoid backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size() == choices.size()) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.size(); i++) {\n        int choice = choices[i];\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.push_back(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop_back();\n        }\n    }\n}\n\n/* Permutations I */\nvector<vector<int>> permutationsI(vector<int> nums) {\n    vector<int> state;\n    vector<bool> selected(nums.size(), false);\n    vector<vector<int>> res;\n    backtrack(state, nums, selected, res);\n    return res;\n}\n
    permutations_i.java
    /* Backtracking: permutations I */\nvoid backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size() == choices.length) {\n        res.add(new ArrayList<Integer>(state));\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.length; i++) {\n        int choice = choices[i];\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.add(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.remove(state.size() - 1);\n        }\n    }\n}\n\n/* Permutations I */\nList<List<Integer>> permutationsI(int[] nums) {\n    List<List<Integer>> res = new ArrayList<List<Integer>>();\n    backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);\n    return res;\n}\n
    permutations_i.cs
    /* Backtracking: permutations I */\nvoid Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.Count == choices.Length) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.Length; i++) {\n        int choice = choices[i];\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.Add(choice);\n            // Proceed to the next choice\n            Backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.RemoveAt(state.Count - 1);\n        }\n    }\n}\n\n/* Permutations I */\nList<List<int>> PermutationsI(int[] nums) {\n    List<List<int>> res = [];\n    Backtrack([], nums, new bool[nums.Length], res);\n    return res;\n}\n
    permutations_i.go
    /* Backtracking: permutations I */\nfunc backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n    // Record a solution when the state length equals the number of elements\n    if len(*state) == len(*choices) {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n    }\n    // Iterate over all choices\n    for i := 0; i < len(*choices); i++ {\n        choice := (*choices)[i]\n        // Prune: do not choose an element more than once\n        if !(*selected)[i] {\n            // Try: make a choice and update the state\n            (*selected)[i] = true\n            *state = append(*state, choice)\n            // Proceed to the next choice\n            backtrackI(state, choices, selected, res)\n            // Backtrack: undo the choice and restore the previous state\n            (*selected)[i] = false\n            *state = (*state)[:len(*state)-1]\n        }\n    }\n}\n\n/* Permutations I */\nfunc permutationsI(nums []int) [][]int {\n    res := make([][]int, 0)\n    state := make([]int, 0)\n    selected := make([]bool, len(nums))\n    backtrackI(&state, &nums, &selected, &res)\n    return res\n}\n
    permutations_i.swift
    /* Backtracking: permutations I */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n    // Record a solution when the state length equals the number of elements\n    if state.count == choices.count {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    for (i, choice) in choices.enumerated() {\n        // Prune: do not choose an element more than once\n        if !selected[i] {\n            // Try: make a choice and update the state\n            selected[i] = true\n            state.append(choice)\n            // Proceed to the next choice\n            backtrack(state: &state, choices: choices, selected: &selected, res: &res)\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false\n            state.removeLast()\n        }\n    }\n}\n\n/* Permutations I */\nfunc permutationsI(nums: [Int]) -> [[Int]] {\n    var state: [Int] = []\n    var selected = Array(repeating: false, count: nums.count)\n    var res: [[Int]] = []\n    backtrack(state: &state, choices: nums, selected: &selected, res: &res)\n    return res\n}\n
    permutations_i.js
    /* Backtracking: permutations I */\nfunction backtrack(state, choices, selected, res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    choices.forEach((choice, i) => {\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations I */\nfunction permutationsI(nums) {\n    const res = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
    permutations_i.ts
    /* Backtracking: permutations I */\nfunction backtrack(\n    state: number[],\n    choices: number[],\n    selected: boolean[],\n    res: number[][]\n): void {\n    // Record a solution when the state length equals the number of elements\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    choices.forEach((choice, i) => {\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations I */\nfunction permutationsI(nums: number[]): number[][] {\n    const res: number[][] = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
    permutations_i.dart
    /* Backtracking: permutations I */\nvoid backtrack(\n  List<int> state,\n  List<int> choices,\n  List<bool> selected,\n  List<List<int>> res,\n) {\n  // Record a solution when the state length equals the number of elements\n  if (state.length == choices.length) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  for (int i = 0; i < choices.length; i++) {\n    int choice = choices[i];\n    // Prune: do not choose an element more than once\n    if (!selected[i]) {\n      // Try: make a choice and update the state\n      selected[i] = true;\n      state.add(choice);\n      // Proceed to the next choice\n      backtrack(state, choices, selected, res);\n      // Backtrack: undo the choice and restore the previous state\n      selected[i] = false;\n      state.removeLast();\n    }\n  }\n}\n\n/* Permutations I */\nList<List<int>> permutationsI(List<int> nums) {\n  List<List<int>> res = [];\n  backtrack([], nums, List.filled(nums.length, false), res);\n  return res;\n}\n
    permutations_i.rs
    /* Backtracking: permutations I */\nfn backtrack(mut state: Vec<i32>, choices: &[i32], selected: &mut [bool], res: &mut Vec<Vec<i32>>) {\n    // Record a solution when the state length equals the number of elements\n    if state.len() == choices.len() {\n        res.push(state);\n        return;\n    }\n    // Iterate over all choices\n    for i in 0..choices.len() {\n        let choice = choices[i];\n        // Prune: do not choose an element more than once\n        if !selected[i] {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state.clone(), choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    }\n}\n\n/* Permutations I */\nfn permutations_i(nums: &mut [i32]) -> Vec<Vec<i32>> {\n    let mut res = Vec::new(); // Result list (permutations)\n    backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res);\n    res\n}\n
    permutations_i.c
    /* Backtracking: permutations I */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n    // Record a solution when the state length equals the number of elements\n    if (stateSize == choicesSize) {\n        res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n        for (int i = 0; i < choicesSize; i++) {\n            res[*resSize][i] = state[i];\n        }\n        (*resSize)++;\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choicesSize; i++) {\n        int choice = choices[i];\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state[stateSize] = choice;\n            // Proceed to the next choice\n            backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n        }\n    }\n}\n\n/* Permutations I */\nint **permutationsI(int *nums, int numsSize, int *returnSize) {\n    int *state = (int *)malloc(numsSize * sizeof(int));\n    bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n    for (int i = 0; i < numsSize; i++) {\n        selected[i] = false;\n    }\n    int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n    *returnSize = 0;\n\n    backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n    free(state);\n    free(selected);\n\n    return res;\n}\n
    permutations_i.kt
    /* Backtracking: permutations I */\nfun backtrack(\n    state: MutableList<Int>,\n    choices: IntArray,\n    selected: BooleanArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size == choices.size) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    for (i in choices.indices) {\n        val choice = choices[i]\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true\n            state.add(choice)\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res)\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false\n            state.removeAt(state.size - 1)\n        }\n    }\n}\n\n/* Permutations I */\nfun permutationsI(nums: IntArray): MutableList<MutableList<Int>?> {\n    val res = mutableListOf<MutableList<Int>?>()\n    backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n    return res\n}\n
    permutations_i.rb
    ### Backtracking: permutations I ###\ndef backtrack(state, choices, selected, res)\n  # Record a solution when the state length equals the number of elements\n  if state.length == choices.length\n    res << state.dup\n    return\n  end\n\n  # Iterate over all choices\n  choices.each_with_index do |choice, i|\n    # Prune: do not choose an element more than once\n    unless selected[i]\n      # Try: make a choice and update the state\n      selected[i] = true\n      state << choice\n      # Proceed to the next choice\n      backtrack(state, choices, selected, res)\n      # Backtrack: undo the choice and restore the previous state\n      selected[i] = false\n      state.pop\n    end\n  end\nend\n\n### Permutations I ###\ndef permutations_i(nums)\n  res = []\n  backtrack([], nums, Array.new(nums.length, false), res)\n  res\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1322-duplicate-elements","level":2,"title":"13.2.2   Duplicate elements","text":"

    Question

    Given an integer array that may contain duplicate elements, return all distinct permutations.

    Suppose the input is \\([1, 1, 2]\\). To distinguish the two occurrences of \\(1\\), denote the second \\(1\\) by \\(\\hat{1}\\).

    As shown in Figure 13-7, half the permutations generated by the previous method are duplicates.

    Figure 13-7   Duplicate permutations

    How can we remove these duplicates? A straightforward approach is to use a hash set to deduplicate the results. However, this is inelegant: branches that generate duplicate permutations are unnecessary and should be identified and pruned in advance, improving efficiency.

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1-pruning-equal-elements","level":3,"title":"1.   Pruning equal elements","text":"

    In Figure 13-8, choosing \\(1\\) or \\(\\hat{1}\\) in the first round is equivalent: every permutation produced under one choice is duplicated under the other. We should therefore prune \\(\\hat{1}\\).

    Similarly, after choosing \\(2\\) in the first round, choosing either \\(1\\) or \\(\\hat{1}\\) in the second produces duplicate branches. Prune the second-round choice of \\(\\hat{1}\\) as well.

    In essence, within any one round of choices, we want to try only one of the elements with the same value.

    Figure 13-8   Pruning duplicate permutations

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2-code-implementation_1","level":3,"title":"2.   Code implementation","text":"

    Building on the previous solution, create a hash set duplicated in each round to record the values already tried in that round and prune repeated values:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_ii.py
    def backtrack(\n    state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n    \"\"\"Backtracking: permutations II\"\"\"\n    # Record a solution when the state length equals the number of elements\n    if len(state) == len(choices):\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    duplicated = set[int]()\n    for i, choice in enumerate(choices):\n        # Prune: do not reuse an element or choose the same value again at this depth\n        if not selected[i] and choice not in duplicated:\n            # Try: make a choice and update the state\n            duplicated.add(choice)  # Record the chosen value\n            selected[i] = True\n            state.append(choice)\n            # Proceed to the next choice\n            backtrack(state, choices, selected, res)\n            # Backtrack: undo the choice and restore the previous state\n            selected[i] = False\n            state.pop()\n\ndef permutations_ii(nums: list[int]) -> list[list[int]]:\n    \"\"\"Permutations II\"\"\"\n    res = []\n    backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n    return res\n
    permutations_ii.cpp
    /* Backtracking: permutations II */\nvoid backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size() == choices.size()) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    unordered_set<int> duplicated;\n    for (int i = 0; i < choices.size(); i++) {\n        int choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && duplicated.find(choice) == duplicated.end()) {\n            // Try: make a choice and update the state\n            duplicated.emplace(choice); // Record the chosen value\n            selected[i] = true;\n            state.push_back(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop_back();\n        }\n    }\n}\n\n/* Permutations II */\nvector<vector<int>> permutationsII(vector<int> nums) {\n    vector<int> state;\n    vector<bool> selected(nums.size(), false);\n    vector<vector<int>> res;\n    backtrack(state, nums, selected, res);\n    return res;\n}\n
    permutations_ii.java
    /* Backtracking: permutations II */\nvoid backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size() == choices.length) {\n        res.add(new ArrayList<Integer>(state));\n        return;\n    }\n    // Iterate over all choices\n    Set<Integer> duplicated = new HashSet<Integer>();\n    for (int i = 0; i < choices.length; i++) {\n        int choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.contains(choice)) {\n            // Try: make a choice and update the state\n            duplicated.add(choice); // Record the chosen value\n            selected[i] = true;\n            state.add(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.remove(state.size() - 1);\n        }\n    }\n}\n\n/* Permutations II */\nList<List<Integer>> permutationsII(int[] nums) {\n    List<List<Integer>> res = new ArrayList<List<Integer>>();\n    backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);\n    return res;\n}\n
    permutations_ii.cs
    /* Backtracking: permutations II */\nvoid Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.Count == choices.Length) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    HashSet<int> duplicated = [];\n    for (int i = 0; i < choices.Length; i++) {\n        int choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.Contains(choice)) {\n            // Try: make a choice and update the state\n            duplicated.Add(choice); // Record the chosen value\n            selected[i] = true;\n            state.Add(choice);\n            // Proceed to the next choice\n            Backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.RemoveAt(state.Count - 1);\n        }\n    }\n}\n\n/* Permutations II */\nList<List<int>> PermutationsII(int[] nums) {\n    List<List<int>> res = [];\n    Backtrack([], nums, new bool[nums.Length], res);\n    return res;\n}\n
    permutations_ii.go
    /* Backtracking: permutations II */\nfunc backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n    // Record a solution when the state length equals the number of elements\n    if len(*state) == len(*choices) {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n    }\n    // Iterate over all choices\n    duplicated := make(map[int]struct{}, 0)\n    for i := 0; i < len(*choices); i++ {\n        choice := (*choices)[i]\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if _, ok := duplicated[choice]; !ok && !(*selected)[i] {\n            // Try: make a choice and update the state\n            // Record the chosen value\n            duplicated[choice] = struct{}{}\n            (*selected)[i] = true\n            *state = append(*state, choice)\n            // Proceed to the next choice\n            backtrackII(state, choices, selected, res)\n            // Backtrack: undo the choice and restore the previous state\n            (*selected)[i] = false\n            *state = (*state)[:len(*state)-1]\n        }\n    }\n}\n\n/* Permutations II */\nfunc permutationsII(nums []int) [][]int {\n    res := make([][]int, 0)\n    state := make([]int, 0)\n    selected := make([]bool, len(nums))\n    backtrackII(&state, &nums, &selected, &res)\n    return res\n}\n
    permutations_ii.swift
    /* Backtracking: permutations II */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n    // Record a solution when the state length equals the number of elements\n    if state.count == choices.count {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    var duplicated: Set<Int> = []\n    for (i, choice) in choices.enumerated() {\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if !selected[i], !duplicated.contains(choice) {\n            // Try: make a choice and update the state\n            duplicated.insert(choice) // Record the chosen value\n            selected[i] = true\n            state.append(choice)\n            // Proceed to the next choice\n            backtrack(state: &state, choices: choices, selected: &selected, res: &res)\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false\n            state.removeLast()\n        }\n    }\n}\n\n/* Permutations II */\nfunc permutationsII(nums: [Int]) -> [[Int]] {\n    var state: [Int] = []\n    var selected = Array(repeating: false, count: nums.count)\n    var res: [[Int]] = []\n    backtrack(state: &state, choices: nums, selected: &selected, res: &res)\n    return res\n}\n
    permutations_ii.js
    /* Backtracking: permutations II */\nfunction backtrack(state, choices, selected, res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    const duplicated = new Set();\n    choices.forEach((choice, i) => {\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.has(choice)) {\n            // Try: make a choice and update the state\n            duplicated.add(choice); // Record the chosen value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations II */\nfunction permutationsII(nums) {\n    const res = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
    permutations_ii.ts
    /* Backtracking: permutations II */\nfunction backtrack(\n    state: number[],\n    choices: number[],\n    selected: boolean[],\n    res: number[][]\n): void {\n    // Record a solution when the state length equals the number of elements\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    const duplicated = new Set();\n    choices.forEach((choice, i) => {\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.has(choice)) {\n            // Try: make a choice and update the state\n            duplicated.add(choice); // Record the chosen value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations II */\nfunction permutationsII(nums: number[]): number[][] {\n    const res: number[][] = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
    permutations_ii.dart
    /* Backtracking: permutations II */\nvoid backtrack(\n  List<int> state,\n  List<int> choices,\n  List<bool> selected,\n  List<List<int>> res,\n) {\n  // Record a solution when the state length equals the number of elements\n  if (state.length == choices.length) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  Set<int> duplicated = {};\n  for (int i = 0; i < choices.length; i++) {\n    int choice = choices[i];\n    // Prune: do not reuse an element or choose the same value again at this depth\n    if (!selected[i] && !duplicated.contains(choice)) {\n      // Try: make a choice and update the state\n      duplicated.add(choice); // Record the chosen value\n      selected[i] = true;\n      state.add(choice);\n      // Proceed to the next choice\n      backtrack(state, choices, selected, res);\n      // Backtrack: undo the choice and restore the previous state\n      selected[i] = false;\n      state.removeLast();\n    }\n  }\n}\n\n/* Permutations II */\nList<List<int>> permutationsII(List<int> nums) {\n  List<List<int>> res = [];\n  backtrack([], nums, List.filled(nums.length, false), res);\n  return res;\n}\n
    permutations_ii.rs
    /* Backtracking: permutations II */\nfn backtrack(mut state: Vec<i32>, choices: &[i32], selected: &mut [bool], res: &mut Vec<Vec<i32>>) {\n    // Record a solution when the state length equals the number of elements\n    if state.len() == choices.len() {\n        res.push(state);\n        return;\n    }\n    // Iterate over all choices\n    let mut duplicated = HashSet::<i32>::new();\n    for i in 0..choices.len() {\n        let choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if !selected[i] && !duplicated.contains(&choice) {\n            // Try: make a choice and update the state\n            duplicated.insert(choice); // Record the chosen value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state.clone(), choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    }\n}\n\n/* Permutations II */\nfn permutations_ii(nums: &mut [i32]) -> Vec<Vec<i32>> {\n    let mut res = Vec::new();\n    backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res);\n    res\n}\n
    permutations_ii.c
    /* Backtracking: permutations II */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n    // Record a solution when the state length equals the number of elements\n    if (stateSize == choicesSize) {\n        res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n        for (int i = 0; i < choicesSize; i++) {\n            res[*resSize][i] = state[i];\n        }\n        (*resSize)++;\n        return;\n    }\n    // Iterate over all choices\n    bool duplicated[MAX_SIZE] = {false};\n    for (int i = 0; i < choicesSize; i++) {\n        int choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated[choice]) {\n            // Try: make a choice and update the state\n            duplicated[choice] = true; // Record the chosen value\n            selected[i] = true;\n            state[stateSize] = choice;\n            // Proceed to the next choice\n            backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n        }\n    }\n}\n\n/* Permutations II */\nint **permutationsII(int *nums, int numsSize, int *returnSize) {\n    int *state = (int *)malloc(numsSize * sizeof(int));\n    bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n    for (int i = 0; i < numsSize; i++) {\n        selected[i] = false;\n    }\n    int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n    *returnSize = 0;\n\n    backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n    free(state);\n    free(selected);\n\n    return res;\n}\n
    permutations_ii.kt
    /* Backtracking: permutations II */\nfun backtrack(\n    state: MutableList<Int>,\n    choices: IntArray,\n    selected: BooleanArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size == choices.size) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    val duplicated = HashSet<Int>()\n    for (i in choices.indices) {\n        val choice = choices[i]\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.contains(choice)) {\n            // Try: make a choice and update the state\n            duplicated.add(choice) // Record the chosen value\n            selected[i] = true\n            state.add(choice)\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res)\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false\n            state.removeAt(state.size - 1)\n        }\n    }\n}\n\n/* Permutations II */\nfun permutationsII(nums: IntArray): MutableList<MutableList<Int>?> {\n    val res = mutableListOf<MutableList<Int>?>()\n    backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n    return res\n}\n
    permutations_ii.rb
    ### Backtracking: permutations II ###\ndef backtrack(state, choices, selected, res)\n  # Record a solution when the state length equals the number of elements\n  if state.length == choices.length\n    res << state.dup\n    return\n  end\n\n  # Iterate over all choices\n  duplicated = Set.new\n  choices.each_with_index do |choice, i|\n    # Prune: do not reuse an element or choose the same value again at this depth\n    if !selected[i] && !duplicated.include?(choice)\n      # Try: make a choice and update the state\n      duplicated.add(choice)\n      selected[i] = true\n      state << choice\n      # Proceed to the next choice\n      backtrack(state, choices, selected, res)\n      # Backtrack: undo the choice and restore the previous state\n      selected[i] = false\n      state.pop\n    end\n  end\nend\n\n### Permutations II ###\ndef permutations_ii(nums)\n  res = []\n  backtrack([], nums, Array.new(nums.length, false), res)\n  res\nend\n
    Visualize execution

    View full screen >

    If all elements are distinct, \\(n\\) elements have \\(n!\\) permutations, where the exclamation mark denotes factorial. Recording each result requires copying a list of length \\(n\\), taking \\(O(n)\\) time. The time complexity is therefore \\(O(n!n)\\).

    The maximum recursion depth is \\(n\\), requiring \\(O(n)\\) space for stack frames. The array selected uses \\(O(n)\\) space. At most \\(n\\) duplicated sets exist at once, using \\(O(n^2)\\) space. The space complexity is therefore \\(O(n^2)\\).

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#3-comparing-the-two-pruning-methods","level":3,"title":"3.   Comparing the two pruning methods","text":"

    Although both selected and duplicated are used for pruning, they serve different purposes.

    • Pruning repeated selections: There is only one selected array throughout the search. It records which elements are in the current state, preventing the same element from appearing twice in state.
    • Pruning equal elements: Each round of choices (each call to backtrack) has its own duplicated set. It records the values already chosen in that round's for loop, ensuring that equal elements are tried only once in that round.

    Figure 13-9 shows the scope of each pruning condition. Each node represents a choice, and the nodes along a path from root to leaf form a permutation.

    Figure 13-9   Scope of the two pruning conditions

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/","level":1,"title":"13.3   Subset sum problem","text":"","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1331-distinct-elements","level":2,"title":"13.3.1   Distinct elements","text":"

    Question

    Given an array of positive integers nums and a positive target integer target, find all combinations whose elements sum to target. The array has no duplicate elements, and each element may be chosen multiple times. Return the combinations as a list containing no duplicates.

    For example, with input \\(\\{3, 4, 5\\}\\) and target \\(9\\), the solutions are \\(\\{3, 3, 3\\}, \\{4, 5\\}\\). Keep two points in mind.

    • Each input element may be chosen an unlimited number of times.
    • Element order does not matter: \\(\\{4, 5\\}\\) and \\(\\{5, 4\\}\\) represent the same subset.
    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1-starting-from-the-permutations-solution","level":3,"title":"1.   Starting from the permutations solution","text":"

    As with permutations, we can view generating a subset as a sequence of choices. Update the sum as we make choices, and record the subset when the sum equals target.

    Unlike permutations, each element may be chosen an unlimited number of times, so we do not need a Boolean selected list. A small modification to the permutations code gives an initial solution:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i_naive.py
    def backtrack(\n    state: list[int],\n    target: int,\n    total: int,\n    choices: list[int],\n    res: list[list[int]],\n):\n    \"\"\"Backtracking: subset sum I\"\"\"\n    # Record a solution when the subset sum equals target\n    if total == target:\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    for i in range(len(choices)):\n        # Prune: skip this choice if the subset sum would exceed target\n        if total + choices[i] > target:\n            continue\n        # Try: make a choice and update the sum total\n        state.append(choices[i])\n        # Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res)\n        # Backtrack: undo the choice and restore the previous state\n        state.pop()\n\ndef subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum I (with duplicate subsets)\"\"\"\n    state = []  # State (subset)\n    total = 0  # Subset sum\n    res = []  # Result list (list of subsets)\n    backtrack(state, target, total, nums, res)\n    return res\n
    subset_sum_i_naive.cpp
    /* Backtracking: subset sum I */\nvoid backtrack(vector<int> &state, int target, int total, vector<int> &choices, vector<vector<int>> &res) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    for (size_t i = 0; i < choices.size(); i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.push_back(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nvector<vector<int>> subsetSumINaive(vector<int> &nums, int target) {\n    vector<int> state;       // State (subset)\n    int total = 0;           // Subset sum\n    vector<vector<int>> res; // Result list (list of subsets)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.java
    /* Backtracking: subset sum I */\nvoid backtrack(List<Integer> state, int target, int total, int[] choices, List<List<Integer>> res) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.length; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.add(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nList<List<Integer>> subsetSumINaive(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    int total = 0; // Subset sum\n    List<List<Integer>> res = new ArrayList<>(); // Result list (list of subsets)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.cs
    /* Backtracking: subset sum I */\nvoid Backtrack(List<int> state, int target, int total, int[] choices, List<List<int>> res) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.Length; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.Add(choices[i]);\n        // Proceed to the next choice\n        Backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nList<List<int>> SubsetSumINaive(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    int total = 0; // Subset sum\n    List<List<int>> res = []; // Result list (list of subsets)\n    Backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.go
    /* Backtracking: subset sum I */\nfunc backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) {\n    // Record a solution when the subset sum equals target\n    if target == total {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Iterate over all choices\n    for i := 0; i < len(*choices); i++ {\n        // Prune: skip this choice if the subset sum would exceed target\n        if total+(*choices)[i] > target {\n            continue\n        }\n        // Try: make a choice and update the sum total\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next choice\n        backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res)\n        // Backtrack: undo the choice and restore the previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfunc subsetSumINaive(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    total := 0              // Subset sum\n    res := make([][]int, 0) // Result list (list of subsets)\n    backtrackSubsetSumINaive(total, target, &state, &nums, &res)\n    return res\n}\n
    subset_sum_i_naive.swift
    /* Backtracking: subset sum I */\nfunc backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) {\n    // Record a solution when the subset sum equals target\n    if total == target {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    for i in choices.indices {\n        // Prune: skip this choice if the subset sum would exceed target\n        if total + choices[i] > target {\n            continue\n        }\n        // Try: make a choice and update the sum total\n        state.append(choices[i])\n        // Proceed to the next choice\n        backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfunc subsetSumINaive(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let total = 0 // Subset sum\n    var res: [[Int]] = [] // Result list (list of subsets)\n    backtrack(state: &state, target: target, total: total, choices: nums, res: &res)\n    return res\n}\n
    subset_sum_i_naive.js
    /* Backtracking: subset sum I */\nfunction backtrack(state, target, total, choices, res) {\n    // Record a solution when the subset sum equals target\n    if (total === target) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    for (let i = 0; i < choices.length; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfunction subsetSumINaive(nums, target) {\n    const state = []; // State (subset)\n    const total = 0; // Subset sum\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.ts
    /* Backtracking: subset sum I */\nfunction backtrack(\n    state: number[],\n    target: number,\n    total: number,\n    choices: number[],\n    res: number[][]\n): void {\n    // Record a solution when the subset sum equals target\n    if (total === target) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    for (let i = 0; i < choices.length; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfunction subsetSumINaive(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    const total = 0; // Subset sum\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.dart
    /* Backtracking: subset sum I */\nvoid backtrack(\n  List<int> state,\n  int target,\n  int total,\n  List<int> choices,\n  List<List<int>> res,\n) {\n  // Record a solution when the subset sum equals target\n  if (total == target) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  for (int i = 0; i < choices.length; i++) {\n    // Prune: skip this choice if the subset sum would exceed target\n    if (total + choices[i] > target) {\n      continue;\n    }\n    // Try: make a choice and update the sum total\n    state.add(choices[i]);\n    // Proceed to the next choice\n    backtrack(state, target, total + choices[i], choices, res);\n    // Backtrack: undo the choice and restore the previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nList<List<int>> subsetSumINaive(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  int total = 0; // Sum of the elements\n  List<List<int>> res = []; // Result list (list of subsets)\n  backtrack(state, target, total, nums, res);\n  return res;\n}\n
    subset_sum_i_naive.rs
    /* Backtracking: subset sum I */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    total: i32,\n    choices: &[i32],\n    res: &mut Vec<Vec<i32>>,\n) {\n    // Record a solution when the subset sum equals target\n    if total == target {\n        res.push(state.clone());\n        return;\n    }\n    // Iterate over all choices\n    for i in 0..choices.len() {\n        // Prune: skip this choice if the subset sum would exceed target\n        if total + choices[i] > target {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    let total = 0; // Subset sum\n    let mut res = Vec::new(); // Result list (list of subsets)\n    backtrack(&mut state, target, total, nums, &mut res);\n    res\n}\n
    subset_sum_i_naive.c
    /* Backtracking: subset sum I */\nvoid backtrack(int target, int total, int *choices, int choicesSize) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        for (int i = 0; i < stateSize; i++) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choicesSize; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state[stateSize++] = choices[i];\n        // Proceed to the next choice\n        backtrack(target, total + choices[i], choices, choicesSize);\n        // Backtrack: undo the choice and restore the previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nvoid subsetSumINaive(int *nums, int numsSize, int target) {\n    resSize = 0; // Initialize the solution count to 0\n    backtrack(target, 0, nums, numsSize);\n}\n
    subset_sum_i_naive.kt
    /* Backtracking: subset sum I */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    total: Int,\n    choices: IntArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    for (i in choices.indices) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue\n        }\n        // Try: make a choice and update the sum total\n        state.add(choices[i])\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfun subsetSumINaive(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    val total = 0 // Subset sum\n    val res = mutableListOf<MutableList<Int>?>() // Result list (list of subsets)\n    backtrack(state, target, total, nums, res)\n    return res\n}\n
    subset_sum_i_naive.rb
    ### Backtracking: subset sum I ###\ndef backtrack(state, target, total, choices, res)\n  # Record a solution when the subset sum equals target\n  if total == target\n    res << state.dup\n    return\n  end\n\n  # Iterate over all choices\n  for i in 0...choices.length\n    # Prune: skip this choice if the subset sum would exceed target\n    next if total + choices[i] > target\n    # Try: make a choice and update the sum total\n    state << choices[i]\n    # Proceed to the next choice\n    backtrack(state, target, total + choices[i], choices, res)\n    # Backtrack: undo the choice and restore the previous state\n    state.pop\n  end\nend\n\n### Solve subset sum I (with duplicate subsets)###\ndef subset_sum_i_naive(nums, target)\n  state = [] # State (subset)\n  total = 0 # Subset sum\n  res = [] # Result list (list of subsets)\n  backtrack(state, target, total, nums, res)\n  res\nend\n
    Visualize execution

    View full screen >

    For input array \\([3, 4, 5]\\) and target \\(9\\), this code returns \\([3, 3, 3], [4, 5], [5, 4]\\). It finds all subsets summing to \\(9\\), but includes the duplicate subsets \\([4, 5]\\) and \\([5, 4]\\).

    The search distinguishes the order of choices, whereas subsets do not. As shown in Figure 13-10, choosing \\(4\\) then \\(5\\) and choosing \\(5\\) then \\(4\\) follow different branches but produce the same subset.

    Figure 13-10   Subset search and pruning when the sum exceeds the target

    One straightforward way to remove duplicate subsets is to deduplicate the result list. However, this is inefficient for two reasons.

    • With many array elements, especially when target is large, the search generates many duplicate subsets.
    • Comparing subsets (arrays) is expensive: we must sort them first, then compare their elements one by one.
    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2-pruning-duplicate-subsets","level":3,"title":"2.   Pruning duplicate subsets","text":"

    Instead, we can eliminate duplicates by pruning during the search. As shown in Figure 13-11, duplicates arise when array elements are chosen in different orders, as in the following example.

    1. Choosing \\(3\\) and then \\(4\\) in the first two rounds generates all subsets beginning with these choices, denoted by \\([3, 4, \\dots]\\).
    2. Later, after choosing \\(4\\) first, we should skip \\(3\\) in the second round, because subsets \\([4, 3, \\dots]\\) duplicate those generated in step 1..

    At each level of the search, choices are tried from left to right, so branches farther to the right have more choices pruned.

    1. Choosing \\(3\\) and then \\(5\\) generates subsets \\([3, 5, \\dots]\\).
    2. Choosing \\(4\\) and then \\(5\\) generates subsets \\([4, 5, \\dots]\\).
    3. If \\(5\\) is chosen first, skip both \\(3\\) and \\(4\\) in the second round, because \\([5, 3, \\dots]\\) and \\([5, 4, \\dots]\\) duplicate the subsets described in steps 1. and 2..

    Figure 13-11   Duplicate subsets caused by different choice orders

    In general, for input \\([x_1, x_2, \\dots, x_n]\\), a sequence of choices \\([x_{i_1}, x_{i_2}, \\dots, x_{i_m}]\\) must satisfy \\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\). Any sequence violating this condition creates a duplicate and should be pruned.

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#3-code-implementation","level":3,"title":"3.   Code implementation","text":"

    To implement this pruning, introduce start to specify where iteration begins. After choosing \\(x_{i}\\), begin the next round at index \\(i\\). This enforces \\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\) and ensures that each subset is unique.

    We also make two further improvements.

    • Sort nums before starting the search. While iterating over choices, stop the loop as soon as the subset sum exceeds target, because all later elements are larger and would also exceed it.
    • Remove the sum variable total and track the sum by subtracting from target. Record a solution when target reaches \\(0\\).
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i.py
    def backtrack(\n    state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n    \"\"\"Backtracking: subset sum I\"\"\"\n    # Record a solution when the remaining target is zero\n    if target == 0:\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    # Pruning 2: iterate from start to avoid generating duplicate subsets\n    for i in range(start, len(choices)):\n        # Pruning 1: exit the loop if this choice exceeds the remaining target\n        # The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0:\n            break\n        # Try: make a choice and update target and start\n        state.append(choices[i])\n        # Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res)\n        # Backtrack: undo the choice and restore the previous state\n        state.pop()\n\ndef subset_sum_i(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum I\"\"\"\n    state = []  # State (subset)\n    nums.sort()  # Sort nums\n    start = 0  # Starting index for iteration\n    res = []  # Result list (list of subsets)\n    backtrack(state, target, nums, start, res)\n    return res\n
    subset_sum_i.cpp
    /* Backtracking: subset sum I */\nvoid backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {\n    // Record a solution when the remaining target reaches 0\n    if (target == 0) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.size(); i++) {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.push_back(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum I */\nvector<vector<int>> subsetSumI(vector<int> &nums, int target) {\n    vector<int> state;              // State (subset)\n    sort(nums.begin(), nums.end()); // Sort nums\n    int start = 0;                  // Starting index for iteration\n    vector<vector<int>> res;        // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.java
    /* Backtracking: subset sum I */\nvoid backtrack(List<Integer> state, int target, int[] choices, int start, List<List<Integer>> res) {\n    // Record a solution when the remaining target is zero, so the subset sums to the original target\n    if (target == 0) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if the next element exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.add(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum I */\nList<List<Integer>> subsetSumI(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    Arrays.sort(nums); // Sort nums\n    int start = 0; // Starting index for iteration\n    List<List<Integer>> res = new ArrayList<>(); // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.cs
    /* Backtracking: subset sum I */\nvoid Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target == 0) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.Length; i++) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.Add(choices[i]);\n        // Proceed to the next choice\n        Backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum I */\nList<List<int>> SubsetSumI(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    Array.Sort(nums); // Sort nums\n    int start = 0; // Starting index for iteration\n    List<List<int>> res = []; // Result list (list of subsets)\n    Backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.go
    /* Backtracking: subset sum I */\nfunc backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) {\n    // Record a solution when the subset reaches the original target sum (remaining target is zero)\n    if target == 0 {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for i := start; i < len(*choices); i++ {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target-(*choices)[i] < 0 {\n            break\n        }\n        // Try: make a choice and update target and start\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next choice\n        backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res)\n        // Backtrack: undo the choice and restore the previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum I */\nfunc subsetSumI(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    sort.Ints(nums)         // Sort nums\n    start := 0              // Starting index for iteration\n    res := make([][]int, 0) // Result list (list of subsets)\n    backtrackSubsetSumI(start, target, &state, &nums, &res)\n    return res\n}\n
    subset_sum_i.swift
    /* Backtracking: subset sum I */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n    // Record a solution when the remaining target reaches zero\n    if target == 0 {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for i in choices.indices.dropFirst(start) {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0 {\n            break\n        }\n        // Try: make a choice and update target and start\n        state.append(choices[i])\n        // Proceed to the next choice\n        backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum I */\nfunc subsetSumI(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let nums = nums.sorted() // Sort nums\n    let start = 0 // Starting index for iteration\n    var res: [[Int]] = [] // Result list (list of subsets)\n    backtrack(state: &state, target: target, choices: nums, start: start, res: &res)\n    return res\n}\n
    subset_sum_i.js
    /* Backtracking: subset sum I */\nfunction backtrack(state, target, choices, start, res) {\n    // Record a solution when the remaining target is zero, so the subset sum equals the original target\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if the next choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfunction subsetSumI(nums, target) {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Starting index for iteration\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.ts
    /* Backtracking: subset sum I */\nfunction backtrack(\n    state: number[],\n    target: number,\n    choices: number[],\n    start: number,\n    res: number[][]\n): void {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfunction subsetSumI(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Starting index for iteration\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.dart
    /* Backtracking: subset sum I */\nvoid backtrack(\n  List<int> state,\n  int target,\n  List<int> choices,\n  int start,\n  List<List<int>> res,\n) {\n  // Record a solution when the subset sum reaches the original target (the remaining target is 0)\n  if (target == 0) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  // Pruning 2: iterate from start to avoid generating duplicate subsets\n  for (int i = start; i < choices.length; i++) {\n    // Pruning 1: exit the loop if this choice exceeds the remaining target\n    // The array is sorted, so later elements are at least as large and also exceed the remaining target\n    if (target - choices[i] < 0) {\n      break;\n    }\n    // Try: make a choice and update target and start\n    state.add(choices[i]);\n    // Proceed to the next choice\n    backtrack(state, target - choices[i], choices, i, res);\n    // Backtrack: undo the choice and restore the previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum I */\nList<List<int>> subsetSumI(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  nums.sort(); // Sort nums\n  int start = 0; // Starting index for iteration\n  List<List<int>> res = []; // Result list (list of subsets)\n  backtrack(state, target, nums, start, res);\n  return res;\n}\n
    subset_sum_i.rs
    /* Backtracking: subset sum I */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    choices: &[i32],\n    start: usize,\n    res: &mut Vec<Vec<i32>>,\n) {\n    // Record a solution when the remaining target is zero\n    if target == 0 {\n        res.push(state.clone());\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for i in start..choices.len() {\n        // Pruning 1: exit the loop if the next element exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0 {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfn subset_sum_i(nums: &mut [i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    nums.sort(); // Sort nums\n    let start = 0; // Starting index for iteration\n    let mut res = Vec::new(); // Result list (list of subsets)\n    backtrack(&mut state, target, nums, start, &mut res);\n    res\n}\n
    subset_sum_i.c
    /* Backtracking: subset sum I */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n    // Record a solution when the remaining target is 0\n    if (target == 0) {\n        for (int i = 0; i < stateSize; ++i) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (int i = start; i < choicesSize; i++) {\n        // Pruning 1: exit the loop if the choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state[stateSize] = choices[i];\n        stateSize++;\n        // Proceed to the next choice\n        backtrack(target - choices[i], choices, choicesSize, i);\n        // Backtrack: undo the choice and restore the previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum I */\nvoid subsetSumI(int *nums, int numsSize, int target) {\n    qsort(nums, numsSize, sizeof(int), cmp); // Sort nums\n    int start = 0;                           // Starting index for iteration\n    backtrack(target, nums, numsSize, start);\n}\n
    subset_sum_i.kt
    /* Backtracking: subset sum I */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    choices: IntArray,\n    start: Int,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target == 0) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (i in start..<choices.size) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break\n        }\n        // Try: make a choice and update target and start\n        state.add(choices[i])\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum I */\nfun subsetSumI(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    nums.sort() // Sort nums\n    val start = 0 // Starting index for iteration\n    val res = mutableListOf<MutableList<Int>?>() // Result list (list of subsets)\n    backtrack(state, target, nums, start, res)\n    return res\n}\n
    subset_sum_i.rb
    ### Backtracking: subset sum I ###\ndef backtrack(state, target, choices, start, res)\n  # Record a solution when the remaining target reaches zero\n  if target.zero?\n    res << state.dup\n    return\n  end\n  # Iterate over all choices\n  # Pruning 2: iterate from start to avoid generating duplicate subsets\n  for i in start...choices.length\n    # Pruning 1: exit the loop if this choice would exceed the remaining target\n    # The array is sorted, so later elements are at least as large and would also exceed the remaining target\n    break if target - choices[i] < 0\n    # Try: make a choice and update target and start\n    state << choices[i]\n    # Proceed to the next choice\n    backtrack(state, target - choices[i], choices, i, res)\n    # Backtrack: undo the choice and restore the previous state\n    state.pop\n  end\nend\n\n### Solve subset sum I ###\ndef subset_sum_i(nums, target)\n  state = [] # State (subset)\n  nums.sort! # Sort nums\n  start = 0 # Starting index for iteration\n  res = [] # Result list (list of subsets)\n  backtrack(state, target, nums, start, res)\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 13-12 shows the complete backtracking process for input \\([3, 4, 5]\\) and target \\(9\\).

    Figure 13-12   Backtracking for subset sum I

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1332-duplicate-elements","level":2,"title":"13.3.2   Duplicate elements","text":"

    Question

    Given an array of positive integers nums and a positive target integer target, find all combinations whose elements sum to target. The array may contain duplicate elements, and each element may be chosen only once. Return the combinations as a list containing no duplicates.

    Unlike the previous problem, the input may contain duplicate elements, which introduces a new issue. For array \\([4, \\hat{4}, 5]\\) and target \\(9\\), for example, the existing code produces \\([4, 5], [\\hat{4}, 5]\\): duplicate subsets.

    These duplicates occur because equal elements are tried more than once in a single round. In Figure 13-13, the first round has three choices, two of which are \\(4\\). They generate duplicate branches and therefore duplicate subsets. The two occurrences of \\(4\\) in the second round cause the same problem.

    Figure 13-13   Duplicate subsets caused by equal elements

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1-pruning-equal-elements","level":3,"title":"1.   Pruning equal elements","text":"

    To solve this, try each value only once in each round. The implementation uses the fact that equal elements are adjacent in a sorted array. Within a round, if an element equals the preceding element, that value has already been tried, so skip the current element.

    In addition, each array element may be chosen only once. Fortunately, start can enforce this constraint too: after choosing \\(x_{i}\\), begin the next round at index \\(i + 1\\). This both eliminates duplicate subsets and prevents reusing an element.

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_ii.py
    def backtrack(\n    state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n    \"\"\"Backtracking: subset sum II\"\"\"\n    # Record a solution when the remaining target is zero\n    if target == 0:\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    # Pruning 2: iterate from start to avoid generating duplicate subsets\n    # Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for i in range(start, len(choices)):\n        # Pruning 1: exit the loop if this choice exceeds the remaining target\n        # The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0:\n            break\n        # Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if i > start and choices[i] == choices[i - 1]:\n            continue\n        # Try: make a choice and update target and start\n        state.append(choices[i])\n        # Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res)\n        # Backtrack: undo the choice and restore the previous state\n        state.pop()\n\ndef subset_sum_ii(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum II\"\"\"\n    state = []  # State (subset)\n    nums.sort()  # Sort nums\n    start = 0  # Starting index for iteration\n    res = []  # Result list (list of subsets)\n    backtrack(state, target, nums, start, res)\n    return res\n
    subset_sum_ii.cpp
    /* Backtracking: subset sum II */\nvoid backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {\n    // Record a solution when the remaining target reaches 0\n    if (target == 0) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: iterate from start to avoid choosing the same element again\n    for (int i = start; i < choices.size(); i++) {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.push_back(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum II */\nvector<vector<int>> subsetSumII(vector<int> &nums, int target) {\n    vector<int> state;              // State (subset)\n    sort(nums.begin(), nums.end()); // Sort nums\n    int start = 0;                  // Starting index for iteration\n    vector<vector<int>> res;        // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.java
    /* Backtracking: subset sum II */\nvoid backtrack(List<Integer> state, int target, int[] choices, int start, List<List<Integer>> res) {\n    // Record a solution when the remaining target is zero, so the subset sums to the original target\n    if (target == 0) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: iterate from start to avoid choosing the same element again\n    for (int i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if the next element exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.add(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum II */\nList<List<Integer>> subsetSumII(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    Arrays.sort(nums); // Sort nums\n    int start = 0; // Starting index for iteration\n    List<List<Integer>> res = new ArrayList<>(); // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.cs
    /* Backtracking: subset sum II */\nvoid Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target == 0) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for (int i = start; i < choices.Length; i++) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.Add(choices[i]);\n        // Proceed to the next choice\n        Backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum II */\nList<List<int>> SubsetSumII(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    Array.Sort(nums); // Sort nums\n    int start = 0; // Starting index for iteration\n    List<List<int>> res = []; // Result list (list of subsets)\n    Backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.go
    /* Backtracking: subset sum II */\nfunc backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) {\n    // Record a solution when the subset reaches the original target sum (remaining target is zero)\n    if target == 0 {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for i := start; i < len(*choices); i++ {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target-(*choices)[i] < 0 {\n            break\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if i > start && (*choices)[i] == (*choices)[i-1] {\n            continue\n        }\n        // Try: make a choice and update target and start\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next choice\n        backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res)\n        // Backtrack: undo the choice and restore the previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum II */\nfunc subsetSumII(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    sort.Ints(nums)         // Sort nums\n    start := 0              // Starting index for iteration\n    res := make([][]int, 0) // Result list (list of subsets)\n    backtrackSubsetSumII(start, target, &state, &nums, &res)\n    return res\n}\n
    subset_sum_ii.swift
    /* Backtracking: subset sum II */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n    // Record a solution when the remaining target reaches zero\n    if target == 0 {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for i in choices.indices.dropFirst(start) {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0 {\n            break\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if i > start, choices[i] == choices[i - 1] {\n            continue\n        }\n        // Try: make a choice and update target and start\n        state.append(choices[i])\n        // Proceed to the next choice\n        backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum II */\nfunc subsetSumII(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let nums = nums.sorted() // Sort nums\n    let start = 0 // Starting index for iteration\n    var res: [[Int]] = [] // Result list (list of subsets)\n    backtrack(state: &state, target: target, choices: nums, start: start, res: &res)\n    return res\n}\n
    subset_sum_ii.js
    /* Backtracking: subset sum II */\nfunction backtrack(state, target, choices, start, res) {\n    // Record a solution when the remaining target is zero, so the subset sum equals the original target\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if the next choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] === choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfunction subsetSumII(nums, target) {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Starting index for iteration\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.ts
    /* Backtracking: subset sum II */\nfunction backtrack(\n    state: number[],\n    target: number,\n    choices: number[],\n    start: number,\n    res: number[][]\n): void {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] === choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfunction subsetSumII(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Starting index for iteration\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.dart
    /* Backtracking: subset sum II */\nvoid backtrack(\n  List<int> state,\n  int target,\n  List<int> choices,\n  int start,\n  List<List<int>> res,\n) {\n  // Record a solution when the subset sum reaches the original target (the remaining target is 0)\n  if (target == 0) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  // Pruning 2: iterate from start to avoid generating duplicate subsets\n  // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n  for (int i = start; i < choices.length; i++) {\n    // Pruning 1: exit the loop if this choice exceeds the remaining target\n    // The array is sorted, so later elements are at least as large and also exceed the remaining target\n    if (target - choices[i] < 0) {\n      break;\n    }\n    // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n    if (i > start && choices[i] == choices[i - 1]) {\n      continue;\n    }\n    // Try: make a choice and update target and start\n    state.add(choices[i]);\n    // Proceed to the next choice\n    backtrack(state, target - choices[i], choices, i + 1, res);\n    // Backtrack: undo the choice and restore the previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum II */\nList<List<int>> subsetSumII(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  nums.sort(); // Sort nums\n  int start = 0; // Starting index for iteration\n  List<List<int>> res = []; // Result list (list of subsets)\n  backtrack(state, target, nums, start, res);\n  return res;\n}\n
    subset_sum_ii.rs
    /* Backtracking: subset sum II */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    choices: &[i32],\n    start: usize,\n    res: &mut Vec<Vec<i32>>,\n) {\n    // Record a solution when the remaining target is zero\n    if target == 0 {\n        res.push(state.clone());\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: iterate from start to avoid choosing the same element again\n    for i in start..choices.len() {\n        // Pruning 1: exit the loop if the next element exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0 {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if i > start && choices[i] == choices[i - 1] {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    nums.sort(); // Sort nums\n    let start = 0; // Starting index for iteration\n    let mut res = Vec::new(); // Result list (list of subsets)\n    backtrack(&mut state, target, nums, start, &mut res);\n    res\n}\n
    subset_sum_ii.c
    /* Backtracking: subset sum II */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n    // Record a solution when the remaining target is 0\n    if (target == 0) {\n        for (int i = 0; i < stateSize; i++) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: iterate from start to avoid choosing the same element again\n    for (int i = start; i < choicesSize; i++) {\n        // Pruning 1: skip this choice if it exceeds the remaining target\n        if (target - choices[i] < 0) {\n            continue;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state[stateSize] = choices[i];\n        stateSize++;\n        // Proceed to the next choice\n        backtrack(target - choices[i], choices, choicesSize, i + 1);\n        // Backtrack: undo the choice and restore the previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum II */\nvoid subsetSumII(int *nums, int numsSize, int target) {\n    // Sort nums\n    qsort(nums, numsSize, sizeof(int), cmp);\n    // Start backtracking\n    backtrack(target, nums, numsSize, 0);\n}\n
    subset_sum_ii.kt
    /* Backtracking: subset sum II */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    choices: IntArray,\n    start: Int,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target == 0) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for (i in start..<choices.size) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue\n        }\n        // Try: make a choice and update target and start\n        state.add(choices[i])\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum II */\nfun subsetSumII(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    nums.sort() // Sort nums\n    val start = 0 // Starting index for iteration\n    val res = mutableListOf<MutableList<Int>?>() // Result list (list of subsets)\n    backtrack(state, target, nums, start, res)\n    return res\n}\n
    subset_sum_ii.rb
    ### Backtracking: subset sum II ###\ndef backtrack(state, target, choices, start, res)\n  # Record a solution when the remaining target reaches zero\n  if target.zero?\n    res << state.dup\n    return\n  end\n\n  # Iterate over all choices\n  # Pruning 2: iterate from start to avoid generating duplicate subsets\n  # Pruning 3: start after the previously chosen index to avoid choosing the same element again\n  for i in start...choices.length\n    # Pruning 1: exit the loop if this choice would exceed the remaining target\n    # The array is sorted, so later elements are at least as large and would also exceed the remaining target\n    break if target - choices[i] < 0\n    # Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n    next if i > start && choices[i] == choices[i - 1]\n    # Try: make a choice and update target and start\n    state << choices[i]\n    # Proceed to the next choice\n    backtrack(state, target - choices[i], choices, i + 1, res)\n    # Backtrack: undo the choice and restore the previous state\n    state.pop\n  end\nend\n\n### Solve subset sum II ###\ndef subset_sum_ii(nums, target)\n  state = [] # State (subset)\n  nums.sort! # Sort nums\n  start = 0 # Starting index for iteration\n  res = [] # Result list (list of subsets)\n  backtrack(state, target, nums, start, res)\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 13-14 shows backtracking for array \\([4, 4, 5]\\) and target \\(9\\), including four types of pruning. Follow the illustration alongside the code comments to understand the full search and how each pruning rule works.

    Figure 13-14   Backtracking for subset sum II

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/summary/","level":1,"title":"13.5   Summary","text":"","path":["Chapter 13.   Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_backtracking/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Backtracking is fundamentally exhaustive search. It traverses the solution space depth first to find solutions meeting the requirements, records them when found, and continues until all solutions are found or traversal is complete.
    • Backtracking alternates between trying a choice and undoing it. It explores choices through depth-first search; when a constraint is violated, it undoes the last choice, restores the previous state, and tries another choice. Trying and undoing are opposite operations.
    • Backtracking problems usually have multiple constraints that enable pruning. Pruning stops unnecessary branches early and can greatly improve search efficiency.
    • Backtracking is mainly used for search and constraint satisfaction problems. It can also solve combinatorial optimization problems, though more efficient or more effective approaches often exist.
    • The permutations problem asks for every possible ordering of a collection's elements. An array tracks whether each element has been selected, pruning branches that select the same element again and ensuring each is used only once.
    • If the input contains equal elements, permutation generation produces duplicate results. We must try each value only once per round, usually by using a hash set.
    • The subset sum problem asks for all subsets of a collection whose sum equals a target. Subsets disregard order, but the search generates all orderings, producing duplicates. Sorting the data before backtracking and using a variable to mark each round's starting index allows us to prune duplicate-generating branches.
    • Equal elements also produce duplicate subsets. Because the array is sorted, we can prune by comparing adjacent elements, ensuring that each value is tried only once per round.
    • The \\(n\\)-queens problem asks for arrangements of \\(n\\) queens on an \\(n \\times n\\) chessboard such that no two queens can attack each other. It imposes row, column, and both diagonal constraints. Placing queens row by row ensures exactly one queen per row.
    • Column and diagonal constraints are handled similarly. An array records whether each column contains a queen, indicating whether a chosen square is valid. Two more arrays track queens on diagonals in each direction. The main challenge is identifying the relationship between row and column indices for squares on the same diagonal.
    ","path":["Chapter 13.   Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_backtracking/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: How are backtracking and recursion related?

    Broadly speaking, backtracking is an algorithmic strategy, while recursion is a tool.

    • Backtracking is usually implemented recursively. It is one application of recursion, specifically to search problems.
    • Recursion expresses the idea of decomposing a problem into subproblems. It is commonly used in divide and conquer, backtracking, and dynamic programming (memoized recursion).
    ","path":["Chapter 13.   Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/","level":1,"title":"Chapter 2.   Complexity analysis","text":"

    Abstract

    Complexity analysis is our guide to time and space in the vast universe of algorithms.

    It leads us to explore these two dimensions in depth as we search for more elegant solutions.

    ","path":["Chapter 2.   Complexity analysis"],"tags":[]},{"location":"chapter_computational_complexity/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 2.1   Evaluating algorithm efficiency
    • 2.2   Iteration and recursion
    • 2.3   Time complexity
    • 2.4   Space complexity
    • 2.5   Summary
    • 2.6   Exercises
    ","path":["Chapter 2.   Complexity analysis"],"tags":[]},{"location":"chapter_computational_complexity/exercises/","level":1,"title":"2.6   Exercises","text":"","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#261-check-your-understanding","level":2,"title":"2.6.1   Check your understanding","text":"","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#1-time-and-space-in-iteration-and-recursion","level":3,"title":"1.   Time and space in iteration and recursion","text":"

    Both functions below compute \\(1 + 2 + \\dots + n\\) (assume \\(n \\ge 1\\)). Set n to 4, follow the program's execution order to answer the questions, and then compare the efficiency of the two approaches.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby complexity_exercises.py
    def sum_iter(n: int) -> int:\n    \"\"\"Sum using iteration\"\"\"\n    res = 0\n    for i in range(1, n + 1):\n        res += i\n    return res\n\ndef sum_recur(n: int) -> int:\n    \"\"\"Sum using recursion\"\"\"\n    if n == 1:\n        return 1\n    return n + sum_recur(n - 1)\n
    complexity_exercises.cpp
    /* Sum using iteration */\nint sumIter(int n) {\n    int res = 0;\n    for (int i = 1; i <= n; ++i) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nint sumRecur(int n) {\n    if (n == 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.java
    /* Sum using iteration */\nint sumIter(int n) {\n    int res = 0;\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nint sumRecur(int n) {\n    if (n == 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.cs
    /* Sum using iteration */\nint SumIter(int n) {\n    int res = 0;\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nint SumRecur(int n) {\n    if (n == 1) {\n        return 1;\n    }\n    return n + SumRecur(n - 1);\n}\n
    complexity_exercises.go
    /* Sum using iteration */\nfunc sumIter(n int) int {\n    res := 0\n    for i := 1; i <= n; i++ {\n        res += i\n    }\n    return res\n}\n\n/* Sum using recursion */\nfunc sumRecur(n int) int {\n    if n == 1 {\n        return 1\n    }\n    return n + sumRecur(n-1)\n}\n
    complexity_exercises.swift
    /* Sum using iteration */\nfunc sumIter(n: Int) -> Int {\n    var res = 0\n    for i in 1 ... n {\n        res += i\n    }\n    return res\n}\n\n/* Sum using recursion */\nfunc sumRecur(n: Int) -> Int {\n    if n == 1 {\n        return 1\n    }\n    return n + sumRecur(n: n - 1)\n}\n
    complexity_exercises.js
    /* Sum using iteration */\nfunction sumIter(n) {\n    let res = 0;\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nfunction sumRecur(n) {\n    if (n === 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.ts
    /* Sum using iteration */\nfunction sumIter(n: number): number {\n    let res = 0;\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nfunction sumRecur(n: number): number {\n    if (n === 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.dart
    /* Sum using iteration */\nint sumIter(int n) {\n  int res = 0;\n  for (int i = 1; i <= n; i++) {\n    res += i;\n  }\n  return res;\n}\n\n/* Sum using recursion */\nint sumRecur(int n) {\n  if (n == 1) {\n    return 1;\n  }\n  return n + sumRecur(n - 1);\n}\n
    complexity_exercises.rs
    /* Sum using iteration */\nfn sum_iter(n: i32) -> i32 {\n    let mut res = 0;\n    for i in 1..=n {\n        res += i;\n    }\n    res\n}\n\n/* Sum using recursion */\nfn sum_recur(n: i32) -> i32 {\n    if n == 1 {\n        return 1;\n    }\n    n + sum_recur(n - 1)\n}\n
    complexity_exercises.c
    /* Sum using iteration */\nint sumIter(int n) {\n    int res = 0;\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nint sumRecur(int n) {\n    if (n == 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.kt
    /* Sum using iteration */\nfun sumIter(n: Int): Int {\n    var res = 0\n    for (i in 1..n) {\n        res += i\n    }\n    return res\n}\n\n/* Sum using recursion */\nfun sumRecur(n: Int): Int {\n    if (n == 1) {\n        return 1\n    }\n    return n + sumRecur(n - 1)\n}\n
    complexity_exercises.rb
    ### Sum using iteration ###\ndef sum_iter(n)\n  res = 0\n  for i in 1..n\n    res += i\n  end\n  res\nend\n\n### Sum using recursion ###\ndef sum_recur(n)\n  return 1 if n == 1\n\n  n + sum_recur(n - 1)\nend\n
    1. When the iterative function runs with n = 4, what is the value of the accumulator res at the end of each iteration?
    2. When the recursive function runs with n = 4, what values does the parameter n take, in order? How is the result computed as the calls return, starting from the deepest call?
    3. What are the time and space complexities of each approach? Explain using the execution steps from questions 1 and 2.
    Reference answer
    1. The loop variable i takes the values 1, 2, 3, 4 in order. At the end of each iteration, res becomes 1, 3, 6, 10, respectively, so the iterative function returns 10.

    2. The parameter n follows the sequence 4 → 3 → 2 → 1. The deepest call returns 1. The remaining calls then compute 2 + 1 = 3, 3 + 3 = 6, and 4 + 6 = 10, in order. At the deepest point, all four function calls are still active.

    3. Both functions perform a number of iterations or calls proportional to \\(n\\), so both have time complexity \\(O(n)\\). Their space complexities differ: the iterative version uses only a constant number of variables, giving \\(O(1)\\). In the recursive version, earlier calls must wait for their results until the base case is reached. The call stack therefore holds up to \\(n\\) calls at once, giving space complexity \\(O(n)\\).

      When analyzing space complexity, consider the space used by recursive calls as well as the variables in the code.

    ","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#2-time-complexity-of-three-code-snippets","level":3,"title":"2.   Time complexity of three code snippets","text":"

    Each of the following three code snippets takes a positive integer \\(n\\) as input. Rank them from lowest to highest time complexity, and state each complexity.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby complexity_exercises.py
    def linear_loop(n: int) -> int:\n    \"\"\"Linear-time loop\"\"\"\n    res = 0\n    for i in range(n):\n        res += i\n    return res\n\ndef quadratic_loop(n: int) -> int:\n    \"\"\"Quadratic-time loop\"\"\"\n    res = 0\n    for i in range(n):\n        for j in range(i, n):\n            res += j\n    return res\n\ndef logarithmic_loop(n: int) -> int:\n    \"\"\"Logarithmic-time loop\"\"\"\n    while n > 1:\n        n //= 2\n    return n\n
    complexity_exercises.cpp
    /* Linear-time loop */\nint linearLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; ++i) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nint quadraticLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; ++i) {\n        for (int j = i; j < n; ++j) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nint logarithmicLoop(int n) {\n    while (n > 1) {\n        n /= 2;\n    }\n    return n;\n}\n
    complexity_exercises.java
    /* Linear-time loop */\nint linearLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nint quadraticLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        for (int j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nint logarithmicLoop(int n) {\n    while (n > 1) {\n        n /= 2;\n    }\n    return n;\n}\n
    complexity_exercises.cs
    /* Linear-time loop */\nint LinearLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nint QuadraticLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        for (int j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nint LogarithmicLoop(int n) {\n    while (n > 1) {\n        n /= 2;\n    }\n    return n;\n}\n
    complexity_exercises.go
    /* Linear-time loop */\nfunc linearLoop(n int) int {\n    res := 0\n    for i := 0; i < n; i++ {\n        res += i\n    }\n    return res\n}\n\n/* Quadratic-time loop */\nfunc quadraticLoop(n int) int {\n    res := 0\n    for i := 0; i < n; i++ {\n        for j := i; j < n; j++ {\n            res += j\n        }\n    }\n    return res\n}\n\n/* Logarithmic-time loop */\nfunc logarithmicLoop(n int) int {\n    for n > 1 {\n        n /= 2\n    }\n    return n\n}\n
    complexity_exercises.swift
    /* Linear-time loop */\nfunc linearLoop(n: Int) -> Int {\n    var res = 0\n    for i in 0 ..< n {\n        res += i\n    }\n    return res\n}\n\n/* Quadratic-time loop */\nfunc quadraticLoop(n: Int) -> Int {\n    var res = 0\n    for i in 0 ..< n {\n        for j in i ..< n {\n            res += j\n        }\n    }\n    return res\n}\n\n/* Logarithmic-time loop */\nfunc logarithmicLoop(n: Int) -> Int {\n    var n = n\n    while n > 1 {\n        n /= 2\n    }\n    return n\n}\n
    complexity_exercises.js
    /* Linear-time loop */\nfunction linearLoop(n) {\n    let res = 0;\n    for (let i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nfunction quadraticLoop(n) {\n    let res = 0;\n    for (let i = 0; i < n; i++) {\n        for (let j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nfunction logarithmicLoop(n) {\n    while (n > 1) {\n        n = Math.floor(n / 2);\n    }\n    return n;\n}\n
    complexity_exercises.ts
    /* Linear-time loop */\nfunction linearLoop(n: number): number {\n    let res = 0;\n    for (let i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nfunction quadraticLoop(n: number): number {\n    let res = 0;\n    for (let i = 0; i < n; i++) {\n        for (let j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nfunction logarithmicLoop(n: number): number {\n    while (n > 1) {\n        n = Math.floor(n / 2);\n    }\n    return n;\n}\n
    complexity_exercises.dart
    /* Linear-time loop */\nint linearLoop(int n) {\n  int res = 0;\n  for (int i = 0; i < n; i++) {\n    res += i;\n  }\n  return res;\n}\n\n/* Quadratic-time loop */\nint quadraticLoop(int n) {\n  int res = 0;\n  for (int i = 0; i < n; i++) {\n    for (int j = i; j < n; j++) {\n      res += j;\n    }\n  }\n  return res;\n}\n\n/* Logarithmic-time loop */\nint logarithmicLoop(int n) {\n  while (n > 1) {\n    n ~/= 2;\n  }\n  return n;\n}\n
    complexity_exercises.rs
    /* Linear-time loop */\nfn linear_loop(n: i32) -> i32 {\n    let mut res = 0;\n    for i in 0..n {\n        res += i;\n    }\n    res\n}\n\n/* Quadratic-time loop */\nfn quadratic_loop(n: i32) -> i32 {\n    let mut res = 0;\n    for i in 0..n {\n        for j in i..n {\n            res += j;\n        }\n    }\n    res\n}\n\n/* Logarithmic-time loop */\nfn logarithmic_loop(mut n: i32) -> i32 {\n    while n > 1 {\n        n /= 2;\n    }\n    n\n}\n
    complexity_exercises.c
    /* Linear-time loop */\nint linearLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nint quadraticLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        for (int j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nint logarithmicLoop(int n) {\n    while (n > 1) {\n        n /= 2;\n    }\n    return n;\n}\n
    complexity_exercises.kt
    /* Linear-time loop */\nfun linearLoop(n: Int): Int {\n    var res = 0\n    for (i in 0 until n) {\n        res += i\n    }\n    return res\n}\n\n/* Quadratic-time loop */\nfun quadraticLoop(n: Int): Int {\n    var res = 0\n    for (i in 0 until n) {\n        for (j in i until n) {\n            res += j\n        }\n    }\n    return res\n}\n\n/* Logarithmic-time loop */\nfun logarithmicLoop(n: Int): Int {\n    var value = n\n    while (value > 1) {\n        value /= 2\n    }\n    return value\n}\n
    complexity_exercises.rb
    ### Linear-time loop ###\ndef linear_loop(n)\n  res = 0\n  for i in 0...n\n    res += i\n  end\n  res\nend\n\n### Quadratic-time loop ###\ndef quadratic_loop(n)\n  res = 0\n  for i in 0...n\n    for j in i...n\n      res += j\n    end\n  end\n  res\nend\n\n### Logarithmic-time loop ###\ndef logarithmic_loop(n)\n  n /= 2 while n > 1\n  n\nend\n
    Reference answer

    From lowest to highest: snippet 3, \\(O(\\log n)\\); snippet 1, \\(O(n)\\); snippet 2, \\(O(n^2)\\). Snippet 3 halves \\(n\\) at each iteration, so it runs approximately \\(\\log_2 n\\) times. Snippet 1 runs its loop exactly \\(n\\) times. In snippet 2, the inner loop runs \\(n,n-1,\\dots,1\\) times in successive iterations, for a total of \\(n(n+1)/2\\), giving quadratic time complexity.

    ","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#3-which-reversal-method-uses-less-space","level":3,"title":"3.   Which reversal method uses less space?","text":"

    There are two ways to reverse all the elements in an array nums:

    1. Create an array res of the same length, copy the elements into it in reverse order, and return it.
    2. Move two indices, i and j, inward from the beginning and end, swapping nums[i] with nums[j] in pairs.

      What is the space complexity of each approach? Which operation is in-place?

    Reference answer
    1. An auxiliary array as long as the input is needed, giving space complexity \\(O(n)\\).

    2. Only two index variables are used, giving space complexity \\(O(1)\\). This is an in-place operation.

      Keep in mind that in-place reversal modifies the input array. Prefer it only when modifying the input is allowed. If the original array must be preserved, the copying cost of approach 1 is unavoidable.

    ","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#262-programming-exercises","level":2,"title":"2.6.2   Programming exercises","text":"","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#1-fibonacci-number","level":3,"title":"1.   Fibonacci number","text":"

    The Fibonacci sequence satisfies \\(F(0)=0\\), \\(F(1)=1\\), and, for \\(n\\ge2\\), \\(F(n)=F(n-1)+F(n-2)\\).

    Given a nonnegative integer n, use a loop to compute and return \\(F(n)\\) without recursion.

    Hints
    1. Handle n equal to 0 or 1 separately first.
    2. Computing the next term requires only the previous two terms; there is no need to store the entire sequence.
    3. When updating the two variables, take care not to overwrite an old value before you have finished using it.

    LeetCode

    ","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/","level":1,"title":"2.2   Iteration and recursion","text":"

    Algorithms often repeat tasks, and repetition is closely tied to complexity analysis. Before introducing time and space complexity, let's examine two basic ways to repeat work in a program: iteration and recursion.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#221-iteration","level":2,"title":"2.2.1   Iteration","text":"

    Iteration is a control structure for repeating a task. A program repeatedly executes a block of code while a condition holds, stopping when it no longer does.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-for-loops","level":3,"title":"1.   For loops","text":"

    A for loop is one of the most common forms of iteration, especially when the number of iterations is known in advance.

    The following function uses a for loop to compute \\(1 + 2 + \\dots + n\\), storing the result in res. In Python, range(a, b) includes the left endpoint but excludes the right, so it visits \\(a, a + 1, \\dots, b-1\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
    def for_loop(n: int) -> int:\n    \"\"\"for loop\"\"\"\n    res = 0\n    # Sum 1, 2, ..., n-1, n in a loop\n    for i in range(1, n + 1):\n        res += i\n    return res\n
    iteration.cpp
    /* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (int i = 1; i <= n; ++i) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.java
    /* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.cs
    /* for loop */\nint ForLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.go
    /* for loop */\nfunc forLoop(n int) int {\n    res := 0\n    // Sum 1, 2, ..., n-1, n in a loop\n    for i := 1; i <= n; i++ {\n        res += i\n    }\n    return res\n}\n
    iteration.swift
    /* for loop */\nfunc forLoop(n: Int) -> Int {\n    var res = 0\n    // Sum 1, 2, ..., n-1, n in a loop\n    for i in 1 ... n {\n        res += i\n    }\n    return res\n}\n
    iteration.js
    /* for loop */\nfunction forLoop(n) {\n    let res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.ts
    /* for loop */\nfunction forLoop(n: number): number {\n    let res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.dart
    /* for loop */\nint forLoop(int n) {\n  int res = 0;\n  // Sum 1, 2, ..., n-1, n in a loop\n  for (int i = 1; i <= n; i++) {\n    res += i;\n  }\n  return res;\n}\n
    iteration.rs
    /* for loop */\nfn for_loop(n: i32) -> i32 {\n    let mut res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for i in 1..=n {\n        res += i;\n    }\n    res\n}\n
    iteration.c
    /* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.kt
    /* for loop */\nfun forLoop(n: Int): Int {\n    var res = 0\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (i in 1..n) {\n        res += i\n    }\n    return res\n}\n
    iteration.rb
    ### for loop ###\ndef for_loop(n)\n  res = 0\n\n  # Sum 1, 2, ..., n-1, n in a loop\n  for i in 1..n\n    res += i\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 2-1 is a flowchart of this summation function.

    Figure 2-1   Flowchart of the summation function

    Its operation count is proportional to the input size \\(n\\): the relationship is linear. Time complexity describes this relationship, as the next section will explain.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-while-loops","level":3,"title":"2.   While loops","text":"

    Like for, a while loop implements iteration. Before each iteration, it checks a condition: if true, execution continues; otherwise, the loop ends.

    Here is a while loop computing \\(1 + 2 + \\dots + n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
    def while_loop(n: int) -> int:\n    \"\"\"while loop\"\"\"\n    res = 0\n    i = 1  # Initialize the loop control variable\n    # Sum 1, 2, ..., n-1, n in a loop\n    while i <= n:\n        res += i\n        i += 1  # Update the loop control variable\n    return res\n
    iteration.cpp
    /* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.java
    /* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.cs
    /* while loop */\nint WhileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i += 1; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.go
    /* while loop */\nfunc whileLoop(n int) int {\n    res := 0\n    // Initialize the loop control variable\n    i := 1\n    // Sum 1, 2, ..., n-1, n in a loop\n    for i <= n {\n        res += i\n        // Update the loop control variable\n        i++\n    }\n    return res\n}\n
    iteration.swift
    /* while loop */\nfunc whileLoop(n: Int) -> Int {\n    var res = 0\n    var i = 1 // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while i <= n {\n        res += i\n        i += 1 // Update the loop control variable\n    }\n    return res\n}\n
    iteration.js
    /* while loop */\nfunction whileLoop(n) {\n    let res = 0;\n    let i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.ts
    /* while loop */\nfunction whileLoop(n: number): number {\n    let res = 0;\n    let i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.dart
    /* while loop */\nint whileLoop(int n) {\n  int res = 0;\n  int i = 1; // Initialize the loop control variable\n  // Sum 1, 2, ..., n-1, n in a loop\n  while (i <= n) {\n    res += i;\n    i++; // Update the loop control variable\n  }\n  return res;\n}\n
    iteration.rs
    /* while loop */\nfn while_loop(n: i32) -> i32 {\n    let mut res = 0;\n    let mut i = 1; // Initialize the loop control variable\n\n    // Sum 1, 2, ..., n-1, n in a loop\n    while i <= n {\n        res += i;\n        i += 1; // Update the loop control variable\n    }\n    res\n}\n
    iteration.c
    /* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.kt
    /* while loop */\nfun whileLoop(n: Int): Int {\n    var res = 0\n    var i = 1 // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i\n        i++ // Update the loop control variable\n    }\n    return res\n}\n
    iteration.rb
    ### while loop ###\ndef while_loop(n)\n  res = 0\n  i = 1 # Initialize the loop control variable\n\n  # Sum 1, 2, ..., n-1, n in a loop\n  while i <= n\n    res += i\n    i += 1 # Update the loop control variable\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    A while loop offers more flexibility than a for loop in how its condition variables are initialized and updated.

    For example, the following code updates the condition variable \\(i\\) twice per iteration, which is less convenient to express with a for loop:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
    def while_loop_ii(n: int) -> int:\n    \"\"\"while loop (two updates)\"\"\"\n    res = 0\n    i = 1  # Initialize the loop control variable\n    # Sum 1, 4, 10, ... in a loop\n    while i <= n:\n        res += i\n        # Update the loop control variable\n        i += 1\n        i *= 2\n    return res\n
    iteration.cpp
    /* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.java
    /* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.cs
    /* while loop (two updates) */\nint WhileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i += 1; \n        i *= 2;\n    }\n    return res;\n}\n
    iteration.go
    /* while loop (two updates) */\nfunc whileLoopII(n int) int {\n    res := 0\n    // Initialize the loop control variable\n    i := 1\n    // Sum 1, 4, 10, ... in a loop\n    for i <= n {\n        res += i\n        // Update the loop control variable\n        i++\n        i *= 2\n    }\n    return res\n}\n
    iteration.swift
    /* while loop (two updates) */\nfunc whileLoopII(n: Int) -> Int {\n    var res = 0\n    var i = 1 // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while i <= n {\n        res += i\n        // Update the loop control variable\n        i += 1\n        i *= 2\n    }\n    return res\n}\n
    iteration.js
    /* while loop (two updates) */\nfunction whileLoopII(n) {\n    let res = 0;\n    let i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.ts
    /* while loop (two updates) */\nfunction whileLoopII(n: number): number {\n    let res = 0;\n    let i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.dart
    /* while loop (two updates) */\nint whileLoopII(int n) {\n  int res = 0;\n  int i = 1; // Initialize the loop control variable\n  // Sum 1, 4, 10, ... in a loop\n  while (i <= n) {\n    res += i;\n    // Update the loop control variable\n    i++;\n    i *= 2;\n  }\n  return res;\n}\n
    iteration.rs
    /* while loop (two updates) */\nfn while_loop_ii(n: i32) -> i32 {\n    let mut res = 0;\n    let mut i = 1; // Initialize the loop control variable\n\n    // Sum 1, 4, 10, ... in a loop\n    while i <= n {\n        res += i;\n        // Update the loop control variable\n        i += 1;\n        i *= 2;\n    }\n    res\n}\n
    iteration.c
    /* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.kt
    /* while loop (two updates) */\nfun whileLoopII(n: Int): Int {\n    var res = 0\n    var i = 1 // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i\n        // Update the loop control variable\n        i++\n        i *= 2\n    }\n    return res\n}\n
    iteration.rb
    ### while loop (two updates)###\ndef while_loop_ii(n)\n  res = 0\n  i = 1 # Initialize the loop control variable\n\n  # Sum 1, 4, 10, ... in a loop\n  while i <= n\n    res += i\n    # Update the loop control variable\n    i += 1\n    i *= 2\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    In general, for loops are more compact, while while loops are more flexible. Both implement iteration; choose according to the problem's needs.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-nested-loops","level":3,"title":"3.   Nested loops","text":"

    We can place one loop inside another, as in the following for-loop example:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
    def nested_for_loop(n: int) -> str:\n    \"\"\"Nested for loops\"\"\"\n    res = \"\"\n    # Loop over i = 1, 2, ..., n-1, n\n    for i in range(1, n + 1):\n        # Loop over j = 1, 2, ..., n-1, n\n        for j in range(1, n + 1):\n            res += f\"({i}, {j}), \"\n    return res\n
    iteration.cpp
    /* Nested for loops */\nstring nestedForLoop(int n) {\n    ostringstream res;\n    // Loop over i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; ++i) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; ++j) {\n            res << \"(\" << i << \", \" << j << \"), \";\n        }\n    }\n    return res.str();\n}\n
    iteration.java
    /* Nested for loops */\nString nestedForLoop(int n) {\n    StringBuilder res = new StringBuilder();\n    // Loop over i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            res.append(\"(\" + i + \", \" + j + \"), \");\n        }\n    }\n    return res.toString();\n}\n
    iteration.cs
    /* Nested for loops */\nstring NestedForLoop(int n) {\n    StringBuilder res = new();\n    // Loop over i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            res.Append($\"({i}, {j}), \");\n        }\n    }\n    return res.ToString();\n}\n
    iteration.go
    /* Nested for loops */\nfunc nestedForLoop(n int) string {\n    res := \"\"\n    // Loop over i = 1, 2, ..., n-1, n\n    for i := 1; i <= n; i++ {\n        for j := 1; j <= n; j++ {\n            // Loop over j = 1, 2, ..., n-1, n\n            res += fmt.Sprintf(\"(%d, %d), \", i, j)\n        }\n    }\n    return res\n}\n
    iteration.swift
    /* Nested for loops */\nfunc nestedForLoop(n: Int) -> String {\n    var res = \"\"\n    // Loop over i = 1, 2, ..., n-1, n\n    for i in 1 ... n {\n        // Loop over j = 1, 2, ..., n-1, n\n        for j in 1 ... n {\n            res.append(\"(\\(i), \\(j)), \")\n        }\n    }\n    return res\n}\n
    iteration.js
    /* Nested for loops */\nfunction nestedForLoop(n) {\n    let res = '';\n    // Loop over i = 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (let j = 1; j <= n; j++) {\n            res += `(${i}, ${j}), `;\n        }\n    }\n    return res;\n}\n
    iteration.ts
    /* Nested for loops */\nfunction nestedForLoop(n: number): string {\n    let res = '';\n    // Loop over i = 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (let j = 1; j <= n; j++) {\n            res += `(${i}, ${j}), `;\n        }\n    }\n    return res;\n}\n
    iteration.dart
    /* Nested for loops */\nString nestedForLoop(int n) {\n  String res = \"\";\n  // Loop over i = 1, 2, ..., n-1, n\n  for (int i = 1; i <= n; i++) {\n    // Loop over j = 1, 2, ..., n-1, n\n    for (int j = 1; j <= n; j++) {\n      res += \"($i, $j), \";\n    }\n  }\n  return res;\n}\n
    iteration.rs
    /* Nested for loops */\nfn nested_for_loop(n: i32) -> String {\n    let mut res = vec![];\n    // Loop over i = 1, 2, ..., n-1, n\n    for i in 1..=n {\n        // Loop over j = 1, 2, ..., n-1, n\n        for j in 1..=n {\n            res.push(format!(\"({}, {}), \", i, j));\n        }\n    }\n    res.join(\"\")\n}\n
    iteration.c
    /* Nested for loops */\nchar *nestedForLoop(int n) {\n    // There are n * n points; each \"(i, j), \" takes at most 6+10*2 characters, plus space for the final null character \\0\n    int size = n * n * 26 + 1;\n    char *res = malloc(size * sizeof(char));\n    // Loop over i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            char tmp[26];\n            snprintf(tmp, sizeof(tmp), \"(%d, %d), \", i, j);\n            strncat(res, tmp, size - strlen(res) - 1);\n        }\n    }\n    return res;\n}\n
    iteration.kt
    /* Nested for loops */\nfun nestedForLoop(n: Int): String {\n    val res = StringBuilder()\n    // Loop over i = 1, 2, ..., n-1, n\n    for (i in 1..n) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (j in 1..n) {\n            res.append(\" ($i, $j), \")\n        }\n    }\n    return res.toString()\n}\n
    iteration.rb
    ### Nested for loops ###\ndef nested_for_loop(n)\n  res = \"\"\n\n  # Loop over i = 1, 2, ..., n-1, n\n  for i in 1..n\n    # Loop over j = 1, 2, ..., n-1, n\n    for j in 1..n\n      res += \"(#{i}, #{j}), \"\n    end\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 2-2 shows the nested loop's flowchart.

    Figure 2-2   Flowchart of nested loops

    Here, the operation count is proportional to \\(n^2\\): runtime has a quadratic relationship with input size \\(n\\).

    Adding further nesting increases the dimension each time, producing cubic, fourth-power, and higher-order time complexity.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#222-recursion","level":2,"title":"2.2.2   Recursion","text":"

    Recursion is an algorithmic strategy in which a function calls itself to solve a problem. It has two main phases.

    1. Descent: The function calls itself more deeply, usually with smaller or simpler arguments, until a base case is reached.
    2. Return: Once the base case is reached, calls return from the deepest level upward, combining results along the way.

    In implementation, recursive code has three main elements.

    1. Base case: Determines when descent stops and returning begins.
    2. Recursive call: The function calls itself, usually with smaller or simpler arguments.
    3. Returned result: The current level returns its result to the previous level.

    In the following code, calling recur(n) computes \\(1 + 2 + \\dots + n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
    def recur(n: int) -> int:\n    \"\"\"Recursion\"\"\"\n    # Base case\n    if n == 1:\n        return 1\n    # Recurse: make a recursive call\n    res = recur(n - 1)\n    # Unwind: return the result\n    return n + res\n
    recursion.cpp
    /* Recursion */\nint recur(int n) {\n    // Base case\n    if (n == 1)\n        return 1;\n    // Recurse: make a recursive call\n    int res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.java
    /* Recursion */\nint recur(int n) {\n    // Base case\n    if (n == 1)\n        return 1;\n    // Recurse: make a recursive call\n    int res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.cs
    /* Recursion */\nint Recur(int n) {\n    // Base case\n    if (n == 1)\n        return 1;\n    // Recurse: make a recursive call\n    int res = Recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.go
    /* Recursion */\nfunc recur(n int) int {\n    // Base case\n    if n == 1 {\n        return 1\n    }\n    // Recurse: make a recursive call\n    res := recur(n - 1)\n    // Unwind: return the result\n    return n + res\n}\n
    recursion.swift
    /* Recursion */\nfunc recur(n: Int) -> Int {\n    // Base case\n    if n == 1 {\n        return 1\n    }\n    // Recurse: make a recursive call\n    let res = recur(n: n - 1)\n    // Unwind: return the result\n    return n + res\n}\n
    recursion.js
    /* Recursion */\nfunction recur(n) {\n    // Base case\n    if (n === 1) return 1;\n    // Recurse: make a recursive call\n    const res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.ts
    /* Recursion */\nfunction recur(n: number): number {\n    // Base case\n    if (n === 1) return 1;\n    // Recurse: make a recursive call\n    const res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.dart
    /* Recursion */\nint recur(int n) {\n  // Base case\n  if (n == 1) return 1;\n  // Recurse: make a recursive call\n  int res = recur(n - 1);\n  // Unwind: return the result\n  return n + res;\n}\n
    recursion.rs
    /* Recursion */\nfn recur(n: i32) -> i32 {\n    // Base case\n    if n == 1 {\n        return 1;\n    }\n    // Recurse: make a recursive call\n    let res = recur(n - 1);\n    // Unwind: return the result\n    n + res\n}\n
    recursion.c
    /* Recursion */\nint recur(int n) {\n    // Base case\n    if (n == 1)\n        return 1;\n    // Recurse: make a recursive call\n    int res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.kt
    /* Recursion */\nfun recur(n: Int): Int {\n    // Base case\n    if (n == 1)\n        return 1\n    // Descending: recursive calls\n    val res = recur(n - 1)\n    // Unwinding: return the result\n    return n + res\n}\n
    recursion.rb
    ### Recursion ###\ndef recur(n)\n  # Base case\n  return 1 if n == 1\n  # Recurse: make a recursive call\n  res = recur(n - 1)\n  # Unwind: return the result\n  n + res\nend\n
    Visualize execution

    View full screen >

    Figure 2-3 illustrates the recursive process.

    Figure 2-3   Recursive process of the summation function

    Iteration and recursion can produce the same answer, but represent two different ways of thinking about and solving a problem.

    • Iteration works bottom up, starting with basic steps and repeatedly performing or accumulating them until the task is complete.
    • Recursion works top down, dividing the original problem into smaller subproblems of the same form, then repeating the division until reaching base cases with known solutions.

    For the summation example, let \\(f(n) = 1 + 2 + \\dots + n\\).

    • Iteration: Loop from \\(1\\) to \\(n\\), accumulating the sum at each step to obtain \\(f(n)\\).
    • Recursion: Decompose the problem as \\(f(n) = n + f(n-1)\\) and keep doing so recursively until the base case \\(f(1) = 1\\).
    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-the-call-stack","level":3,"title":"1.   The call stack","text":"

    Each time a recursive function calls itself, the system allocates memory for the new call's local variables, return address, and other information. This has two consequences.

    • The call's context is stored in a stack frame until it returns. Recursion therefore usually uses more memory than iteration.
    • Each recursive call adds overhead. Recursion is therefore usually slower than a loop.

    As shown in Figure 2-4, before the base case is reached, \\(n\\) recursive calls remain active at once: the recursion depth is \\(n\\).

    Figure 2-4   Depth of recursive calls

    Programming languages usually limit recursion depth, and excessively deep recursion may cause a stack overflow.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-tail-recursion","level":3,"title":"2.   Tail recursion","text":"

    Interestingly, if the recursive call is the last operation before a function returns, a compiler or interpreter can optimize it to use as little space as iteration. This is called tail recursion.

    • Ordinary recursion: After a call returns, its caller must continue executing code, so the caller's context must be preserved.
    • Tail recursion: Nothing remains to be done after the recursive call returns, so the caller's context need not be retained.

    For \\(1 + 2 + \\dots + n\\), passing the accumulated result res as a parameter gives a tail-recursive solution:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
    def tail_recur(n, res):\n    \"\"\"Tail recursion\"\"\"\n    # Base case\n    if n == 0:\n        return res\n    # Tail-recursive call\n    return tail_recur(n - 1, res + n)\n
    recursion.cpp
    /* Tail recursion */\nint tailRecur(int n, int res) {\n    // Base case\n    if (n == 0)\n        return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.java
    /* Tail recursion */\nint tailRecur(int n, int res) {\n    // Base case\n    if (n == 0)\n        return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.cs
    /* Tail recursion */\nint TailRecur(int n, int res) {\n    // Base case\n    if (n == 0)\n        return res;\n    // Tail-recursive call\n    return TailRecur(n - 1, res + n);\n}\n
    recursion.go
    /* Tail recursion */\nfunc tailRecur(n int, res int) int {\n    // Base case\n    if n == 0 {\n        return res\n    }\n    // Tail-recursive call\n    return tailRecur(n-1, res+n)\n}\n
    recursion.swift
    /* Tail recursion */\nfunc tailRecur(n: Int, res: Int) -> Int {\n    // Base case\n    if n == 0 {\n        return res\n    }\n    // Tail-recursive call\n    return tailRecur(n: n - 1, res: res + n)\n}\n
    recursion.js
    /* Tail recursion */\nfunction tailRecur(n, res) {\n    // Base case\n    if (n === 0) return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.ts
    /* Tail recursion */\nfunction tailRecur(n: number, res: number): number {\n    // Base case\n    if (n === 0) return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.dart
    /* Tail recursion */\nint tailRecur(int n, int res) {\n  // Base case\n  if (n == 0) return res;\n  // Tail-recursive call\n  return tailRecur(n - 1, res + n);\n}\n
    recursion.rs
    /* Tail recursion */\nfn tail_recur(n: i32, res: i32) -> i32 {\n    // Base case\n    if n == 0 {\n        return res;\n    }\n    // Tail-recursive call\n    tail_recur(n - 1, res + n)\n}\n
    recursion.c
    /* Tail recursion */\nint tailRecur(int n, int res) {\n    // Base case\n    if (n == 0)\n        return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.kt
    /* Tail recursion */\ntailrec fun tailRecur(n: Int, res: Int): Int {\n    // Add the tailrec modifier to enable tail-call optimization\n    // Base case\n    if (n == 0)\n        return res\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n)\n}\n
    recursion.rb
    ### Tail recursion ###\ndef tail_recur(n, res)\n  # Base case\n  return res if n == 0\n  # Tail-recursive call\n  tail_recur(n - 1, res + n)\nend\n
    Visualize execution

    View full screen >

    Figure 2-5 illustrates tail recursion. Ordinary and tail recursion perform their additions at different points.

    • Ordinary recursion: Addition occurs during the return phase, after each deeper call returns.
    • Tail recursion: Addition occurs during descent; the return phase simply returns through the levels.

    Figure 2-5   Tail-recursive summation

    Tip

    Many compilers and interpreters do not support tail-recursion optimization. Python, for example, does not optimize it by default, so even a tail-recursive function can run into a stack overflow.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-recursion-trees","level":3,"title":"3.   Recursion trees","text":"

    For divide-and-conquer problems, recursion often provides a more intuitive approach and more readable code than iteration. Consider the Fibonacci sequence.

    Question

    Given the Fibonacci sequence \\(0, 1, 1, 2, 3, 5, 8, 13, \\dots\\), find its \\(n\\)-th number.

    Let \\(f(n)\\) be its \\(n\\)-th number. Two facts follow.

    • The first two numbers are \\(f(1) = 0\\) and \\(f(2) = 1\\).
    • Every later number is the sum of the two preceding numbers: \\(f(n) = f(n - 1) + f(n - 2)\\).

    Use the recurrence for recursive calls and the first two numbers as base cases. Calling fib(n) then returns the \\(n\\)-th Fibonacci number:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
    def fib(n: int) -> int:\n    \"\"\"Fibonacci sequence: recursion\"\"\"\n    # Base cases: f(1) = 0, f(2) = 1\n    if n == 1 or n == 2:\n        return n - 1\n    # Recursive calls: f(n) = f(n-1) + f(n-2)\n    res = fib(n - 1) + fib(n - 2)\n    # Return the result f(n)\n    return res\n
    recursion.cpp
    /* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.java
    /* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.cs
    /* Fibonacci sequence: recursion */\nint Fib(int n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    int res = Fib(n - 1) + Fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.go
    /* Fibonacci sequence: recursion */\nfunc fib(n int) int {\n    // Base cases: f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1\n    }\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    res := fib(n-1) + fib(n-2)\n    // Return the result f(n)\n    return res\n}\n
    recursion.swift
    /* Fibonacci sequence: recursion */\nfunc fib(n: Int) -> Int {\n    // Base cases: f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1\n    }\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    let res = fib(n: n - 1) + fib(n: n - 2)\n    // Return the result f(n)\n    return res\n}\n
    recursion.js
    /* Fibonacci sequence: recursion */\nfunction fib(n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n === 1 || n === 2) return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    const res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.ts
    /* Fibonacci sequence: recursion */\nfunction fib(n: number): number {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n === 1 || n === 2) return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    const res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.dart
    /* Fibonacci sequence: recursion */\nint fib(int n) {\n  // Base cases: f(1) = 0, f(2) = 1\n  if (n == 1 || n == 2) return n - 1;\n  // Recursive calls: f(n) = f(n-1) + f(n-2)\n  int res = fib(n - 1) + fib(n - 2);\n  // Return the result f(n)\n  return res;\n}\n
    recursion.rs
    /* Fibonacci sequence: recursion */\nfn fib(n: i32) -> i32 {\n    // Base cases: f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1;\n    }\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    let res = fib(n - 1) + fib(n - 2);\n    // Return the result\n    res\n}\n
    recursion.c
    /* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.kt
    /* Fibonacci sequence: recursion */\nfun fib(n: Int): Int {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    val res = fib(n - 1) + fib(n - 2)\n    // Return the result f(n)\n    return res\n}\n
    recursion.rb
    ### Fibonacci sequence: recursion ###\ndef fib(n)\n  # Base cases: f(1) = 0, f(2) = 1\n  return n - 1 if n == 1 || n == 2\n  # Recursive calls: f(n) = f(n-1) + f(n-2)\n  res = fib(n - 1) + fib(n - 2)\n  # Return the result f(n)\n  res\nend\n
    Visualize execution

    View full screen >

    The function makes two recursive calls, so each call creates two branches. Continuing this process produces a recursion tree with \\(n\\) levels, as shown in Figure 2-6.

    Figure 2-6   Recursion tree for the Fibonacci sequence

    Recursion embodies the idea of breaking a problem into smaller subproblems, a crucial divide-and-conquer strategy.

    • In algorithms, searching, sorting, backtracking, divide and conquer, dynamic programming, and many other techniques use this idea directly or indirectly.
    • In data structures, recursion naturally handles linked lists, trees, and graphs because divide-and-conquer reasoning suits them well.
    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#223-comparing-iteration-and-recursion","level":2,"title":"2.2.3   Comparing iteration and recursion","text":"

    Table 2-1 summarizes their differences in implementation, performance, and suitability.

    Table 2-1   Comparing iteration and recursion

    Iteration Recursion Implementation Loops A function calls itself Time efficiency Usually high, with no function-call overhead Each call adds overhead Memory use Usually a fixed amount of memory Accumulated calls may require many stack frames Suitable problems Simple repetitive tasks, with straightforward, readable code Subproblem decomposition, including trees, graphs, divide and conquer, and backtracking; concise, clear structure

    Tip

    If the following discussion feels difficult, return to it after reading the section on stacks.

    How are iteration and recursion connected? In the summation example, additions occur during the return phase. The first function called is the last to finish its addition, matching a stack's last-in, first-out behavior.

    The terms call stack and stack frame already suggest this close relationship.

    1. Descent: A function call adds a new stack frame to the call stack, holding local variables, parameters, the return address, and other data.
    2. Return: When the function finishes, its frame is removed, restoring the caller's execution context.

    Thus, an explicit stack can simulate the call stack, converting recursion into iteration:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
    def for_loop_recur(n: int) -> int:\n    \"\"\"Simulate recursion using iteration\"\"\"\n    # Use an explicit stack to simulate the system call stack\n    stack = []\n    res = 0\n    # Simulate the recursive-call phase\n    for i in range(n, 0, -1):\n        # Simulate recursive calls by pushing onto the stack\n        stack.append(i)\n    # Simulate unwinding and accumulating the result\n    while stack:\n        # Simulate unwinding by popping from the stack\n        res += stack.pop()\n    # res = 1+2+3+...+n\n    return res\n
    recursion.cpp
    /* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    stack<int> stack;\n    int res = 0;\n    // Recurse: make a recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while (!stack.empty()) {\n        // Simulate unwinding by popping from the stack\n        res += stack.top();\n        stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.java
    /* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    Stack<Integer> stack = new Stack<>();\n    int res = 0;\n    // Recurse: make a recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while (!stack.isEmpty()) {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.cs
    /* Simulate recursion using iteration */\nint ForLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    Stack<int> stack = new();\n    int res = 0;\n    // Recurse: make a recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.Push(i);\n    }\n    // Unwind: return the result\n    while (stack.Count > 0) {\n        // Simulate unwinding by popping from the stack\n        res += stack.Pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.go
    /* Simulate recursion using iteration */\nfunc forLoopRecur(n int) int {\n    // Use an explicit stack to simulate the system call stack\n    stack := list.New()\n    res := 0\n    // Descending: simulate recursive calls\n    for i := n; i > 0; i-- {\n        // Simulate recursive calls by pushing onto the stack\n        stack.PushBack(i)\n    }\n    // Unwinding: simulate returning results\n    for stack.Len() != 0 {\n        // Simulate unwinding by popping from the stack\n        res += stack.Back().Value.(int)\n        stack.Remove(stack.Back())\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
    recursion.swift
    /* Simulate recursion using iteration */\nfunc forLoopRecur(n: Int) -> Int {\n    // Use an explicit stack to simulate the system call stack\n    var stack: [Int] = []\n    var res = 0\n    // Descending: simulate recursive calls\n    for i in (1 ... n).reversed() {\n        // Simulate recursive calls by pushing onto the stack\n        stack.append(i)\n    }\n    // Unwinding: simulate returning results\n    while !stack.isEmpty {\n        // Simulate unwinding by popping from the stack\n        res += stack.removeLast()\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
    recursion.js
    /* Simulate recursion using iteration */\nfunction forLoopRecur(n) {\n    // Use an explicit stack to simulate the system call stack\n    const stack = [];\n    let res = 0;\n    // Recurse: make a recursive call\n    for (let i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while (stack.length) {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.ts
    /* Simulate recursion using iteration */\nfunction forLoopRecur(n: number): number {\n    // Use an explicit stack to simulate the system call stack \n    const stack: number[] = [];\n    let res: number = 0;\n    // Recurse: make a recursive call\n    for (let i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while (stack.length) {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.dart
    /* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n  // Use an explicit stack to simulate the system call stack\n  List<int> stack = [];\n  int res = 0;\n  // Recurse: make a recursive call\n  for (int i = n; i > 0; i--) {\n    // Simulate recursive calls by pushing onto the stack\n    stack.add(i);\n  }\n  // Unwind: return the result\n  while (!stack.isEmpty) {\n    // Simulate unwinding by popping from the stack\n    res += stack.removeLast();\n  }\n  // res = 1+2+3+...+n\n  return res;\n}\n
    recursion.rs
    /* Simulate recursion using iteration */\nfn for_loop_recur(n: i32) -> i32 {\n    // Use an explicit stack to simulate the system call stack\n    let mut stack = Vec::new();\n    let mut res = 0;\n    // Recurse: make a recursive call\n    for i in (1..=n).rev() {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while !stack.is_empty() {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop().unwrap();\n    }\n    // res = 1+2+3+...+n\n    res\n}\n
    recursion.c
    /* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    int stack[1000]; // Use a large array to simulate a stack\n    int top = -1;    // Top-of-stack index\n    int res = 0;\n    // Recurse: make a recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack[1 + top++] = i;\n    }\n    // Unwind: return the result\n    while (top >= 0) {\n        // Simulate unwinding by popping from the stack\n        res += stack[top--];\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.kt
    /* Simulate recursion using iteration */\nfun forLoopRecur(n: Int): Int {\n    // Use an explicit stack to simulate the system call stack\n    val stack = Stack<Int>()\n    var res = 0\n    // Descending: recursive calls\n    for (i in n downTo 0) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i)\n    }\n    // Unwinding: return the result\n    while (stack.isNotEmpty()) {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop()\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
    recursion.rb
    ### Simulate recursion using iteration ###\ndef for_loop_recur(n)\n  # Use an explicit stack to simulate the system call stack\n  stack = []\n  res = 0\n\n  # Recurse: make a recursive call\n  for i in n.downto(0)\n    # Simulate recursive calls by pushing onto the stack\n    stack << i\n  end\n  # Unwind: return the result\n  while !stack.empty?\n    res += stack.pop\n  end\n\n  # res = 1+2+3+...+n\n  res\nend\n
    Visualize execution

    View full screen >

    The resulting iterative code is more complicated. Although iteration and recursion can often be converted into each other, doing so is not always worthwhile.

    • The converted code may be harder to understand and less readable.
    • Simulating the call stack can be very difficult for complex problems.

    The choice between iteration and recursion depends on the problem. In practice, weigh their strengths and weaknesses and choose the approach that fits the situation.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/","level":1,"title":"2.1   Evaluating algorithm efficiency","text":"

    Algorithm design pursues two goals in sequence.

    1. Find a solution: The algorithm must reliably produce correct results across the specified input range.
    2. Find the best solution: When several approaches exist, we want the most efficient algorithm possible.

    Once correctness is established, efficiency becomes a primary measure of an algorithm's quality. It has two dimensions.

    • Time efficiency: How long the algorithm takes to run.
    • Space efficiency: How much memory it uses.

    Our goal is to design data structures and algorithms that are both fast and economical with memory. Evaluating efficiency effectively lets us compare algorithms and guide their design and optimization.

    There are two main evaluation methods: practical testing and theoretical estimation.

    ","path":["Chapter 2.   Complexity analysis","2.1   Evaluating algorithm efficiency"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#211-practical-testing","level":2,"title":"2.1.1   Practical testing","text":"

    Suppose algorithms A and B solve the same problem. The most direct comparison is to run both on a computer and measure runtime and memory use. This reflects actual behavior, but has substantial limitations.

    First, it is difficult to remove the influence of the test environment. Hardware affects performance: a highly parallel algorithm benefits from a multicore CPU, while a memory-intensive algorithm benefits from faster RAM. Results may differ across machines, so a fair average would require testing on many kinds of hardware, which is impractical.

    Second, comprehensive testing consumes substantial resources. Efficiency changes with input size. Algorithm A may be faster than B for small inputs, while the reverse holds for large inputs. A convincing conclusion requires testing many input sizes, consuming considerable computing resources.

    ","path":["Chapter 2.   Complexity analysis","2.1   Evaluating algorithm efficiency"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#212-theoretical-estimation","level":2,"title":"2.1.2   Theoretical estimation","text":"

    Given these limitations, we can estimate efficiency through calculation alone. This is called asymptotic complexity analysis, or simply complexity analysis.

    Complexity analysis relates an algorithm's time and space requirements to its input size. It describes how runtime and memory use grow as the input gets larger. We can unpack this definition into three points.

    • Time and space resources correspond to time complexity and space complexity.
    • Growth in input size means complexity relates efficiency to the amount of input data.
    • Growth trends mean we study how quickly time or space increases, rather than its exact value.

    Complexity analysis overcomes limitations of practical testing in several ways.

    • It does not require running code, saving energy and computing resources.
    • It is independent of the test environment, so its conclusions apply across platforms.
    • It reveals efficiency at different input sizes, especially for large inputs.

    Tip

    If complexity still feels unclear, do not worry. The following sections will explain it in detail.

    Complexity analysis gives us a yardstick for evaluating the time and space an algorithm needs and comparing different algorithms.

    As a mathematical concept, complexity can be abstract and challenging for beginners. This makes it a difficult topic to introduce first. Yet discussing a data structure or algorithm's properties inevitably involves runtime and memory use.

    Before studying data structures and algorithms in depth, build an initial understanding of complexity analysis so that you can analyze simple algorithms.

    ","path":["Chapter 2.   Complexity analysis","2.1   Evaluating algorithm efficiency"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/","level":1,"title":"2.4   Space complexity","text":"

    Space complexity measures how an algorithm's memory use grows as the input gets larger. It is much like time complexity, with memory usage replacing runtime.

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#241-space-used-by-an-algorithm","level":2,"title":"2.4.1   Space used by an algorithm","text":"

    The memory an algorithm uses during execution falls into these main categories.

    • Input space: Stores the input data.
    • Temporary space: Stores variables, objects, function contexts, and other working data.
    • Output space: Stores the output data.

    Space complexity generally counts temporary space plus output space.

    Temporary space can be divided further into three parts.

    • Temporary data: Constants, variables, objects, and other data used during execution.
    • Stack frames: Context data for function calls. Each call creates a frame at the top of the stack, which is released when the function returns.
    • Instruction space: Compiled program instructions, usually ignored in the analysis.

    We usually count temporary data, stack frames, and output data when analyzing space complexity, as shown in Figure 2-15.

    Figure 2-15   Space used by an algorithm

    The corresponding code is:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class Node:\n    \"\"\"Class\"\"\"\n    def __init__(self, x: int):\n        self.val: int = x              # Node value\n        self.next: Node | None = None  # Reference to the next node\n\ndef function() -> int:\n    \"\"\"Function\"\"\"\n    # Perform some operations...\n    return 0\n\ndef algorithm(n) -> int:  # Input data\n    A = 0                 # Temporary data (constant, conventionally named in uppercase)\n    b = 0                 # Temporary data (variable)\n    node = Node(0)        # Temporary data (object)\n    c = function()        # Stack frame (function call)\n    return A + b + c      # Output data\n
    /* Structure */\nstruct Node {\n    int val;\n    Node *next;\n    Node(int x) : val(x), next(nullptr) {}\n};\n\n/* Function */\nint func() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) {        // Input data\n    const int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node* node = new Node(0); // Temporary data (object)\n    int c = func();           // Stack frame (function call)\n    return a + b + c;         // Output data\n}\n
    /* Class */\nclass Node {\n    int val;\n    Node next;\n    Node(int x) { val = x; }\n}\n\n/* Function */\nint function() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) {        // Input data\n    final int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node node = new Node(0);  // Temporary data (object)\n    int c = function();       // Stack frame (function call)\n    return a + b + c;         // Output data\n}\n
    /* Class */\nclass Node(int x) {\n    int val = x;\n    Node next;\n}\n\n/* Function */\nint Function() {\n    // Perform some operations...\n    return 0;\n}\n\nint Algorithm(int n) {        // Input data\n    const int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node node = new(0);       // Temporary data (object)\n    int c = Function();       // Stack frame (function call)\n    return a + b + c;         // Output data\n}\n
    /* Structure */\ntype node struct {\n    val  int\n    next *node\n}\n\n/* Create a node structure  */\nfunc newNode(val int) *node {\n    return &node{val: val}\n}\n\n/* Function */\nfunc function() int {\n    // Perform some operations...\n    return 0\n}\n\nfunc algorithm(n int) int { // Input data\n    const a = 0             // Temporary data (constant)\n    b := 0                  // Temporary data (variable)\n    newNode(0)              // Temporary data (object)\n    c := function()         // Stack frame (function call)\n    return a + b + c        // Output data\n}\n
    /* Class */\nclass Node {\n    var val: Int\n    var next: Node?\n\n    init(x: Int) {\n        val = x\n    }\n}\n\n/* Function */\nfunc function() -> Int {\n    // Perform some operations...\n    return 0\n}\n\nfunc algorithm(n: Int) -> Int { // Input data\n    let a = 0             // Temporary data (constant)\n    var b = 0             // Temporary data (variable)\n    let node = Node(x: 0) // Temporary data (object)\n    let c = function()    // Stack frame (function call)\n    return a + b + c      // Output data\n}\n
    /* Class */\nclass Node {\n    val;\n    next;\n    constructor(val) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.next = null;                       // Reference to the next node\n    }\n}\n\n/* Function */\nfunction constFunc() {\n    // Perform some operations\n    return 0;\n}\n\nfunction algorithm(n) {       // Input data\n    const a = 0;              // Temporary data (constant)\n    let b = 0;                // Temporary data (variable)\n    const node = new Node(0); // Temporary data (object)\n    const c = constFunc();    // Stack frame (function call)\n    return a + b + c;         // Output data\n}\n
    /* Class */\nclass Node {\n    val: number;\n    next: Node | null;\n    constructor(val?: number) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.next = null;                       // Reference to the next node\n    }\n}\n\n/* Function */\nfunction constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n\nfunction algorithm(n: number): number { // Input data\n    const a = 0;                        // Temporary data (constant)\n    let b = 0;                          // Temporary data (variable)\n    const node = new Node(0);           // Temporary data (object)\n    const c = constFunc();              // Stack frame (function call)\n    return a + b + c;                   // Output data\n}\n
    /* Class */\nclass Node {\n  int val;\n  Node next;\n  Node(this.val, [this.next]);\n}\n\n/* Function */\nint function() {\n  // Perform some operations...\n  return 0;\n}\n\nint algorithm(int n) {  // Input data\n  const int a = 0;      // Temporary data (constant)\n  int b = 0;            // Temporary data (variable)\n  Node node = Node(0);  // Temporary data (object)\n  int c = function();   // Stack frame (function call)\n  return a + b + c;     // Output data\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Structure */\nstruct Node {\n    val: i32,\n    next: Option<Rc<RefCell<Node>>>,\n}\n\n/* Create a Node structure */\nimpl Node {\n    fn new(val: i32) -> Self {\n        Self { val: val, next: None }\n    }\n}\n\n/* Function */\nfn function() -> i32 {      \n    // Perform some operations...\n    return 0;\n}\n\nfn algorithm(n: i32) -> i32 {       // Input data\n    const a: i32 = 0;               // Temporary data (constant)\n    let mut b = 0;                  // Temporary data (variable)\n    let node = Node::new(0);        // Temporary data (object)\n    let c = function();             // Stack frame (function call)\n    return a + b + c;               // Output data\n}\n
    /* Function */\nint func() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) { // Input data\n    const int a = 0;   // Temporary data (constant)\n    int b = 0;         // Temporary data (variable)\n    int c = func();    // Stack frame (function call)\n    return a + b + c;  // Output data\n}\n
    /* Class */\nclass Node(var _val: Int) {\n    var next: Node? = null\n}\n\n/* Function */\nfun function(): Int {\n    // Perform some operations...\n    return 0\n}\n\nfun algorithm(n: Int): Int { // Input data\n    val a = 0                // Temporary data (constant)\n    var b = 0                // Temporary data (variable)\n    val node = Node(0)       // Temporary data (object)\n    val c = function()       // Stack frame (function call)\n    return a + b + c         // Output data\n}\n
    ### Class ###\nclass Node\n    attr_accessor :val      # Node value\n    attr_accessor :next     # Reference to the next node\n\n    def initialize(x)\n        @val = x\n    end\nend\n\n### Function ###\ndef function\n    # Perform some operations...\n    0\nend\n\n### Algorithm ###\ndef algorithm(n)        # Input data\n    a = 0               # Temporary data (constant)\n    b = 0               # Temporary data (variable)\n    node = Node.new(0)  # Temporary data (object)\n    c = function        # Stack frame (function call)\n    a + b + c           # Output data\nend\n
    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#242-deriving-space-complexity","level":2,"title":"2.4.2   Deriving space complexity","text":"

    The approach is much like time complexity analysis, except that we count memory used rather than operations performed.

    Unlike time complexity, we usually focus only on worst-case space complexity. Memory is a hard requirement: enough must be available for every valid input.

    In the following code, \"worst case\" has two meanings.

    1. The worst input: When \\(n < 10\\), space complexity is \\(O(1)\\). When \\(n > 10\\), initializing nums requires \\(O(n)\\) space, so worst-case space complexity is \\(O(n)\\).
    2. Peak memory during execution: Before the final line, the program uses \\(O(1)\\) space. Initializing nums raises usage to \\(O(n)\\), so worst-case space complexity is \\(O(n)\\).
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def algorithm(n: int):\n    a = 0               # O(1)\n    b = [0] * 10000     # O(1)\n    if n > 10:\n        nums = [0] * n  # O(n)\n
    void algorithm(int n) {\n    int a = 0;               // O(1)\n    vector<int> b(10000);    // O(1)\n    if (n > 10)\n        vector<int> nums(n); // O(n)\n}\n
    void algorithm(int n) {\n    int a = 0;                   // O(1)\n    int[] b = new int[10000];    // O(1)\n    if (n > 10)\n        int[] nums = new int[n]; // O(n)\n}\n
    void Algorithm(int n) {\n    int a = 0;                   // O(1)\n    int[] b = new int[10000];    // O(1)\n    if (n > 10) {\n        int[] nums = new int[n]; // O(n)\n    }\n}\n
    func algorithm(n int) {\n    a := 0                      // O(1)\n    b := make([]int, 10000)     // O(1)\n    var nums []int\n    if n > 10 {\n        nums := make([]int, n)  // O(n)\n    }\n    fmt.Println(a, b, nums)\n}\n
    func algorithm(n: Int) {\n    let a = 0 // O(1)\n    let b = Array(repeating: 0, count: 10000) // O(1)\n    if n > 10 {\n        let nums = Array(repeating: 0, count: n) // O(n)\n    }\n}\n
    function algorithm(n) {\n    const a = 0;                   // O(1)\n    const b = new Array(10000);    // O(1)\n    if (n > 10) {\n        const nums = new Array(n); // O(n)\n    }\n}\n
    function algorithm(n: number): void {\n    const a = 0;                   // O(1)\n    const b = new Array(10000);    // O(1)\n    if (n > 10) {\n        const nums = new Array(n); // O(n)\n    }\n}\n
    void algorithm(int n) {\n  int a = 0;                            // O(1)\n  List<int> b = List.filled(10000, 0);  // O(1)\n  if (n > 10) {\n    List<int> nums = List.filled(n, 0); // O(n)\n  }\n}\n
    fn algorithm(n: i32) {\n    let a = 0;                              // O(1)\n    let b = [0; 10000];                     // O(1)\n    if n > 10 {\n        let nums = vec![0; n as usize];     // O(n)\n    }\n}\n
    void algorithm(int n) {\n    int a = 0;               // O(1)\n    int b[10000];            // O(1)\n    if (n > 10)\n        int nums[n] = {0};   // O(n)\n}\n
    fun algorithm(n: Int) {\n    val a = 0                    // O(1)\n    val b = IntArray(10000)      // O(1)\n    if (n > 10) {\n        val nums = IntArray(n)   // O(n)\n    }\n}\n
    def algorithm(n)\n    a = 0                           # O(1)\n    b = Array.new(10000)            # O(1)\n    nums = Array.new(n) if n > 10   # O(n)\nend\n

    For recursive functions, remember to count stack frames. Consider the following code:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def function() -> int:\n    # Perform some operations\n    return 0\n\ndef loop(n: int):\n    \"\"\"The loop uses O(1) space\"\"\"\n    for _ in range(n):\n        function()\n\ndef recur(n: int):\n    \"\"\"The recursion uses O(n) space\"\"\"\n    if n == 1:\n        return\n    return recur(n - 1)\n
    int func() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n/* The recursion uses O(n) space */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
    int function() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        function();\n    }\n}\n/* The recursion uses O(n) space */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
    int Function() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nvoid Loop(int n) {\n    for (int i = 0; i < n; i++) {\n        Function();\n    }\n}\n/* The recursion uses O(n) space */\nint Recur(int n) {\n    if (n == 1) return 1;\n    return Recur(n - 1);\n}\n
    func function() int {\n    // Perform some operations\n    return 0\n}\n\n/* The loop uses O(1) space */\nfunc loop(n int) {\n    for i := 0; i < n; i++ {\n        function()\n    }\n}\n\n/* The recursion uses O(n) space */\nfunc recur(n int) {\n    if n == 1 {\n        return\n    }\n    recur(n - 1)\n}\n
    @discardableResult\nfunc function() -> Int {\n    // Perform some operations\n    return 0\n}\n\n/* The loop uses O(1) space */\nfunc loop(n: Int) {\n    for _ in 0 ..< n {\n        function()\n    }\n}\n\n/* The recursion uses O(n) space */\nfunc recur(n: Int) {\n    if n == 1 {\n        return\n    }\n    recur(n: n - 1)\n}\n
    function constFunc() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nfunction loop(n) {\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n/* The recursion uses O(n) space */\nfunction recur(n) {\n    if (n === 1) return;\n    return recur(n - 1);\n}\n
    function constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nfunction loop(n: number): void {\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n/* The recursion uses O(n) space */\nfunction recur(n: number): void {\n    if (n === 1) return;\n    return recur(n - 1);\n}\n
    int function() {\n  // Perform some operations\n  return 0;\n}\n/* The loop uses O(1) space */\nvoid loop(int n) {\n  for (int i = 0; i < n; i++) {\n    function();\n  }\n}\n/* The recursion uses O(n) space */\nvoid recur(int n) {\n  if (n == 1) return;\n  recur(n - 1);\n}\n
    fn function() -> i32 {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nfn loop(n: i32) {\n    for i in 0..n {\n        function();\n    }\n}\n/* The recursion uses O(n) space */\nfn recur(n: i32) {\n    if n == 1 {\n        return;\n    }\n    recur(n - 1);\n}\n
    int func() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n/* The recursion uses O(n) space */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
    fun function(): Int {\n    // Perform some operations\n    return 0\n}\n/* The loop uses O(1) space */\nfun loop(n: Int) {\n    for (i in 0..<n) {\n        function()\n    }\n}\n/* The recursion uses O(n) space */\nfun recur(n: Int) {\n    if (n == 1) return\n    return recur(n - 1)\n}\n
    def function\n    # Perform some operations\n    0\nend\n\n### The loop uses O(1) space ###\ndef loop(n)\n    (0...n).each { function }\nend\n\n### The recursion uses O(n) space ###\ndef recur(n)\n    return if n == 1\n    recur(n - 1)\nend\n

    Both loop() and recur() take \\(O(n)\\) time, but their space complexities differ.

    • loop() calls function() \\(n\\) times. Each call returns and releases its stack frame before the next iteration, so space complexity remains \\(O(1)\\).
    • During recur(), \\(n\\) calls can remain active at once, using \\(O(n)\\) space for stack frames.
    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#243-common-complexity-classes","level":2,"title":"2.4.3   Common complexity classes","text":"

    For input size \\(n\\), Figure 2-16 shows common space complexities in increasing order of growth.

    \\[ \\begin{aligned} & O(1) < O(\\log n) < O(n) < O(n^2) < O(2^n) \\newline & \\text{Constant} < \\text{Logarithmic} < \\text{Linear} < \\text{Quadratic} < \\text{Exponential} \\end{aligned} \\]

    Figure 2-16   Common space complexity classes

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#1-constant-space-o1","level":3,"title":"1.   Constant space \\(O(1)\\)","text":"

    Constant space commonly occurs when the number of constants, variables, and objects is independent of input size \\(n\\).

    Memory for variables initialized or functions called within a loop is released by the next iteration, so it does not accumulate. Space complexity remains \\(O(1)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def function() -> int:\n    \"\"\"Function\"\"\"\n    # Perform some operations\n    return 0\n\ndef constant(n: int):\n    \"\"\"Constant complexity\"\"\"\n    # Constants, variables, and objects occupy O(1) space\n    a = 0\n    nums = [0] * 10000\n    node = ListNode(0)\n    # Variables in the loop occupy O(1) space\n    for _ in range(n):\n        c = 0\n    # Function calls in the loop occupy O(1) space\n    for _ in range(n):\n        function()\n
    space_complexity.cpp
    /* Function */\nint func() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nvoid constant(int n) {\n    // Constants, variables, and objects occupy O(1) space\n    const int a = 0;\n    int b = 0;\n    vector<int> nums(10000);\n    ListNode node(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n
    space_complexity.java
    /* Function */\nint function() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nvoid constant(int n) {\n    // Constants, variables, and objects occupy O(1) space\n    final int a = 0;\n    int b = 0;\n    int[] nums = new int[10000];\n    ListNode node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        function();\n    }\n}\n
    space_complexity.cs
    /* Function */\nint Function() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nvoid Constant(int n) {\n    // Constants, variables, and objects occupy O(1) space\n    int a = 0;\n    int b = 0;\n    int[] nums = new int[10000];\n    ListNode node = new(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        Function();\n    }\n}\n
    space_complexity.go
    /* Function */\nfunc function() int {\n    // Perform some operations...\n    return 0\n}\n\n/* Constant complexity */\nfunc spaceConstant(n int) {\n    // Constants, variables, and objects occupy O(1) space\n    const a = 0\n    b := 0\n    nums := make([]int, 10000)\n    node := newNode(0)\n    // Variables in the loop occupy O(1) space\n    var c int\n    for i := 0; i < n; i++ {\n        c = 0\n    }\n    // Function calls in the loop occupy O(1) space\n    for i := 0; i < n; i++ {\n        function()\n    }\n    b += 0\n    c += 0\n    nums[0] = 0\n    node.val = 0\n}\n
    space_complexity.swift
    /* Function */\n@discardableResult\nfunc function() -> Int {\n    // Perform some operations\n    return 0\n}\n\n/* Constant complexity */\nfunc constant(n: Int) {\n    // Constants, variables, and objects occupy O(1) space\n    let a = 0\n    var b = 0\n    let nums = Array(repeating: 0, count: 10000)\n    let node = ListNode(x: 0)\n    // Variables in the loop occupy O(1) space\n    for _ in 0 ..< n {\n        let c = 0\n    }\n    // Function calls in the loop occupy O(1) space\n    for _ in 0 ..< n {\n        function()\n    }\n}\n
    space_complexity.js
    /* Function */\nfunction constFunc() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nfunction constant(n) {\n    // Constants, variables, and objects occupy O(1) space\n    const a = 0;\n    const b = 0;\n    const nums = new Array(10000);\n    const node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        const c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n
    space_complexity.ts
    /* Function */\nfunction constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nfunction constant(n: number): void {\n    // Constants, variables, and objects occupy O(1) space\n    const a = 0;\n    const b = 0;\n    const nums = new Array(10000);\n    const node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        const c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n
    space_complexity.dart
    /* Function */\nint function() {\n  // Perform some operations\n  return 0;\n}\n\n/* Constant complexity */\nvoid constant(int n) {\n  // Constants, variables, and objects occupy O(1) space\n  final int a = 0;\n  int b = 0;\n  List<int> nums = List.filled(10000, 0);\n  ListNode node = ListNode(0);\n  // Variables in the loop occupy O(1) space\n  for (var i = 0; i < n; i++) {\n    int c = 0;\n  }\n  // Function calls in the loop occupy O(1) space\n  for (var i = 0; i < n; i++) {\n    function();\n  }\n}\n
    space_complexity.rs
    /* Function */\nfn function() -> i32 {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\n#[allow(unused)]\nfn constant(n: i32) {\n    // Constants, variables, and objects occupy O(1) space\n    const A: i32 = 0;\n    let b = 0;\n    let nums = vec![0; 10000];\n    let node = ListNode::new(0);\n    // Variables in the loop occupy O(1) space\n    for i in 0..n {\n        let c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for i in 0..n {\n        function();\n    }\n}\n
    space_complexity.c
    /* Function */\nint func() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nvoid constant(int n) {\n    // Constants, variables, and objects occupy O(1) space\n    const int a = 0;\n    int b = 0;\n    int nums[1000];\n    ListNode *node = newListNode(0);\n    free(node);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n
    space_complexity.kt
    /* Function */\nfun function(): Int {\n    // Perform some operations\n    return 0\n}\n\n/* Constant complexity */\nfun constant(n: Int) {\n    // Constants, variables, and objects occupy O(1) space\n    val a = 0\n    var b = 0\n    val nums = Array(10000) { 0 }\n    val node = ListNode(0)\n    // Variables in the loop occupy O(1) space\n    for (i in 0..<n) {\n        val c = 0\n    }\n    // Function calls in the loop occupy O(1) space\n    for (i in 0..<n) {\n        function()\n    }\n}\n
    space_complexity.rb
    ### Function ###\ndef function\n  # Perform some operations\n  0\nend\n\n### Constant complexity ###\ndef constant(n)\n  # Constants, variables, and objects occupy O(1) space\n  a = 0\n  nums = [0] * 10000\n  node = ListNode.new\n\n  # Variables in the loop occupy O(1) space\n  (0...n).each { c = 0 }\n  # Function calls in the loop occupy O(1) space\n  (0...n).each { function }\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#2-linear-space-on","level":3,"title":"2.   Linear space \\(O(n)\\)","text":"

    Linear space commonly occurs in arrays, linked lists, stacks, and queues whose element counts are proportional to \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def linear(n: int):\n    \"\"\"Linear complexity\"\"\"\n    # A list of length n occupies O(n) space\n    nums = [0] * n\n    # A hash table with n entries occupies O(n) space\n    hmap = dict[int, str]()\n    for i in range(n):\n        hmap[i] = str(i)\n
    space_complexity.cpp
    /* Linear complexity */\nvoid linear(int n) {\n    // An array of length n uses O(n) space\n    vector<int> nums(n);\n    // A list of length n occupies O(n) space\n    vector<ListNode> nodes;\n    for (int i = 0; i < n; i++) {\n        nodes.push_back(ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    unordered_map<int, string> map;\n    for (int i = 0; i < n; i++) {\n        map[i] = to_string(i);\n    }\n}\n
    space_complexity.java
    /* Linear complexity */\nvoid linear(int n) {\n    // An array of length n uses O(n) space\n    int[] nums = new int[n];\n    // A list of length n occupies O(n) space\n    List<ListNode> nodes = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        nodes.add(new ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    Map<Integer, String> map = new HashMap<>();\n    for (int i = 0; i < n; i++) {\n        map.put(i, String.valueOf(i));\n    }\n}\n
    space_complexity.cs
    /* Linear complexity */\nvoid Linear(int n) {\n    // An array of length n uses O(n) space\n    int[] nums = new int[n];\n    // A list of length n occupies O(n) space\n    List<ListNode> nodes = [];\n    for (int i = 0; i < n; i++) {\n        nodes.Add(new ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    Dictionary<int, string> map = [];\n    for (int i = 0; i < n; i++) {\n        map.Add(i, i.ToString());\n    }\n}\n
    space_complexity.go
    /* Linear complexity */\nfunc spaceLinear(n int) {\n    // An array of length n uses O(n) space\n    _ = make([]int, n)\n    // A list of length n occupies O(n) space\n    var nodes []*node\n    for i := 0; i < n; i++ {\n        nodes = append(nodes, newNode(i))\n    }\n    // A hash table with n entries occupies O(n) space\n    m := make(map[int]string, n)\n    for i := 0; i < n; i++ {\n        m[i] = strconv.Itoa(i)\n    }\n}\n
    space_complexity.swift
    /* Linear complexity */\nfunc linear(n: Int) {\n    // An array of length n uses O(n) space\n    let nums = Array(repeating: 0, count: n)\n    // A list of length n occupies O(n) space\n    let nodes = (0 ..< n).map { ListNode(x: $0) }\n    // A hash table with n entries occupies O(n) space\n    let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, \"\\($0)\") })\n}\n
    space_complexity.js
    /* Linear complexity */\nfunction linear(n) {\n    // An array of length n uses O(n) space\n    const nums = new Array(n);\n    // A list of length n occupies O(n) space\n    const nodes = [];\n    for (let i = 0; i < n; i++) {\n        nodes.push(new ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    const map = new Map();\n    for (let i = 0; i < n; i++) {\n        map.set(i, i.toString());\n    }\n}\n
    space_complexity.ts
    /* Linear complexity */\nfunction linear(n: number): void {\n    // An array of length n uses O(n) space\n    const nums = new Array(n);\n    // A list of length n occupies O(n) space\n    const nodes: ListNode[] = [];\n    for (let i = 0; i < n; i++) {\n        nodes.push(new ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    const map = new Map();\n    for (let i = 0; i < n; i++) {\n        map.set(i, i.toString());\n    }\n}\n
    space_complexity.dart
    /* Linear complexity */\nvoid linear(int n) {\n  // An array of length n uses O(n) space\n  List<int> nums = List.filled(n, 0);\n  // A list of length n occupies O(n) space\n  List<ListNode> nodes = [];\n  for (var i = 0; i < n; i++) {\n    nodes.add(ListNode(i));\n  }\n  // A hash table with n entries occupies O(n) space\n  Map<int, String> map = HashMap();\n  for (var i = 0; i < n; i++) {\n    map.putIfAbsent(i, () => i.toString());\n  }\n}\n
    space_complexity.rs
    /* Linear complexity */\n#[allow(unused)]\nfn linear(n: i32) {\n    // An array of length n uses O(n) space\n    let mut nums = vec![0; n as usize];\n    // A list of length n occupies O(n) space\n    let mut nodes = Vec::new();\n    for i in 0..n {\n        nodes.push(ListNode::new(i))\n    }\n    // A hash table with n entries occupies O(n) space\n    let mut map = HashMap::new();\n    for i in 0..n {\n        map.insert(i, i.to_string());\n    }\n}\n
    space_complexity.c
    /* Hash table */\ntypedef struct {\n    int key;\n    int val;\n    UT_hash_handle hh; // Implemented using uthash.h\n} HashTable;\n\n/* Linear complexity */\nvoid linear(int n) {\n    // An array of length n uses O(n) space\n    int *nums = malloc(sizeof(int) * n);\n    free(nums);\n\n    // A list of length n occupies O(n) space\n    ListNode **nodes = malloc(sizeof(ListNode *) * n);\n    for (int i = 0; i < n; i++) {\n        nodes[i] = newListNode(i);\n    }\n    // Free memory\n    for (int i = 0; i < n; i++) {\n        free(nodes[i]);\n    }\n    free(nodes);\n\n    // A hash table with n entries occupies O(n) space\n    HashTable *h = NULL;\n    for (int i = 0; i < n; i++) {\n        HashTable *tmp = malloc(sizeof(HashTable));\n        tmp->key = i;\n        tmp->val = i;\n        HASH_ADD_INT(h, key, tmp);\n    }\n\n    // Free memory\n    HashTable *curr, *tmp;\n    HASH_ITER(hh, h, curr, tmp) {\n        HASH_DEL(h, curr);\n        free(curr);\n    }\n}\n
    space_complexity.kt
    /* Linear complexity */\nfun linear(n: Int) {\n    // An array of length n uses O(n) space\n    val nums = Array(n) { 0 }\n    // A list of length n occupies O(n) space\n    val nodes = mutableListOf<ListNode>()\n    for (i in 0..<n) {\n        nodes.add(ListNode(i))\n    }\n    // A hash table with n entries occupies O(n) space\n    val map = mutableMapOf<Int, String>()\n    for (i in 0..<n) {\n        map[i] = i.toString()\n    }\n}\n
    space_complexity.rb
    ### Linear complexity ###\ndef linear(n)\n  # A list of length n occupies O(n) space\n  nums = Array.new(n, 0)\n\n  # A hash table with n entries occupies O(n) space\n  hmap = {}\n  for i in 0...n\n    hmap[i] = i.to_s\n  end\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 2-17, this function has recursion depth \\(n\\), with \\(n\\) active calls to linear_recur() at once, using \\(O(n)\\) space for stack frames:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def linear_recur(n: int):\n    \"\"\"Linear complexity (recursive implementation)\"\"\"\n    print(\"Recursion: n =\", n)\n    if n == 1:\n        return\n    linear_recur(n - 1)\n
    space_complexity.cpp
    /* Linear complexity (recursive implementation) */\nvoid linearRecur(int n) {\n    cout << \"Recursion: n = \" << n << endl;\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
    space_complexity.java
    /* Linear complexity (recursive implementation) */\nvoid linearRecur(int n) {\n    System.out.println(\"Recursive call: n = \" + n);\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
    space_complexity.cs
    /* Linear complexity (recursive implementation) */\nvoid LinearRecur(int n) {\n    Console.WriteLine(\"Recursive call: n = \" + n);\n    if (n == 1) return;\n    LinearRecur(n - 1);\n}\n
    space_complexity.go
    /* Linear complexity (recursive implementation) */\nfunc spaceLinearRecur(n int) {\n    fmt.Println(\"Recursive call: n =\", n)\n    if n == 1 {\n        return\n    }\n    spaceLinearRecur(n - 1)\n}\n
    space_complexity.swift
    /* Linear complexity (recursive implementation) */\nfunc linearRecur(n: Int) {\n    print(\"Recursion: n = \\(n)\")\n    if n == 1 {\n        return\n    }\n    linearRecur(n: n - 1)\n}\n
    space_complexity.js
    /* Linear complexity (recursive implementation) */\nfunction linearRecur(n) {\n    console.log(`Recursive call with n = ${n}`);\n    if (n === 1) return;\n    linearRecur(n - 1);\n}\n
    space_complexity.ts
    /* Linear complexity (recursive implementation) */\nfunction linearRecur(n: number): void {\n    console.log(`Recursive call with n = ${n}`);\n    if (n === 1) return;\n    linearRecur(n - 1);\n}\n
    space_complexity.dart
    /* Linear complexity (recursive implementation) */\nvoid linearRecur(int n) {\n  print('Recursive call: n = $n');\n  if (n == 1) return;\n  linearRecur(n - 1);\n}\n
    space_complexity.rs
    /* Linear complexity (recursive implementation) */\nfn linear_recur(n: i32) {\n    println!(\"Recursive call n = {}\", n);\n    if n == 1 {\n        return;\n    };\n    linear_recur(n - 1);\n}\n
    space_complexity.c
    /* Linear complexity (recursive implementation) */\nvoid linearRecur(int n) {\n    printf(\"Recursive call n = %d\\r\\n\", n);\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
    space_complexity.kt
    /* Linear complexity (recursive implementation) */\nfun linearRecur(n: Int) {\n    println(\"Recursive call: n = $n\")\n    if (n == 1)\n        return\n    linearRecur(n - 1)\n}\n
    space_complexity.rb
    ### Linear complexity (recursive implementation)###\ndef linear_recur(n)\n  puts \"Recursion: n = #{n}\"\n  return if n == 1\n  linear_recur(n - 1)\nend\n
    Visualize execution

    View full screen >

    Figure 2-17   Linear space complexity from recursion

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#3-quadratic-space-on2","level":3,"title":"3.   Quadratic space \\(O(n^2)\\)","text":"

    Quadratic space commonly occurs in matrices and graphs whose element counts grow with the square of \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def quadratic(n: int):\n    \"\"\"Quadratic complexity\"\"\"\n    # The two-dimensional list occupies O(n^2) space\n    num_matrix = [[0] * n for _ in range(n)]\n
    space_complexity.cpp
    /* Quadratic complexity */\nvoid quadratic(int n) {\n    // The two-dimensional list occupies O(n^2) space\n    vector<vector<int>> numMatrix;\n    for (int i = 0; i < n; i++) {\n        vector<int> tmp;\n        for (int j = 0; j < n; j++) {\n            tmp.push_back(0);\n        }\n        numMatrix.push_back(tmp);\n    }\n}\n
    space_complexity.java
    /* Quadratic complexity */\nvoid quadratic(int n) {\n    // The matrix uses O(n^2) space\n    int[][] numMatrix = new int[n][n];\n    // The two-dimensional list occupies O(n^2) space\n    List<List<Integer>> numList = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        List<Integer> tmp = new ArrayList<>();\n        for (int j = 0; j < n; j++) {\n            tmp.add(0);\n        }\n        numList.add(tmp);\n    }\n}\n
    space_complexity.cs
    /* Quadratic complexity */\nvoid Quadratic(int n) {\n    // The matrix uses O(n^2) space\n    int[,] numMatrix = new int[n, n];\n    // The two-dimensional list occupies O(n^2) space\n    List<List<int>> numList = [];\n    for (int i = 0; i < n; i++) {\n        List<int> tmp = [];\n        for (int j = 0; j < n; j++) {\n            tmp.Add(0);\n        }\n        numList.Add(tmp);\n    }\n}\n
    space_complexity.go
    /* Quadratic complexity */\nfunc spaceQuadratic(n int) {\n    // The matrix uses O(n^2) space\n    numMatrix := make([][]int, n)\n    for i := 0; i < n; i++ {\n        numMatrix[i] = make([]int, n)\n    }\n}\n
    space_complexity.swift
    /* Quadratic complexity */\nfunc quadratic(n: Int) {\n    // A fully materialized two-dimensional list uses O(n^2) space; Swift may share repeated row storage\n    let numList = Array(repeating: Array(repeating: 0, count: n), count: n)\n}\n
    space_complexity.js
    /* Quadratic complexity */\nfunction quadratic(n) {\n    // The matrix uses O(n^2) space\n    const numMatrix = Array(n)\n        .fill(null)\n        .map(() => Array(n).fill(null));\n    // The two-dimensional list occupies O(n^2) space\n    const numList = [];\n    for (let i = 0; i < n; i++) {\n        const tmp = [];\n        for (let j = 0; j < n; j++) {\n            tmp.push(0);\n        }\n        numList.push(tmp);\n    }\n}\n
    space_complexity.ts
    /* Quadratic complexity */\nfunction quadratic(n: number): void {\n    // The matrix uses O(n^2) space\n    const numMatrix = Array(n)\n        .fill(null)\n        .map(() => Array(n).fill(null));\n    // The two-dimensional list occupies O(n^2) space\n    const numList = [];\n    for (let i = 0; i < n; i++) {\n        const tmp = [];\n        for (let j = 0; j < n; j++) {\n            tmp.push(0);\n        }\n        numList.push(tmp);\n    }\n}\n
    space_complexity.dart
    /* Quadratic complexity */\nvoid quadratic(int n) {\n  // The matrix uses O(n^2) space\n  List<List<int>> numMatrix = List.generate(n, (_) => List.filled(n, 0));\n  // The two-dimensional list occupies O(n^2) space\n  List<List<int>> numList = [];\n  for (var i = 0; i < n; i++) {\n    List<int> tmp = [];\n    for (int j = 0; j < n; j++) {\n      tmp.add(0);\n    }\n    numList.add(tmp);\n  }\n}\n
    space_complexity.rs
    /* Quadratic complexity */\n#[allow(unused)]\nfn quadratic(n: i32) {\n    // The matrix uses O(n^2) space\n    let num_matrix = vec![vec![0; n as usize]; n as usize];\n    // The two-dimensional list occupies O(n^2) space\n    let mut num_list = Vec::new();\n    for i in 0..n {\n        let mut tmp = Vec::new();\n        for j in 0..n {\n            tmp.push(0);\n        }\n        num_list.push(tmp);\n    }\n}\n
    space_complexity.c
    /* Quadratic complexity */\nvoid quadratic(int n) {\n    // The two-dimensional list occupies O(n^2) space\n    int **numMatrix = malloc(sizeof(int *) * n);\n    for (int i = 0; i < n; i++) {\n        int *tmp = malloc(sizeof(int) * n);\n        for (int j = 0; j < n; j++) {\n            tmp[j] = 0;\n        }\n        numMatrix[i] = tmp;\n    }\n\n    // Free memory\n    for (int i = 0; i < n; i++) {\n        free(numMatrix[i]);\n    }\n    free(numMatrix);\n}\n
    space_complexity.kt
    /* Quadratic complexity */\nfun quadratic(n: Int) {\n    // A full n*n matrix uses O(n^2) space; only its outer array is allocated here\n    val numMatrix = arrayOfNulls<Array<Int>?>(n)\n    // The two-dimensional list occupies O(n^2) space\n    val numList = mutableListOf<MutableList<Int>>()\n    for (i in 0..<n) {\n        val tmp = mutableListOf<Int>()\n        for (j in 0..<n) {\n            tmp.add(0)\n        }\n        numList.add(tmp)\n    }\n}\n
    space_complexity.rb
    ### Quadratic complexity ###\ndef quadratic(n)\n  # The two-dimensional list occupies O(n^2) space\n  Array.new(n) { Array.new(n, 0) }\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 2-18, the recursion depth is \\(n\\), and each call initializes an array. Their lengths are \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), and \\(1\\), averaging \\(n / 2\\), so total space is \\(O(n^2)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def quadratic_recur(n: int) -> int:\n    \"\"\"Quadratic complexity (recursive implementation)\"\"\"\n    if n <= 0:\n        return 0\n    # The lengths of array nums are n, n-1, ..., 2, 1\n    nums = [0] * n\n    return quadratic_recur(n - 1)\n
    space_complexity.cpp
    /* Quadratic complexity (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    vector<int> nums(n);\n    cout << \"Recursion: n = \" << n << \" , length of nums = \" << nums.size() << endl;\n    return quadraticRecur(n - 1);\n}\n
    space_complexity.java
    /* Quadratic complexity (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    // The lengths of array nums are n, n-1, ..., 2, 1\n    int[] nums = new int[n];\n    System.out.println(\"Recursive call: n = \" + n + \" with nums length = \" + nums.length);\n    return quadraticRecur(n - 1);\n}\n
    space_complexity.cs
    /* Quadratic complexity (recursive implementation) */\nint QuadraticRecur(int n) {\n    if (n <= 0) return 0;\n    int[] nums = new int[n];\n    Console.WriteLine(\"Recursive call: n = \" + n + \" with nums length = \" + nums.Length);\n    return QuadraticRecur(n - 1);\n}\n
    space_complexity.go
    /* Quadratic complexity (recursive implementation) */\nfunc spaceQuadraticRecur(n int) int {\n    if n <= 0 {\n        return 0\n    }\n    nums := make([]int, n)\n    fmt.Printf(\"Recursive call n = %d: length of nums = %d \\n\", n, len(nums))\n    return spaceQuadraticRecur(n - 1)\n}\n
    space_complexity.swift
    /* Quadratic complexity (recursive implementation) */\n@discardableResult\nfunc quadraticRecur(n: Int) -> Int {\n    if n <= 0 {\n        return 0\n    }\n    // The lengths of array nums are n, n-1, ..., 2, 1\n    let nums = Array(repeating: 0, count: n)\n    print(\"At recursion n = \\(n), nums has length \\(nums.count)\")\n    return quadraticRecur(n: n - 1)\n}\n
    space_complexity.js
    /* Quadratic complexity (recursive implementation) */\nfunction quadraticRecur(n) {\n    if (n <= 0) return 0;\n    const nums = new Array(n);\n    console.log(`In the recursive call with n = ${n}, nums has length ${nums.length}`);\n    return quadraticRecur(n - 1);\n}\n
    space_complexity.ts
    /* Quadratic complexity (recursive implementation) */\nfunction quadraticRecur(n: number): number {\n    if (n <= 0) return 0;\n    const nums = new Array(n);\n    console.log(`In the recursive call with n = ${n}, nums has length ${nums.length}`);\n    return quadraticRecur(n - 1);\n}\n
    space_complexity.dart
    /* Quadratic complexity (recursive implementation) */\nint quadraticRecur(int n) {\n  if (n <= 0) return 0;\n  List<int> nums = List.filled(n, 0);\n  print('In the recursive call with n = $n, length of nums = ${nums.length}');\n  return quadraticRecur(n - 1);\n}\n
    space_complexity.rs
    /* Quadratic complexity (recursive implementation) */\nfn quadratic_recur(n: i32) -> i32 {\n    if n <= 0 {\n        return 0;\n    };\n    // The lengths of array nums are n, n-1, ..., 2, 1\n    let nums = vec![0; n as usize];\n    println!(\"In recursive call n = {}, nums has length {}\", n, nums.len());\n    return quadratic_recur(n - 1);\n}\n
    space_complexity.c
    /* Quadratic complexity (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    int *nums = malloc(sizeof(int) * n);\n    printf(\"In recursive call n = %d, nums has length = %d\\r\\n\", n, n);\n    int res = quadraticRecur(n - 1);\n    free(nums);\n    return res;\n}\n
    space_complexity.kt
    /* Quadratic allocation total (recursive example; tailrec removes recursive stack growth) */\ntailrec fun quadraticRecur(n: Int): Int {\n    if (n <= 0)\n        return 0\n    // The lengths of array nums are n, n-1, ..., 2, 1\n    val nums = Array(n) { 0 }\n    println(\"In the recursive call with n = $n, nums has length ${nums.size}\")\n    return quadraticRecur(n - 1)\n}\n
    space_complexity.rb
    ### Quadratic complexity (recursive implementation)###\ndef quadratic_recur(n)\n  return 0 unless n > 0\n\n  # The lengths of array nums are n, n-1, ..., 2, 1\n  nums = Array.new(n, 0)\n  quadratic_recur(n - 1)\nend\n
    Visualize execution

    View full screen >

    Figure 2-18   Quadratic space complexity from recursion

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#4-exponential-space-o2n","level":3,"title":"4.   Exponential space \\(O(2^n)\\)","text":"

    Exponential space commonly occurs in binary trees. In Figure 2-19, a perfect binary tree with \\(n\\) levels contains \\(2^n - 1\\) nodes and uses \\(O(2^n)\\) space:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def build_tree(n: int) -> TreeNode | None:\n    \"\"\"Exponential complexity (building a perfect binary tree)\"\"\"\n    if n == 0:\n        return None\n    root = TreeNode(0)\n    root.left = build_tree(n - 1)\n    root.right = build_tree(n - 1)\n    return root\n
    space_complexity.cpp
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode *buildTree(int n) {\n    if (n == 0)\n        return nullptr;\n    TreeNode *root = new TreeNode(0);\n    root->left = buildTree(n - 1);\n    root->right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.java
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode buildTree(int n) {\n    if (n == 0)\n        return null;\n    TreeNode root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.cs
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode? BuildTree(int n) {\n    if (n == 0) return null;\n    TreeNode root = new(0) {\n        left = BuildTree(n - 1),\n        right = BuildTree(n - 1)\n    };\n    return root;\n}\n
    space_complexity.go
    /* Exponential complexity (building a perfect binary tree) */\nfunc buildTree(n int) *TreeNode {\n    if n == 0 {\n        return nil\n    }\n    root := NewTreeNode(0)\n    root.Left = buildTree(n - 1)\n    root.Right = buildTree(n - 1)\n    return root\n}\n
    space_complexity.swift
    /* Exponential complexity (building a perfect binary tree) */\nfunc buildTree(n: Int) -> TreeNode? {\n    if n == 0 {\n        return nil\n    }\n    let root = TreeNode(x: 0)\n    root.left = buildTree(n: n - 1)\n    root.right = buildTree(n: n - 1)\n    return root\n}\n
    space_complexity.js
    /* Exponential complexity (building a perfect binary tree) */\nfunction buildTree(n) {\n    if (n === 0) return null;\n    const root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.ts
    /* Exponential complexity (building a perfect binary tree) */\nfunction buildTree(n: number): TreeNode | null {\n    if (n === 0) return null;\n    const root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.dart
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode? buildTree(int n) {\n  if (n == 0) return null;\n  TreeNode root = TreeNode(0);\n  root.left = buildTree(n - 1);\n  root.right = buildTree(n - 1);\n  return root;\n}\n
    space_complexity.rs
    /* Exponential complexity (building a perfect binary tree) */\nfn build_tree(n: i32) -> Option<Rc<RefCell<TreeNode>>> {\n    if n == 0 {\n        return None;\n    };\n    let root = TreeNode::new(0);\n    root.borrow_mut().left = build_tree(n - 1);\n    root.borrow_mut().right = build_tree(n - 1);\n    return Some(root);\n}\n
    space_complexity.c
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode *buildTree(int n) {\n    if (n == 0)\n        return NULL;\n    TreeNode *root = newTreeNode(0);\n    root->left = buildTree(n - 1);\n    root->right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.kt
    /* Exponential complexity (building a perfect binary tree) */\nfun buildTree(n: Int): TreeNode? {\n    if (n == 0)\n        return null\n    val root = TreeNode(0)\n    root.left = buildTree(n - 1)\n    root.right = buildTree(n - 1)\n    return root\n}\n
    space_complexity.rb
    ### Exponential complexity (building a perfect binary tree)###\ndef build_tree(n)\n  return if n == 0\n\n  TreeNode.new.tap do |root|\n    root.left = build_tree(n - 1)\n    root.right = build_tree(n - 1)\n  end\nend\n
    Visualize execution

    View full screen >

    Figure 2-19   Exponential space used by a perfect binary tree

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#5-logarithmic-space-olog-n","level":3,"title":"5.   Logarithmic space \\(O(\\log n)\\)","text":"

    Logarithmic space often appears in divide-and-conquer algorithms. For example, merge sort splits an array of length \\(n\\) in half at each recursive step, producing a recursion tree of height \\(\\log n\\) and using \\(O(\\log n)\\) space for stack frames.

    Another example is converting a number to a string. A positive integer \\(n\\) has \\(\\lfloor \\log_{10} n \\rfloor + 1\\) digits, giving a string of length \\(\\lfloor \\log_{10} n \\rfloor + 1\\). The space complexity is therefore \\(O(\\log_{10} n + 1) = O(\\log n)\\).

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#244-balancing-time-and-space","level":2,"title":"2.4.4   Balancing time and space","text":"

    Ideally, we would minimize both time and space complexity. In practice, optimizing both simultaneously is usually difficult.

    Reducing time complexity often increases space complexity, and vice versa. Using more memory to run faster is called trading space for time; accepting slower execution to use less memory is trading time for space.

    The choice depends on which resource matters more. Time is usually more valuable than space, so trading space for time is common. For very large datasets, however, keeping space complexity under control is also crucial.

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/summary/","level":1,"title":"2.5   Summary","text":"","path":["Chapter 2.   Complexity analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"

    Evaluating algorithm efficiency

    • Time and space efficiency are the two main measures of algorithm quality.
    • Practical testing measures efficiency, but cannot easily remove environmental effects and consumes substantial computing resources.
    • Complexity analysis avoids those limitations, applies across platforms, and reveals efficiency at different input sizes.

    Time complexity

    • Time complexity describes how runtime grows with input size. It is useful for evaluating efficiency, but may not distinguish algorithms accurately for small inputs or equal complexity classes.
    • Worst-case time complexity is expressed with big-\\(O\\) notation, an asymptotic upper bound describing the growth of the operation count \\(T(n)\\) as \\(n\\) tends to positive infinity.
    • Deriving time complexity takes two steps: count operations, then determine the asymptotic upper bound.
    • Common complexities, from slower to faster growth, include \\(O(1)\\), \\(O(\\log n)\\), \\(O(n)\\), \\(O(n \\log n)\\), \\(O(n^2)\\), \\(O(2^n)\\), and \\(O(n!)\\).
    • Some algorithms' complexity depends on the input's arrangement. We distinguish worst-case, best-case, and average-case complexity. Best-case complexity is rarely used because reaching it generally requires strict input conditions.
    • Average-case complexity describes performance on random inputs and is closest to typical practical behavior. Calculating it requires an input distribution and its mathematical expectation.

    Space complexity

    • Like time complexity, space complexity describes a growth trend: how memory usage increases with input size.
    • Memory used during execution includes input, temporary, and output space. Input space is usually excluded from space complexity. Temporary space includes temporary data, stack frames, and instructions; stack frames usually affect complexity only in recursive functions.
    • We usually consider worst-case space complexity, measuring memory under the worst input and at the moment of maximum usage.
    • Common space complexities, from slower to faster growth, include \\(O(1)\\), \\(O(\\log n)\\), \\(O(n)\\), \\(O(n^2)\\), and \\(O(2^n)\\).
    ","path":["Chapter 2.   Complexity analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Does tail recursion have \\(O(1)\\) space complexity?

    In theory, tail recursion can be optimized to \\(O(1)\\) space. However, most languages, including Java, Python, C++, Go, and C#, do not automatically optimize tail recursion, so its space complexity is usually treated as \\(O(n)\\).

    Q: What is the difference between a function and a method?

    A function can run independently, with all arguments passed explicitly. A method is associated with an object, receives that object implicitly when called, and can operate on the instance's data.

    Here are examples from several common languages.

    • C is procedural and has no built-in object-oriented model, so it has functions only. We can simulate object-oriented programming with structures (struct); functions associated with a structure then act like methods in other languages.
    • Java and C# are object-oriented, and executable blocks (methods) are usually part of a class. Static methods behave like functions: they belong to the class and cannot access a particular instance's variables directly.
    • C++ and Python support both procedural programming with functions and object-oriented programming with methods.

    Q: Does the common space complexity chart show absolute memory usage?

    No. It illustrates growth trends rather than absolute amounts of memory.

    For \\(n = 8\\), the plotted values may not match the corresponding functions exactly. Each curve includes a constant term that adjusts it to a visually convenient range.

    In practice, these constant terms are usually unknown, so complexity alone cannot generally identify the best solution for \\(n = 8\\). For \\(n = 8^5\\), the choice is easier because growth rates dominate.

    Q: Do algorithms sometimes trade time for space, or space for time, depending on the application?

    Most practical applications trade space for time. Database indexes, for example, often use B+ trees or hash indexes, consuming substantial memory to achieve \\(O(\\log n)\\) or even \\(O(1)\\) query time.

    When memory is scarce, the reverse tradeoff may be useful. In embedded development, an engineer might replace a hash table with sequential array search to save memory, accepting slower lookups.

    ","path":["Chapter 2.   Complexity analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/","level":1,"title":"2.3   Time complexity","text":"

    Runtime gives a direct, accurate picture of an algorithm's efficiency. How might we predict the runtime of a piece of code precisely?

    1. Specify the execution platform, including hardware, programming language, and system environment, all of which affect performance.
    2. Determine the time required by each operation: for example, addition + might take 1 ns, multiplication * 10 ns, and print() 5 ns.
    3. Count every operation in the code and sum their execution times to obtain the total runtime.

    For example, consider the following code with input size \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    # On a particular execution platform\ndef algorithm(n: int):\n    a = 2      # 1 ns\n    a = a + 1  # 1 ns\n    a = a * 2  # 10 ns\n    # Repeat n times\n    for _ in range(n):  # 1 ns\n        print(0)        # 5 ns\n
    // On a particular execution platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Repeat n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        cout << 0 << endl;         // 5 ns\n    }\n}\n
    // On a particular execution platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Repeat n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        System.out.println(0);     // 5 ns\n    }\n}\n
    // On a particular execution platform\nvoid Algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Repeat n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        Console.WriteLine(0);      // 5 ns\n    }\n}\n
    // On a particular execution platform\nfunc algorithm(n int) {\n    a := 2     // 1 ns\n    a = a + 1  // 1 ns\n    a = a * 2  // 10 ns\n    // Repeat n times\n    for i := 0; i < n; i++ {  // 1 ns\n        fmt.Println(a)        // 5 ns\n    }\n}\n
    // On a particular execution platform\nfunc algorithm(n: Int) {\n    var a = 2 // 1 ns\n    a = a + 1 // 1 ns\n    a = a * 2 // 10 ns\n    // Repeat n times\n    for _ in 0 ..< n { // 1 ns\n        print(0) // 5 ns\n    }\n}\n
    // On a particular execution platform\nfunction algorithm(n) {\n    var a = 2; // 1 ns\n    a = a + 1; // 1 ns\n    a = a * 2; // 10 ns\n    // Repeat n times\n    for(let i = 0; i < n; i++) { // 1 ns\n        console.log(0); // 5 ns\n    }\n}\n
    // On a particular execution platform\nfunction algorithm(n: number): void {\n    var a: number = 2; // 1 ns\n    a = a + 1; // 1 ns\n    a = a * 2; // 10 ns\n    // Repeat n times\n    for(let i = 0; i < n; i++) { // 1 ns\n        console.log(0); // 5 ns\n    }\n}\n
    // On a particular execution platform\nvoid algorithm(int n) {\n  int a = 2; // 1 ns\n  a = a + 1; // 1 ns\n  a = a * 2; // 10 ns\n  // Repeat n times\n  for (int i = 0; i < n; i++) { // 1 ns\n    print(0); // 5 ns\n  }\n}\n
    // On a particular execution platform\nfn algorithm(n: i32) {\n    let mut a = 2;      // 1 ns\n    a = a + 1;          // 1 ns\n    a = a * 2;          // 10 ns\n    // Repeat n times\n    for _ in 0..n {     // 1 ns\n        println!(\"{}\", 0);  // 5 ns\n    }\n}\n
    // On a particular execution platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Repeat n times\n    for (int i = 0; i < n; i++) {   // 1 ns\n        printf(\"%d\", 0);            // 5 ns\n    }\n}\n
    // On a particular execution platform\nfun algorithm(n: Int) {\n    var a = 2 // 1 ns\n    a = a + 1 // 1 ns\n    a = a * 2 // 10 ns\n    // Repeat n times\n    for (i in 0..<n) {  // 1 ns\n        println(0)      // 5 ns\n    }\n}\n
    # On a particular execution platform\ndef algorithm(n)\n    a = 2       # 1 ns\n    a = a + 1   # 1 ns\n    a = a * 2   # 10 ns\n    # Repeat n times\n    (0...n).each do # 1 ns\n        puts 0      # 5 ns\n    end\nend\n

    This gives a runtime of \\((6n + 12)\\) ns:

    \\[ 1 + 1 + 10 + (1 + 5) \\times n = 6n + 12 \\]

    In practice, calculating runtime this way is neither suitable nor realistic. We do not want estimates tied to one platform, since algorithms run on many platforms. Also, the time taken by each operation is difficult to determine, making accurate prediction very hard.

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#231-measuring-growth-in-runtime","level":2,"title":"2.3.1   Measuring growth in runtime","text":"

    Time complexity analysis measures how runtime grows as the input gets larger, rather than the runtime itself.

    To make this abstract idea concrete, consider three algorithms, A, B, and C, with input size \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    # Algorithm A: constant time complexity\ndef algorithm_A(n: int):\n    print(0)\n# Algorithm B: linear time complexity\ndef algorithm_B(n: int):\n    for _ in range(n):\n        print(0)\n# Algorithm C: constant time complexity\ndef algorithm_C(n: int):\n    for _ in range(1000000):\n        print(0)\n
    // Algorithm A: constant time complexity\nvoid algorithm_A(int n) {\n    cout << 0 << endl;\n}\n// Algorithm B: linear time complexity\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        cout << 0 << endl;\n    }\n}\n// Algorithm C: constant time complexity\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        cout << 0 << endl;\n    }\n}\n
    // Algorithm A: constant time complexity\nvoid algorithm_A(int n) {\n    System.out.println(0);\n}\n// Algorithm B: linear time complexity\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        System.out.println(0);\n    }\n}\n// Algorithm C: constant time complexity\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        System.out.println(0);\n    }\n}\n
    // Algorithm A: constant time complexity\nvoid AlgorithmA(int n) {\n    Console.WriteLine(0);\n}\n// Algorithm B: linear time complexity\nvoid AlgorithmB(int n) {\n    for (int i = 0; i < n; i++) {\n        Console.WriteLine(0);\n    }\n}\n// Algorithm C: constant time complexity\nvoid AlgorithmC(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        Console.WriteLine(0);\n    }\n}\n
    // Algorithm A: constant time complexity\nfunc algorithm_A(n int) {\n    fmt.Println(0)\n}\n// Algorithm B: linear time complexity\nfunc algorithm_B(n int) {\n    for i := 0; i < n; i++ {\n        fmt.Println(0)\n    }\n}\n// Algorithm C: constant time complexity\nfunc algorithm_C(n int) {\n    for i := 0; i < 1000000; i++ {\n        fmt.Println(0)\n    }\n}\n
    // Algorithm A: constant time complexity\nfunc algorithmA(n: Int) {\n    print(0)\n}\n\n// Algorithm B: linear time complexity\nfunc algorithmB(n: Int) {\n    for _ in 0 ..< n {\n        print(0)\n    }\n}\n\n// Algorithm C: constant time complexity\nfunc algorithmC(n: Int) {\n    for _ in 0 ..< 1_000_000 {\n        print(0)\n    }\n}\n
    // Algorithm A: constant time complexity\nfunction algorithm_A(n) {\n    console.log(0);\n}\n// Algorithm B: linear time complexity\nfunction algorithm_B(n) {\n    for (let i = 0; i < n; i++) {\n        console.log(0);\n    }\n}\n// Algorithm C: constant time complexity\nfunction algorithm_C(n) {\n    for (let i = 0; i < 1000000; i++) {\n        console.log(0);\n    }\n}\n
    // Algorithm A: constant time complexity\nfunction algorithm_A(n: number): void {\n    console.log(0);\n}\n// Algorithm B: linear time complexity\nfunction algorithm_B(n: number): void {\n    for (let i = 0; i < n; i++) {\n        console.log(0);\n    }\n}\n// Algorithm C: constant time complexity\nfunction algorithm_C(n: number): void {\n    for (let i = 0; i < 1000000; i++) {\n        console.log(0);\n    }\n}\n
    // Algorithm A: constant time complexity\nvoid algorithmA(int n) {\n  print(0);\n}\n// Algorithm B: linear time complexity\nvoid algorithmB(int n) {\n  for (int i = 0; i < n; i++) {\n    print(0);\n  }\n}\n// Algorithm C: constant time complexity\nvoid algorithmC(int n) {\n  for (int i = 0; i < 1000000; i++) {\n    print(0);\n  }\n}\n
    // Algorithm A: constant time complexity\nfn algorithm_A(n: i32) {\n    println!(\"{}\", 0);\n}\n// Algorithm B: linear time complexity\nfn algorithm_B(n: i32) {\n    for _ in 0..n {\n        println!(\"{}\", 0);\n    }\n}\n// Algorithm C: constant time complexity\nfn algorithm_C(n: i32) {\n    for _ in 0..1000000 {\n        println!(\"{}\", 0);\n    }\n}\n
    // Algorithm A: constant time complexity\nvoid algorithm_A(int n) {\n    printf(\"%d\", 0);\n}\n// Algorithm B: linear time complexity\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        printf(\"%d\", 0);\n    }\n}\n// Algorithm C: constant time complexity\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        printf(\"%d\", 0);\n    }\n}\n
    // Algorithm A: constant time complexity\nfun algoritm_A(n: Int) {\n    println(0)\n}\n// Algorithm B: linear time complexity\nfun algorithm_B(n: Int) {\n    for (i in 0..<n){\n        println(0)\n    }\n}\n// Algorithm C: constant time complexity\nfun algorithm_C(n: Int) {\n    for (i in 0..<1000000) {\n        println(0)\n    }\n}\n
    # Algorithm A: constant time complexity\ndef algorithm_A(n)\n    puts 0\nend\n\n# Algorithm B: linear time complexity\ndef algorithm_B(n)\n    (0...n).each { puts 0 }\nend\n\n# Algorithm C: constant time complexity\ndef algorithm_C(n)\n    (0...1_000_000).each { puts 0 }\nend\n

    Figure 2-7 shows their time complexities.

    • Algorithm A performs \\(1\\) print operation. Its runtime does not grow with \\(n\\), so it has constant time complexity.
    • Algorithm B prints \\(n\\) times. Its runtime grows linearly with \\(n\\), so it has linear time complexity.
    • Algorithm C prints \\(1000000\\) times. It may take a long time, but that time is independent of input size \\(n\\). Its complexity is therefore constant, like A.

    Figure 2-7   Runtime growth of algorithms A, B, and C

    How does time complexity analysis differ from calculating runtime directly?

    • Time complexity evaluates efficiency effectively. Algorithm B grows linearly, becoming slower than A when \\(n > 1\\) and slower than C when \\(n > 1000000\\). With sufficiently large input size \\(n\\), a constant-time algorithm will outperform a linear-time one. This is what a growth trend tells us.
    • Time complexity is easier to derive. The execution platform and kinds of elementary operations do not determine the growth trend. We can treat each elementary operation as taking the same unit of time, reducing runtime estimation to operation counting and greatly simplifying analysis.
    • Time complexity has limitations. Algorithms A and C have the same complexity but very different runtimes. Although B has higher complexity than C, it is much faster for small \\(n\\). Complexity alone may not settle such comparisons. Even so, it remains the most effective and widely used way to evaluate algorithm efficiency.
    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#232-asymptotic-upper-bounds","level":2,"title":"2.3.2   Asymptotic upper bounds","text":"

    Consider this function with input size \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def algorithm(n: int):\n    a = 1      # +1\n    a = a + 1  # +1\n    a = a * 2  # +1\n    # Repeat n times\n    for i in range(n):  # +1\n        print(0)        # +1\n
    void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Repeat n times\n    for (int i = 0; i < n; i++) { // +1 (i ++ executes each iteration)\n        cout << 0 << endl;    // +1\n    }\n}\n
    void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Repeat n times\n    for (int i = 0; i < n; i++) { // +1 (i ++ executes each iteration)\n        System.out.println(0);    // +1\n    }\n}\n
    void Algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Repeat n times\n    for (int i = 0; i < n; i++) {   // +1 (i ++ executes each iteration)\n        Console.WriteLine(0);   // +1\n    }\n}\n
    func algorithm(n int) {\n    a := 1      // +1\n    a = a + 1   // +1\n    a = a * 2   // +1\n    // Repeat n times\n    for i := 0; i < n; i++ {   // +1\n        fmt.Println(a)         // +1\n    }\n}\n
    func algorithm(n: Int) {\n    var a = 1 // +1\n    a = a + 1 // +1\n    a = a * 2 // +1\n    // Repeat n times\n    for _ in 0 ..< n { // +1\n        print(0) // +1\n    }\n}\n
    function algorithm(n) {\n    var a = 1; // +1\n    a += 1; // +1\n    a *= 2; // +1\n    // Repeat n times\n    for(let i = 0; i < n; i++){ // +1 (i ++ executes each iteration)\n        console.log(0); // +1\n    }\n}\n
    function algorithm(n: number): void{\n    var a: number = 1; // +1\n    a += 1; // +1\n    a *= 2; // +1\n    // Repeat n times\n    for(let i = 0; i < n; i++){ // +1 (i ++ executes each iteration)\n        console.log(0); // +1\n    }\n}\n
    void algorithm(int n) {\n  int a = 1; // +1\n  a = a + 1; // +1\n  a = a * 2; // +1\n  // Repeat n times\n  for (int i = 0; i < n; i++) { // +1 (i ++ executes each iteration)\n    print(0); // +1\n  }\n}\n
    fn algorithm(n: i32) {\n    let mut a = 1;   // +1\n    a = a + 1;      // +1\n    a = a * 2;      // +1\n\n    // Repeat n times\n    for _ in 0..n { // +1 (i ++ executes each iteration)\n        println!(\"{}\", 0); // +1\n    }\n}\n
    void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Repeat n times\n    for (int i = 0; i < n; i++) {   // +1 (i ++ executes each iteration)\n        printf(\"%d\", 0);            // +1\n    }\n}\n
    fun algorithm(n: Int) {\n    var a = 1 // +1\n    a = a + 1 // +1\n    a = a * 2 // +1\n    // Repeat n times\n    for (i in 0..<n) { // +1 (i ++ executes each iteration)\n        println(0) // +1\n    }\n}\n
    def algorithm(n)\n    a = 1       # +1\n    a = a + 1   # +1\n    a = a * 2   # +1\n    # Repeat n times\n    (0...n).each do # +1\n        puts 0      # +1\n    end\nend\n

    Let \\(T(n)\\) be the operation count as a function of input size \\(n\\). For the function above:

    \\[ T(n) = 3 + 2n \\]

    Since \\(T(n)\\) is a first-degree function, runtime grows linearly, giving linear time complexity.

    We write linear time complexity as \\(O(n)\\). This is called big-\\(O\\) notation, and it gives an asymptotic upper bound on \\(T(n)\\).

    Time complexity analysis essentially finds an asymptotic upper bound on the operation count \\(T(n)\\). It has a precise mathematical definition.

    Asymptotic upper bound

    If a positive real constant \\(c\\) and a real number \\(n_0\\) exist such that \\(T(n) \\leq c \\cdot f(n)\\) for all \\(n > n_0\\), then \\(f(n)\\) is an asymptotic upper bound on \\(T(n)\\), written \\(T(n) = O(f(n))\\).

    As shown in Figure 2-8, we seek a function \\(f(n)\\) such that, as \\(n\\) tends to infinity, \\(T(n)\\) and \\(f(n)\\) have the same order of growth, differing only by a constant factor \\(c\\).

    Figure 2-8   Asymptotic upper bound of a function

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#233-how-to-derive-time-complexity","level":2,"title":"2.3.3   How to derive time complexity","text":"

    The mathematics of asymptotic bounds may feel unfamiliar. Do not worry if it is not fully clear yet: learn the practical method first, and its meaning will become clearer with practice.

    Once we identify \\(f(n)\\), the time complexity is \\(O(f(n))\\). Finding the asymptotic upper bound \\(f(n)\\) takes two steps: count operations, then determine the bound.

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-step-1-count-operations","level":3,"title":"1.   Step 1: Count operations","text":"

    We can count code operations line by line. Since the constant factor \\(c\\) in \\(c \\cdot f(n)\\) may be arbitrarily large, coefficients and constant terms in \\(T(n)\\) can be ignored. This gives three useful shortcuts.

    1. Ignore constant terms in \\(T(n)\\). They are independent of \\(n\\) and do not affect complexity.
    2. Drop coefficients. For example, \\(2n\\) or \\(5n + 1\\) iterations can be counted simply as \\(n\\), because the coefficient of \\(n\\) does not affect complexity.
    3. Multiply for nested loops. Multiply the outer and inner iteration counts, applying shortcuts 1. and 2. to each level.

    We can apply these shortcuts to the following function:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def algorithm(n: int):\n    a = 1      # +0 (shortcut 1)\n    a = a + n  # +0 (shortcut 1)\n    # +n (shortcut 2)\n    for i in range(5 * n + 1):\n        print(0)\n    # +n*n (shortcut 3)\n    for i in range(2 * n):\n        for j in range(n + 1):\n            print(0)\n
    void algorithm(int n) {\n    int a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        cout << 0 << endl;\n    }\n    // +n*n (shortcut 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            cout << 0 << endl;\n        }\n    }\n}\n
    void algorithm(int n) {\n    int a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        System.out.println(0);\n    }\n    // +n*n (shortcut 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            System.out.println(0);\n        }\n    }\n}\n
    void Algorithm(int n) {\n    int a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        Console.WriteLine(0);\n    }\n    // +n*n (shortcut 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            Console.WriteLine(0);\n        }\n    }\n}\n
    func algorithm(n int) {\n    a := 1     // +0 (shortcut 1)\n    a = a + n  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for i := 0; i < 5 * n + 1; i++ {\n        fmt.Println(0)\n    }\n    // +n*n (shortcut 3)\n    for i := 0; i < 2 * n; i++ {\n        for j := 0; j < n + 1; j++ {\n            fmt.Println(0)\n        }\n    }\n}\n
    func algorithm(n: Int) {\n    var a = 1 // +0 (shortcut 1)\n    a = a + n // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for _ in 0 ..< (5 * n + 1) {\n        print(0)\n    }\n    // +n*n (shortcut 3)\n    for _ in 0 ..< (2 * n) {\n        for _ in 0 ..< (n + 1) {\n            print(0)\n        }\n    }\n}\n
    function algorithm(n) {\n    let a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (let i = 0; i < 5 * n + 1; i++) {\n        console.log(0);\n    }\n    // +n*n (shortcut 3)\n    for (let i = 0; i < 2 * n; i++) {\n        for (let j = 0; j < n + 1; j++) {\n            console.log(0);\n        }\n    }\n}\n
    function algorithm(n: number): void {\n    let a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (let i = 0; i < 5 * n + 1; i++) {\n        console.log(0);\n    }\n    // +n*n (shortcut 3)\n    for (let i = 0; i < 2 * n; i++) {\n        for (let j = 0; j < n + 1; j++) {\n            console.log(0);\n        }\n    }\n}\n
    void algorithm(int n) {\n  int a = 1; // +0 (shortcut 1)\n  a = a + n; // +0 (shortcut 1)\n  // +n (shortcut 2)\n  for (int i = 0; i < 5 * n + 1; i++) {\n    print(0);\n  }\n  // +n*n (shortcut 3)\n  for (int i = 0; i < 2 * n; i++) {\n    for (int j = 0; j < n + 1; j++) {\n      print(0);\n    }\n  }\n}\n
    fn algorithm(n: i32) {\n    let mut a = 1;     // +0 (shortcut 1)\n    a = a + n;        // +0 (shortcut 1)\n\n    // +n (shortcut 2)\n    for i in 0..(5 * n + 1) {\n        println!(\"{}\", 0);\n    }\n\n    // +n*n (shortcut 3)\n    for i in 0..(2 * n) {\n        for j in 0..(n + 1) {\n            println!(\"{}\", 0);\n        }\n    }\n}\n
    void algorithm(int n) {\n    int a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        printf(\"%d\", 0);\n    }\n    // +n*n (shortcut 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            printf(\"%d\", 0);\n        }\n    }\n}\n
    fun algorithm(n: Int) {\n    var a = 1   // +0 (shortcut 1)\n    a = a + n   // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (i in 0..<5 * n + 1) {\n        println(0)\n    }\n    // +n*n (shortcut 3)\n    for (i in 0..<2 * n) {\n        for (j in 0..<n + 1) {\n            println(0)\n        }\n    }\n}\n
    def algorithm(n)\n    a = 1       # +0 (shortcut 1)\n    a = a + n   # +0 (shortcut 1)\n    # +n (shortcut 2)\n    (0...(5 * n + 1)).each do { puts 0 }\n    # +n*n (shortcut 3)\n    (0...(2 * n)).each do\n        (0...(n + 1)).each do { puts 0 }\n    end\nend\n

    The formulas below compare full and simplified counts. Both give time complexity \\(O(n^2)\\).

    \\[ \\begin{aligned} T(n) & = 2n(n + 1) + (5n + 1) + 2 & \\text{Full count (-.-|||)} \\newline & = 2n^2 + 7n + 3 \\newline T(n) & = n^2 + n & \\text{Shortcut count (o.O)} \\end{aligned} \\]","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-step-2-determine-the-asymptotic-upper-bound","level":3,"title":"2.   Step 2: Determine the asymptotic upper bound","text":"

    The highest-order term in \\(T(n)\\) determines time complexity. As \\(n\\) tends to infinity, that term dominates, and the others become negligible.

    Table 2-2 includes deliberately extreme coefficients to emphasize that coefficients cannot overcome growth order. As \\(n\\) tends to infinity, those constants become insignificant.

    Table 2-2   Operation counts and their time complexities

    Operation count \\(T(n)\\) Time complexity \\(O(f(n))\\) \\(100000\\) \\(O(1)\\) \\(3n + 2\\) \\(O(n)\\) \\(2n^2 + 3n + 2\\) \\(O(n^2)\\) \\(n^3 + 10000n^2\\) \\(O(n^3)\\) \\(2^n + 10000n^{10000}\\) \\(O(2^n)\\)","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#234-common-complexity-classes","level":2,"title":"2.3.4   Common complexity classes","text":"

    For input size \\(n\\), common time complexities are shown below in increasing order of growth.

    \\[ \\begin{aligned} & O(1) < O(\\log n) < O(n) < O(n \\log n) < O(n^2) < O(2^n) < O(n!) \\newline & \\text{Constant} < \\text{Logarithmic} < \\text{Linear} < \\text{Linearithmic} < \\text{Quadratic} < \\text{Exponential} < \\text{Factorial} \\end{aligned} \\]

    Figure 2-9   Common time complexity classes

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-constant-time-o1","level":3,"title":"1.   Constant time \\(O(1)\\)","text":"

    A constant operation count is independent of input size \\(n\\): changing \\(n\\) does not change the count.

    In the following function, size may be large, but it is independent of input size \\(n\\), so the time complexity remains \\(O(1)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def constant(n: int) -> int:\n    \"\"\"Constant complexity\"\"\"\n    count = 0\n    size = 100000\n    for _ in range(size):\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Constant complexity */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
    time_complexity.java
    /* Constant complexity */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
    time_complexity.cs
    /* Constant complexity */\nint Constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
    time_complexity.go
    /* Constant complexity */\nfunc constant(n int) int {\n    count := 0\n    size := 100000\n    for i := 0; i < size; i++ {\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Constant complexity */\nfunc constant(n: Int) -> Int {\n    var count = 0\n    let size = 100_000\n    for _ in 0 ..< size {\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Constant complexity */\nfunction constant(n) {\n    let count = 0;\n    const size = 100000;\n    for (let i = 0; i < size; i++) count++;\n    return count;\n}\n
    time_complexity.ts
    /* Constant complexity */\nfunction constant(n: number): number {\n    let count = 0;\n    const size = 100000;\n    for (let i = 0; i < size; i++) count++;\n    return count;\n}\n
    time_complexity.dart
    /* Constant complexity */\nint constant(int n) {\n  int count = 0;\n  int size = 100000;\n  for (var i = 0; i < size; i++) {\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Constant complexity */\nfn constant(n: i32) -> i32 {\n    _ = n;\n    let mut count = 0;\n    let size = 100_000;\n    for _ in 0..size {\n        count += 1;\n    }\n    count\n}\n
    time_complexity.c
    /* Constant complexity */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Constant complexity */\nfun constant(n: Int): Int {\n    var count = 0\n    val size = 100000\n    for (i in 0..<size)\n        count++\n    return count\n}\n
    time_complexity.rb
    ### Constant complexity ###\ndef constant(n)\n  count = 0\n  size = 100000\n\n  (0...size).each { count += 1 }\n\n  count\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-linear-time-on","level":3,"title":"2.   Linear time \\(O(n)\\)","text":"

    A linear operation count grows proportionally to input size \\(n\\). It commonly appears in a single loop:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def linear(n: int) -> int:\n    \"\"\"Linear complexity\"\"\"\n    count = 0\n    for _ in range(n):\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Linear complexity */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
    time_complexity.java
    /* Linear complexity */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
    time_complexity.cs
    /* Linear complexity */\nint Linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
    time_complexity.go
    /* Linear complexity */\nfunc linear(n int) int {\n    count := 0\n    for i := 0; i < n; i++ {\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Linear complexity */\nfunc linear(n: Int) -> Int {\n    var count = 0\n    for _ in 0 ..< n {\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Linear complexity */\nfunction linear(n) {\n    let count = 0;\n    for (let i = 0; i < n; i++) count++;\n    return count;\n}\n
    time_complexity.ts
    /* Linear complexity */\nfunction linear(n: number): number {\n    let count = 0;\n    for (let i = 0; i < n; i++) count++;\n    return count;\n}\n
    time_complexity.dart
    /* Linear complexity */\nint linear(int n) {\n  int count = 0;\n  for (var i = 0; i < n; i++) {\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Linear complexity */\nfn linear(n: i32) -> i32 {\n    let mut count = 0;\n    for _ in 0..n {\n        count += 1;\n    }\n    count\n}\n
    time_complexity.c
    /* Linear complexity */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Linear complexity */\nfun linear(n: Int): Int {\n    var count = 0\n    for (i in 0..<n)\n        count++\n    return count\n}\n
    time_complexity.rb
    ### Linear complexity ###\ndef linear(n)\n  count = 0\n  (0...n).each { count += 1 }\n  count\nend\n
    Visualize execution

    View full screen >

    Traversing an array or linked list takes \\(O(n)\\) time, where \\(n\\) is its length:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def array_traversal(nums: list[int]) -> int:\n    \"\"\"Linear complexity (array traversal)\"\"\"\n    count = 0\n    # The number of iterations is proportional to the array length\n    for num in nums:\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Linear complexity (array traversal) */\nint arrayTraversal(vector<int> &nums) {\n    int count = 0;\n    // The number of iterations is proportional to the array length\n    for (int num : nums) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.java
    /* Linear complexity (array traversal) */\nint arrayTraversal(int[] nums) {\n    int count = 0;\n    // The number of iterations is proportional to the array length\n    for (int num : nums) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Linear complexity (array traversal) */\nint ArrayTraversal(int[] nums) {\n    int count = 0;\n    // The number of iterations is proportional to the array length\n    foreach (int num in nums) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.go
    /* Linear complexity (array traversal) */\nfunc arrayTraversal(nums []int) int {\n    count := 0\n    // The number of iterations is proportional to the array length\n    for range nums {\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Linear complexity (array traversal) */\nfunc arrayTraversal(nums: [Int]) -> Int {\n    var count = 0\n    // The number of iterations is proportional to the array length\n    for _ in nums {\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Linear complexity (array traversal) */\nfunction arrayTraversal(nums) {\n    let count = 0;\n    // The number of iterations is proportional to the array length\n    for (let i = 0; i < nums.length; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Linear complexity (array traversal) */\nfunction arrayTraversal(nums: number[]): number {\n    let count = 0;\n    // The number of iterations is proportional to the array length\n    for (let i = 0; i < nums.length; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Linear complexity (array traversal) */\nint arrayTraversal(List<int> nums) {\n  int count = 0;\n  // The number of iterations is proportional to the array length\n  for (var _num in nums) {\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Linear complexity (array traversal) */\nfn array_traversal(nums: &[i32]) -> i32 {\n    let mut count = 0;\n    // The number of iterations is proportional to the array length\n    for _ in nums {\n        count += 1;\n    }\n    count\n}\n
    time_complexity.c
    /* Linear complexity (array traversal) */\nint arrayTraversal(int *nums, int n) {\n    int count = 0;\n    // The number of iterations is proportional to the array length\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Linear complexity (array traversal) */\nfun arrayTraversal(nums: IntArray): Int {\n    var count = 0\n    // The number of iterations is proportional to the array length\n    for (num in nums) {\n        count++\n    }\n    return count\n}\n
    time_complexity.rb
    ### Linear complexity (array traversal)###\ndef array_traversal(nums)\n  count = 0\n\n  # The number of iterations is proportional to the array length\n  for num in nums\n    count += 1\n  end\n\n  count\nend\n
    Visualize execution

    View full screen >

    The meaning of input size \\(n\\) depends on the input type. In the first example, it is the variable \\(n\\); in the second, it is the array length \\(n\\).

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#3-quadratic-time-on2","level":3,"title":"3.   Quadratic time \\(O(n^2)\\)","text":"

    A quadratic operation count grows with the square of input size \\(n\\). It commonly appears in nested loops where both levels take \\(O(n)\\) time, giving \\(O(n^2)\\) overall:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def quadratic(n: int) -> int:\n    \"\"\"Quadratic complexity\"\"\"\n    count = 0\n    # The number of iterations is proportional to the square of the input size n\n    for i in range(n):\n        for j in range(n):\n            count += 1\n    return count\n
    time_complexity.cpp
    /* Quadratic complexity */\nint quadratic(int n) {\n    int count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.java
    /* Quadratic complexity */\nint quadratic(int n) {\n    int count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Quadratic complexity */\nint Quadratic(int n) {\n    int count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.go
    /* Quadratic complexity */\nfunc quadratic(n int) int {\n    count := 0\n    // The number of iterations is proportional to the square of the input size n\n    for i := 0; i < n; i++ {\n        for j := 0; j < n; j++ {\n            count++\n        }\n    }\n    return count\n}\n
    time_complexity.swift
    /* Quadratic complexity */\nfunc quadratic(n: Int) -> Int {\n    var count = 0\n    // The number of iterations is proportional to the square of the input size n\n    for _ in 0 ..< n {\n        for _ in 0 ..< n {\n            count += 1\n        }\n    }\n    return count\n}\n
    time_complexity.js
    /* Quadratic complexity */\nfunction quadratic(n) {\n    let count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Quadratic complexity */\nfunction quadratic(n: number): number {\n    let count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Quadratic complexity */\nint quadratic(int n) {\n  int count = 0;\n  // The number of iterations is proportional to the square of the input size n\n  for (int i = 0; i < n; i++) {\n    for (int j = 0; j < n; j++) {\n      count++;\n    }\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Quadratic complexity */\nfn quadratic(n: i32) -> i32 {\n    let mut count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for _ in 0..n {\n        for _ in 0..n {\n            count += 1;\n        }\n    }\n    count\n}\n
    time_complexity.c
    /* Quadratic complexity */\nint quadratic(int n) {\n    int count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Quadratic complexity */\nfun quadratic(n: Int): Int {\n    var count = 0\n    // The number of iterations is proportional to the square of the input size n\n    for (i in 0..<n) {\n        for (j in 0..<n) {\n            count++\n        }\n    }\n    return count\n}\n
    time_complexity.rb
    ### Quadratic complexity ###\ndef quadratic(n)\n  count = 0\n\n  # The number of iterations is proportional to the square of the input size n\n  for i in 0...n\n    for j in 0...n\n      count += 1\n    end\n  end\n\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-10 compares constant, linear, and quadratic time complexity.

    Figure 2-10   Constant, linear, and quadratic time complexity

    In bubble sort, for example, the outer loop runs \\(n - 1\\) times, while the inner loop runs \\(n-1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), and \\(1\\) times, averaging \\(n / 2\\). The resulting complexity is \\(O((n - 1) n / 2) = O(n^2)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def bubble_sort(nums: list[int]) -> int:\n    \"\"\"Quadratic complexity (bubble sort)\"\"\"\n    count = 0  # Counter\n    # Outer loop: the unsorted range is [0, i]\n    for i in range(len(nums) - 1, 0, -1):\n        # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                tmp: int = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = tmp\n                count += 3  # Swapping two elements takes 3 elementary operations\n    return count\n
    time_complexity.cpp
    /* Quadratic complexity (bubble sort) */\nint bubbleSort(vector<int> &nums) {\n    int count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.java
    /* Quadratic complexity (bubble sort) */\nint bubbleSort(int[] nums) {\n    int count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Quadratic complexity (bubble sort) */\nint BubbleSort(int[] nums) {\n    int count = 0;  // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n                count += 3;  // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.go
    /* Quadratic complexity (bubble sort) */\nfunc bubbleSort(nums []int) int {\n    count := 0 // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                tmp := nums[j]\n                nums[j] = nums[j+1]\n                nums[j+1] = tmp\n                count += 3 // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count\n}\n
    time_complexity.swift
    /* Quadratic complexity (bubble sort) */\nfunc bubbleSort(nums: inout [Int]) -> Int {\n    var count = 0 // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = tmp\n                count += 3 // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count\n}\n
    time_complexity.js
    /* Quadratic complexity (bubble sort) */\nfunction bubbleSort(nums) {\n    let count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Quadratic complexity (bubble sort) */\nfunction bubbleSort(nums: number[]): number {\n    let count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Quadratic complexity (bubble sort) */\nint bubbleSort(List<int> nums) {\n  int count = 0; // Counter\n  // Outer loop: the unsorted range is [0, i]\n  for (var i = nums.length - 1; i > 0; i--) {\n    // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for (var j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n        count += 3; // Swapping two elements takes 3 elementary operations\n      }\n    }\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Quadratic complexity (bubble sort) */\nfn bubble_sort(nums: &mut [i32]) -> i32 {\n    let mut count = 0; // Counter\n\n    // Outer loop: the unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    count\n}\n
    time_complexity.c
    /* Quadratic complexity (bubble sort) */\nint bubbleSort(int *nums, int n) {\n    int count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = n - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Quadratic complexity (bubble sort) */\nfun bubbleSort(nums: IntArray): Int {\n    var count = 0 // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n                count += 3 // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count\n}\n
    time_complexity.rb
    ### Quadratic complexity (bubble sort)###\ndef bubble_sort(nums)\n  count = 0  # Counter\n\n  # Outer loop: the unsorted range is [0, i]\n  for i in (nums.length - 1).downto(0)\n    # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        tmp = nums[j]\n        nums[j] = nums[j + 1]\n        nums[j + 1] = tmp\n        count += 3 # Swapping two elements takes 3 elementary operations\n      end\n    end\n  end\n\n  count\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#4-exponential-time-o2n","level":3,"title":"4.   Exponential time \\(O(2^n)\\)","text":"

    Cell division is a classic example of exponential growth. Starting with \\(1\\) cell, one round produces \\(2\\), two rounds produce \\(4\\), and after \\(n\\) rounds there are \\(2^n\\) cells.

    The figure and code below simulate cell division in \\(O(2^n)\\) time. The input \\(n\\) is the number of rounds, while the returned count is the total number of individual divisions.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def exponential(n: int) -> int:\n    \"\"\"Exponential complexity (iterative implementation)\"\"\"\n    count = 0\n    base = 1\n    # Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in range(n):\n        for _ in range(base):\n            count += 1\n        base *= 2\n    # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n
    time_complexity.cpp
    /* Exponential complexity (iterative implementation) */\nint exponential(int n) {\n    int count = 0, base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.java
    /* Exponential complexity (iterative implementation) */\nint exponential(int n) {\n    int count = 0, base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.cs
    /* Exponential complexity (iterative implementation) */\nint Exponential(int n) {\n    int count = 0, bas = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < bas; j++) {\n            count++;\n        }\n        bas *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.go
    /* Exponential complexity (iterative implementation)*/\nfunc exponential(n int) int {\n    count, base := 0, 1\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for i := 0; i < n; i++ {\n        for j := 0; j < base; j++ {\n            count++\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
    time_complexity.swift
    /* Exponential complexity (iterative implementation) */\nfunc exponential(n: Int) -> Int {\n    var count = 0\n    var base = 1\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in 0 ..< n {\n        for _ in 0 ..< base {\n            count += 1\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
    time_complexity.js
    /* Exponential complexity (iterative implementation) */\nfunction exponential(n) {\n    let count = 0,\n        base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.ts
    /* Exponential complexity (iterative implementation) */\nfunction exponential(n: number): number {\n    let count = 0,\n        base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.dart
    /* Exponential complexity (iterative implementation) */\nint exponential(int n) {\n  int count = 0, base = 1;\n  // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n  for (var i = 0; i < n; i++) {\n    for (var j = 0; j < base; j++) {\n      count++;\n    }\n    base *= 2;\n  }\n  // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n  return count;\n}\n
    time_complexity.rs
    /* Exponential complexity (iterative implementation) */\nfn exponential(n: i32) -> i32 {\n    let mut count = 0;\n    let mut base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in 0..n {\n        for _ in 0..base {\n            count += 1\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    count\n}\n
    time_complexity.c
    /* Exponential complexity (iterative implementation) */\nint exponential(int n) {\n    int count = 0;\n    int bas = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < bas; j++) {\n            count++;\n        }\n        bas *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.kt
    /* Exponential complexity (iterative implementation) */\nfun exponential(n: Int): Int {\n    var count = 0\n    var base = 1\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (i in 0..<n) {\n        for (j in 0..<base) {\n            count++\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
    time_complexity.rb
    ### Exponential complexity (iterative implementation)###\ndef exponential(n)\n  count, base = 0, 1\n\n  # Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n  (0...n).each do\n    (0...base).each { count += 1 }\n    base *= 2\n  end\n\n  # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-11   Exponential time complexity

    Exponential time often appears in recursive algorithms. The following function branches into two calls at each level, stopping after \\(n\\) levels:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def exp_recur(n: int) -> int:\n    \"\"\"Exponential complexity (recursive implementation)\"\"\"\n    if n == 1:\n        return 1\n    return exp_recur(n - 1) + exp_recur(n - 1) + 1\n
    time_complexity.cpp
    /* Exponential complexity (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.java
    /* Exponential complexity (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.cs
    /* Exponential complexity (recursive implementation) */\nint ExpRecur(int n) {\n    if (n == 1) return 1;\n    return ExpRecur(n - 1) + ExpRecur(n - 1) + 1;\n}\n
    time_complexity.go
    /* Exponential complexity (recursive implementation)*/\nfunc expRecur(n int) int {\n    if n == 1 {\n        return 1\n    }\n    return expRecur(n-1) + expRecur(n-1) + 1\n}\n
    time_complexity.swift
    /* Exponential complexity (recursive implementation) */\nfunc expRecur(n: Int) -> Int {\n    if n == 1 {\n        return 1\n    }\n    return expRecur(n: n - 1) + expRecur(n: n - 1) + 1\n}\n
    time_complexity.js
    /* Exponential complexity (recursive implementation) */\nfunction expRecur(n) {\n    if (n === 1) return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.ts
    /* Exponential complexity (recursive implementation) */\nfunction expRecur(n: number): number {\n    if (n === 1) return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.dart
    /* Exponential complexity (recursive implementation) */\nint expRecur(int n) {\n  if (n == 1) return 1;\n  return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.rs
    /* Exponential complexity (recursive implementation) */\nfn exp_recur(n: i32) -> i32 {\n    if n == 1 {\n        return 1;\n    }\n    exp_recur(n - 1) + exp_recur(n - 1) + 1\n}\n
    time_complexity.c
    /* Exponential complexity (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.kt
    /* Exponential complexity (recursive implementation) */\nfun expRecur(n: Int): Int {\n    if (n == 1) {\n        return 1\n    }\n    return expRecur(n - 1) + expRecur(n - 1) + 1\n}\n
    time_complexity.rb
    ### Exponential complexity (recursive implementation)###\ndef exp_recur(n)\n  return 1 if n == 1\n  exp_recur(n - 1) + exp_recur(n - 1) + 1\nend\n
    Visualize execution

    View full screen >

    Exponential growth is very rapid and common in exhaustive approaches such as brute-force search and backtracking. It is unacceptable for large inputs, which usually require approaches such as dynamic programming or greedy algorithms.

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#5-logarithmic-time-olog-n","level":3,"title":"5.   Logarithmic time \\(O(\\log n)\\)","text":"

    Logarithmic growth corresponds to repeatedly halving a quantity, the reverse of exponential growth. Starting with input size \\(n\\), halving each round gives \\(\\log_2 n\\) iterations, the inverse of \\(2^n\\).

    The figure and code below simulate repeated halving, taking \\(O(\\log_2 n)\\) time, usually written \\(O(\\log n)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def logarithmic(n: int) -> int:\n    \"\"\"Logarithmic complexity (iterative implementation)\"\"\"\n    count = 0\n    while n > 1:\n        n = n / 2\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Logarithmic complexity (iterative implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.java
    /* Logarithmic complexity (iterative implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Logarithmic complexity (iterative implementation) */\nint Logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n /= 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.go
    /* Logarithmic complexity (iterative implementation)*/\nfunc logarithmic(n int) int {\n    count := 0\n    for n > 1 {\n        n = n / 2\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Logarithmic complexity (iterative implementation) */\nfunc logarithmic(n: Int) -> Int {\n    var count = 0\n    var n = n\n    while n > 1 {\n        n = n / 2\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Logarithmic complexity (iterative implementation) */\nfunction logarithmic(n) {\n    let count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Logarithmic complexity (iterative implementation) */\nfunction logarithmic(n: number): number {\n    let count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Logarithmic complexity (iterative implementation) */\nint logarithmic(int n) {\n  int count = 0;\n  while (n > 1) {\n    n = n ~/ 2;\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Logarithmic complexity (iterative implementation) */\nfn logarithmic(mut n: i32) -> i32 {\n    let mut count = 0;\n    while n > 1 {\n        n = n / 2;\n        count += 1;\n    }\n    count\n}\n
    time_complexity.c
    /* Logarithmic complexity (iterative implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Logarithmic complexity (iterative implementation) */\nfun logarithmic(n: Int): Int {\n    var n1 = n\n    var count = 0\n    while (n1 > 1) {\n        n1 /= 2\n        count++\n    }\n    return count\n}\n
    time_complexity.rb
    ### Logarithmic complexity (iterative implementation)###\ndef logarithmic(n)\n  count = 0\n\n  while n > 1\n    n /= 2\n    count += 1\n  end\n\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-12   Logarithmic time complexity

    Like exponential growth, logarithmic growth often occurs in recursion. The following code produces a recursion tree of height \\(\\log_2 n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def log_recur(n: int) -> int:\n    \"\"\"Logarithmic complexity (recursive implementation)\"\"\"\n    if n <= 1:\n        return 0\n    return log_recur(n / 2) + 1\n
    time_complexity.cpp
    /* Logarithmic complexity (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.java
    /* Logarithmic complexity (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.cs
    /* Logarithmic complexity (recursive implementation) */\nint LogRecur(int n) {\n    if (n <= 1) return 0;\n    return LogRecur(n / 2) + 1;\n}\n
    time_complexity.go
    /* Logarithmic complexity (recursive implementation)*/\nfunc logRecur(n int) int {\n    if n <= 1 {\n        return 0\n    }\n    return logRecur(n/2) + 1\n}\n
    time_complexity.swift
    /* Logarithmic complexity (recursive implementation) */\nfunc logRecur(n: Int) -> Int {\n    if n <= 1 {\n        return 0\n    }\n    return logRecur(n: n / 2) + 1\n}\n
    time_complexity.js
    /* Logarithmic complexity (recursive implementation) */\nfunction logRecur(n) {\n    if (n <= 1) return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.ts
    /* Logarithmic complexity (recursive implementation) */\nfunction logRecur(n: number): number {\n    if (n <= 1) return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.dart
    /* Logarithmic complexity (recursive implementation) */\nint logRecur(int n) {\n  if (n <= 1) return 0;\n  return logRecur(n ~/ 2) + 1;\n}\n
    time_complexity.rs
    /* Logarithmic complexity (recursive implementation) */\nfn log_recur(n: i32) -> i32 {\n    if n <= 1 {\n        return 0;\n    }\n    log_recur(n / 2) + 1\n}\n
    time_complexity.c
    /* Logarithmic complexity (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.kt
    /* Logarithmic complexity (recursive implementation) */\nfun logRecur(n: Int): Int {\n    if (n <= 1)\n        return 0\n    return logRecur(n / 2) + 1\n}\n
    time_complexity.rb
    ### Logarithmic complexity (recursive implementation)###\ndef log_recur(n)\n  return 0 unless n > 1\n  log_recur(n / 2) + 1\nend\n
    Visualize execution

    View full screen >

    Logarithmic time often arises in divide-and-conquer algorithms, reflecting the idea of dividing a problem into smaller, simpler parts. It grows slowly and is highly desirable, second only to constant time among these classes.

    What is the base of \\(O(\\log n)\\)?

    More precisely, reducing the size by a factor of \\(m\\) gives \\(O(\\log_m n)\\). The change-of-base formula shows that different fixed bases yield the same complexity:

    \\[ O(\\log_m n) = O(\\log_k n / \\log_k m) = O(\\log_k n) \\]

    Thus, changing the base \\(m\\) does not affect the complexity. We usually omit \\(m\\) and simply write \\(O(\\log n)\\).

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#6-linearithmic-time-on-log-n","level":3,"title":"6.   Linearithmic time \\(O(n \\log n)\\)","text":"

    Linearithmic time often appears when one level of a nested process takes \\(O(\\log n)\\) and another takes \\(O(n)\\). An example follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def linear_log_recur(n: int) -> int:\n    \"\"\"Linearithmic complexity\"\"\"\n    if n <= 1:\n        return 1\n    # Split into two subproblems, each half the original size\n    count = linear_log_recur(n // 2) + linear_log_recur(n // 2)\n    # The current subproblem includes n operations\n    for _ in range(n):\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Linearithmic complexity */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.java
    /* Linearithmic complexity */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Linearithmic complexity */\nint LinearLogRecur(int n) {\n    if (n <= 1) return 1;\n    int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.go
    /* Linearithmic complexity */\nfunc linearLogRecur(n int) int {\n    if n <= 1 {\n        return 1\n    }\n    count := linearLogRecur(n/2) + linearLogRecur(n/2)\n    for i := 0; i < n; i++ {\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Linearithmic complexity */\nfunc linearLogRecur(n: Int) -> Int {\n    if n <= 1 {\n        return 1\n    }\n    var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2)\n    for _ in stride(from: 0, to: n, by: 1) {\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Linearithmic complexity */\nfunction linearLogRecur(n) {\n    if (n <= 1) return 1;\n    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (let i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Linearithmic complexity */\nfunction linearLogRecur(n: number): number {\n    if (n <= 1) return 1;\n    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (let i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Linearithmic complexity */\nint linearLogRecur(int n) {\n  if (n <= 1) return 1;\n  int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2);\n  for (var i = 0; i < n; i++) {\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Linearithmic complexity */\nfn linear_log_recur(n: i32) -> i32 {\n    if n <= 1 {\n        return 1;\n    }\n    let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2);\n    for _ in 0..n {\n        count += 1;\n    }\n    return count;\n}\n
    time_complexity.c
    /* Linearithmic complexity */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Linearithmic complexity */\nfun linearLogRecur(n: Int): Int {\n    if (n <= 1)\n        return 1\n    var count = linearLogRecur(n / 2) + linearLogRecur(n / 2)\n    for (i in 0..<n) {\n        count++\n    }\n    return count\n}\n
    time_complexity.rb
    ### Linearithmic complexity ###\ndef linear_log_recur(n)\n  return 1 unless n > 1\n\n  count = linear_log_recur(n / 2) + linear_log_recur(n / 2)\n  (0...n).each { count += 1 }\n\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-13 shows how linearithmic growth arises. Each level of the binary tree performs \\(n\\) operations, and there are \\(\\log_2 n + 1\\) levels, giving \\(O(n \\log n)\\) time.

    Figure 2-13   Linearithmic time complexity

    Common sorting algorithms, including quick sort, merge sort, and heap sort, typically take \\(O(n \\log n)\\) time.

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#7-factorial-time-on","level":3,"title":"7.   Factorial time \\(O(n!)\\)","text":"

    Factorial growth arises when generating every permutation of \\(n\\) distinct elements. The number of permutations is:

    \\[ n! = n \\times (n - 1) \\times (n - 2) \\times \\dots \\times 2 \\times 1 \\]

    Algorithms with factorial time complexity are usually implemented recursively. As the figure and code below show, each call creates \\(n\\) branches at the first level, \\(n - 1\\) at the second, and so on until level \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def factorial_recur(n: int) -> int:\n    \"\"\"Factorial complexity (recursive implementation)\"\"\"\n    if n == 0:\n        return 1\n    count = 0\n    # Split one problem into n subproblems\n    for _ in range(n):\n        count += factorial_recur(n - 1)\n    return count\n
    time_complexity.cpp
    /* Factorial complexity (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    // Split one problem into n subproblems\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.java
    /* Factorial complexity (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    // Split one problem into n subproblems\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Factorial complexity (recursive implementation) */\nint FactorialRecur(int n) {\n    if (n == 0) return 1;\n    int count = 0;\n    // Split one problem into n subproblems\n    for (int i = 0; i < n; i++) {\n        count += FactorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.go
    /* Factorial complexity (recursive implementation) */\nfunc factorialRecur(n int) int {\n    if n == 0 {\n        return 1\n    }\n    count := 0\n    // Split one problem into n subproblems\n    for i := 0; i < n; i++ {\n        count += factorialRecur(n - 1)\n    }\n    return count\n}\n
    time_complexity.swift
    /* Factorial complexity (recursive implementation) */\nfunc factorialRecur(n: Int) -> Int {\n    if n == 0 {\n        return 1\n    }\n    var count = 0\n    // Split one problem into n subproblems\n    for _ in 0 ..< n {\n        count += factorialRecur(n: n - 1)\n    }\n    return count\n}\n
    time_complexity.js
    /* Factorial complexity (recursive implementation) */\nfunction factorialRecur(n) {\n    if (n === 0) return 1;\n    let count = 0;\n    // Split one problem into n subproblems\n    for (let i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Factorial complexity (recursive implementation) */\nfunction factorialRecur(n: number): number {\n    if (n === 0) return 1;\n    let count = 0;\n    // Split one problem into n subproblems\n    for (let i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Factorial complexity (recursive implementation) */\nint factorialRecur(int n) {\n  if (n == 0) return 1;\n  int count = 0;\n  // Split one problem into n subproblems\n  for (var i = 0; i < n; i++) {\n    count += factorialRecur(n - 1);\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Factorial complexity (recursive implementation) */\nfn factorial_recur(n: i32) -> i32 {\n    if n == 0 {\n        return 1;\n    }\n    let mut count = 0;\n    // Split one problem into n subproblems\n    for _ in 0..n {\n        count += factorial_recur(n - 1);\n    }\n    count\n}\n
    time_complexity.c
    /* Factorial complexity (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Factorial complexity (recursive implementation) */\nfun factorialRecur(n: Int): Int {\n    if (n == 0)\n        return 1\n    var count = 0\n    // Split one problem into n subproblems\n    for (i in 0..<n) {\n        count += factorialRecur(n - 1)\n    }\n    return count\n}\n
    time_complexity.rb
    ### Factorial complexity (recursive implementation)###\ndef factorial_recur(n)\n  return 1 if n == 0\n\n  count = 0\n  # Split one problem into n subproblems\n  (0...n).each { count += factorial_recur(n - 1) }\n\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-14   Factorial time complexity

    Since \\(n! > 2^n\\) whenever \\(n \\geq 4\\), factorial growth is faster than exponential growth and is also unacceptable for large \\(n\\).

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#235-worst-case-best-case-and-average-case-time-complexity","level":2,"title":"2.3.5   Worst-case, best-case, and average-case time complexity","text":"

    An algorithm's efficiency often depends on how its input data is arranged. Suppose nums is an array of length \\(n\\) containing every integer from \\(1\\) through \\(n\\) exactly once in a random order. The task is to return the index of \\(1\\). We can conclude the following.

    • If nums = [?, ?, ..., 1], with \\(1\\) last, we must traverse the whole array, giving worst-case time complexity \\(O(n)\\).
    • If nums = [1, ?, ?, ...], with \\(1\\) first, no further traversal is needed regardless of array length, giving best-case time complexity \\(\\Omega(1)\\).

    Worst-case time complexity corresponds to an asymptotic upper bound, expressed with big-\\(O\\) notation. Best-case time complexity corresponds to an asymptotic lower bound, expressed with \\(\\Omega\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby worst_best_time_complexity.py
    def random_numbers(n: int) -> list[int]:\n    \"\"\"Generate an array containing 1, 2, ..., n in shuffled order\"\"\"\n    # Generate the array nums = 1, 2, 3, ..., n\n    nums = [i for i in range(1, n + 1)]\n    # Randomly shuffle the array elements\n    random.shuffle(nums)\n    return nums\n\ndef find_one(nums: list[int]) -> int:\n    \"\"\"Find the index of the number 1 in array nums\"\"\"\n    for i in range(len(nums)):\n        # When 1 is at the start of the array, the best-case time complexity is O(1)\n        # When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if nums[i] == 1:\n            return i\n    return -1\n
    worst_best_time_complexity.cpp
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nvector<int> randomNumbers(int n) {\n    vector<int> nums(n);\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Use the system time to generate a random seed\n    unsigned seed = chrono::system_clock::now().time_since_epoch().count();\n    // Randomly shuffle the array elements\n    shuffle(nums.begin(), nums.end(), default_random_engine(seed));\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nint findOne(vector<int> &nums) {\n    for (int i = 0; i < nums.size(); i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
    worst_best_time_complexity.java
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nint[] randomNumbers(int n) {\n    Integer[] nums = new Integer[n];\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle the array elements\n    Collections.shuffle(Arrays.asList(nums));\n    // Integer[] -> int[]\n    int[] res = new int[n];\n    for (int i = 0; i < n; i++) {\n        res[i] = nums[i];\n    }\n    return res;\n}\n\n/* Find the index of the number 1 in array nums */\nint findOne(int[] nums) {\n    for (int i = 0; i < nums.length; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
    worst_best_time_complexity.cs
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nint[] RandomNumbers(int n) {\n    int[] nums = new int[n];\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n\n    // Randomly shuffle the array elements\n    for (int i = 0; i < nums.Length; i++) {\n        int index = new Random().Next(i, nums.Length);\n        (nums[i], nums[index]) = (nums[index], nums[i]);\n    }\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nint FindOne(int[] nums) {\n    for (int i = 0; i < nums.Length; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
    worst_best_time_complexity.go
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfunc randomNumbers(n int) []int {\n    nums := make([]int, n)\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for i := 0; i < n; i++ {\n        nums[i] = i + 1\n    }\n    // Randomly shuffle the array elements\n    rand.Shuffle(len(nums), func(i, j int) {\n        nums[i], nums[j] = nums[j], nums[i]\n    })\n    return nums\n}\n\n/* Find the index of the number 1 in array nums */\nfunc findOne(nums []int) int {\n    for i := 0; i < len(nums); i++ {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if nums[i] == 1 {\n            return i\n        }\n    }\n    return -1\n}\n
    worst_best_time_complexity.swift
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfunc randomNumbers(n: Int) -> [Int] {\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    var nums = Array(1 ... n)\n    // Randomly shuffle the array elements\n    nums.shuffle()\n    return nums\n}\n\n/* Find the index of the number 1 in array nums */\nfunc findOne(nums: [Int]) -> Int {\n    for i in nums.indices {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if nums[i] == 1 {\n            return i\n        }\n    }\n    return -1\n}\n
    worst_best_time_complexity.js
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfunction randomNumbers(n) {\n    const nums = Array(n);\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (let i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle the array elements\n    for (let i = 0; i < n; i++) {\n        const r = Math.floor(Math.random() * (i + 1));\n        const temp = nums[i];\n        nums[i] = nums[r];\n        nums[r] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nfunction findOne(nums) {\n    for (let i = 0; i < nums.length; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] === 1) {\n            return i;\n        }\n    }\n    return -1;\n}\n
    worst_best_time_complexity.ts
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfunction randomNumbers(n: number): number[] {\n    const nums = Array(n);\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (let i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle the array elements\n    for (let i = 0; i < n; i++) {\n        const r = Math.floor(Math.random() * (i + 1));\n        const temp = nums[i];\n        nums[i] = nums[r];\n        nums[r] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nfunction findOne(nums: number[]): number {\n    for (let i = 0; i < nums.length; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] === 1) {\n            return i;\n        }\n    }\n    return -1;\n}\n
    worst_best_time_complexity.dart
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nList<int> randomNumbers(int n) {\n  final nums = List.filled(n, 0);\n  // Generate the array nums = { 1, 2, 3, ..., n }\n  for (var i = 0; i < n; i++) {\n    nums[i] = i + 1;\n  }\n  // Randomly shuffle the array elements\n  nums.shuffle();\n\n  return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nint findOne(List<int> nums) {\n  for (var i = 0; i < nums.length; i++) {\n    // When 1 is at the start of the array, the best-case time complexity is O(1)\n    // When 1 is at the end of the array, the worst-case time complexity is O(n)\n    if (nums[i] == 1) return i;\n  }\n\n  return -1;\n}\n
    worst_best_time_complexity.rs
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfn random_numbers(n: i32) -> Vec<i32> {\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    let mut nums = (1..=n).collect::<Vec<i32>>();\n    // Randomly shuffle the array elements\n    nums.shuffle(&mut thread_rng());\n    nums\n}\n\n/* Find the index of the number 1 in array nums */\nfn find_one(nums: &[i32]) -> Option<usize> {\n    for i in 0..nums.len() {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if nums[i] == 1 {\n            return Some(i);\n        }\n    }\n    None\n}\n
    worst_best_time_complexity.c
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nint *randomNumbers(int n) {\n    // Allocate heap memory for a one-dimensional array of n int elements\n    int *nums = (int *)malloc(n * sizeof(int));\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle the array elements\n    for (int i = n - 1; i > 0; i--) {\n        int j = rand() % (i + 1);\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nint findOne(int *nums, int n) {\n    for (int i = 0; i < n; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
    worst_best_time_complexity.kt
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfun randomNumbers(n: Int): Array<Int?> {\n    val nums = IntArray(n)\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (i in 0..<n) {\n        nums[i] = i + 1\n    }\n    // Randomly shuffle the array elements\n    nums.shuffle()\n    val res = arrayOfNulls<Int>(n)\n    for (i in 0..<n) {\n        res[i] = nums[i]\n    }\n    return res\n}\n\n/* Find the index of the number 1 in array nums */\nfun findOne(nums: Array<Int?>): Int {\n    for (i in nums.indices) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i\n    }\n    return -1\n}\n
    worst_best_time_complexity.rb
    ### Generate an array containing 1, 2, ..., n in shuffled order ###\ndef random_numbers(n)\n  # Generate the array nums = 1, 2, 3, ..., n\n  nums = Array.new(n) { |i| i + 1 }\n  # Randomly shuffle the array elements\n  nums.shuffle!\nend\n\n### Find the index of the number 1 in array nums ###\ndef find_one(nums)\n  for i in 0...nums.length\n    # When 1 is at the start of the array, the best-case time complexity is O(1)\n    # When 1 is at the end of the array, the worst-case time complexity is O(n)\n    return i if nums[i] == 1\n  end\n\n  -1\nend\n
    Visualize execution

    View full screen >

    Best-case complexity is rarely used in practice because it may occur only with low probability and can be misleading. Worst-case complexity is more useful because it gives a safe performance bound, allowing us to use the algorithm with confidence.

    In this example, both the worst and best cases occur only for particular arrangements, perhaps with low probability, and may not reflect ordinary performance. Average-case complexity describes performance on random inputs and is expressed with \\(\\Theta\\).

    For some algorithms, the average case is easy to derive. In the example above, \\(1\\) is equally likely to occur at any index in the shuffled array. The average number of iterations is half the array length, \\(n / 2\\), giving \\(\\Theta(n / 2) = \\Theta(n)\\).

    For more complex algorithms, average-case complexity can be difficult to calculate because the overall expectation under the input distribution is hard to derive. In such cases, we usually judge efficiency by worst-case complexity.

    Why is \\(\\Theta\\) rarely seen?

    Perhaps because \\(O\\) is so familiar, it is often used for average-case complexity too. Strictly speaking, this usage is not standard. In this book and other resources, read a statement such as \"average-case time complexity \\(O(n)\\)\" as \\(\\Theta(n)\\).

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_data_structure/","level":1,"title":"Chapter 3.   Data structures","text":"

    Abstract

    Data structures provide a framework that is both sturdy and versatile.

    They offer a blueprint for organizing data, a foundation on which algorithms come to life.

    ","path":["Chapter 3.   Data structures"],"tags":[]},{"location":"chapter_data_structure/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 3.1   Classifying data structures
    • 3.2   Basic data types
    • 3.3   Number encoding *
    • 3.4   Character encoding *
    • 3.5   Summary
    • 3.6   Exercises
    ","path":["Chapter 3.   Data structures"],"tags":[]},{"location":"chapter_data_structure/basic_data_types/","level":1,"title":"3.2   Basic data types","text":"

    Computer data comes in many forms: text, images, video, audio, 3D models, and more. Although organized differently, all these forms are built from basic data types.

    Basic data types are types on which the CPU can operate directly and which algorithms use directly. The main types include the following.

    • Integer types: byte, short, int, and long.
    • Floating-point types: float and double, used to represent fractional numbers.
    • The character type char, used for letters in different languages, punctuation, and even emoji.
    • The Boolean type bool, used for yes-or-no values.

    Basic data types are stored in binary form. One binary digit is \\(1\\) bit. In the vast majority of modern operating systems, \\(1\\) byte consists of \\(8\\) bits.

    A basic data type's value range depends on the space it occupies. Taking Java as an example:

    • An integer byte occupies \\(1\\) byte = \\(8\\) bits and can represent \\(2^{8}\\) distinct values.
    • An integer int occupies \\(4\\) bytes = \\(32\\) bits and can represent \\(2^{32}\\) distinct values.

    Table 3-1 lists storage sizes, value ranges, and default values for Java's basic data types. There is no need to memorize it; understand the general picture and refer back when needed.

    Table 3-1   Storage sizes and value ranges of basic data types

    Type Name Storage size Minimum value Maximum value Default value Integer byte 1 byte \\(-2^7\\) (\\(-128\\)) \\(2^7 - 1\\) (\\(127\\)) \\(0\\) short 2 bytes \\(-2^{15}\\) \\(2^{15} - 1\\) \\(0\\) int 4 bytes \\(-2^{31}\\) \\(2^{31} - 1\\) \\(0\\) long 8 bytes \\(-2^{63}\\) \\(2^{63} - 1\\) \\(0\\) Floating-point float 4 bytes \\(1.175 \\times 10^{-38}\\) \\(3.403 \\times 10^{38}\\) \\(0.0\\text{f}\\) double 8 bytes \\(2.225 \\times 10^{-308}\\) \\(1.798 \\times 10^{308}\\) \\(0.0\\) Character char 2 bytes \\(0\\) \\(2^{16} - 1\\) \\(0\\) Boolean bool 1 byte \\(\\text{false}\\) \\(\\text{true}\\) \\(\\text{false}\\)

    Table 3-1 describes Java's basic data types. Each programming language defines its own types, whose sizes, value ranges, and default values may differ.

    • In Python, an int can be arbitrarily large, limited only by available memory. A float is a 64-bit double-precision number. There is no char type: a single character is a str of length 1.
    • C and C++ do not fix the sizes of basic data types; they vary by implementation and platform. Table 3-1 follows the LP64 data model, used by 64-bit Unix operating systems including Linux and macOS.
    • A char occupies 1 byte in C and C++. In most programming languages, character size depends on the encoding used; see the character encoding section.
    • Although a Boolean needs only 1 bit (\\(0\\) or \\(1\\)), it usually occupies 1 byte in memory because modern CPUs typically use a byte as the smallest addressable memory unit.

    How do basic data types relate to data structures? A data structure is a way to organize and store data in a computer. The emphasis is on structure, rather than the data itself.

    To represent a row of numbers, an array comes naturally to mind. Its linear structure captures adjacency and ordering, regardless of whether the stored values are integers (int), fractional numbers (float), or characters (char).

    In other words, basic data types specify what kind of data is stored, while data structures specify how it is organized. The following code uses the same data structure, an array, for different basic types, including int, float, char, and bool.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    # Initialize arrays with different basic data types\nnumbers: list[int] = [0] * 5\ndecimals: list[float] = [0.0] * 5\n# A Python character is a string of length 1\ncharacters: list[str] = ['0'] * 5\nbools: list[bool] = [False] * 5\n# Python lists can hold different basic data types and object references\ndata = [0, 0.0, 'a', False, ListNode(0)]\n
    // Initialize arrays with different basic data types\nint numbers[5];\nfloat decimals[5];\nchar characters[5];\nbool bools[5];\n
    // Initialize arrays with different basic data types\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nboolean[] bools = new boolean[5];\n
    // Initialize arrays with different basic data types\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nbool[] bools = new bool[5];\n
    // Initialize arrays with different basic data types\nvar numbers = [5]int{}\nvar decimals = [5]float64{}\nvar characters = [5]byte{}\nvar bools = [5]bool{}\n
    // Initialize arrays with different basic data types\nlet numbers = Array(repeating: 0, count: 5)\nlet decimals = Array(repeating: 0.0, count: 5)\nlet characters: [Character] = Array(repeating: \"a\", count: 5)\nlet bools = Array(repeating: false, count: 5)\n
    // JavaScript arrays can hold different basic data types and objects\nconst array = [0, 0.0, 'a', false];\n
    // Initialize arrays with different basic data types\nconst numbers: number[] = [];\nconst characters: string[] = [];\nconst bools: boolean[] = [];\n
    // Initialize arrays with different basic data types\nList<int> numbers = List.filled(5, 0);\nList<double> decimals = List.filled(5, 0.0);\nList<String> characters = List.filled(5, 'a');\nList<bool> bools = List.filled(5, false);\n
    // Initialize arrays with different basic data types\nlet numbers: Vec<i32> = vec![0; 5];\nlet decimals: Vec<f32> = vec![0.0; 5];\nlet characters: Vec<char> = vec!['0'; 5];\nlet bools: Vec<bool> = vec![false; 5];\n
    // Initialize arrays with different basic data types\nint numbers[10];\nfloat decimals[10];\nchar characters[10];\nbool bools[10];\n
    // Initialize arrays with different basic data types\nval numbers = IntArray(5)\nval decinals = FloatArray(5)\nval characters = CharArray(5)\nval bools = BooleanArray(5)\n
    # Ruby arrays can hold different basic data types and object references\ndata = [0, 0.0, 'a', false, ListNode(0)]\n
    Visualize execution

    View full screen >

    ","path":["Chapter 3.   Data structures","3.2   Basic data types"],"tags":[]},{"location":"chapter_data_structure/character_encoding/","level":1,"title":"3.4   Character encoding *","text":"

    All computer data is stored as binary numbers, including characters of type char. To represent characters, we need a character set that assigns each character a unique binary number. The computer can then convert numbers to characters by looking them up.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#341-ascii-character-set","level":2,"title":"3.4.1   ASCII character set","text":"

    ASCII, short for American Standard Code for Information Interchange, was the earliest character set. It uses 7 bits (the lower 7 bits of a byte) per character, allowing up to 128 characters. As shown in Figure 3-6, ASCII includes uppercase and lowercase English letters, digits 0 ~ 9, punctuation, and control characters such as newlines and tabs.

    Figure 3-6   ASCII codes

    However, ASCII can represent only English text. As computers spread worldwide, EASCII character sets emerged to support more languages. Extending ASCII from 7 bits to 8 allowed 256 distinct characters.

    Different regions developed their own EASCII character sets. The first 128 characters remained identical to ASCII, while the remaining 128 varied to meet different languages' needs.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#342-gbk-character-set","level":2,"title":"3.4.2   GBK character set","text":"

    Eventually, even EASCII proved too small for many languages. Chinese, for example, has nearly 100,000 characters, with thousands used daily. In 1980, China's National Bureau of Standards released GB2312, containing 6,763 Chinese characters and meeting most basic needs for processing Chinese text.

    GB2312 could not handle some rare and traditional characters. GBK extended it to 21,886 Chinese characters. In GBK encoding, ASCII characters use one byte, and Chinese characters use two.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#343-unicode-character-set","level":2,"title":"3.4.3   Unicode character set","text":"

    As computing grew, character sets and encoding standards proliferated, creating problems. Most covered only particular languages and did not work well in multilingual settings. Multiple standards also existed for the same language, so exchanging text between computers using different encodings could produce garbled text.

    Researchers asked: could a sufficiently comprehensive character set, containing all the world's languages and symbols, solve both multilingual compatibility and garbled-text problems? This idea led to Unicode.

    Unicode, whose Chinese name means \"unified code\", can theoretically accommodate over a million characters. It aims to bring the world's characters into one universal set for processing and displaying different languages, reducing garbled text caused by incompatible standards. Since its release in 1991, it has continued adding languages and characters. As of September 2022, Unicode contained 149,186 characters, including letters, symbols, and emoji.

    As a universal character set, Unicode assigns each character a unique code point, or character number, from U+0000 to U+10FFFF. It does not, by itself, specify how these code points are stored in a computer. How can a system parse text containing code points of different lengths? For example, how does it distinguish one two-byte character from two one-byte characters?

    One straightforward solution is to store every character in a fixed-width encoding. In Figure 3-7, each character in \"Hello\" occupies 1 byte, while each in \"算法\" occupies 2 bytes. Padding the high bits with zeros lets us encode every character in \"Hello 算法\" with 2 bytes. The system can then read one character every 2 bytes to recover the phrase.

    Figure 3-7   Example of Unicode encoding

    ASCII has already shown that English text needs only 1 byte per character. This scheme would double its storage compared with ASCII, wasting memory. We therefore need a more efficient Unicode encoding.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#344-utf-8-encoding","level":2,"title":"3.4.4   UTF-8 encoding","text":"

    UTF-8 is now the most widely used Unicode encoding worldwide. It is variable-length, using 1 to 4 bytes per character depending on the character. ASCII characters need 1 byte, Latin and Greek letters need 2, common Chinese characters need 3, and some less common characters need 4.

    UTF-8's rules have two cases.

    • For a one-byte character, set the highest bit to \\(0\\) and the remaining 7 bits to its Unicode code point. ASCII occupies the first 128 Unicode code points, so UTF-8 is backward-compatible with ASCII. UTF-8 can therefore decode old ASCII text.
    • For an \\(n\\)-byte character, where \\(n > 1\\), set the first byte's leading \\(n\\) bits to \\(1\\) and the next bit, \\(n + 1\\), to \\(0\\). Starting with the second byte, set the leading 2 bits of every byte to \\(10\\). Fill the remaining bits with the character's Unicode code point.

    Figure 3-8 shows UTF-8 for \"Hello算法\". Since the leading \\(n\\) bits are \\(1\\), the system can determine the character length \\(n\\) by counting the leading \\(1\\) bits.

    Why do all remaining bytes begin with \\(10\\)? This \\(10\\) acts as a check marker. If decoding starts at the wrong byte, the leading \\(10\\) helps the system quickly detect the problem.

    The prefix \\(10\\) works as a marker because no character's first byte can begin with \\(10\\) under UTF-8's rules. To see this by contradiction, suppose it did begin with \\(10\\). The single leading one would imply a length of \\(1\\) byte, corresponding to ASCII, but an ASCII byte must begin with \\(0\\), a contradiction.

    Figure 3-8   Example of UTF-8 encoding

    Two other common encodings are:

    • UTF-16: Uses 2 or 4 bytes per character. ASCII and common non-English characters use 2 bytes; some characters require 4. For a two-byte character, its UTF-16 encoding equals its Unicode code point.
    • UTF-32: Uses 4 bytes for every character. It therefore takes more space than UTF-8 or UTF-16, especially for text containing mostly ASCII characters.

    In terms of storage, UTF-8 is efficient for English characters because each uses only 1 byte. UTF-16 is more efficient for some non-English characters, such as Chinese, using 2 bytes where UTF-8 may require 3.

    In terms of compatibility, UTF-8 is the most widely supported, and many tools and libraries prioritize it.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#345-character-encoding-in-programming-languages","level":2,"title":"3.4.5   Character encoding in programming languages","text":"

    Historically, most programming languages have stored strings at runtime using fixed-width encodings such as UTF-16 or UTF-32. Fixed-width encoding lets us treat a string as an array, with the following advantages.

    • Random access: UTF-16 strings support straightforward random access. With variable-length UTF-8, finding the \\(i\\)-th character requires traversing from the beginning to the \\(i\\)-th character, taking \\(O(n)\\) time.
    • Character counting: Similarly, finding the length of a UTF-16 string is an \\(O(1)\\) operation, while finding the character count of a UTF-8 string requires traversing the whole string.
    • String operations: Operations such as splitting, concatenation, insertion, and deletion are easier on UTF-16 strings. With UTF-8, they usually require extra work to avoid producing invalid encodings.

    The design of string representations is an interesting subject involving many factors.

    • Java's String uses UTF-16, with 2 bytes per character. When Java was designed, 16 bits were thought sufficient for every character. This turned out to be incorrect: Unicode later expanded beyond 16 bits, so a Java character may now require a pair of 16-bit values called a surrogate pair.
    • JavaScript and TypeScript use UTF-16 for similar historical reasons. When Netscape introduced JavaScript in 1995, Unicode was still young, and 16 bits could represent all its characters at the time.
    • C# uses UTF-16 largely because Microsoft designed .NET, and many Microsoft technologies, including Windows, use UTF-16 extensively.

    Having underestimated the number of characters, these languages had to adopt surrogate pairs for Unicode characters beyond 16 bits. This was a compromise. A character in such a string may occupy 2 or 4 bytes, losing the benefit of fixed width, and handling surrogate pairs requires additional code, making programming and debugging more complex.

    For these reasons, some languages use different representations.

    • Python's str uses Unicode with a flexible representation whose character width depends on the largest code point in the string. If all characters are ASCII, each uses 1 byte. If some exceed ASCII but all lie within the Basic Multilingual Plane (BMP), each uses 2 bytes. If any lie outside the BMP, each uses 4 bytes.
    • Go's string uses UTF-8 internally. Go also provides rune to represent a single Unicode code point.
    • Rust's str and String use UTF-8 internally, while char represents a single Unicode code point.

    These descriptions concern string storage within programming languages. How strings are stored in files or transmitted over a network is a separate question. For file storage and network transmission, strings are usually encoded as UTF-8 for compatibility and space efficiency.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/","level":1,"title":"3.1   Classifying data structures","text":"

    Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified by their logical structure and physical structure.

    ","path":["Chapter 3.   Data structures","3.1   Classifying data structures"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#311-logical-structure-linear-and-nonlinear","level":2,"title":"3.1.1   Logical structure: linear and nonlinear","text":"

    Logical structure describes the relationships between data elements. Arrays and linked lists arrange data in sequence, expressing linear relationships. Trees arrange data hierarchically from top to bottom, expressing ancestry and descent. Graphs consist of vertices and edges that express complex network relationships.

    As shown in Figure 3-1, logical structures fall into two broad categories: linear and nonlinear. A linear structure arranges data in a logical sequence; a nonlinear structure does not.

    • Linear data structures: Arrays, linked lists, stacks, queues, and hash tables. Elements have one-to-one sequential relationships.
    • Nonlinear data structures: Trees, heaps, graphs, and hash tables.

    Nonlinear data structures can be divided further into tree and network structures.

    • Tree structures: Trees, heaps, and hash tables, with one-to-many relationships.
    • Network structures: Graphs, with many-to-many relationships.

    Figure 3-1   Linear and nonlinear data structures

    ","path":["Chapter 3.   Data structures","3.1   Classifying data structures"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#312-physical-structure-contiguous-and-scattered-storage","level":2,"title":"3.1.2   Physical structure: contiguous and scattered storage","text":"

    While an algorithm runs, the data it processes is mainly stored in memory. Figure 3-2 shows a memory module, with each black chip containing storage space. We can imagine memory as a huge Excel spreadsheet, where each cell holds a certain amount of data.

    The system uses memory addresses to access data at particular locations. As shown in Figure 3-2, the computer assigns each cell a number according to specific rules, giving every memory location a unique address. Programs use these addresses to access data.

    Figure 3-2   Memory module, storage space, and memory addresses

    Tip

    The Excel spreadsheet analogy is a simplification. Real memory is more complex and involves address spaces, memory management, caching, virtual memory, and physical memory.

    Memory is a resource used by all programs. When a program occupies a region of memory, other programs usually cannot use that region at the same time. Memory resources are therefore an important consideration when designing data structures and algorithms. For example, an algorithm's peak memory usage should not exceed the system's available memory. If no large contiguous region is available, the chosen data structure must support scattered storage.

    As shown in Figure 3-3, physical structure describes how data is stored in computer memory: either contiguously (arrays) or in scattered locations (linked lists). It determines how data can be accessed, updated, inserted, and deleted. The two storage forms have complementary strengths in time and space efficiency.

    Figure 3-3   Contiguous and scattered storage

    All data structures are implemented using arrays, linked lists, or a combination of the two. For example, stacks and queues can use either arrays or linked lists, while hash tables may use both.

    • Array-based implementations: Stacks, queues, hash tables, trees, heaps, graphs, matrices, tensors (arrays with dimension \\(\\geq 3\\)), and more.
    • Linked-list-based implementations: Stacks, queues, hash tables, trees, heaps, graphs, and more.

    A linked list can change length during execution after initialization, so it is called a dynamic data structure. An array's length is fixed after initialization, so it is called a static data structure. However, reallocating memory can change an array's length, providing a degree of dynamic behavior.

    Tip

    If physical structure feels difficult to understand, read the next chapter first and then return to this section.

    ","path":["Chapter 3.   Data structures","3.1   Classifying data structures"],"tags":[]},{"location":"chapter_data_structure/exercises/","level":1,"title":"3.6   Exercises","text":"","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#361-check-your-understanding","level":2,"title":"3.6.1   Check your understanding","text":"","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#1-relationships-between-data-in-everyday-situations","level":3,"title":"1.   Relationships between data in everyday situations","text":"

    For each situation below, choose a linear, tree, or network structure based on the relationships between the data, and explain your choice:

    1. Students stand in a line, each concerned only with the people immediately in front of and behind them.
    2. A school is organized in a hierarchy: school → grade → class.
    3. City roads connect intersections. An intersection can lead to several others, and the roads may form cycles.
    Reference answer
    1. A linear structure. Apart from the first and last people, each person has exactly one neighbor in front and one behind, forming a line of relationships.

    2. A tree structure. Each class belongs to a grade, and each grade belongs to the school, forming a hierarchy from top to bottom.

    3. A network structure. An intersection can connect to several others, and routes can form cycles. These relationships cannot be arranged in a single sequence or a strict hierarchy.

      To identify a structure, first examine the relationships between its elements, rather than how much memory it occupies.

    ","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#2-storing-a-logical-sequence-in-memory","level":3,"title":"2.   Storing a logical sequence in memory","text":"

    Consider two simplified memory layouts for storing the logical sequence A → B → C:

    • Layout A: A, B, C are stored in memory cells numbered 20, 21, 22, respectively.
    • Layout B: A, B, C are stored in cells numbered 20, 7, 31, respectively. A records the location of B, and B records the location of C.
    1. Which layout uses contiguous storage, and which uses noncontiguous storage?
    2. Which layout is more like an array, and which is more like a linked list?
    3. The cell numbers in layout B are not in ascending order. Why can it still represent the logical sequence A → B → C?
    Reference answer
    1. Layout A uses consecutive memory cells, so its storage is contiguous. In layout B, nodes occupy separate locations, so its storage is noncontiguous.

    2. Layout A is more like an array; layout B is more like a linked list.

    3. The logical order is determined by the links recorded between nodes, not by the numerical order of their memory cell numbers. The location stored in A leads to B, and the location stored in B leads to C, so we can still visit A, B, C in sequence.

      This also shows that logical and physical structure are two ways of looking at the same data.

    ","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#3-data-types-and-structures-in-homework-records","level":3,"title":"3.   Data types and structures in homework records","text":"

    A study group records whether four students have submitted their homework, in seating order:

    [true, false, true, true]

    1. Which basic data type is suitable for each element?
    2. These four elements form a sequence in seating order. What logical structure does this use?
    3. If the group later records homework scores instead, as [90, 0, 85, 100], has the type of content changed, or the way it is organized? Explain.
    Reference answer
    1. Each element represents only yes or no, so the Boolean type bool is appropriate.

    2. The elements form a linear structure in seating order and can be stored in an array.

    3. The content type has changed: the elements are now integers instead of Boolean values. Their organization has not changed. The data still forms a sequence in seating order and can still use the linear structure of an array.

      A basic data type describes what is stored; a data structure describes how the data is organized.

    ","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#362-programming-exercises","level":2,"title":"3.6.2   Programming exercises","text":"","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#1-counting-ones-in-a-binary-representation","level":3,"title":"1.   Counting ones in a binary representation","text":"

    Given a nonnegative integer n, count the ones in its binary representation.

    Use bitwise operations. Do not convert the binary representation to a string or use a built-in function that counts the ones directly.

    Hints
    1. n & 1 extracts the rightmost bit of n, allowing you to check whether it is 1.
    2. Shifting right by one bit discards the current rightmost bit; most languages use the >> operator.
    3. After implementing the method that checks each bit and shifts right, observe that n & (n - 1) changes the rightmost 1 in n to 0.

    LeetCode

    ","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/number_encoding/","level":1,"title":"3.3   Number encoding *","text":"

    Tip

    Sections marked with * in their titles are optional. If you are short on time or find them difficult, you can skip them initially and return after completing the required sections.

    ","path":["Chapter 3.   Data structures","3.3   Number encoding *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#331-sign-magnitude-ones-complement-and-twos-complement","level":2,"title":"3.3.1   Sign-magnitude, one's complement, and two's complement","text":"

    The previous table showed that every integer type represents one more negative value than positive value. For example, byte ranges over \\([-128, 127]\\). Understanding this counterintuitive fact requires sign-magnitude, one's complement, and two's complement.

    First, integers are stored in two's complement form. Before explaining why, let's define the three representations.

    • Sign-magnitude: The highest bit is the sign: \\(0\\) for positive and \\(1\\) for negative. The remaining bits represent the magnitude.
    • One's complement: A positive number has the same representation as sign-magnitude. For a negative number, invert every bit except the sign bit of its sign-magnitude representation.
    • Two's complement: A positive number again has the same representation as sign-magnitude. For a negative number, add \\(1\\) to its one's complement.

    Figure 3-4 shows how to convert among these representations.

    Figure 3-4   Converting between sign-magnitude, one's complement, and two's complement

    Although sign-magnitude is intuitive, it has limitations. First, negative sign-magnitude values cannot be used directly in ordinary binary arithmetic. For example, directly adding \\(1 + (-2)\\) in this representation gives \\(-3\\), which is clearly wrong.

    \\[ \\begin{aligned} & 1 + (-2) \\newline & \\rightarrow 0000 \\; 0001 + 1000 \\; 0010 \\newline & = 1000 \\; 0011 \\newline & \\rightarrow -3 \\end{aligned} \\]

    To address this, computers introduced one's complement. Convert the operands to one's complement, calculate \\(1 + (-2)\\), and convert the result back to sign-magnitude to obtain the correct answer, \\(-1\\).

    \\[ \\begin{aligned} & 1 + (-2) \\newline & \\rightarrow 0000 \\; 0001 \\; \\text{(sign-magnitude)} + 1000 \\; 0010 \\; \\text{(sign-magnitude)} \\newline & = 0000 \\; 0001 \\; \\text{(one's complement)} + 1111 \\; 1101 \\; \\text{(one's complement)} \\newline & = 1111 \\; 1110 \\; \\text{(one's complement)} \\newline & = 1000 \\; 0001 \\; \\text{(sign-magnitude)} \\newline & \\rightarrow -1 \\end{aligned} \\]

    Second, sign-magnitude has two representations of zero, \\(+0\\) and \\(-0\\). Two binary encodings for zero can create ambiguity. Conditional checks may be wrong if they do not handle both forms, while special handling requires extra checks that can reduce efficiency.

    \\[ \\begin{aligned} +0 & \\rightarrow 0000 \\; 0000 \\newline -0 & \\rightarrow 1000 \\; 0000 \\end{aligned} \\]

    One's complement also has positive and negative zero, so computers introduced two's complement. Consider converting negative zero through all three representations:

    \\[ \\begin{aligned} -0 \\rightarrow \\; & 1000 \\; 0000 \\; \\text{(sign-magnitude)} \\newline = \\; & 1111 \\; 1111 \\; \\text{(one's complement)} \\newline = 1 \\; & 0000 \\; 0000 \\; \\text{(two's complement)} \\newline \\end{aligned} \\]

    Adding \\(1\\) to the one's complement of negative zero produces a carry. A byte has only 8 bits, so the \\(1\\) carried into the ninth bit is discarded. Negative zero's two's complement is therefore \\(0000 \\; 0000\\), the same as positive zero. Two's complement has only one zero, eliminating the ambiguity.

    One question remains: if byte ranges over \\([-128, 127]\\), where does the extra negative value \\(-128\\) come from? Every integer in \\([-127, +127]\\) has sign-magnitude, one's complement, and two's complement representations, and its sign-magnitude and two's complement forms can be converted into each other.

    However, the two's complement value \\(1000 \\; 0000\\) is an exception with no corresponding sign-magnitude form. Applying the conversion gives \\(0000 \\; 0000\\), but that represents \\(0\\), whose two's complement should be itself. The special pattern \\(1000 \\; 0000\\) is defined to represent \\(-128\\). Indeed, two's complement addition of \\((-1) + (-127)\\) gives \\(-128\\).

    \\[ \\begin{aligned} & (-127) + (-1) \\newline & \\rightarrow 1111 \\; 1111 \\; \\text{(sign-magnitude)} + 1000 \\; 0001 \\; \\text{(sign-magnitude)} \\newline & = 1000 \\; 0000 \\; \\text{(one's complement)} + 1111 \\; 1110 \\; \\text{(one's complement)} \\newline & = 1000 \\; 0001 \\; \\text{(two's complement)} + 1111 \\; 1111 \\; \\text{(two's complement)} \\newline & = 1000 \\; 0000 \\; \\text{(two's complement)} \\newline & \\rightarrow -128 \\end{aligned} \\]

    All the calculations above use addition. This points to an important fact: computer arithmetic circuits are largely built around addition. Compared with operations such as multiplication, division, and subtraction, addition is simpler to implement in hardware, easier to parallelize, and faster.

    This does not mean computers can only add. Combining addition with basic logical operations makes other arithmetic possible. For example, subtraction \\(a - b\\) becomes addition \\(a + (-b)\\), while multiplication and division can be expressed as repeated additions or subtractions.

    We can now summarize why computers use two's complement. It lets the same circuitry and operations handle both positive and negative addition, without dedicated subtraction circuitry or special handling for positive and negative zero. This simplifies hardware and improves efficiency.

    Two's complement is an elegant design. Space limits our discussion here, but interested readers are encouraged to explore it further.

    ","path":["Chapter 3.   Data structures","3.3   Number encoding *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#332-floating-point-encoding","level":2,"title":"3.3.2   Floating-point encoding","text":"

    Both int and float occupy 4 bytes, yet float has a much wider range. This seems surprising: since float also represents fractions, should its range not be smaller?

    The reason is that float uses a different representation. Write a 32-bit binary number as:

    \\[ b_{31} b_{30} b_{29} \\ldots b_2 b_1 b_0 \\]

    Under IEEE 754, a 32-bit float has three parts.

    • Sign \\(\\mathrm{S}\\): 1 bit, corresponding to \\(b_{31}\\).
    • Exponent \\(\\mathrm{E}\\): 8 bits, corresponding to \\(b_{30} b_{29} \\ldots b_{23}\\).
    • Fraction \\(\\mathrm{N}\\): 23 bits, corresponding to \\(b_{22} b_{21} \\ldots b_0\\).

    The value represented by the binary float is:

    \\[ \\text {val} = (-1)^{b_{31}} \\times 2^{\\left(b_{30} b_{29} \\ldots b_{23}\\right)_2-127} \\times\\left(1 . b_{22} b_{21} \\ldots b_0\\right)_2 \\]

    In decimal notation, this becomes:

    \\[ \\text {val}=(-1)^{\\mathrm{S}} \\times 2^{\\mathrm{E} -127} \\times (1 + \\mathrm{N}) \\]

    The components have these ranges:

    \\[ \\begin{aligned} \\mathrm{S} \\in & \\{ 0, 1\\}, \\quad \\mathrm{E} \\in \\{ 1, 2, \\dots, 254 \\} \\newline (1 + \\mathrm{N}) = & (1 + \\sum_{i=1}^{23} b_{23-i} 2^{-i}) \\subset [1, 2 - 2^{-23}] \\end{aligned} \\]

    Figure 3-5   Example of an IEEE 754 float calculation

    In Figure 3-5, \\(\\mathrm{S} = 0\\), \\(\\mathrm{E} = 124\\), and \\(\\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\\), giving:

    \\[ \\text { val } = (-1)^0 \\times 2^{124 - 127} \\times (1 + 0.375) = 0.171875 \\]

    We can now answer the original question: the exponent gives float a much wider range than int. The largest positive float is \\(2^{254 - 127} \\times (2 - 2^{-23}) \\approx 3.4 \\times 10^{38}\\); changing the sign bit gives the most negative value.

    The wider range of float comes at the cost of precision. An int uses all 32 bits to represent evenly spaced integer values. Because float includes an exponent, the gap between adjacent representable values tends to grow as their magnitude increases.

    As shown in Table 3-2, exponent values \\(\\mathrm{E} = 0\\) and \\(\\mathrm{E} = 255\\) have special meanings, representing zero, infinity, and \\(\\mathrm{NaN}\\), among other values.

    Table 3-2   Meanings of exponent values

    Exponent E Fraction \\(\\mathrm{N} = 0\\) Fraction \\(\\mathrm{N} \\ne 0\\) Formula \\(0\\) \\(\\pm 0\\) Subnormal number \\((-1)^{\\mathrm{S}} \\times 2^{-126} \\times (0.\\mathrm{N})\\) \\(1, 2, \\dots, 254\\) Normal number Normal number \\((-1)^{\\mathrm{S}} \\times 2^{(\\mathrm{E} -127)} \\times (1.\\mathrm{N})\\) \\(255\\) \\(\\pm \\infty\\) \\(\\mathrm{NaN}\\)

    Subnormal numbers substantially improve precision near zero. The smallest positive normal number is \\(2^{-126}\\), and the smallest positive subnormal number is \\(2^{-126} \\times 2^{-23}\\).

    Double-precision double uses a similar representation, which we will not detail here.

    ","path":["Chapter 3.   Data structures","3.3   Number encoding *"],"tags":[]},{"location":"chapter_data_structure/summary/","level":1,"title":"3.5   Summary","text":"","path":["Chapter 3.   Data structures","3.5   Summary"],"tags":[]},{"location":"chapter_data_structure/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Data structures can be classified by logical and physical structure. Logical structure describes relationships between data elements, while physical structure describes how data is stored in memory.
    • Common logical structures are linear, tree, and network structures. We usually classify data structures as linear (arrays, linked lists, stacks, queues) or nonlinear (trees, graphs, heaps). A hash table implementation may contain both linear and nonlinear structures.
    • During execution, data resides in computer memory. Each location has a memory address, which programs use to access its data.
    • The two main physical structures are contiguous storage (arrays) and scattered storage (linked lists). All data structures are implemented using arrays, linked lists, or both.
    • Basic data types include integers (byte, short, int, long), floating-point numbers (float, double), characters (char), and Booleans (bool). Their value ranges depend on storage size and representation.
    • Sign-magnitude, one's complement, and two's complement are three ways to encode numbers, and can be converted into one another. In sign-magnitude representation, the highest bit is the sign and the remaining bits give the magnitude.
    • Integers are stored in two's complement. This lets the computer handle positive and negative addition uniformly, avoids separate hardware circuitry for subtraction, and eliminates the ambiguity of positive and negative zero.
    • A floating-point encoding consists of 1 sign bit, 8 exponent bits, and 23 fraction bits. The exponent provides a much wider range than integers, at the cost of precision.
    • ASCII was the earliest English character set. It uses 1 byte and contains 128 characters. GBK is a common Chinese character set with over 20,000 Chinese characters. Unicode aims to provide a comprehensive character standard for the world's languages, avoiding garbled text caused by incompatible encodings.
    • UTF-8 is the most popular Unicode encoding and is widely compatible. Its variable length makes it extensible and space-efficient. UTF-16 and UTF-32 use fixed lengths. For Chinese text, UTF-16 uses less space than UTF-8. Languages such as Java and C# use UTF-16 by default.
    ","path":["Chapter 3.   Data structures","3.5   Summary"],"tags":[]},{"location":"chapter_data_structure/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Why can a hash table contain both linear and nonlinear data structures?

    A hash table is built on an array. To handle hash collisions, it may use separate chaining, discussed later in the Hash collisions section: each array bucket points to a linked list, which may be converted to a tree (usually a red-black tree) if it grows beyond a threshold.

    From a storage perspective, each bucket in the underlying array may contain a value, a linked list, or a tree. A hash table may therefore contain both linear structures (arrays and linked lists) and nonlinear structures (trees).

    Q: Does char occupy 1 byte?

    The size of char depends on the programming language's encoding. For example, Java, JavaScript, TypeScript, and C# use UTF-16 to store Unicode code points, so their char type occupies 2 bytes.

    Q: Is calling array-based structures static misleading? A stack supports dynamic operations such as pushing and popping.

    A stack does support dynamic data operations, but its underlying structure remains static in length. Although array-based structures can add and remove elements dynamically, their capacity is fixed. When the data exceeds the allocated capacity, a larger array must be created and the old contents copied into it.

    Q: When we create a stack or queue without specifying its size, why is it still called a static data structure?

    In high-level languages, the class automatically chooses an initial capacity for a stack or queue. For example, Java's ArrayList typically starts with a capacity of 10. Resizing is also automatic; see the later section on lists.

    Q: Converting sign-magnitude to two's complement uses \"invert, then add 1\". Its inverse should be \"subtract 1, then invert\", so why does \"invert, then add 1\" also work in the reverse direction?

    These conversions are really computations of a complement. If \\(a + b = c\\), we call \\(a\\) the complement of \\(b\\) with respect to \\(c\\), and conversely \\(b\\) the complement of \\(a\\) with respect to \\(c\\).

    Consider the \\(n = 4\\)-bit binary number \\(0010\\). Treating it as sign-magnitude and disregarding the sign bit, we obtain its two's complement by inverting and adding 1:

    \\[ 0010 \\rightarrow 1101 \\rightarrow 1110 \\]

    The two representations sum to \\(0010 + 1110 = 10000\\). Thus, \\(1110\\) is the complement of \\(0010\\) with respect to \\(10000\\). The \"invert, then add 1\" operation computes the complement with respect to \\(10000\\).

    What is the complement of \\(1110\\) with respect to \\(10000\\)? We can again invert and add 1:

    \\[ 1110 \\rightarrow 0001 \\rightarrow 0010 \\]

    The two representations are complements of each other with respect to \\(10000\\), so conversion in either direction can use the same operation: invert, then add 1.

    Of course, we can also recover the sign-magnitude form of \\(1110\\) by the inverse operation: subtract 1, then invert.

    \\[ 1110 \\rightarrow 1101 \\rightarrow 0010 \\]

    Both \"invert, then add 1\" and \"subtract 1, then invert\" compute the complement with respect to \\(10000\\), so they are equivalent.

    In essence, inverting computes the complement with respect to \\(1111\\), since sign-magnitude + one's complement = 1111. Adding 1 to the one's complement then gives the complement with respect to \\(10000\\).

    The example uses \\(n = 4\\), but the reasoning extends to binary numbers of any bit width.

    ","path":["Chapter 3.   Data structures","3.5   Summary"],"tags":[]},{"location":"chapter_divide_and_conquer/","level":1,"title":"Chapter 12.   Divide and conquer","text":"

    Abstract

    A difficult problem is broken down layer by layer, becoming simpler with each division.

    Divide and conquer reveals an important truth: start with the simple parts, and complexity becomes manageable.

    ","path":["Chapter 12.   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 12.1   Divide and conquer
    • 12.2   Divide-and-conquer search
    • 12.3   Building a binary tree
    • 12.4   Tower of Hanoi
    • 12.5   Summary
    • 12.6   Exercises
    ","path":["Chapter 12.   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/","level":1,"title":"12.2   Divide-and-conquer search","text":"

    We have learned that search algorithms fall into two broad categories.

    • Brute-force search: Traverses a data structure, taking \\(O(n)\\) time.
    • Adaptive search: Uses a particular organization of data or prior information to achieve \\(O(\\log n)\\) or even \\(O(1)\\) time.

    In fact, search algorithms that run in \\(O(\\log n)\\) time are usually based on divide and conquer, as seen in binary search and trees.

    • Each step of binary search reduces the problem of finding a target in an array to a smaller problem of finding it in half of the array. This continues until the array is empty or the target is found.
    • Trees exemplify divide and conquer. In data structures such as binary search trees, AVL trees, and heaps, various operations take \\(O(\\log n)\\) time.

    Binary search uses divide and conquer in the following way.

    • The problem can be divided: By comparing the middle element with the target, binary search recursively reduces the original problem of searching an array to the subproblem of searching half of that array.
    • The subproblems are independent: Binary search handles only one subproblem at each step, unaffected by other subproblems.
    • The subproblem solutions need not be combined: Binary search seeks a particular element, so there is no need to combine solutions. Solving the subproblem also solves the original problem.

    Divide and conquer makes searching faster because brute-force search eliminates only one option per step, whereas divide-and-conquer search can eliminate half the options.

    ","path":["Chapter 12.   Divide and conquer","12.2   Divide-and-conquer search"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/#1-implementing-binary-search-with-divide-and-conquer","level":3,"title":"1.   Implementing binary search with divide and conquer","text":"

    Earlier chapters implemented binary search iteratively. We will now implement it recursively using divide and conquer.

    Question

    Given a sorted array nums of length \\(n\\) with distinct elements, find the element target.

    From a divide-and-conquer perspective, let \\(f(i, j)\\) denote the subproblem for the search interval \\([i, j]\\).

    Starting with the original problem \\(f(0, n-1)\\), perform binary search as follows.

    1. Calculate the midpoint \\(m\\) of the search interval \\([i, j]\\) and use it to eliminate half the interval.
    2. Recursively solve the subproblem of half the size, either \\(f(i, m-1)\\) or \\(f(m+1, j)\\).
    3. Repeat steps 1. and 2., returning when target is found or the interval is empty.

    Figure 12-4 shows the divide-and-conquer process for finding \\(6\\) in an array using binary search.

    Figure 12-4   Divide-and-conquer process for binary search

    In the implementation, we define a recursive function dfs() to solve \\(f(i, j)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_recur.py
    def dfs(nums: list[int], target: int, i: int, j: int) -> int:\n    \"\"\"Binary search: problem f(i, j)\"\"\"\n    # If the interval is empty, the target is absent; return -1\n    if i > j:\n        return -1\n    # Calculate the midpoint index m\n    m = (i + j) // 2\n    if nums[m] < target:\n        # Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j)\n    elif nums[m] > target:\n        # Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1)\n    else:\n        # The target element was found; return its index\n        return m\n\ndef binary_search(nums: list[int], target: int) -> int:\n    \"\"\"Binary search\"\"\"\n    n = len(nums)\n    # Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1)\n
    binary_search_recur.cpp
    /* Binary search: problem f(i, j) */\nint dfs(vector<int> &nums, int target, int i, int j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(vector<int> &nums, int target) {\n    int n = nums.size();\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.java
    /* Binary search: problem f(i, j) */\nint dfs(int[] nums, int target, int i, int j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(int[] nums, int target) {\n    int n = nums.length;\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.cs
    /* Binary search: problem f(i, j) */\nint DFS(int[] nums, int target, int i, int j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return DFS(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return DFS(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint BinarySearch(int[] nums, int target) {\n    int n = nums.Length;\n    // Solve problem f(0, n-1)\n    return DFS(nums, target, 0, n - 1);\n}\n
    binary_search_recur.go
    /* Binary search: problem f(i, j) */\nfunc dfs(nums []int, target, i, j int) int {\n    // If the interval is empty, the target is absent; return -1\n    if i > j {\n        return -1\n    }\n    //    Calculate the midpoint index\n    m := i + ((j - i) >> 1)\n    //Compare the midpoint element with the target\n    if nums[m] < target {\n        // If it is smaller, recurse on the right half\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m+1, j)\n    } else if nums[m] > target {\n        // If it is larger, recurse on the left half\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m-1)\n    } else {\n        // The target element was found; return its index\n        return m\n    }\n}\n\n/* Binary search */\nfunc binarySearch(nums []int, target int) int {\n    n := len(nums)\n    return dfs(nums, target, 0, n-1)\n}\n
    binary_search_recur.swift
    /* Binary search: problem f(i, j) */\nfunc dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int {\n    // If the interval is empty, the target is absent; return -1\n    if i > j {\n        return -1\n    }\n    // Calculate the midpoint index m\n    let m = (i + j) / 2\n    if nums[m] < target {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums: nums, target: target, i: m + 1, j: j)\n    } else if nums[m] > target {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums: nums, target: target, i: i, j: m - 1)\n    } else {\n        // The target element was found; return its index\n        return m\n    }\n}\n\n/* Binary search */\nfunc binarySearch(nums: [Int], target: Int) -> Int {\n    // Solve problem f(0, n-1)\n    dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1)\n}\n
    binary_search_recur.js
    /* Binary search: problem f(i, j) */\nfunction dfs(nums, target, i, j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    const m = i + ((j - i) >> 1);\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfunction binarySearch(nums, target) {\n    const n = nums.length;\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.ts
    /* Binary search: problem f(i, j) */\nfunction dfs(nums: number[], target: number, i: number, j: number): number {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    const m = i + ((j - i) >> 1);\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfunction binarySearch(nums: number[], target: number): number {\n    const n = nums.length;\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.dart
    /* Binary search: problem f(i, j) */\nint dfs(List<int> nums, int target, int i, int j) {\n  // If the interval is empty, the target is absent; return -1\n  if (i > j) {\n    return -1;\n  }\n  // Calculate the midpoint index m\n  int m = (i + j) ~/ 2;\n  if (nums[m] < target) {\n    // Recurse on subproblem f(m+1, j)\n    return dfs(nums, target, m + 1, j);\n  } else if (nums[m] > target) {\n    // Recurse on subproblem f(i, m-1)\n    return dfs(nums, target, i, m - 1);\n  } else {\n    // The target element was found; return its index\n    return m;\n  }\n}\n\n/* Binary search */\nint binarySearch(List<int> nums, int target) {\n  int n = nums.length;\n  // Solve problem f(0, n-1)\n  return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.rs
    /* Binary search: problem f(i, j) */\nfn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 {\n    // If the interval is empty, the target is absent; return -1\n    if i > j {\n        return -1;\n    }\n    let m: i32 = i + (j - i) / 2;\n    if nums[m as usize] < target {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if nums[m as usize] > target {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfn binary_search(nums: &[i32], target: i32) -> i32 {\n    let n = nums.len() as i32;\n    // Solve problem f(0, n-1)\n    dfs(nums, target, 0, n - 1)\n}\n
    binary_search_recur.c
    /* Binary search: problem f(i, j) */\nint dfs(int nums[], int target, int i, int j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(int nums[], int target, int numsSize) {\n    int n = numsSize;\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.kt
    /* Binary search: problem f(i, j) */\nfun dfs(\n    nums: IntArray,\n    target: Int,\n    i: Int,\n    j: Int\n): Int {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1\n    }\n    // Calculate the midpoint index m\n    val m = (i + j) / 2\n    return if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        dfs(nums, target, m + 1, j)\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        dfs(nums, target, i, m - 1)\n    } else {\n        // The target element was found; return its index\n        m\n    }\n}\n\n/* Binary search */\nfun binarySearch(nums: IntArray, target: Int): Int {\n    val n = nums.size\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1)\n}\n
    binary_search_recur.rb
    ### Binary search: problem f(i, j) ###\ndef dfs(nums, target, i, j)\n  # If the interval is empty, the target is absent; return -1\n  return -1 if i > j\n\n  # Calculate the midpoint index m\n  m = (i + j) / 2\n\n  if nums[m] < target\n    # Recurse on subproblem f(m+1, j)\n    return dfs(nums, target, m + 1, j)\n  elsif nums[m] > target\n    # Recurse on subproblem f(i, m-1)\n    return dfs(nums, target, i, m - 1)\n  else\n    # The target element was found; return its index\n    return m\n  end\nend\n\n### Binary search ###\ndef binary_search(nums, target)\n  n = nums.length\n  # Solve problem f(0, n-1)\n  dfs(nums, target, 0, n - 1)\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 12.   Divide and conquer","12.2   Divide-and-conquer search"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/","level":1,"title":"12.3   Building a binary tree","text":"

    Question

    Given a binary tree's preorder traversal preorder and inorder traversal inorder, reconstruct the tree and return its root node. Assume that all node values are distinct, as shown in Figure 12-5.

    Figure 12-5   Example input for reconstructing a binary tree

    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#1-identifying-a-divide-and-conquer-problem","level":3,"title":"1.   Identifying a divide-and-conquer problem","text":"

    Reconstructing a binary tree from preorder and inorder is a typical divide-and-conquer problem.

    • The problem can be divided: We can split the original problem into two subproblems, building the left subtree and building the right subtree, plus one operation: initializing the root node. Each subtree can be divided in the same way into smaller subtrees, until we reach the smallest subproblem, an empty subtree.
    • The subproblems are independent: The left and right subtrees are independent and do not overlap. To build the left subtree, we need only the portions of the preorder and inorder traversals corresponding to it. The same applies to the right subtree.
    • The subproblem solutions can be combined: Once we have built the left and right subtrees, we can attach them to the root to obtain the solution to the original problem.
    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#2-dividing-the-subtrees","level":3,"title":"2.   Dividing the subtrees","text":"

    The analysis above shows that divide and conquer applies, but how do we use preorder and inorder to separate the left and right subtrees?

    By definition, both preorder and inorder consist of three parts.

    • Preorder: [ root | left subtree | right subtree ]. For the tree above, this is [ 3 | 9 | 2 1 7 ].
    • Inorder: [ left subtree | root | right subtree ]. For the tree above, this is [ 9 | 3 | 1 2 7 ].

    Using the example above, we can divide the traversals with the steps shown in Figure 12-6.

    1. The first element in the preorder traversal, 3, is the root value.
    2. Find the index of root value 3 in inorder, then use it to divide inorder into [ 9 | 3 | 1 2 7 ].
    3. This division of inorder tells us that the left and right subtrees have 1 and 3 nodes, respectively, allowing us to divide preorder into [ 3 | 9 | 2 1 7 ].

    Figure 12-6   Separating subtrees in preorder and inorder traversals

    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#3-describing-subtree-intervals-with-variables","level":3,"title":"3.   Describing subtree intervals with variables","text":"

    Using this method, we have identified the index intervals for the root, left subtree, and right subtree in preorder and inorder. We need a few pointer variables to describe these intervals.

    • Let \\(i\\) be the index of the current tree's root in preorder.
    • Let \\(m\\) be the index of the current tree's root in inorder.
    • Let \\([l, r]\\) be the interval occupied by the current tree in inorder.

    As shown in Table 12-1, these variables describe the root index in preorder and each subtree's interval in inorder.

    Table 12-1   Root and subtree indices in preorder and inorder traversals

    Root index in preorder Subtree interval in inorder Current tree \\(i\\) \\([l, r]\\) Left subtree \\(i + 1\\) \\([l, m-1]\\) Right subtree \\(i + 1 + (m - l)\\) \\([m+1, r]\\)

    The term \\((m-l)\\) in the right subtree's root index is the number of nodes in the left subtree. Figure 12-7 helps illustrate this.

    Figure 12-7   Index intervals for the root and the left and right subtrees

    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#4-code-implementation","level":3,"title":"4.   Code implementation","text":"

    To find \\(m\\) efficiently, we use a hash table hmap mapping each element of inorder to its index:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby build_tree.py
    def dfs(\n    preorder: list[int],\n    inorder_map: dict[int, int],\n    i: int,\n    l: int,\n    r: int,\n) -> TreeNode | None:\n    \"\"\"Build a binary tree: divide and conquer\"\"\"\n    # Stop when the subtree interval is empty\n    if r - l < 0:\n        return None\n    # Initialize the root node\n    root = TreeNode(preorder[i])\n    # Look up m to separate the left and right subtrees\n    m = inorder_map[preorder[i]]\n    # Subproblem: build the left subtree\n    root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n    # Subproblem: build the right subtree\n    root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n    # Return the root node\n    return root\n\ndef build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None:\n    \"\"\"Build a binary tree\"\"\"\n    # Initialize a hash table mapping each element in inorder to its index\n    inorder_map = {val: i for i, val in enumerate(inorder)}\n    root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1)\n    return root\n
    build_tree.cpp
    /* Build a binary tree: divide and conquer */\nTreeNode *dfs(vector<int> &preorder, unordered_map<int, int> &inorderMap, int i, int l, int r) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0)\n        return NULL;\n    // Initialize the root node\n    TreeNode *root = new TreeNode(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root->left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nTreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) {\n    // Initialize a hash table mapping each element in inorder to its index\n    unordered_map<int, int> inorderMap;\n    for (int i = 0; i < inorder.size(); i++) {\n        inorderMap[inorder[i]] = i;\n    }\n    TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1);\n    return root;\n}\n
    build_tree.java
    /* Build a binary tree: divide and conquer */\nTreeNode dfs(int[] preorder, Map<Integer, Integer> inorderMap, int i, int l, int r) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0)\n        return null;\n    // Initialize the root node\n    TreeNode root = new TreeNode(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    int m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nTreeNode buildTree(int[] preorder, int[] inorder) {\n    // Initialize a hash table mapping each element in inorder to its index\n    Map<Integer, Integer> inorderMap = new HashMap<>();\n    for (int i = 0; i < inorder.length; i++) {\n        inorderMap.put(inorder[i], i);\n    }\n    TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
    build_tree.cs
    /* Build a binary tree: divide and conquer */\nTreeNode? DFS(int[] preorder, Dictionary<int, int> inorderMap, int i, int l, int r) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0)\n        return null;\n    // Initialize the root node\n    TreeNode root = new(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root.left = DFS(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nTreeNode? BuildTree(int[] preorder, int[] inorder) {\n    // Initialize a hash table mapping each element in inorder to its index\n    Dictionary<int, int> inorderMap = [];\n    for (int i = 0; i < inorder.Length; i++) {\n        inorderMap.TryAdd(inorder[i], i);\n    }\n    TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1);\n    return root;\n}\n
    build_tree.go
    /* Build a binary tree: divide and conquer */\nfunc dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode {\n    // Stop when the subtree interval is empty\n    if r-l < 0 {\n        return nil\n    }\n    // Initialize the root node\n    root := NewTreeNode(preorder[i])\n    // Look up m to separate the left and right subtrees\n    m := inorderMap[preorder[i]]\n    // Subproblem: build the left subtree\n    root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1)\n    // Subproblem: build the right subtree\n    root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r)\n    // Return the root node\n    return root\n}\n\n/* Build a binary tree */\nfunc buildTree(preorder, inorder []int) *TreeNode {\n    // Initialize a hash table mapping each element in inorder to its index\n    inorderMap := make(map[int]int, len(inorder))\n    for i := 0; i < len(inorder); i++ {\n        inorderMap[inorder[i]] = i\n    }\n\n    root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1)\n    return root\n}\n
    build_tree.swift
    /* Build a binary tree: divide and conquer */\nfunc dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? {\n    // Stop when the subtree interval is empty\n    if r - l < 0 {\n        return nil\n    }\n    // Initialize the root node\n    let root = TreeNode(x: preorder[i])\n    // Look up m to separate the left and right subtrees\n    let m = inorderMap[preorder[i]]!\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1)\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r)\n    // Return the root node\n    return root\n}\n\n/* Build a binary tree */\nfunc buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? {\n    // Initialize a hash table mapping each element in inorder to its index\n    let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }\n    return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1)\n}\n
    build_tree.js
    /* Build a binary tree: divide and conquer */\nfunction dfs(preorder, inorderMap, i, l, r) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0) return null;\n    // Initialize the root node\n    const root = new TreeNode(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    const m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nfunction buildTree(preorder, inorder) {\n    // Initialize a hash table mapping each element in inorder to its index\n    let inorderMap = new Map();\n    for (let i = 0; i < inorder.length; i++) {\n        inorderMap.set(inorder[i], i);\n    }\n    const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
    build_tree.ts
    /* Build a binary tree: divide and conquer */\nfunction dfs(\n    preorder: number[],\n    inorderMap: Map<number, number>,\n    i: number,\n    l: number,\n    r: number\n): TreeNode | null {\n    // Stop when the subtree interval is empty\n    if (r - l < 0) return null;\n    // Initialize the root node\n    const root: TreeNode = new TreeNode(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    const m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nfunction buildTree(preorder: number[], inorder: number[]): TreeNode | null {\n    // Initialize a hash table mapping each element in inorder to its index\n    let inorderMap = new Map<number, number>();\n    for (let i = 0; i < inorder.length; i++) {\n        inorderMap.set(inorder[i], i);\n    }\n    const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
    build_tree.dart
    /* Build a binary tree: divide and conquer */\nTreeNode? dfs(\n  List<int> preorder,\n  Map<int, int> inorderMap,\n  int i,\n  int l,\n  int r,\n) {\n  // Stop when the subtree interval is empty\n  if (r - l < 0) {\n    return null;\n  }\n  // Initialize the root node\n  TreeNode? root = TreeNode(preorder[i]);\n  // Look up m to separate the left and right subtrees\n  int m = inorderMap[preorder[i]]!;\n  // Subproblem: build the left subtree\n  root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n  // Subproblem: build the right subtree\n  root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n  // Return the root node\n  return root;\n}\n\n/* Build a binary tree */\nTreeNode? buildTree(List<int> preorder, List<int> inorder) {\n  // Initialize a hash table mapping each element in inorder to its index\n  Map<int, int> inorderMap = {};\n  for (int i = 0; i < inorder.length; i++) {\n    inorderMap[inorder[i]] = i;\n  }\n  TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n  return root;\n}\n
    build_tree.rs
    /* Build a binary tree: divide and conquer */\nfn dfs(\n    preorder: &[i32],\n    inorder_map: &HashMap<i32, i32>,\n    i: i32,\n    l: i32,\n    r: i32,\n) -> Option<Rc<RefCell<TreeNode>>> {\n    // Stop when the subtree interval is empty\n    if r - l < 0 {\n        return None;\n    }\n    // Initialize the root node\n    let root = TreeNode::new(preorder[i as usize]);\n    // Look up m to separate the left and right subtrees\n    let m = inorder_map.get(&preorder[i as usize]).unwrap();\n    // Subproblem: build the left subtree\n    root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    Some(root)\n}\n\n/* Build a binary tree */\nfn build_tree(preorder: &[i32], inorder: &[i32]) -> Option<Rc<RefCell<TreeNode>>> {\n    // Initialize a hash table mapping each element in inorder to its index\n    let mut inorder_map: HashMap<i32, i32> = HashMap::new();\n    for i in 0..inorder.len() {\n        inorder_map.insert(inorder[i], i as i32);\n    }\n    let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1);\n    root\n}\n
    build_tree.c
    /* Build a binary tree: divide and conquer */\nTreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0)\n        return NULL;\n    // Initialize the root node\n    TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode));\n    root->val = preorder[i];\n    root->left = NULL;\n    root->right = NULL;\n    // Look up m to separate the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size);\n    // Subproblem: build the right subtree\n    root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nTreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) {\n    // Initialize a lookup array mapping inorder values to their indices\n    int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE);\n    for (int i = 0; i < inorderSize; i++) {\n        inorderMap[inorder[i]] = i;\n    }\n    TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize);\n    free(inorderMap);\n    return root;\n}\n
    build_tree.kt
    /* Build a binary tree: divide and conquer */\nfun dfs(\n    preorder: IntArray,\n    inorderMap: Map<Int?, Int?>,\n    i: Int,\n    l: Int,\n    r: Int\n): TreeNode? {\n    // Stop when the subtree interval is empty\n    if (r - l < 0) return null\n    // Initialize the root node\n    val root = TreeNode(preorder[i])\n    // Look up m to separate the left and right subtrees\n    val m = inorderMap[preorder[i]]!!\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1)\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r)\n    // Return the root node\n    return root\n}\n\n/* Build a binary tree */\nfun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {\n    // Initialize a hash table mapping each element in inorder to its index\n    val inorderMap = HashMap<Int?, Int?>()\n    for (i in inorder.indices) {\n        inorderMap[inorder[i]] = i\n    }\n    val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1)\n    return root\n}\n
    build_tree.rb
    ### Build a binary tree: divide and conquer ###\ndef dfs(preorder, inorder_map, i, l, r)\n  # Stop when the subtree interval is empty\n  return if r - l < 0\n\n  # Initialize the root node\n  root = TreeNode.new(preorder[i])\n  # Look up m to separate the left and right subtrees\n  m = inorder_map[preorder[i]]\n  # Subproblem: build the left subtree\n  root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n  # Subproblem: build the right subtree\n  root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n\n  # Return the root node\n  root\nend\n\n### Build a binary tree ###\ndef build_tree(preorder, inorder)\n  # Initialize a hash table mapping each element in inorder to its index\n  inorder_map = {}\n  inorder.each_with_index { |val, i| inorder_map[val] = i }\n  dfs(preorder, inorder_map, 0, 0, inorder.length - 1)\nend\n
    Visualize execution

    View full screen >

    Figure 12-8 shows the recursive reconstruction process. Nodes are created during recursive descent, while edges (references) are established as the recursive calls return.

    <1><2><3><4><5><6><7><8><9>

    Figure 12-8   Recursive process for building a binary tree

    Figure 12-9 shows how preorder and inorder are divided in each recursive call.

    Figure 12-9   Division within each recursive call

    For a tree with \\(n\\) nodes, initializing each node (executing one call to dfs()) takes \\(O(1)\\) time. The overall time complexity is therefore \\(O(n)\\).

    The hash table maps inorder elements to indices, requiring \\(O(n)\\) space. In the worst case, the binary tree degenerates into a linked list, and the recursion depth reaches \\(n\\), using \\(O(n)\\) space for stack frames. The overall space complexity is therefore \\(O(n)\\).

    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/","level":1,"title":"12.1   Divide and conquer","text":"

    Divide and conquer is a fundamental and widely used algorithmic strategy. Usually implemented recursively, it has two stages: dividing the problem and combining its solutions.

    1. Divide (the partitioning stage): Recursively split the original problem into two or more subproblems until the smallest subproblems are reached.
    2. Conquer (the combining stage): Starting with the known solutions to the smallest subproblems, combine solutions from the bottom up to construct the solution to the original problem.

    As shown in Figure 12-1, merge sort is a classic application of divide and conquer.

    1. Divide: Recursively split the original array (the original problem) into two subarrays (subproblems) until each subarray contains just one element (the smallest subproblem).
    2. Conquer: Merge the sorted subarrays (subproblem solutions) from the bottom up to obtain the sorted original array (the solution to the original problem).

    Figure 12-1   Divide-and-conquer strategy in merge sort

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1211-recognizing-divide-and-conquer-problems","level":2,"title":"12.1.1   Recognizing divide-and-conquer problems","text":"

    The following criteria usually help determine whether divide and conquer is suitable for a problem.

    1. The problem can be divided: The original problem can be split into smaller, similar subproblems, which can in turn be divided recursively in the same way.
    2. The subproblems are independent: They neither overlap nor depend on one another, so they can be solved independently.
    3. The subproblem solutions can be combined: Combining them yields the solution to the original problem.

    Merge sort clearly satisfies all three criteria.

    1. The problem can be divided: Recursively split an array (the original problem) into two subarrays (subproblems).
    2. The subproblems are independent: Each subarray can be sorted independently.
    3. The subproblem solutions can be combined: Merge the two sorted subarrays (subproblem solutions) into a single sorted array (the solution to the original problem).
    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1212-improving-efficiency-with-divide-and-conquer","level":2,"title":"12.1.2   Improving efficiency with divide and conquer","text":"

    Divide and conquer not only solves algorithmic problems effectively, but often improves efficiency as well. Quick sort, merge sort, and heap sort outperform selection sort, bubble sort, and insertion sort because they use this strategy.

    This raises a question: why does divide and conquer improve efficiency, and what is the underlying reason? Why is dividing a large problem, solving its subproblems, and combining their solutions faster than solving it directly? We can examine this from two perspectives: the number of operations and parallel computation.

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1-reducing-the-number-of-operations","level":3,"title":"1.   Reducing the number of operations","text":"

    Consider bubble sort, which takes \\(O(n^2)\\) time to sort an array of length \\(n\\). Suppose we split the array at its midpoint, as shown in Figure 12-2. Splitting takes \\(O(n)\\) time, sorting each half takes \\(O((n / 2)^2)\\) time, and merging the halves takes \\(O(n)\\) time. The total time complexity is:

    \\[ O(n + (\\frac{n}{2})^2 \\times 2 + n) = O(\\frac{n^2}{2} + 2n) \\]

    Figure 12-2   Bubble sort before and after dividing the array

    Now consider the following inequality, whose left and right sides represent the total operation counts before and after division:

    \\[ \\begin{aligned} n^2 & > \\frac{n^2}{2} + 2n \\newline n^2 - \\frac{n^2}{2} - 2n & > 0 \\newline n(n - 4) & > 0 \\end{aligned} \\]

    When \\(n > 4\\), dividing the array reduces the number of operations and should make sorting faster. The time complexity is still quadratic, \\(O(n^2)\\); only the constant factor has decreased.

    What if we keep splitting each subarray into two at its midpoint until every subarray contains just one element? This is precisely the idea behind merge sort, which takes \\(O(n \\log n)\\) time.

    What if we introduce more split points, dividing the original array evenly into \\(k\\) subarrays? This resembles bucket sort, which is well suited to sorting large amounts of data and can theoretically achieve \\(O(n + k)\\) time.

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#2-using-parallel-computation","level":3,"title":"2.   Using parallel computation","text":"

    The subproblems produced by divide and conquer are independent, so they can usually be solved in parallel. Thus, divide and conquer can both reduce time complexity and help the operating system parallelize the work.

    Parallelization is especially effective on multicore or multiprocessor systems. Multiple subproblems can be processed simultaneously, making fuller use of computing resources and substantially reducing total runtime.

    For example, in the bucket sort shown in Figure 12-3, a large dataset is evenly distributed among buckets. The sorting tasks for different buckets can then be assigned to different computing units, and their results combined afterward.

    Figure 12-3   Parallel computation in bucket sort

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1213-common-applications-of-divide-and-conquer","level":2,"title":"12.1.3   Common applications of divide and conquer","text":"

    Divide and conquer solves many classic algorithmic problems.

    • Closest pair of points: Divide a set of points into two parts, find the closest pair within each part, then find the closest pair spanning the two parts.
    • Large integer multiplication: Karatsuba's algorithm, for example, breaks multiplication of large integers into several smaller integer multiplications and additions.
    • Matrix multiplication: Strassen's algorithm, for example, breaks multiplication of large matrices into multiple smaller matrix multiplications and additions.
    • Tower of Hanoi: This problem can be solved recursively and is a classic application of divide and conquer.
    • Counting inversions: In a sequence, an earlier number and a later number form an inversion if the earlier one is larger. Inversions can be counted using divide and conquer with merge sort.

    Divide and conquer is also widely used in designing algorithms and data structures.

    • Binary search: Split a sorted array at its middle index, compare the middle element with the target to decide which half to discard, then repeat the same operation on the remaining interval.
    • Merge sort: This was described at the start of the section.
    • Quick sort: Choose a pivot and divide the array into two subarrays, one containing elements smaller than the pivot and the other containing larger elements. Repeat the division on both parts until each subarray contains just one element.
    • Bucket sort: Distribute data among multiple buckets, sort the elements in each bucket, and then take the elements from the buckets in order to obtain a sorted array.
    • Trees: Operations such as search, insertion, and deletion in binary search trees, AVL trees, red-black trees, B-trees, and B+ trees can be viewed as applications of divide and conquer.
    • Heaps: A heap is a special kind of complete binary tree. Its operations, such as insertion, deletion, and heapification, implicitly use divide and conquer.
    • Hash tables: Although hash tables do not directly use divide and conquer, some collision-handling strategies use it indirectly. For example, long linked lists in separate chaining may be converted into red-black trees to speed up lookups.

    As we can see, divide and conquer quietly underlies many algorithms and data structures, often without being explicitly named.

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/","level":1,"title":"12.6   Exercises","text":"","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1261-check-your-understanding","level":2,"title":"12.6.1   Check your understanding","text":"","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1-which-tasks-suit-divide-and-conquer","level":3,"title":"1.   Which tasks suit divide and conquer?","text":"

    A student wants to solve the following tasks by splitting each in half, solving the halves separately, and combining the results. For each task, decide whether divide and conquer is suitable, is possible but does not reduce the total work, or is unsuitable because the halves cannot be solved independently. Explain.

    1. Sort an unsorted array.
    2. Find the maximum value in an array.
    3. Execute a sequence of stack operations push(x) and pop() in order, outputting the element returned by each pop().
    Reference answer
    1. Suitable: split in half, sort the halves independently, and merge in \\(O(n)\\) time. This is exactly merge sort.
    2. Possible, but without reducing the total work: the two halves must still examine all \\(n\\) elements between them, taking \\(O(n)\\) time, just like a direct scan.
    3. The halves cannot be solved independently: the stack contents at the start of the second half depend on the results of the first half. The second half therefore cannot run independently of the first half's result.
    ","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#2-how-fast-exponentiation-reduces-computation","level":3,"title":"2.   How fast exponentiation reduces computation","text":"

    The following recursive function uses divide and conquer to compute \\(x^n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby fast_power.py
    def fast_pow(x: int, n: int) -> int:\n    \"\"\"Fast exponentiation\"\"\"\n    if n == 0:\n        return 1\n    half = fast_pow(x, n // 2)\n    if n % 2 == 0:\n        return half * half\n    return half * half * x\n
    fast_power.cpp
    /* Fast exponentiation */\nint fastPow(int x, int n) {\n    if (n == 0) {\n        return 1;\n    }\n    int half = fastPow(x, n / 2);\n    if (n % 2 == 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.java
    /* Fast exponentiation */\nint fastPow(int x, int n) {\n    if (n == 0) {\n        return 1;\n    }\n    int half = fastPow(x, n / 2);\n    if (n % 2 == 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.cs
    /* Fast exponentiation */\nint FastPow(int x, int n) {\n    if (n == 0) {\n        return 1;\n    }\n    int half = FastPow(x, n / 2);\n    if (n % 2 == 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.go
    /* Fast exponentiation */\nfunc fastPow(x, n int) int {\n    if n == 0 {\n        return 1\n    }\n    half := fastPow(x, n/2)\n    if n%2 == 0 {\n        return half * half\n    }\n    return half * half * x\n}\n
    fast_power.swift
    /* Fast exponentiation */\nfunc fastPow(x: Int, n: Int) -> Int {\n    if n == 0 {\n        return 1\n    }\n    let half = fastPow(x: x, n: n / 2)\n    if n % 2 == 0 {\n        return half * half\n    }\n    return half * half * x\n}\n
    fast_power.js
    /* Fast exponentiation */\nfunction fastPow(x, n) {\n    if (n === 0) {\n        return 1;\n    }\n    const half = fastPow(x, Math.floor(n / 2));\n    if (n % 2 === 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.ts
    /* Fast exponentiation */\nfunction fastPow(x: number, n: number): number {\n    if (n === 0) {\n        return 1;\n    }\n    const half = fastPow(x, Math.floor(n / 2));\n    if (n % 2 === 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.dart
    /* Fast exponentiation */\nint fastPow(int x, int n) {\n  if (n == 0) {\n    return 1;\n  }\n  int half = fastPow(x, n ~/ 2);\n  if (n % 2 == 0) {\n    return half * half;\n  }\n  return half * half * x;\n}\n
    fast_power.rs
    /* Fast exponentiation */\nfn fast_pow(x: i32, n: i32) -> i32 {\n    if n == 0 {\n        return 1;\n    }\n    let half = fast_pow(x, n / 2);\n    if n % 2 == 0 {\n        return half * half;\n    }\n    half * half * x\n}\n
    fast_power.c
    /* Fast exponentiation */\nint fastPow(int x, int n) {\n    if (n == 0) {\n        return 1;\n    }\n    int half = fastPow(x, n / 2);\n    if (n % 2 == 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.kt
    /* Fast exponentiation */\nfun fastPow(x: Int, n: Int): Int {\n    if (n == 0) {\n        return 1\n    }\n    val half = fastPow(x, n / 2)\n    if (n % 2 == 0) {\n        return half * half\n    }\n    return half * half * x\n}\n
    fast_power.rb
    ### Fast exponentiation ###\ndef fast_pow(x, n)\n  return 1 if n == 0\n\n  half = fast_pow(x, n / 2)\n  return half * half if n.even?\n\n  half * half * x\nend\n

    Run it with x = 3 and n = 5:

    1. What values does n take in successive recursive calls?
    2. Starting with the deepest call, what value does each call return?
    3. Why should the recursive result be saved in half instead of making the same recursive call on each side of the multiplication?
    Reference answer
    1. The parameter follows 5 → 2 → 1 → 0, halving the exponent each time until the base case is reached.

    2. When n = 0, the return value is 1. When n = 1, it is \\(1×1×3=3\\). When n = 2, it is \\(3×3=9\\); when n = 5, it is \\(9×9×3=243\\).

    3. Calling the same subproblem on both sides of the multiplication repeats exactly the same computation. Saving the result in half means each level makes only one recursive call, with recursion depth approximately \\(\\log n\\). Making two calls would cause substantial repeated computation.

    ","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#3-splitting-traversal-sequences-into-left-and-right-subtrees","level":3,"title":"3.   Splitting traversal sequences into left and right subtrees","text":"

    A binary tree with distinct node values has these preorder and inorder traversals:

    • Preorder: [A, B, D, E, C]
    • Inorder: [D, B, E, A, C]

    Split only at the root level; there is no need to recurse further or draw the entire tree.

    1. What is the root node?
    2. Which parts of the inorder sequence belong to the left and right subtrees?
    3. Which parts of the preorder sequence belong to the left and right subtrees? What are the root's immediate children?
    Reference answer
    1. The first node in preorder is the root, so the root is A.

    2. A splits the inorder sequence into two parts: [D, B, E] for the left subtree and [C] for the right subtree.

    3. The left subtree has three nodes, so the three preorder elements after root A belong to the left subtree: [B, D, E]. The remaining [C] belongs to the right subtree. Thus, the root's left child is B, and its right child is C.

    ","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1262-programming-exercises","level":2,"title":"12.6.2   Programming exercises","text":"","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1-fast-exponentiation","level":3,"title":"1.   Fast exponentiation","text":"

    Given a real number x and an integer n, compute \\(x^n\\) without calling a built-in power function. Use recursive divide and conquer: halve the exponent at each step and reuse the computed subproblem result. For this problem, \\(x^0=1\\), including when x = 0. When n < 0, x != 0 is guaranteed, and the answer can be expressed as \\((1/x)^{-n}\\).

    Hints
    1. When n is 0, the answer is 1.
    2. After computing x to the power n // 2, save it in half instead of making the recursive call a second time.
    3. When n < 0, first replace x with 1 / x and n with -n. In C++ or Java, you can convert n to a 64-bit integer first to avoid overflow when negating the smallest 32-bit integer.

    LeetCode

    ","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/","level":1,"title":"12.4   Tower of Hanoi","text":"

    In merge sort and binary tree reconstruction, we divided the original problem into two subproblems, each half its size. The Tower of Hanoi uses a different division strategy.

    Question

    Given three rods called A, B, and C, initially rod A holds \\(n\\) disks arranged from smallest at the top to largest at the bottom. Move all \\(n\\) disks to rod C while preserving their order, as shown in Figure 12-10. The following rules apply.

    1. A disk can only be removed from the top of one rod and placed on top of another.
    2. Only one disk may be moved at a time.
    3. A smaller disk must always be above a larger disk.

    Figure 12-10   Example of the Tower of Hanoi

    Let \\(f(i)\\) denote a Tower of Hanoi problem of size \\(i\\). For example, \\(f(3)\\) means moving \\(3\\) disks from A to C.

    ","path":["Chapter 12.   Divide and conquer","12.4   Tower of Hanoi"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#1-considering-the-base-cases","level":3,"title":"1.   Considering the base cases","text":"

    For \\(f(1)\\), with just one disk, simply move it directly from A to C, as shown in Figure 12-11.

    <1><2>

    Figure 12-11   Solution for a problem of size 1

    For \\(f(2)\\), with two disks, we need to use B because the smaller disk must remain above the larger one, as shown in Figure 12-12.

    1. Move the smaller top disk from A to B.
    2. Move the larger disk from A to C.
    3. Move the smaller disk from B to C.
    <1><2><3><4>

    Figure 12-12   Solution for a problem of size 2

    We can summarize the solution to \\(f(2)\\) as moving two disks from A to C using B. Here, C is the destination rod and B is the auxiliary rod.

    ","path":["Chapter 12.   Divide and conquer","12.4   Tower of Hanoi"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#2-dividing-the-problem","level":3,"title":"2.   Dividing the problem","text":"

    For \\(f(3)\\), with three disks, the situation is a little more complicated.

    Since we know how to solve \\(f(1)\\) and \\(f(2)\\), we can use divide and conquer: treat the top two disks on A as a group and follow the steps in Figure 12-13. This moves all three disks from A to C.

    1. Use B as the destination and C as the auxiliary rod to move two disks from A to B.
    2. Move the remaining disk directly from A to C.
    3. Use C as the destination and A as the auxiliary rod to move two disks from B to C.
    <1><2><3><4>

    Figure 12-13   Solution for a problem of size 3

    In essence, we have divided \\(f(3)\\) into two \\(f(2)\\) subproblems and one \\(f(1)\\) subproblem. Solving these three subproblems in order solves the original problem. This shows that the subproblems are independent and their solutions can be combined.

    We can now state the divide-and-conquer strategy for the Tower of Hanoi, illustrated in Figure 12-14: divide \\(f(n)\\) into two subproblems \\(f(n-1)\\) and one subproblem \\(f(1)\\), then solve them in this order.

    1. Move \\(n-1\\) disks from A to B using C.
    2. Move the remaining \\(1\\) disk directly from A to C.
    3. Move \\(n-1\\) disks from B to C using A.

    Each of the two \\(f(n-1)\\) subproblems can be divided recursively in the same way until we reach the smallest subproblem, \\(f(1)\\). The solution to \\(f(1)\\) is a single move.

    Figure 12-14   Divide-and-conquer strategy for the Tower of Hanoi

    ","path":["Chapter 12.   Divide and conquer","12.4   Tower of Hanoi"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#3-code-implementation","level":3,"title":"3.   Code implementation","text":"

    We define a recursive function dfs(i, src, buf, tar) that moves the top \\(i\\) disks from rod src to destination rod tar using auxiliary rod buf:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hanota.py
    def move(src: list[int], tar: list[int]):\n    \"\"\"Move one disk\"\"\"\n    # Take a disk from the top of src\n    pan = src.pop()\n    # Place the disk on top of tar\n    tar.append(pan)\n\ndef dfs(i: int, src: list[int], buf: list[int], tar: list[int]):\n    \"\"\"Solve the Tower of Hanoi problem f(i)\"\"\"\n    # If this subproblem has only one disk to move, move it directly from src to tar\n    if i == 1:\n        move(src, tar)\n        return\n    # Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf)\n    # Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar)\n    # Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar)\n\ndef solve_hanota(A: list[int], B: list[int], C: list[int]):\n    \"\"\"Solve the Tower of Hanoi problem\"\"\"\n    n = len(A)\n    # Move the top n disks from A to C using B\n    dfs(n, A, B, C)\n
    hanota.cpp
    /* Move one disk */\nvoid move(vector<int> &src, vector<int> &tar) {\n    // Take a disk from the top of src\n    int pan = src.back();\n    src.pop_back();\n    // Place the disk on top of tar\n    tar.push_back(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, vector<int> &src, vector<int> &buf, vector<int> &tar) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i == 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(vector<int> &A, vector<int> &B, vector<int> &C) {\n    int n = A.size();\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.java
    /* Move one disk */\nvoid move(List<Integer> src, List<Integer> tar) {\n    // Take a disk from the top of src\n    Integer pan = src.remove(src.size() - 1);\n    // Place the disk on top of tar\n    tar.add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, List<Integer> src, List<Integer> buf, List<Integer> tar) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i == 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(List<Integer> A, List<Integer> B, List<Integer> C) {\n    int n = A.size();\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.cs
    /* Move one disk */\nvoid Move(List<int> src, List<int> tar) {\n    // Take a disk from the top of src\n    int pan = src[^1];\n    src.RemoveAt(src.Count - 1);\n    // Place the disk on top of tar\n    tar.Add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid DFS(int i, List<int> src, List<int> buf, List<int> tar) {\n    // If only one disk in this subproblem remains on src, move it directly to tar\n    if (i == 1) {\n        Move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    DFS(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    Move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    DFS(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid SolveHanota(List<int> A, List<int> B, List<int> C) {\n    int n = A.Count;\n    // Move the top n disks from A to C using B\n    DFS(n, A, B, C);\n}\n
    hanota.go
    /* Move one disk */\nfunc move(src, tar *list.List) {\n    // Take a disk from the top of src\n    pan := src.Back()\n    // Place the disk on top of tar\n    tar.PushBack(pan.Value)\n    // Remove the disk from the top of src\n    src.Remove(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunc dfsHanota(i int, src, buf, tar *list.List) {\n    // If only one disk in this subproblem remains on src, move it directly to tar\n    if i == 1 {\n        move(src, tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfsHanota(i-1, src, tar, buf)\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfsHanota(i-1, buf, src, tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfunc solveHanota(A, B, C *list.List) {\n    n := A.Len()\n    // Move the top n disks from A to C using B\n    dfsHanota(n, A, B, C)\n}\n
    hanota.swift
    /* Move one disk */\nfunc move(src: inout [Int], tar: inout [Int]) {\n    // Take a disk from the top of src\n    let pan = src.popLast()!\n    // Place the disk on top of tar\n    tar.append(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunc dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) {\n    // If only one disk in this subproblem remains on src, move it directly to tar\n    if i == 1 {\n        move(src: &src, tar: &tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i: i - 1, src: &src, buf: &tar, tar: &buf)\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src: &src, tar: &tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i: i - 1, src: &buf, buf: &src, tar: &tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfunc solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) {\n    let n = A.count\n    // The end of each list represents the top of the peg\n    // Move the top n disks from A to C using B\n    dfs(i: n, src: &A, buf: &B, tar: &C)\n}\n
    hanota.js
    /* Move one disk */\nfunction move(src, tar) {\n    // Take a disk from the top of src\n    const pan = src.pop();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunction dfs(i, src, buf, tar) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i === 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfunction solveHanota(A, B, C) {\n    const n = A.length;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.ts
    /* Move one disk */\nfunction move(src: number[], tar: number[]): void {\n    // Take a disk from the top of src\n    const pan = src.pop();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunction dfs(i: number, src: number[], buf: number[], tar: number[]): void {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i === 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfunction solveHanota(A: number[], B: number[], C: number[]): void {\n    const n = A.length;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.dart
    /* Move one disk */\nvoid move(List<int> src, List<int> tar) {\n  // Take a disk from the top of src\n  int pan = src.removeLast();\n  // Place the disk on top of tar\n  tar.add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, List<int> src, List<int> buf, List<int> tar) {\n  // If only one disk remains to be moved from src, move it directly to tar\n  if (i == 1) {\n    move(src, tar);\n    return;\n  }\n  // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n  dfs(i - 1, src, tar, buf);\n  // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n  move(src, tar);\n  // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n  dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(List<int> A, List<int> B, List<int> C) {\n  int n = A.length;\n  // Move the top n disks from A to C using B\n  dfs(n, A, B, C);\n}\n
    hanota.rs
    /* Move one disk */\nfn move_pan(src: &mut Vec<i32>, tar: &mut Vec<i32>) {\n    // Take a disk from the top of src\n    let pan = src.pop().unwrap();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfn dfs(i: i32, src: &mut Vec<i32>, buf: &mut Vec<i32>, tar: &mut Vec<i32>) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if i == 1 {\n        move_pan(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move_pan(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfn solve_hanota(A: &mut Vec<i32>, B: &mut Vec<i32>, C: &mut Vec<i32>) {\n    let n = A.len() as i32;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.c
    /* Move one disk */\nvoid move(int *src, int *srcSize, int *tar, int *tarSize) {\n    // Take a disk from the top of src\n    int pan = src[*srcSize - 1];\n    src[*srcSize - 1] = 0;\n    (*srcSize)--;\n    // Place the disk on top of tar\n    tar[*tarSize] = pan;\n    (*tarSize)++;\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i == 1) {\n        move(src, srcSize, tar, tarSize);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, srcSize, tar, tarSize);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) {\n    // Move the top n disks from A to C using B\n    dfs(*ASize, A, ASize, B, BSize, C, CSize);\n}\n
    hanota.kt
    /* Move one disk */\nfun move(src: MutableList<Int>, tar: MutableList<Int>) {\n    // Take a disk from the top of src\n    val pan = src.removeAt(src.size - 1)\n    // Place the disk on top of tar\n    tar.add(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfun dfs(i: Int, src: MutableList<Int>, buf: MutableList<Int>, tar: MutableList<Int>) {\n    // If only one disk in this subproblem remains on src, move it directly to tar\n    if (i == 1) {\n        move(src, tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf)\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfun solveHanota(A: MutableList<Int>, B: MutableList<Int>, C: MutableList<Int>) {\n    val n = A.size\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C)\n}\n
    hanota.rb
    ### Move one disk ###\ndef move(src, tar)\n  # Take a disk from the top of src\n  pan = src.pop\n  # Place the disk on top of tar\n  tar << pan\nend\n\n### Solve the Tower of Hanoi problem f(i) ###\ndef dfs(i, src, buf, tar)\n  # If only one disk remains to be moved from src, move it directly to tar\n  if i == 1\n    move(src, tar)\n    return\n  end\n\n  # Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n  dfs(i - 1, src, tar, buf)\n  # Subproblem f(1): move the remaining disk in the current subproblem from src to tar\n  move(src, tar)\n  # Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n  dfs(i - 1, buf, src, tar)\nend\n\n### Solve the Tower of Hanoi problem ###\ndef solve_hanota(_A, _B, _C)\n  n = _A.length\n  # Move the top n disks from A to C using B\n  dfs(n, _A, _B, _C)\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 12-15, the Tower of Hanoi forms a recursion tree of height \\(n\\). Each node represents a subproblem and corresponds to a call to dfs(). The time complexity is therefore \\(O(2^n)\\), and the space complexity is \\(O(n)\\).

    Figure 12-15   Recursion tree for the Tower of Hanoi

    Quote

    The Tower of Hanoi comes from an ancient legend. In a temple in ancient India, monks had three tall diamond rods and \\(64\\) gold disks of different sizes. They continually moved the disks, believing that the world would end when the last disk was placed in its correct position.

    Even at one move per second, however, this would take about \\(2^{64} \\approx 1.84×10^{19}\\) seconds, or roughly \\(585\\) billion years, far longer than current estimates of the age of the universe. If the legend is true, we should have little reason to worry about the end of the world.

    ","path":["Chapter 12.   Divide and conquer","12.4   Tower of Hanoi"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/","level":1,"title":"12.5   Summary","text":"","path":["Chapter 12.   Divide and conquer","12.5   Summary"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Divide and conquer is a common algorithm design strategy with two stages: dividing the problem and combining solutions. It is usually implemented recursively.
    • To recognize a divide-and-conquer problem, ask whether it can be divided, whether the subproblems are independent, and whether their solutions can be combined.
    • Merge sort is a classic example. It recursively divides an array into two equally sized subarrays until each contains one element, then merges them level by level to complete the sort.
    • Divide and conquer often improves efficiency by reducing the number of operations and making it easier to parallelize the work.
    • Divide and conquer solves many algorithmic problems and appears widely in the design of data structures and algorithms.
    • Adaptive search is more efficient than brute-force search. Search algorithms with \\(O(\\log n)\\) time complexity are usually based on divide and conquer.
    • Binary search is another classic example, with no step for combining subproblem solutions. It can be implemented through recursive divide and conquer.
    • Reconstructing a binary tree can be divided into building its left and right subtrees. This is achieved by partitioning index intervals in the preorder and inorder traversals.
    • A Tower of Hanoi problem of size \\(n\\) can be divided into two subproblems of size \\(n-1\\) and one of size \\(1\\). Solving these three subproblems in order solves the original problem.
    ","path":["Chapter 12.   Divide and conquer","12.5   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/","level":1,"title":"Chapter 14.   Dynamic programming","text":"

    Abstract

    Streams flow into rivers, and rivers flow into the sea.

    Dynamic programming brings together solutions to small problems to answer a larger one, guiding us step by step to the other shore.

    ","path":["Chapter 14.   Dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 14.1   Introduction to dynamic programming
    • 14.2   Properties of dynamic programming problems
    • 14.3   Solving dynamic programming problems
    • 14.4   0-1 knapsack problem
    • 14.5   Unbounded knapsack problem
    • 14.6   Edit distance problem
    • 14.7   Summary
    • 14.8   Exercises
    ","path":["Chapter 14.   Dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/","level":1,"title":"14.2   Properties of dynamic programming problems","text":"

    The previous section showed how dynamic programming solves a problem by decomposing it into subproblems. Decomposition is a general algorithmic idea, but divide and conquer, dynamic programming, and backtracking use it differently.

    • Divide and conquer recursively splits a problem into independent subproblems until the smallest ones are reached, then combines their solutions as the recursion returns to solve the original problem.
    • Dynamic programming also decomposes problems recursively. Its main difference from divide and conquer is that its subproblems depend on one another, and many overlapping subproblems appear during decomposition.
    • Backtracking enumerates possible solutions by trying choices and undoing them, using pruning to avoid unnecessary branches. A solution consists of a sequence of decisions, and the prefix before each decision can be viewed as a subproblem.

    Dynamic programming is often used for optimization problems. Besides overlapping subproblems, these problems have two key properties: optimal substructure and the no-aftereffect property.

    ","path":["Chapter 14.   Dynamic programming","14.2   Properties of dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1421-optimal-substructure","level":2,"title":"14.2.1   Optimal substructure","text":"

    Let's modify the climbing stairs problem slightly to illustrate optimal substructure more clearly.

    Minimum cost to climb stairs

    Given a staircase, you can climb \\(1\\) or \\(2\\) steps at a time. Each step has a nonnegative integer indicating the cost paid on that step. A nonnegative integer array \\(cost\\) gives the costs: \\(cost[i]\\) is the cost of step \\(i\\), and \\(cost[0]\\) refers to the ground (the starting point). What is the minimum cost of reaching the top?

    As shown in Figure 14-6, if steps \\(1\\), \\(2\\), and \\(3\\) cost \\(1\\), \\(10\\), and \\(1\\), respectively, the minimum cost of reaching step \\(3\\) from the ground is \\(2\\).

    Figure 14-6   Minimum cost of reaching step 3

    Let \\(dp[i]\\) be the total cost of reaching step \\(i\\). Since step \\(i\\) can only be reached from step \\(i - 1\\) or \\(i - 2\\), \\(dp[i]\\) must be either \\(dp[i - 1] + cost[i]\\) or \\(dp[i - 2] + cost[i]\\). To minimize the cost, choose the smaller of the two:

    \\[ dp[i] = \\min(dp[i-1], dp[i-2]) + cost[i] \\]

    This introduces the meaning of optimal substructure: an optimal solution to the original problem is constructed from optimal solutions to its subproblems.

    This problem clearly has optimal substructure: choose the better of \\(dp[i-1]\\) and \\(dp[i-2]\\) and use it to construct the optimal solution \\(dp[i]\\).

    Does the climbing stairs problem from the previous section also have optimal substructure? Its goal is to count solutions, so it seems to be a counting problem. But rephrase the goal as finding the maximum number of solutions, and although the problem remains equivalent, optimal substructure becomes apparent: the maximum number of ways to reach step \\(n\\) is the sum of the maximum numbers for steps \\(n-1\\) and \\(n-2\\). The interpretation of optimal substructure is therefore flexible and can vary by problem.

    The state transition equation, together with the initial states \\(dp[1] = cost[1]\\) and \\(dp[2] = cost[2]\\), gives the following dynamic programming code:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py
    def min_cost_climbing_stairs_dp(cost: list[int]) -> int:\n    \"\"\"Minimum cost to climb stairs: dynamic programming\"\"\"\n    n = len(cost) - 1\n    if n == 1 or n == 2:\n        return cost[n]\n    # Initialize the dp table to store subproblem solutions\n    dp = [0] * (n + 1)\n    # Initial states: set the solutions to the smallest subproblems\n    dp[1], dp[2] = cost[1], cost[2]\n    # State transitions: solve progressively larger subproblems using smaller ones\n    for i in range(3, n + 1):\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    return dp[n]\n
    min_cost_climbing_stairs_dp.cpp
    /* Minimum cost to climb stairs: dynamic programming */\nint minCostClimbingStairsDP(vector<int> &cost) {\n    int n = cost.size() - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize the dp table to store subproblem solutions\n    vector<int> dp(n + 1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.java
    /* Minimum cost to climb stairs: dynamic programming */\nint minCostClimbingStairsDP(int[] cost) {\n    int n = cost.length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize the dp table to store subproblem solutions\n    int[] dp = new int[n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.cs
    /* Minimum cost to climb stairs: dynamic programming */\nint MinCostClimbingStairsDP(int[] cost) {\n    int n = cost.Length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize the dp table to store subproblem solutions\n    int[] dp = new int[n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.go
    /* Minimum cost to climb stairs: dynamic programming */\nfunc minCostClimbingStairsDP(cost []int) int {\n    n := len(cost) - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    min := func(a, b int) int {\n        if a < b {\n            return a\n        }\n        return b\n    }\n    // Initialize the dp table to store subproblem solutions\n    dp := make([]int, n+1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i] = min(dp[i-1], dp[i-2]) + cost[i]\n    }\n    return dp[n]\n}\n
    min_cost_climbing_stairs_dp.swift
    /* Minimum cost to climb stairs: dynamic programming */\nfunc minCostClimbingStairsDP(cost: [Int]) -> Int {\n    let n = cost.count - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    // Initialize the dp table to store subproblem solutions\n    var dp = Array(repeating: 0, count: n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3 ... n {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    }\n    return dp[n]\n}\n
    min_cost_climbing_stairs_dp.js
    /* Minimum cost to climb stairs: dynamic programming */\nfunction minCostClimbingStairsDP(cost) {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    // Initialize the dp table to store subproblem solutions\n    const dp = new Array(n + 1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.ts
    /* Minimum cost to climb stairs: dynamic programming */\nfunction minCostClimbingStairsDP(cost: Array<number>): number {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    // Initialize the dp table to store subproblem solutions\n    const dp = new Array(n + 1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.dart
    /* Minimum cost to climb stairs: dynamic programming */\nint minCostClimbingStairsDP(List<int> cost) {\n  int n = cost.length - 1;\n  if (n == 1 || n == 2) return cost[n];\n  // Initialize the dp table to store subproblem solutions\n  List<int> dp = List.filled(n + 1, 0);\n  // Initial states: set the solutions to the smallest subproblems\n  dp[1] = cost[1];\n  dp[2] = cost[2];\n  // State transitions: solve progressively larger subproblems using smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n  }\n  return dp[n];\n}\n
    min_cost_climbing_stairs_dp.rs
    /* Minimum cost to climb stairs: dynamic programming */\nfn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 {\n    let n = cost.len() - 1;\n    if n == 1 || n == 2 {\n        return cost[n];\n    }\n    // Initialize the dp table to store subproblem solutions\n    let mut dp = vec![-1; n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3..=n {\n        dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    dp[n]\n}\n
    min_cost_climbing_stairs_dp.c
    /* Minimum cost to climb stairs: dynamic programming */\nint minCostClimbingStairsDP(int cost[], int costSize) {\n    int n = costSize - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize the dp table to store subproblem solutions\n    int *dp = calloc(n + 1, sizeof(int));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    int res = dp[n];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    min_cost_climbing_stairs_dp.kt
    /* Minimum cost to climb stairs: dynamic programming */\nfun minCostClimbingStairsDP(cost: IntArray): Int {\n    val n = cost.size - 1\n    if (n == 1 || n == 2) return cost[n]\n    // Initialize the dp table to store subproblem solutions\n    val dp = IntArray(n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (i in 3..n) {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    }\n    return dp[n]\n}\n
    min_cost_climbing_stairs_dp.rb
    ### Minimum cost to climb stairs: dynamic programming ###\ndef min_cost_climbing_stairs_dp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  # Initialize the dp table to store subproblem solutions\n  dp = Array.new(n + 1, 0)\n  # Initial states: set the solutions to the smallest subproblems\n  dp[1], dp[2] = cost[1], cost[2]\n  # State transitions: solve progressively larger subproblems using smaller ones\n  (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n  dp[n]\nend\n
    Visualize execution

    View full screen >

    Figure 14-7 illustrates this dynamic programming process.

    Figure 14-7   Dynamic programming for the minimum climbing cost

    We can also reduce the state storage from a one-dimensional array to scalar variables, lowering the space complexity from \\(O(n)\\) to \\(O(1)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py
    def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int:\n    \"\"\"Minimum cost to climb stairs: space-optimized dynamic programming\"\"\"\n    n = len(cost) - 1\n    if n == 1 or n == 2:\n        return cost[n]\n    a, b = cost[1], cost[2]\n    for i in range(3, n + 1):\n        a, b = b, min(a, b) + cost[i]\n    return b\n
    min_cost_climbing_stairs_dp.cpp
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(vector<int> &cost) {\n    int n = cost.size() - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.java
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(int[] cost) {\n    int n = cost.length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.cs
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint MinCostClimbingStairsDPComp(int[] cost) {\n    int n = cost.Length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = Math.Min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.go
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfunc minCostClimbingStairsDPComp(cost []int) int {\n    n := len(cost) - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    min := func(a, b int) int {\n        if a < b {\n            return a\n        }\n        return b\n    }\n    // Initial states: set the solutions to the smallest subproblems\n    a, b := cost[1], cost[2]\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        tmp := b\n        b = min(a, tmp) + cost[i]\n        a = tmp\n    }\n    return b\n}\n
    min_cost_climbing_stairs_dp.swift
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfunc minCostClimbingStairsDPComp(cost: [Int]) -> Int {\n    let n = cost.count - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    var (a, b) = (cost[1], cost[2])\n    for i in 3 ... n {\n        (a, b) = (b, min(a, b) + cost[i])\n    }\n    return b\n}\n
    min_cost_climbing_stairs_dp.js
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfunction minCostClimbingStairsDPComp(cost) {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    let a = cost[1],\n        b = cost[2];\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.ts
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfunction minCostClimbingStairsDPComp(cost: Array<number>): number {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    let a = cost[1],\n        b = cost[2];\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.dart
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(List<int> cost) {\n  int n = cost.length - 1;\n  if (n == 1 || n == 2) return cost[n];\n  int a = cost[1], b = cost[2];\n  for (int i = 3; i <= n; i++) {\n    int tmp = b;\n    b = min(a, tmp) + cost[i];\n    a = tmp;\n  }\n  return b;\n}\n
    min_cost_climbing_stairs_dp.rs
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 {\n    let n = cost.len() - 1;\n    if n == 1 || n == 2 {\n        return cost[n];\n    };\n    let (mut a, mut b) = (cost[1], cost[2]);\n    for i in 3..=n {\n        let tmp = b;\n        b = cmp::min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    b\n}\n
    min_cost_climbing_stairs_dp.c
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(int cost[], int costSize) {\n    int n = costSize - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = myMin(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.kt
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfun minCostClimbingStairsDPComp(cost: IntArray): Int {\n    val n = cost.size - 1\n    if (n == 1 || n == 2) return cost[n]\n    var a = cost[1]\n    var b = cost[2]\n    for (i in 3..n) {\n        val tmp = b\n        b = min(a, tmp) + cost[i]\n        a = tmp\n    }\n    return b\n}\n
    min_cost_climbing_stairs_dp.rb
    ### Minimum cost to climb stairs: dynamic programming ###\ndef min_cost_climbing_stairs_dp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  # Initialize the dp table to store subproblem solutions\n  dp = Array.new(n + 1, 0)\n  # Initial states: set the solutions to the smallest subproblems\n  dp[1], dp[2] = cost[1], cost[2]\n  # State transitions: solve progressively larger subproblems using smaller ones\n  (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n  dp[n]\nend\n\n# Minimum cost to climb stairs: space-optimized dynamic programming\ndef min_cost_climbing_stairs_dp_comp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  a, b = cost[1], cost[2]\n  (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] }\n  b\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.2   Properties of dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1422-no-aftereffect-property","level":2,"title":"14.2.2   No-aftereffect property","text":"

    The no-aftereffect property is central to effective dynamic programming: given a fully specified state, its future depends only on that state, not on the sequence of states that led to it.

    In the climbing stairs problem, state \\(i\\) can lead to \\(i+1\\) or \\(i+2\\), corresponding to moves of \\(1\\) or \\(2\\) steps. Neither choice requires us to consider states before \\(i\\): they do not affect the future of state \\(i\\).

    Adding a constraint changes the situation.

    Climbing stairs with a constraint

    Given a staircase with \\(n\\) steps, you may climb \\(1\\) or \\(2\\) steps at a time, but may not make two consecutive moves of \\(1\\) step. How many ways are there to reach the top?

    As shown in Figure 14-8, only \\(2\\) ways remain to reach step \\(3\\): taking \\(1\\) step three times in a row violates the constraint and is discarded.

    Figure 14-8   Number of ways to reach step 3 with the constraint

    If the previous move climbed \\(1\\) step, the next must climb \\(2\\) steps. Thus, the next choice cannot be determined by the current state (the current step number) alone; it also depends on the preceding state (the previous step number).

    The no-aftereffect property no longer holds with this state definition, and \\(dp[i] = dp[i-1] + dp[i-2]\\) is no longer valid. Using \\(dp[i-1]\\) means taking \\(1\\) step now, but it includes many ways in which the preceding move also took \\(1\\) step. We cannot simply add all of \\(dp[i-1]\\) to \\(dp[i]\\) without violating the constraint.

    We therefore expand the state definition: state \\([i, j]\\) means we are on step \\(i\\) and the previous move climbed \\(j\\) steps, where \\(j \\in \\{1, 2\\}\\). This distinguishes whether the preceding move climbed \\(1\\) or \\(2\\) steps, allowing us to determine how the current state could have been reached.

    • If the previous move climbed \\(1\\) step, the move before it must have climbed \\(2\\) steps. Thus, \\(dp[i, 1]\\) can only come from \\(dp[i-1, 2]\\).
    • If the previous move climbed \\(2\\) steps, the move before it could have climbed \\(1\\) or \\(2\\) steps. Thus, \\(dp[i, 2]\\) can come from \\(dp[i-2, 1]\\) or \\(dp[i-2, 2]\\).

    As shown in Figure 14-9, \\(dp[i, j]\\) now counts the ways to reach state \\([i, j]\\). The state transition equations are:

    \\[ \\begin{cases} dp[i, 1] = dp[i-1, 2] \\\\ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \\end{cases} \\]

    Figure 14-9   Recurrence with the constraint

    Finally, return \\(dp[n, 1] + dp[n, 2]\\), the total number of ways to reach step \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_constraint_dp.py
    def climbing_stairs_constraint_dp(n: int) -> int:\n    \"\"\"Climbing stairs with constraints: dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return 1\n    # Initialize the dp table to store subproblem solutions\n    dp = [[0] * 3 for _ in range(n + 1)]\n    # Initial states: set the solutions to the smallest subproblems\n    dp[1][1], dp[1][2] = 1, 0\n    dp[2][1], dp[2][2] = 0, 1\n    # State transitions: solve progressively larger subproblems using smaller ones\n    for i in range(3, n + 1):\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    return dp[n][1] + dp[n][2]\n
    climbing_stairs_constraint_dp.cpp
    /* Climbing stairs with constraints: dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    vector<vector<int>> dp(n + 1, vector<int>(3, 0));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.java
    /* Climbing stairs with constraints: dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    int[][] dp = new int[n + 1][3];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.cs
    /* Climbing stairs with constraints: dynamic programming */\nint ClimbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    int[,] dp = new int[n + 1, 3];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1, 1] = 1;\n    dp[1, 2] = 0;\n    dp[2, 1] = 0;\n    dp[2, 2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i, 1] = dp[i - 1, 2];\n        dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2];\n    }\n    return dp[n, 1] + dp[n, 2];\n}\n
    climbing_stairs_constraint_dp.go
    /* Climbing stairs with constraints: dynamic programming */\nfunc climbingStairsConstraintDP(n int) int {\n    if n == 1 || n == 2 {\n        return 1\n    }\n    // Initialize the dp table to store subproblem solutions\n    dp := make([][3]int, n+1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i][1] = dp[i-1][2]\n        dp[i][2] = dp[i-2][1] + dp[i-2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
    climbing_stairs_constraint_dp.swift
    /* Climbing stairs with constraints: dynamic programming */\nfunc climbingStairsConstraintDP(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return 1\n    }\n    // Initialize the dp table to store subproblem solutions\n    var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3 ... n {\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
    climbing_stairs_constraint_dp.js
    /* Climbing stairs with constraints: dynamic programming */\nfunction climbingStairsConstraintDP(n) {\n    if (n === 1 || n === 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    const dp = Array.from(new Array(n + 1), () => new Array(3));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.ts
    /* Climbing stairs with constraints: dynamic programming */\nfunction climbingStairsConstraintDP(n: number): number {\n    if (n === 1 || n === 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    const dp = Array.from({ length: n + 1 }, () => new Array(3));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.dart
    /* Climbing stairs with constraints: dynamic programming */\nint climbingStairsConstraintDP(int n) {\n  if (n == 1 || n == 2) {\n    return 1;\n  }\n  // Initialize the dp table to store subproblem solutions\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(3, 0));\n  // Initial states: set the solutions to the smallest subproblems\n  dp[1][1] = 1;\n  dp[1][2] = 0;\n  dp[2][1] = 0;\n  dp[2][2] = 1;\n  // State transitions: solve progressively larger subproblems using smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i][1] = dp[i - 1][2];\n    dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n  }\n  return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.rs
    /* Climbing stairs with constraints: dynamic programming */\nfn climbing_stairs_constraint_dp(n: usize) -> i32 {\n    if n == 1 || n == 2 {\n        return 1;\n    };\n    // Initialize the dp table to store subproblem solutions\n    let mut dp = vec![vec![-1; 3]; n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3..=n {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    dp[n][1] + dp[n][2]\n}\n
    climbing_stairs_constraint_dp.c
    /* Climbing stairs with constraints: dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(3, sizeof(int));\n    }\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    int res = dp[n][1] + dp[n][2];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
    climbing_stairs_constraint_dp.kt
    /* Climbing stairs with constraints: dynamic programming */\nfun climbingStairsConstraintDP(n: Int): Int {\n    if (n == 1 || n == 2) {\n        return 1\n    }\n    // Initialize the dp table to store subproblem solutions\n    val dp = Array(n + 1) { IntArray(3) }\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (i in 3..n) {\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
    climbing_stairs_constraint_dp.rb
    ### Climbing stairs with constraints: dynamic programming ###\ndef climbing_stairs_constraint_dp(n)\n  return 1 if n == 1 || n == 2\n\n  # Initialize the dp table to store subproblem solutions\n  dp = Array.new(n + 1) { Array.new(3, 0) }\n  # Initial states: set the solutions to the smallest subproblems\n  dp[1][1], dp[1][2] = 1, 0\n  dp[2][1], dp[2][2] = 0, 1\n  # State transitions: solve progressively larger subproblems using smaller ones\n  for i in 3...(n + 1)\n    dp[i][1] = dp[i - 1][2]\n    dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n  end\n\n  dp[n][1] + dp[n][2]\nend\n
    Visualize execution

    View full screen >

    In this example, only one preceding state mattered, so expanding the state definition restored the no-aftereffect property. Some problems, however, depend much more strongly on their history.

    Climbing stairs that generate obstacles

    Given a staircase with \\(n\\) steps, you may climb \\(1\\) or \\(2\\) steps at a time. Whenever you reach step \\(i\\), the system places an obstacle on step \\(2i\\), and you may never land on step \\(2i\\) afterward. For example, if your first two moves land on steps \\(2\\) and \\(3\\), then steps \\(4\\) and \\(6\\) are blocked for all later moves. How many ways are there to reach the top?

    Here, each next move depends on all previous states, because every move places an obstacle higher up and affects future moves. Such problems are often difficult to solve with dynamic programming.

    Many complex combinatorial optimization problems, such as the traveling salesperson problem, do not satisfy the no-aftereffect property. For such problems, we often choose other methods, such as heuristic search, genetic algorithms, or reinforcement learning, to obtain useful locally optimal solutions within a limited time.

    ","path":["Chapter 14.   Dynamic programming","14.2   Properties of dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/","level":1,"title":"14.3   Solving dynamic programming problems","text":"

    The previous two sections introduced the main properties of dynamic programming problems. We will now explore two practical questions.

    1. How can we recognize a dynamic programming problem?
    2. Where should we start, and what are the steps for solving one?
    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1431-recognizing-the-problem","level":2,"title":"14.3.1   Recognizing the problem","text":"

    In general, a problem is suitable for dynamic programming if it has overlapping subproblems, optimal substructure, and the no-aftereffect property. These can be difficult to identify directly from a problem statement, so we usually start with a broader test: could backtracking (exhaustive search) solve it?

    Problems suited to backtracking usually fit a decision tree model. They can be represented as a tree in which each node is a decision and each path is a sequence of decisions.

    In other words, if a problem has clear choices and a solution is produced through a sequence of them, it fits the decision tree model and can usually be solved by backtracking.

    Beyond this, some signs make dynamic programming more promising.

    • The problem asks for an optimum, such as a maximum, minimum, greatest number, or smallest number.
    • States can be represented by a list, a multidimensional matrix, or a tree, and a recurrence relates each state to nearby states.

    Other signs make it less promising.

    • The goal is to find all possible solutions rather than an optimal one.
    • The problem explicitly involves permutations or combinations and asks for multiple concrete arrangements.

    If a problem fits the decision tree model and has clear positive signs, we can tentatively treat it as a dynamic programming problem and verify that assumption while solving it.

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1432-steps-for-solving-the-problem","level":2,"title":"14.3.2   Steps for solving the problem","text":"

    The process varies with the problem's nature and difficulty, but usually includes describing the decisions, defining states, creating a \\(dp\\) table, deriving a state transition equation, and identifying boundary conditions.

    To make these steps concrete, we will use the classic minimum path sum problem.

    Question

    Given an \\(n \\times m\\) grid grid, each cell contains a nonnegative integer representing its cost. A robot starts at the top-left cell and can move one cell down or right at a time until it reaches the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right.

    In the example shown in Figure 14-10, the minimum path sum is \\(13\\).

    Figure 14-10   Example input for minimum path sum

    Step 1: Identify each decision, define the state, and establish the \\(dp\\) table

    Each decision moves one cell down or right. If the current cell has indices \\([i, j]\\), the next indices are \\([i+1, j]\\) or \\([i, j+1]\\). The state therefore contains two variables, the row and column indices, and is denoted by \\([i, j]\\).

    State \\([i, j]\\) corresponds to the subproblem of finding the minimum path sum from the starting point \\([0, 0]\\) to \\([i, j]\\). Its solution is \\(dp[i, j]\\).

    This gives the two-dimensional \\(dp\\) matrix shown in Figure 14-11, with the same dimensions as the input \\(grid\\).

    Figure 14-11   State definition and dp table

    Note

    Dynamic programming and backtracking can be described as sequences of decisions. A state consists of the decision variables and should contain every variable needed to describe progress, with enough information to derive the next state.

    Each state corresponds to a subproblem. A \\(dp\\) table stores the solutions to all subproblems, with one dimension of the \\(dp\\) table for each independent state variable. In essence, the \\(dp\\) table maps states to subproblem solutions.

    Step 2: Identify optimal substructure and derive the state transition equation

    State \\([i, j]\\) can only be reached from the cell above, \\([i-1, j]\\), or the cell to the left, \\([i, j-1]\\). The optimal substructure is therefore that the minimum path sum to \\([i, j]\\) is determined by the smaller of the minimum path sums to \\([i, j-1]\\) and \\([i-1, j]\\).

    This gives the state transition equation illustrated in Figure 14-12:

    \\[ dp[i, j] = \\min(dp[i-1, j], dp[i, j-1]) + grid[i, j] \\]

    Figure 14-12   Optimal substructure and state transition equation

    Note

    Using the defined \\(dp\\) table, examine the relationship between the original problem and its subproblems. Find how to construct an optimal solution from optimal subproblem solutions: this is the optimal substructure.

    Once we identify optimal substructure, we can use it to build the state transition equation.

    Step 3: Determine the boundary conditions and evaluation order

    A state in the first row can only come from its left, and a state in the first column can only come from above. Thus, the first row \\(i = 0\\) and first column \\(j = 0\\) define the boundary conditions.

    As shown in Figure 14-13, each cell depends on the cells to its left and above. We therefore traverse the matrix with an outer loop over rows and an inner loop over columns.

    Figure 14-13   Boundary conditions and evaluation order

    Note

    Boundary conditions initialize the \\(dp\\) table in dynamic programming and support pruning in search.

    The key to choosing an evaluation order is ensuring that all smaller subproblems needed by the current state have already been solved correctly.

    We could now write the dynamic programming solution directly. However, decomposing a problem is a top-down way of thinking, so implementing \"brute-force search \\(\\rightarrow\\) memoized search \\(\\rightarrow\\) dynamic programming\" often follows our reasoning more naturally.

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1-method-1-brute-force-search","level":3,"title":"1.   Method 1: Brute-force search","text":"

    Starting from \\([i, j]\\), recursively decompose the problem into smaller states \\([i-1, j]\\) and \\([i, j-1]\\). The recursive function has the following elements.

    • Parameters: The state \\([i, j]\\).
    • Return value: The minimum path sum \\(dp[i, j]\\) from \\([0, 0]\\) to \\([i, j]\\).
    • Base case: When \\(i = 0\\) and \\(j = 0\\), return \\(grid[0, 0]\\).
    • Pruning: If \\(i < 0\\) or \\(j < 0\\), the indices are out of bounds. Return cost \\(+\\infty\\) to mark the path as infeasible.

    The implementation is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
    def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int:\n    \"\"\"Minimum path sum: brute-force search\"\"\"\n    # Stop searching at the top-left cell\n    if i == 0 and j == 0:\n        return grid[0][0]\n    # If a row or column index is out of bounds, return a cost of +∞\n    if i < 0 or j < 0:\n        return inf\n    # Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    up = min_path_sum_dfs(grid, i - 1, j)\n    left = min_path_sum_dfs(grid, i, j - 1)\n    # Return the minimum path cost from the top-left corner to (i, j)\n    return min(left, up) + grid[i][j]\n
    min_path_sum.cpp
    /* Minimum path sum: brute-force search */\nint minPathSumDFS(vector<vector<int>> &grid, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n}\n
    min_path_sum.java
    /* Minimum path sum: brute-force search */\nint minPathSumDFS(int[][] grid, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Integer.MAX_VALUE;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
    min_path_sum.cs
    /* Minimum path sum: brute-force search */\nint MinPathSumDFS(int[][] grid, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return int.MaxValue;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    int up = MinPathSumDFS(grid, i - 1, j);\n    int left = MinPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return Math.Min(left, up) + grid[i][j];\n}\n
    min_path_sum.go
    /* Minimum path sum: brute-force search */\nfunc minPathSumDFS(grid [][]int, i, j int) int {\n    // Stop searching at the top-left cell\n    if i == 0 && j == 0 {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return math.MaxInt to represent an infinite cost\n    if i < 0 || j < 0 {\n        return math.MaxInt\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    up := minPathSumDFS(grid, i-1, j)\n    left := minPathSumDFS(grid, i, j-1)\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return int(math.Min(float64(left), float64(up))) + grid[i][j]\n}\n
    min_path_sum.swift
    /* Minimum path sum: brute-force search */\nfunc minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int {\n    // Stop searching at the top-left cell\n    if i == 0, j == 0 {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return Int.max to represent an infinite cost\n    if i < 0 || j < 0 {\n        return .max\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    let up = minPathSumDFS(grid: grid, i: i - 1, j: j)\n    let left = minPathSumDFS(grid: grid, i: i, j: j - 1)\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return min(left, up) + grid[i][j]\n}\n
    min_path_sum.js
    /* Minimum path sum: brute-force search */\nfunction minPathSumDFS(grid, i, j) {\n    // Stop searching at the top-left cell\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    const up = minPathSumDFS(grid, i - 1, j);\n    const left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
    min_path_sum.ts
    /* Minimum path sum: brute-force search */\nfunction minPathSumDFS(\n    grid: Array<Array<number>>,\n    i: number,\n    j: number\n): number {\n    // Stop searching at the top-left cell\n    if (i === 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    const up = minPathSumDFS(grid, i - 1, j);\n    const left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
    min_path_sum.dart
    /* Minimum path sum: brute-force search */\nint minPathSumDFS(List<List<int>> grid, int i, int j) {\n  // Stop searching at the top-left cell\n  if (i == 0 && j == 0) {\n    return grid[0][0];\n  }\n  // If a row or column index is out of bounds, return a large cost representing +∞\n  if (i < 0 || j < 0) {\n    // Dart int has no infinity value; use 2^31 as a sentinel for this example\n    return BigInt.from(2).pow(31).toInt();\n  }\n  // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n  int up = minPathSumDFS(grid, i - 1, j);\n  int left = minPathSumDFS(grid, i, j - 1);\n  // Return the minimum path cost from the top-left corner to (i, j)\n  return min(left, up) + grid[i][j];\n}\n
    min_path_sum.rs
    /* Minimum path sum: brute-force search */\nfn min_path_sum_dfs(grid: &Vec<Vec<i32>>, i: i32, j: i32) -> i32 {\n    // Stop searching at the top-left cell\n    if i == 0 && j == 0 {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return i32::MAX to represent infinite cost\n    if i < 0 || j < 0 {\n        return i32::MAX;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    let up = min_path_sum_dfs(grid, i - 1, j);\n    let left = min_path_sum_dfs(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    std::cmp::min(left, up) + grid[i as usize][j as usize]\n}\n
    min_path_sum.c
    /* Minimum path sum: brute-force search */\nint minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n}\n
    min_path_sum.kt
    /* Minimum path sum: brute-force search */\nfun minPathSumDFS(grid: Array<IntArray>, i: Int, j: Int): Int {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return Int.MAX_VALUE as an infinite-cost sentinel\n    if (i < 0 || j < 0) {\n        return Int.MAX_VALUE\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    val up = minPathSumDFS(grid, i - 1, j)\n    val left = minPathSumDFS(grid, i, j - 1)\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return min(left, up) + grid[i][j]\n}\n
    min_path_sum.rb
    ### Minimum path sum: brute-force search ###\ndef min_path_sum_dfs(grid, i, j)\n  # Stop searching at the top-left cell\n  return grid[i][j] if i == 0 && j == 0\n  # If a row or column index is out of bounds, return a cost of +∞\n  return Float::INFINITY if i < 0 || j < 0\n  # Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n  up = min_path_sum_dfs(grid, i - 1, j)\n  left = min_path_sum_dfs(grid, i, j - 1)\n  # Return the minimum path cost from the top-left corner to (i, j)\n  [left, up].min + grid[i][j]\nend\n
    Visualize execution

    View full screen >

    Figure 14-14 shows the recursion tree rooted at \\(dp[2, 1]\\). It includes overlapping subproblems, whose number grows rapidly with the size of grid.

    The underlying reason for overlapping subproblems is that multiple paths can reach the same cell from the top-left corner.

    Figure 14-14   Recursion tree for brute-force search

    Each state offers two choices, down and right, and reaching the bottom-right corner takes \\(m + n - 2\\) moves. The worst-case time complexity is therefore \\(O(2^{m + n})\\), where \\(n\\) and \\(m\\) are the numbers of rows and columns. This estimate ignores the grid boundaries, where only one choice remains, so the actual number of paths is smaller.

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#2-method-2-memoized-search","level":3,"title":"2.   Method 2: Memoized search","text":"

    Introduce a memoization table mem with the same dimensions as grid to store subproblem solutions and prune repeated subproblems:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
    def min_path_sum_dfs_mem(\n    grid: list[list[int]], mem: list[list[int]], i: int, j: int\n) -> int:\n    \"\"\"Minimum path sum: memoized search\"\"\"\n    # Stop searching at the top-left cell\n    if i == 0 and j == 0:\n        return grid[0][0]\n    # If a row or column index is out of bounds, return a cost of +∞\n    if i < 0 or j < 0:\n        return inf\n    # If a result has already been recorded, return it directly\n    if mem[i][j] != -1:\n        return mem[i][j]\n    # Minimum path costs to the cells on the left and above\n    up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n    left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n    # Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n
    min_path_sum.cpp
    /* Minimum path sum: memoized search */\nint minPathSumDFSMem(vector<vector<int>> &grid, vector<vector<int>> &mem, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n    return mem[i][j];\n}\n
    min_path_sum.java
    /* Minimum path sum: memoized search */\nint minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Integer.MAX_VALUE;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
    min_path_sum.cs
    /* Minimum path sum: memoized search */\nint MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return int.MaxValue;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    int up = MinPathSumDFSMem(grid, mem, i - 1, j);\n    int left = MinPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = Math.Min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
    min_path_sum.go
    /* Minimum path sum: memoized search */\nfunc minPathSumDFSMem(grid, mem [][]int, i, j int) int {\n    // Stop searching at the top-left cell\n    if i == 0 && j == 0 {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return math.MaxInt to represent an infinite cost\n    if i < 0 || j < 0 {\n        return math.MaxInt\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][j] != -1 {\n        return mem[i][j]\n    }\n    // Minimum path costs to the cells on the left and above\n    up := minPathSumDFSMem(grid, mem, i-1, j)\n    left := minPathSumDFSMem(grid, mem, i, j-1)\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j]\n    return mem[i][j]\n}\n
    min_path_sum.swift
    /* Minimum path sum: memoized search */\nfunc minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int {\n    // Stop searching at the top-left cell\n    if i == 0, j == 0 {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return Int.max to represent an infinite cost\n    if i < 0 || j < 0 {\n        return .max\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][j] != -1 {\n        return mem[i][j]\n    }\n    // Minimum path costs to the cells on the left and above\n    let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j)\n    let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1)\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n}\n
    min_path_sum.js
    /* Minimum path sum: memoized search */\nfunction minPathSumDFSMem(grid, mem, i, j) {\n    // Stop searching at the top-left cell\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] !== -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    const up = minPathSumDFSMem(grid, mem, i - 1, j);\n    const left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
    min_path_sum.ts
    /* Minimum path sum: memoized search */\nfunction minPathSumDFSMem(\n    grid: Array<Array<number>>,\n    mem: Array<Array<number>>,\n    i: number,\n    j: number\n): number {\n    // Stop searching at the top-left cell\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    const up = minPathSumDFSMem(grid, mem, i - 1, j);\n    const left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
    min_path_sum.dart
    /* Minimum path sum: memoized search */\nint minPathSumDFSMem(List<List<int>> grid, List<List<int>> mem, int i, int j) {\n  // Stop searching at the top-left cell\n  if (i == 0 && j == 0) {\n    return grid[0][0];\n  }\n  // If a row or column index is out of bounds, return a large cost representing +∞\n  if (i < 0 || j < 0) {\n    // Dart int has no infinity value; use 2^31 as a sentinel for this example\n    return BigInt.from(2).pow(31).toInt();\n  }\n  // If a result has already been recorded, return it directly\n  if (mem[i][j] != -1) {\n    return mem[i][j];\n  }\n  // Minimum path costs to the cells on the left and above\n  int up = minPathSumDFSMem(grid, mem, i - 1, j);\n  int left = minPathSumDFSMem(grid, mem, i, j - 1);\n  // Record and return the minimum path cost from the top-left corner to (i, j)\n  mem[i][j] = min(left, up) + grid[i][j];\n  return mem[i][j];\n}\n
    min_path_sum.rs
    /* Minimum path sum: memoized search */\nfn min_path_sum_dfs_mem(grid: &Vec<Vec<i32>>, mem: &mut Vec<Vec<i32>>, i: i32, j: i32) -> i32 {\n    // Stop searching at the top-left cell\n    if i == 0 && j == 0 {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return i32::MAX to represent infinite cost\n    if i < 0 || j < 0 {\n        return i32::MAX;\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i as usize][j as usize] != -1 {\n        return mem[i as usize][j as usize];\n    }\n    // Minimum path costs to the cells on the left and above\n    let up = min_path_sum_dfs_mem(grid, mem, i - 1, j);\n    let left = min_path_sum_dfs_mem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize];\n    mem[i as usize][j as usize]\n}\n
    min_path_sum.c
    /* Minimum path sum: memoized search */\nint minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n    return mem[i][j];\n}\n
    min_path_sum.kt
    /* Minimum path sum: memoized search */\nfun minPathSumDFSMem(\n    grid: Array<IntArray>,\n    mem: Array<IntArray>,\n    i: Int,\n    j: Int\n): Int {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return Int.MAX_VALUE as an infinite-cost sentinel\n    if (i < 0 || j < 0) {\n        return Int.MAX_VALUE\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j]\n    }\n    // Minimum path costs to the cells on the left and above\n    val up = minPathSumDFSMem(grid, mem, i - 1, j)\n    val left = minPathSumDFSMem(grid, mem, i, j - 1)\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n}\n
    min_path_sum.rb
    ### Minimum path sum: memoized search ###\ndef min_path_sum_dfs_mem(grid, mem, i, j)\n  # Stop searching at the top-left cell\n  return grid[0][0] if i == 0 && j == 0\n  # If a row or column index is out of bounds, return a cost of +∞\n  return Float::INFINITY if i < 0 || j < 0\n  # If a result has already been recorded, return it directly\n  return mem[i][j] if mem[i][j] != -1\n  # Minimum path costs to the cells on the left and above\n  up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n  left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n  # Record and return the minimum path cost from the top-left corner to (i, j)\n  mem[i][j] = [left, up].min + grid[i][j]\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-15, memoization ensures that every subproblem is solved only once. The time complexity is therefore determined by the number of states, the grid size \\(O(nm)\\).

    Figure 14-15   Recursion tree for memoized search

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#3-method-3-dynamic-programming","level":3,"title":"3.   Method 3: Dynamic programming","text":"

    The following code implements dynamic programming iteratively:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
    def min_path_sum_dp(grid: list[list[int]]) -> int:\n    \"\"\"Minimum path sum: dynamic programming\"\"\"\n    n, m = len(grid), len(grid[0])\n    # Initialize the dp table\n    dp = [[0] * m for _ in range(n)]\n    dp[0][0] = grid[0][0]\n    # State transitions: first row\n    for j in range(1, m):\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    # State transitions: first column\n    for i in range(1, n):\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    # State transitions: remaining rows and columns\n    for i in range(1, n):\n        for j in range(1, m):\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n    return dp[n - 1][m - 1]\n
    min_path_sum.cpp
    /* Minimum path sum: dynamic programming */\nint minPathSumDP(vector<vector<int>> &grid) {\n    int n = grid.size(), m = grid[0].size();\n    // Initialize the dp table\n    vector<vector<int>> dp(n, vector<int>(m));\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
    min_path_sum.java
    /* Minimum path sum: dynamic programming */\nint minPathSumDP(int[][] grid) {\n    int n = grid.length, m = grid[0].length;\n    // Initialize the dp table\n    int[][] dp = new int[n][m];\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
    min_path_sum.cs
    /* Minimum path sum: dynamic programming */\nint MinPathSumDP(int[][] grid) {\n    int n = grid.Length, m = grid[0].Length;\n    // Initialize the dp table\n    int[,] dp = new int[n, m];\n    dp[0, 0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[0, j] = dp[0, j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (int i = 1; i < n; i++) {\n        dp[i, 0] = dp[i - 1, 0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1, m - 1];\n}\n
    min_path_sum.go
    /* Minimum path sum: dynamic programming */\nfunc minPathSumDP(grid [][]int) int {\n    n, m := len(grid), len(grid[0])\n    // Initialize the dp table\n    dp := make([][]int, n)\n    for i := 0; i < n; i++ {\n        dp[i] = make([]int, m)\n    }\n    dp[0][0] = grid[0][0]\n    // State transitions: first row\n    for j := 1; j < m; j++ {\n        dp[0][j] = dp[0][j-1] + grid[0][j]\n    }\n    // State transitions: first column\n    for i := 1; i < n; i++ {\n        dp[i][0] = dp[i-1][0] + grid[i][0]\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i < n; i++ {\n        for j := 1; j < m; j++ {\n            dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j]\n        }\n    }\n    return dp[n-1][m-1]\n}\n
    min_path_sum.swift
    /* Minimum path sum: dynamic programming */\nfunc minPathSumDP(grid: [[Int]]) -> Int {\n    let n = grid.count\n    let m = grid[0].count\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: m), count: n)\n    dp[0][0] = grid[0][0]\n    // State transitions: first row\n    for j in 1 ..< m {\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    }\n    // State transitions: first column\n    for i in 1 ..< n {\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    }\n    // State transitions: remaining rows and columns\n    for i in 1 ..< n {\n        for j in 1 ..< m {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n        }\n    }\n    return dp[n - 1][m - 1]\n}\n
    min_path_sum.js
    /* Minimum path sum: dynamic programming */\nfunction minPathSumDP(grid) {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n }, () =>\n        Array.from({ length: m }, () => 0)\n    );\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (let j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (let i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i < n; i++) {\n        for (let j = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
    min_path_sum.ts
    /* Minimum path sum: dynamic programming */\nfunction minPathSumDP(grid: Array<Array<number>>): number {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n }, () =>\n        Array.from({ length: m }, () => 0)\n    );\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (let j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (let i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i < n; i++) {\n        for (let j: number = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
    min_path_sum.dart
    /* Minimum path sum: dynamic programming */\nint minPathSumDP(List<List<int>> grid) {\n  int n = grid.length, m = grid[0].length;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n, (i) => List.filled(m, 0));\n  dp[0][0] = grid[0][0];\n  // State transitions: first row\n  for (int j = 1; j < m; j++) {\n    dp[0][j] = dp[0][j - 1] + grid[0][j];\n  }\n  // State transitions: first column\n  for (int i = 1; i < n; i++) {\n    dp[i][0] = dp[i - 1][0] + grid[i][0];\n  }\n  // State transitions: remaining rows and columns\n  for (int i = 1; i < n; i++) {\n    for (int j = 1; j < m; j++) {\n      dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n    }\n  }\n  return dp[n - 1][m - 1];\n}\n
    min_path_sum.rs
    /* Minimum path sum: dynamic programming */\nfn min_path_sum_dp(grid: &Vec<Vec<i32>>) -> i32 {\n    let (n, m) = (grid.len(), grid[0].len());\n    // Initialize the dp table\n    let mut dp = vec![vec![0; m]; n];\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for j in 1..m {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for i in 1..n {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for i in 1..n {\n        for j in 1..m {\n            dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    dp[n - 1][m - 1]\n}\n
    min_path_sum.c
    /* Minimum path sum: dynamic programming */\nint minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n    // Initialize the dp table\n    int **dp = malloc(n * sizeof(int *));\n    for (int i = 0; i < n; i++) {\n        dp[i] = calloc(m, sizeof(int));\n    }\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    int res = dp[n - 1][m - 1];\n    // Free memory\n    for (int i = 0; i < n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
    min_path_sum.kt
    /* Minimum path sum: dynamic programming */\nfun minPathSumDP(grid: Array<IntArray>): Int {\n    val n = grid.size\n    val m = grid[0].size\n    // Initialize the dp table\n    val dp = Array(n) { IntArray(m) }\n    dp[0][0] = grid[0][0]\n    // State transitions: first row\n    for (j in 1..<m) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    }\n    // State transitions: first column\n    for (i in 1..<n) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    }\n    // State transitions: remaining rows and columns\n    for (i in 1..<n) {\n        for (j in 1..<m) {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n        }\n    }\n    return dp[n - 1][m - 1]\n}\n
    min_path_sum.rb
    ### Minimum path sum: dynamic programming ###\ndef min_path_sum_dp(grid)\n  n, m = grid.length, grid.first.length\n  # Initialize the dp table\n  dp = Array.new(n) { Array.new(m, 0) }\n  dp[0][0] = grid[0][0]\n  # State transitions: first row\n  (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }\n  # State transitions: first column\n  (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }\n  # State transitions: remaining rows and columns\n  for i in 1...n\n    for j in 1...m\n      dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j]\n    end\n  end\n  dp[n -1][m -1]\nend\n
    Visualize execution

    View full screen >

    Figure 14-16 shows the state transitions for minimum path sum. They visit the entire grid, so the time complexity is \\(O(nm)\\).

    The array dp has dimensions \\(n \\times m\\), so the space complexity is \\(O(nm)\\).

    <1><2><3><4><5><6><7><8><9><10><11><12>

    Figure 14-16   Dynamic programming for minimum path sum

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#4-space-optimization","level":3,"title":"4.   Space optimization","text":"

    Each cell depends only on the cells to its left and above, so a single-row array is enough to represent the \\(dp\\) table.

    Because dp stores only one row of states, we cannot initialize the entire first column in advance. Instead, update its entry when processing each row:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
    def min_path_sum_dp_comp(grid: list[list[int]]) -> int:\n    \"\"\"Minimum path sum: space-optimized dynamic programming\"\"\"\n    n, m = len(grid), len(grid[0])\n    # Initialize the dp table\n    dp = [0] * m\n    # State transitions: first row\n    dp[0] = grid[0][0]\n    for j in range(1, m):\n        dp[j] = dp[j - 1] + grid[0][j]\n    # State transitions: remaining rows\n    for i in range(1, n):\n        # State transitions: first column\n        dp[0] = dp[0] + grid[i][0]\n        # State transitions: remaining columns\n        for j in range(1, m):\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n    return dp[m - 1]\n
    min_path_sum.cpp
    /* Minimum path sum: space-optimized dynamic programming */\nint minPathSumDPComp(vector<vector<int>> &grid) {\n    int n = grid.size(), m = grid[0].size();\n    // Initialize the dp table\n    vector<int> dp(m);\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.java
    /* Minimum path sum: space-optimized dynamic programming */\nint minPathSumDPComp(int[][] grid) {\n    int n = grid.length, m = grid[0].length;\n    // Initialize the dp table\n    int[] dp = new int[m];\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.cs
    /* Minimum path sum: space-optimized dynamic programming */\nint MinPathSumDPComp(int[][] grid) {\n    int n = grid.Length, m = grid[0].Length;\n    // Initialize the dp table\n    int[] dp = new int[m];\n    dp[0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.go
    /* Minimum path sum: space-optimized dynamic programming */\nfunc minPathSumDPComp(grid [][]int) int {\n    n, m := len(grid), len(grid[0])\n    // Initialize the dp table\n    dp := make([]int, m)\n    // State transitions: first row\n    dp[0] = grid[0][0]\n    for j := 1; j < m; j++ {\n        dp[j] = dp[j-1] + grid[0][j]\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i < n; i++ {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transitions: remaining columns\n        for j := 1; j < m; j++ {\n            dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j]\n        }\n    }\n    return dp[m-1]\n}\n
    min_path_sum.swift
    /* Minimum path sum: space-optimized dynamic programming */\nfunc minPathSumDPComp(grid: [[Int]]) -> Int {\n    let n = grid.count\n    let m = grid[0].count\n    // Initialize the dp table\n    var dp = Array(repeating: 0, count: m)\n    // State transitions: first row\n    dp[0] = grid[0][0]\n    for j in 1 ..< m {\n        dp[j] = dp[j - 1] + grid[0][j]\n    }\n    // State transitions: remaining rows\n    for i in 1 ..< n {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transitions: remaining columns\n        for j in 1 ..< m {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n        }\n    }\n    return dp[m - 1]\n}\n
    min_path_sum.js
    /* Minimum path sum: space-optimized dynamic programming */\nfunction minPathSumDPComp(grid) {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize the dp table\n    const dp = new Array(m);\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (let j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (let i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (let j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.ts
    /* Minimum path sum: space-optimized dynamic programming */\nfunction minPathSumDPComp(grid: Array<Array<number>>): number {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize the dp table\n    const dp = new Array(m);\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (let j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (let i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (let j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.dart
    /* Minimum path sum: space-optimized dynamic programming */\nint minPathSumDPComp(List<List<int>> grid) {\n  int n = grid.length, m = grid[0].length;\n  // Initialize the dp table\n  List<int> dp = List.filled(m, 0);\n  dp[0] = grid[0][0];\n  for (int j = 1; j < m; j++) {\n    dp[j] = dp[j - 1] + grid[0][j];\n  }\n  // State transitions: remaining rows\n  for (int i = 1; i < n; i++) {\n    // State transitions: first column\n    dp[0] = dp[0] + grid[i][0];\n    // State transitions: remaining columns\n    for (int j = 1; j < m; j++) {\n      dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n    }\n  }\n  return dp[m - 1];\n}\n
    min_path_sum.rs
    /* Minimum path sum: space-optimized dynamic programming */\nfn min_path_sum_dp_comp(grid: &Vec<Vec<i32>>) -> i32 {\n    let (n, m) = (grid.len(), grid[0].len());\n    // Initialize the dp table\n    let mut dp = vec![0; m];\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for j in 1..m {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for i in 1..n {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for j in 1..m {\n            dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    dp[m - 1]\n}\n
    min_path_sum.c
    /* Minimum path sum: space-optimized dynamic programming */\nint minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n    // Initialize the dp table\n    int *dp = calloc(m, sizeof(int));\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    int res = dp[m - 1];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    min_path_sum.kt
    /* Minimum path sum: space-optimized dynamic programming */\nfun minPathSumDPComp(grid: Array<IntArray>): Int {\n    val n = grid.size\n    val m = grid[0].size\n    // Initialize the dp table\n    val dp = IntArray(m)\n    // State transitions: first row\n    dp[0] = grid[0][0]\n    for (j in 1..<m) {\n        dp[j] = dp[j - 1] + grid[0][j]\n    }\n    // State transitions: remaining rows\n    for (i in 1..<n) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transitions: remaining columns\n        for (j in 1..<m) {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n        }\n    }\n    return dp[m - 1]\n}\n
    min_path_sum.rb
    ### Minimum path sum: space-optimized dynamic programming ###\ndef min_path_sum_dp_comp(grid)\n  n, m = grid.length, grid.first.length\n  # Initialize the dp table\n  dp = Array.new(m, 0)\n  # State transitions: first row\n  dp[0] = grid[0][0]\n  (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }\n  # State transitions: remaining rows\n  for i in 1...n\n    # State transitions: first column\n    dp[0] = dp[0] + grid[i][0]\n    # State transitions: remaining columns\n    (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] }\n  end\n  dp[m - 1]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/","level":1,"title":"14.6   Edit distance problem","text":"

    Edit distance, also called Levenshtein distance, is the minimum number of edits needed to transform one string into another. It is commonly used in information retrieval and natural language processing to measure similarity between sequences.

    Question

    Given two strings \\(s\\) and \\(t\\), return the minimum number of edits needed to transform \\(s\\) into \\(t\\).

    Three edits are allowed: insert a character, delete a character, or replace a character with any other character.

    As shown in Figure 14-27, transforming kitten into sitting takes 3 edits: 2 replacements and 1 insertion. Transforming hello into algo also takes 3 edits: 2 replacements and 1 deletion.

    Figure 14-27   Examples of edit distance

    Edit distance fits naturally into a decision tree model. Strings are nodes, and each decision (an edit) is an edge.

    As shown in Figure 14-28, without restrictions on the edits, each node can have many outgoing edges, one for each possible operation. Thus, many paths can transform hello into algo.

    From the decision tree perspective, the goal is to find the shortest path between nodes hello and algo.

    Figure 14-28   Edit distance as a decision tree

    ","path":["Chapter 14.   Dynamic programming","14.6   Edit distance problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#1-dynamic-programming-approach","level":3,"title":"1.   Dynamic programming approach","text":"

    Step 1: Identify each decision, define the state, and establish the \\(dp\\) table

    Each decision applies one edit to string \\(s\\).

    We want each edit to reduce the problem's size so that we can form subproblems. Let the lengths of \\(s\\) and \\(t\\) be \\(n\\) and \\(m\\). First consider their last characters, \\(s[n-1]\\) and \\(t[m-1]\\).

    • If \\(s[n-1]\\) and \\(t[m-1]\\) match, skip them and consider \\(s[n-2]\\) and \\(t[m-2]\\).
    • If \\(s[n-1]\\) and \\(t[m-1]\\) differ, apply an edit to \\(s\\) (insertion, deletion, or replacement) to match the endings, allowing us to move on to a smaller problem.

    Each decision (edit) in \\(s\\) changes the characters still to be matched in \\(s\\) and \\(t\\). The state therefore specifies the \\(i\\)-th and \\(j\\)-th characters currently under consideration in \\(s\\) and \\(t\\), denoted by \\([i, j]\\).

    State \\([i, j]\\) represents the subproblem of finding the minimum number of edits needed to transform the first \\(i\\) characters of \\(s\\) into the first \\(j\\) characters of \\(t\\).

    This gives a two-dimensional \\(dp\\) table of size \\((n+1) \\times (m+1)\\).

    Step 2: Identify optimal substructure and derive the state transition equation

    For subproblem \\(dp[i, j]\\), the final characters are \\(s[i-1]\\) and \\(t[j-1]\\). The three possible edits give the cases shown in Figure 14-29.

    1. Insert \\(t[j-1]\\) after \\(s[i-1]\\), leaving subproblem \\(dp[i, j-1]\\).
    2. Delete \\(s[i-1]\\), leaving subproblem \\(dp[i-1, j]\\).
    3. Replace \\(s[i-1]\\) with \\(t[j-1]\\), leaving subproblem \\(dp[i-1, j-1]\\).

    Figure 14-29   State transitions for edit distance

    The optimal substructure follows: the minimum edit count \\(dp[i, j]\\) is the smallest of \\(dp[i, j-1]\\), \\(dp[i-1, j]\\), and \\(dp[i-1, j-1]\\), plus \\(1\\) for the current edit. The state transition equation is:

    \\[ dp[i, j] = \\min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 \\]

    When \\(s[i-1]\\) and \\(t[j-1]\\) match, no edit is needed for the current characters, and the state transition equation becomes:

    \\[ dp[i, j] = dp[i-1, j-1] \\]

    Step 3: Determine the boundary conditions and evaluation order

    If both strings are empty, \\(0\\) edits are needed, so \\(dp[0, 0] = 0\\). If \\(s\\) is empty but \\(t\\) is not, the minimum edit count is the length of \\(t\\), giving the first row \\(dp[0, j] = j\\). If \\(s\\) is nonempty but \\(t\\) is empty, the count is the length of \\(s\\), giving the first column \\(dp[i, 0] = i\\).

    The solution \\(dp[i, j]\\) depends on entries to its left, above, and upper left, so two nested loops can traverse the \\(dp\\) table in increasing index order.

    ","path":["Chapter 14.   Dynamic programming","14.6   Edit distance problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py
    def edit_distance_dp(s: str, t: str) -> int:\n    \"\"\"Edit distance: dynamic programming\"\"\"\n    n, m = len(s), len(t)\n    dp = [[0] * (m + 1) for _ in range(n + 1)]\n    # State transitions: first row and first column\n    for i in range(1, n + 1):\n        dp[i][0] = i\n    for j in range(1, m + 1):\n        dp[0][j] = j\n    # State transitions: remaining rows and columns\n    for i in range(1, n + 1):\n        for j in range(1, m + 1):\n            if s[i - 1] == t[j - 1]:\n                # If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1]\n            else:\n                # Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1\n    return dp[n][m]\n
    edit_distance.cpp
    /* Edit distance: dynamic programming */\nint editDistanceDP(string s, string t) {\n    int n = s.length(), m = t.length();\n    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));\n    // State transitions: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
    edit_distance.java
    /* Edit distance: dynamic programming */\nint editDistanceDP(String s, String t) {\n    int n = s.length(), m = t.length();\n    int[][] dp = new int[n + 1][m + 1];\n    // State transitions: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) == t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
    edit_distance.cs
    /* Edit distance: dynamic programming */\nint EditDistanceDP(string s, string t) {\n    int n = s.Length, m = t.Length;\n    int[,] dp = new int[n + 1, m + 1];\n    // State transitions: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i, 0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0, j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[i, j] = dp[i - 1, j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n, m];\n}\n
    edit_distance.go
    /* Edit distance: dynamic programming */\nfunc editDistanceDP(s string, t string) int {\n    n := len(s)\n    m := len(t)\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, m+1)\n    }\n    // State transitions: first row and first column\n    for i := 1; i <= n; i++ {\n        dp[i][0] = i\n    }\n    for j := 1; j <= m; j++ {\n        dp[0][j] = j\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i <= n; i++ {\n        for j := 1; j <= m; j++ {\n            if s[i-1] == t[j-1] {\n                // If the two bytes are equal, skip both (each byte is one character for ASCII input)\n                dp[i][j] = dp[i-1][j-1]\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
    edit_distance.swift
    /* Edit distance: dynamic programming */\nfunc editDistanceDP(s: String, t: String) -> Int {\n    let n = s.utf8CString.count\n    let m = t.utf8CString.count\n    var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1)\n    // State transitions: first row and first column\n    for i in 1 ... n {\n        dp[i][0] = i\n    }\n    for j in 1 ... m {\n        dp[0][j] = j\n    }\n    // State transitions: remaining rows and columns\n    for i in 1 ... n {\n        for j in 1 ... m {\n            if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n                // If the two current UTF-8 bytes match, skip both\n                dp[i][j] = dp[i - 1][j - 1]\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
    edit_distance.js
    /* Edit distance: dynamic programming */\nfunction editDistanceDP(s, t) {\n    const n = s.length,\n        m = t.length;\n    const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));\n    // State transitions: first row and first column\n    for (let i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (let j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] =\n                    Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
    edit_distance.ts
    /* Edit distance: dynamic programming */\nfunction editDistanceDP(s: string, t: string): number {\n    const n = s.length,\n        m = t.length;\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: m + 1 }, () => 0)\n    );\n    // State transitions: first row and first column\n    for (let i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (let j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] =\n                    Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
    edit_distance.dart
    /* Edit distance: dynamic programming */\nint editDistanceDP(String s, String t) {\n  int n = s.length, m = t.length;\n  List<List<int>> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0));\n  // State transitions: first row and first column\n  for (int i = 1; i <= n; i++) {\n    dp[i][0] = i;\n  }\n  for (int j = 1; j <= m; j++) {\n    dp[0][j] = j;\n  }\n  // State transitions: remaining rows and columns\n  for (int i = 1; i <= n; i++) {\n    for (int j = 1; j <= m; j++) {\n      if (s[i - 1] == t[j - 1]) {\n        // If the two characters are equal, skip both of them\n        dp[i][j] = dp[i - 1][j - 1];\n      } else {\n        // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n        dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n      }\n    }\n  }\n  return dp[n][m];\n}\n
    edit_distance.rs
    /* Edit distance: dynamic programming */\nfn edit_distance_dp(s: &str, t: &str) -> i32 {\n    let (n, m) = (s.len(), t.len());\n    let mut dp = vec![vec![0; m + 1]; n + 1];\n    // State transitions: first row and first column\n    for i in 1..=n {\n        dp[i][0] = i as i32;\n    }\n    for j in 1..m {\n        dp[0][j] = j as i32;\n    }\n    // State transitions: remaining rows and columns\n    for i in 1..=n {\n        for j in 1..=m {\n            if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] =\n                    std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    dp[n][m]\n}\n
    edit_distance.c
    /* Edit distance: dynamic programming */\nint editDistanceDP(char *s, char *t, int n, int m) {\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(m + 1, sizeof(int));\n    }\n    // State transitions: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    int res = dp[n][m];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
    edit_distance.kt
    /* Edit distance: dynamic programming */\nfun editDistanceDP(s: String, t: String): Int {\n    val n = s.length\n    val m = t.length\n    val dp = Array(n + 1) { IntArray(m + 1) }\n    // State transitions: first row and first column\n    for (i in 1..n) {\n        dp[i][0] = i\n    }\n    for (j in 1..m) {\n        dp[0][j] = j\n    }\n    // State transitions: remaining rows and columns\n    for (i in 1..n) {\n        for (j in 1..m) {\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1]\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
    edit_distance.rb
    ### Edit distance: dynamic programming ###\ndef edit_distance_dp(s, t)\n  n, m = s.length, t.length\n  dp = Array.new(n + 1) { Array.new(m + 1, 0) }\n  # State transitions: first row and first column\n  (1...(n + 1)).each { |i| dp[i][0] = i }\n  (1...(m + 1)).each { |j| dp[0][j] = j }\n  # State transitions: remaining rows and columns\n  for i in 1...(n + 1)\n    for j in 1...(m +1)\n      if s[i - 1] == t[j - 1]\n        # If the two characters are equal, skip both of them\n        dp[i][j] = dp[i - 1][j - 1]\n      else\n        # Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n        dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1\n      end\n    end\n  end\n  dp[n][m]\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-30, the state transition process resembles that of the knapsack problem: both fill a two-dimensional grid.

    <1><2><3><4><5><6><7><8><9><10><11><12><13><14><15>

    Figure 14-30   Dynamic programming for edit distance

    ","path":["Chapter 14.   Dynamic programming","14.6   Edit distance problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#3-space-optimization","level":3,"title":"3.   Space optimization","text":"

    The state \\(dp[i,j]\\) depends on \\(dp[i-1, j]\\) above, \\(dp[i, j-1]\\) to the left, and \\(dp[i-1, j-1]\\) at the upper left. Forward iteration loses \\(dp[i-1, j-1]\\), while backward iteration cannot compute \\(dp[i, j-1]\\) in advance. Neither order alone is sufficient.

    We can use a variable leftup to preserve the upper-left solution \\(dp[i-1, j-1]\\), leaving only the left and upper entries to consider. The situation then matches unbounded knapsack, and forward iteration works. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py
    def edit_distance_dp_comp(s: str, t: str) -> int:\n    \"\"\"Edit distance: space-optimized dynamic programming\"\"\"\n    n, m = len(s), len(t)\n    dp = [0] * (m + 1)\n    # State transitions: first row\n    for j in range(1, m + 1):\n        dp[j] = j\n    # State transitions: remaining rows\n    for i in range(1, n + 1):\n        # State transitions: first column\n        leftup = dp[0]  # Temporarily store dp[i-1, j-1]\n        dp[0] += 1\n        # State transitions: remaining columns\n        for j in range(1, m + 1):\n            temp = dp[j]\n            if s[i - 1] == t[j - 1]:\n                # If the two characters are equal, skip both of them\n                dp[j] = leftup\n            else:\n                # Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = min(dp[j - 1], dp[j], leftup) + 1\n            leftup = temp  # Update to dp[i-1, j-1] for the next iteration\n    return dp[m]\n
    edit_distance.cpp
    /* Edit distance: space-optimized dynamic programming */\nint editDistanceDPComp(string s, string t) {\n    int n = s.length(), m = t.length();\n    vector<int> dp(m + 1, 0);\n    // State transitions: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i <= n; i++) {\n        // State transitions: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.java
    /* Edit distance: space-optimized dynamic programming */\nint editDistanceDPComp(String s, String t) {\n    int n = s.length(), m = t.length();\n    int[] dp = new int[m + 1];\n    // State transitions: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i <= n; i++) {\n        // State transitions: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s.charAt(i - 1) == t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.cs
    /* Edit distance: space-optimized dynamic programming */\nint EditDistanceDPComp(string s, string t) {\n    int n = s.Length, m = t.Length;\n    int[] dp = new int[m + 1];\n    // State transitions: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i <= n; i++) {\n        // State transitions: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.go
    /* Edit distance: space-optimized dynamic programming */\nfunc editDistanceDPComp(s string, t string) int {\n    n := len(s)\n    m := len(t)\n    dp := make([]int, m+1)\n    // State transitions: first row\n    for j := 1; j <= m; j++ {\n        dp[j] = j\n    }\n    // State transitions: remaining rows\n    for i := 1; i <= n; i++ {\n        // State transitions: first column\n        leftUp := dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transitions: remaining columns\n        for j := 1; j <= m; j++ {\n            temp := dp[j]\n            if s[i-1] == t[j-1] {\n                // If the two bytes are equal, skip both (each byte is one character for ASCII input)\n                dp[j] = leftUp\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1\n            }\n            leftUp = temp // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m]\n}\n
    edit_distance.swift
    /* Edit distance: space-optimized dynamic programming */\nfunc editDistanceDPComp(s: String, t: String) -> Int {\n    let n = s.utf8CString.count\n    let m = t.utf8CString.count\n    var dp = Array(repeating: 0, count: m + 1)\n    // State transitions: first row\n    for j in 1 ... m {\n        dp[j] = j\n    }\n    // State transitions: remaining rows\n    for i in 1 ... n {\n        // State transitions: first column\n        var leftup = dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transitions: remaining columns\n        for j in 1 ... m {\n            let temp = dp[j]\n            if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n                // If the two current UTF-8 bytes match, skip both\n                dp[j] = leftup\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n            }\n            leftup = temp // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m]\n}\n
    edit_distance.js
    /* Edit distance: space-optimized dynamic programming */\nfunction editDistanceDPComp(s, t) {\n    const n = s.length,\n        m = t.length;\n    const dp = new Array(m + 1).fill(0);\n    // State transitions: first row\n    for (let j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (let i = 1; i <= n; i++) {\n        // State transitions: first column\n        let leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (let j = 1; j <= m; j++) {\n            const temp = dp[j];\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.ts
    /* Edit distance: space-optimized dynamic programming */\nfunction editDistanceDPComp(s: string, t: string): number {\n    const n = s.length,\n        m = t.length;\n    const dp = new Array(m + 1).fill(0);\n    // State transitions: first row\n    for (let j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (let i = 1; i <= n; i++) {\n        // State transitions: first column\n        let leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (let j = 1; j <= m; j++) {\n            const temp = dp[j];\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.dart
    /* Edit distance: space-optimized dynamic programming */\nint editDistanceDPComp(String s, String t) {\n  int n = s.length, m = t.length;\n  List<int> dp = List.filled(m + 1, 0);\n  // State transitions: first row\n  for (int j = 1; j <= m; j++) {\n    dp[j] = j;\n  }\n  // State transitions: remaining rows\n  for (int i = 1; i <= n; i++) {\n    // State transitions: first column\n    int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n    dp[0] = i;\n    // State transitions: remaining columns\n    for (int j = 1; j <= m; j++) {\n      int temp = dp[j];\n      if (s[i - 1] == t[j - 1]) {\n        // If the two characters are equal, skip both of them\n        dp[j] = leftup;\n      } else {\n        // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n        dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n      }\n      leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n    }\n  }\n  return dp[m];\n}\n
    edit_distance.rs
    /* Edit distance: space-optimized dynamic programming */\nfn edit_distance_dp_comp(s: &str, t: &str) -> i32 {\n    let (n, m) = (s.len(), t.len());\n    let mut dp = vec![0; m + 1];\n    // State transitions: first row\n    for j in 1..m {\n        dp[j] = j as i32;\n    }\n    // State transitions: remaining rows\n    for i in 1..=n {\n        // State transitions: first column\n        let mut leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i as i32;\n        // State transitions: remaining columns\n        for j in 1..=m {\n            let temp = dp[j];\n            if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    dp[m]\n}\n
    edit_distance.c
    /* Edit distance: space-optimized dynamic programming */\nint editDistanceDPComp(char *s, char *t, int n, int m) {\n    int *dp = calloc(m + 1, sizeof(int));\n    // State transitions: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i <= n; i++) {\n        // State transitions: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    int res = dp[m];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    edit_distance.kt
    /* Edit distance: space-optimized dynamic programming */\nfun editDistanceDPComp(s: String, t: String): Int {\n    val n = s.length\n    val m = t.length\n    val dp = IntArray(m + 1)\n    // State transitions: first row\n    for (j in 1..m) {\n        dp[j] = j\n    }\n    // State transitions: remaining rows\n    for (i in 1..n) {\n        // State transitions: first column\n        var leftup = dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transitions: remaining columns\n        for (j in 1..m) {\n            val temp = dp[j]\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n            }\n            leftup = temp // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m]\n}\n
    edit_distance.rb
    ### Edit distance: space-optimized dynamic programming ###\ndef edit_distance_dp_comp(s, t)\n  n, m = s.length, t.length\n  dp = Array.new(m + 1, 0)\n  # State transitions: first row\n  (1...(m + 1)).each { |j| dp[j] = j }\n  # State transitions: remaining rows\n  for i in 1...(n + 1)\n    # State transitions: first column\n    leftup = dp.first # Temporarily store dp[i-1, j-1]\n    dp[0] += 1\n    # State transitions: remaining columns\n    for j in 1...(m + 1)\n      temp = dp[j]\n      if s[i - 1] == t[j - 1]\n        # If the two characters are equal, skip both of them\n        dp[j] = leftup\n      else\n        # Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n        dp[j] = [dp[j - 1], dp[j], leftup].min + 1\n      end\n      leftup = temp # Update to dp[i-1, j-1] for the next iteration\n    end\n  end\n  dp[m]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.6   Edit distance problem"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/","level":1,"title":"14.8   Exercises","text":"","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1481-check-your-understanding","level":2,"title":"14.8.1   Check your understanding","text":"","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1-when-is-dynamic-programming-a-good-fit","level":3,"title":"1.   When is dynamic programming a good fit?","text":"

    A student says, “Whenever you can write a recurrence relation, you should use dynamic programming.” For each task below, decide whether dynamic programming, backtracking, or a loop or mathematical formula without a dp table is the best fit. Give one key reason for each choice.

    1. Find the minimum number of coins needed to make an amount of 6 using denominations [1, 3, 4]. Each denomination can be used any number of times.
    2. Output all permutations of [1, 2, 3].
    3. Calculate \\(1 + 2 + \\dots + n\\).

      For the task you consider suitable for dynamic programming, also explain what dp[i] represents.

    Reference answer
    1. Dynamic programming is a good fit. Let dp[i] be the minimum number of coins needed to make an amount of i. For each coin c whose denomination does not exceed i, consider dp[i-c] + 1 as a candidate answer, then take the minimum of these candidates. Different choices repeatedly lead to the same amounts, and an optimal solution for a larger amount can be built from optimal solutions for smaller amounts. The answer for an amount of 6 is 2: 3 + 3.

    2. Use backtracking. The task requires generating all 6 permutations individually. Backtracking systematically makes a choice, continues the search, then undoes the choice and explores other branches. Whatever method you use, outputting all the permutations requires enumerating them.

    3. A loop or the formula for an arithmetic series is sufficient. Although you can write S(i) = S(i-1) + i, calculating S(i) depends on just one smaller value, S(i-1). Each partial sum needs to be calculated only once, so there are no overlapping subproblems and no need for a dp table. Being able to write a recurrence does not mean you need dynamic programming.

    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#2-how-do-you-calculate-a-cell-in-the-knapsack-table","level":3,"title":"2.   How do you calculate a cell in the knapsack table?","text":"

    Consider a 0-1 knapsack problem with item weights wgt = [1, 2, 3], values val = [5, 11, 15], and a knapsack capacity of 4. Here, dp[i][c] is the maximum value obtainable using only the first \\(i\\) items with a capacity limit of \\(c\\). The knapsack does not have to be filled exactly.

    Calculate just the state dp[3][4]. You are given dp[2][4] = 16 and dp[2][1] = 5.

    1. What is the candidate value if you do not take item 3?
    2. If you take item 3, how much capacity remains, and what is the candidate value?
    3. What should dp[3][4] be? Which items does this solution take?
    Reference answer
    1. If you do not take item 3, use the result for the first two items. The candidate value is dp[2][4] = 16.

    2. Item 3 weighs 3, so taking it leaves a capacity of \\(4-3=1\\). The candidate value is dp[2][1] + 15 = 5 + 15 = 20.

    3. Comparing 16 and 20 gives dp[3][4] = 20. This solution takes items 1 and 3, with a total weight of \\(1+3=4\\) and a total value of \\(5+15=20\\).

      Calculating this state illustrates a single “take it or leave it” comparison in the 0-1 knapsack problem.

    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#3-in-what-order-should-knapsack-capacities-be-updated","level":3,"title":"3.   In what order should knapsack capacities be updated?","text":"

    A 0-1 knapsack problem has just one item, with weight 2 and value 5. The knapsack capacity is 4. Each item can be taken at most once, and the initial one-dimensional array is dp = [0, 0, 0, 0, 0].

    A student processes this item by updating capacities from 2 to 4:

    • Updating dp[2] gives 5.
    • Updating dp[3] also gives 5.
    • When updating dp[4], the student uses the newly computed dp[2], obtaining dp[4] = 10.
    1. Is dp[4] = 10 correct? Why or why not?
    2. Given that each item can be taken at most once, what should dp[4] be?
    3. When processing each item, should capacities be updated from largest to smallest or from smallest to largest? What problem does this prevent?
    Reference answer
    1. The result is incorrect. A value of 10 means taking the item worth 5 twice, which violates the condition that each item can be taken at most once.

    2. The knapsack can contain only this one item, so the correct value of dp[4] is 5.

    3. Update capacities from largest to smallest: 4, then 3, then 2. This ensures that when calculating dp[c], the value read from dp[c-2] is still the result from before the current item was processed. The current item therefore cannot be used more than once in the same round of updates.

    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1482-programming-exercises","level":2,"title":"14.8.2   Programming exercises","text":"","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1-counting-ways-to-climb-stairs","level":3,"title":"1.   Counting ways to climb stairs","text":"

    A staircase has n steps. You can climb either 1 or 2 steps at a time and must land exactly on step n. Calculate the number of different ways to climb the staircase. Assume n >= 1; two ways differ only in their sequence of 1-step and 2-step moves. Use a one-dimensional dynamic programming array. For now, do not use the space optimization that retains only two states.

    Hints
    1. The final move to step i must be either a 1-step or a 2-step move.
    2. This gives dp[i] = dp[i-1] + dp[i-2].
    3. Handle n = 1 and n = 2 first, then fill the table starting at step 3.

    LeetCode

    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#2-0-1-knapsack","level":3,"title":"2.   0-1 knapsack","text":"

    You are given arrays wgt and val of equal length. Item i has a positive integer weight wgt[i] and a nonnegative integer value val[i]. The knapsack capacity cap is a nonnegative integer. Each item can be taken at most once. Find the maximum total value that fits in the knapsack without exceeding cap in total weight. Use one-dimensional dynamic programming.

    Hints
    1. Initialize an array dp of length cap + 1, where dp[c] is the maximum value obtainable with a capacity limit of c.
    2. When processing item i, compare dp[c] for leaving it out with dp[c-wgt[i]] + val[i] for taking it.
    3. Update capacities from largest to smallest to avoid taking the current item more than once in the same round.
    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/","level":1,"title":"14.1   Introduction to dynamic programming","text":"

    Dynamic programming is an important algorithmic paradigm. It breaks a problem into smaller subproblems and stores their solutions to avoid repeated computation, greatly improving time efficiency.

    We will start with a classic example, give a brute-force backtracking solution, identify its overlapping subproblems, and gradually derive a more efficient dynamic programming solution.

    Climbing stairs

    Given a staircase with \\(n\\) steps, you may climb \\(1\\) or \\(2\\) steps at a time. How many ways are there to reach the top?

    As shown in Figure 14-1, a staircase with \\(3\\) steps has \\(3\\) possible ways to reach the top.

    Figure 14-1   Number of ways to reach step 3

    The goal is to count solutions, so we can use backtracking to enumerate every possibility. View climbing as a sequence of choices: starting from the ground, choose to climb \\(1\\) or \\(2\\) steps each time. Add \\(1\\) to the count whenever we reach the top, and prune any branch that goes beyond it. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_backtrack.py
    def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int:\n    \"\"\"Backtracking\"\"\"\n    # When step n is reached, increase the number of ways by 1\n    if state == n:\n        res[0] += 1\n    # Iterate over all choices\n    for choice in choices:\n        # Prune: do not go beyond step n\n        if state + choice > n:\n            continue\n        # Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res)\n        # Backtrack\n\ndef climbing_stairs_backtrack(n: int) -> int:\n    \"\"\"Climbing stairs: backtracking\"\"\"\n    choices = [1, 2]  # Choose to climb 1 or 2 steps\n    state = 0  # Start climbing from step 0\n    res = [0]  # Use res[0] to record the number of ways\n    backtrack(choices, state, n, res)\n    return res[0]\n
    climbing_stairs_backtrack.cpp
    /* Backtracking */\nvoid backtrack(vector<int> &choices, int state, int n, vector<int> &res) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res[0]++;\n    // Iterate over all choices\n    for (auto &choice : choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n)\n            continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nint climbingStairsBacktrack(int n) {\n    vector<int> choices = {1, 2}; // Choose to climb 1 or 2 steps\n    int state = 0;                // Start climbing from step 0\n    vector<int> res = {0};        // Use res[0] to record the number of ways\n    backtrack(choices, state, n, res);\n    return res[0];\n}\n
    climbing_stairs_backtrack.java
    /* Backtracking */\nvoid backtrack(List<Integer> choices, int state, int n, List<Integer> res) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res.set(0, res.get(0) + 1);\n    // Iterate over all choices\n    for (Integer choice : choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n)\n            continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nint climbingStairsBacktrack(int n) {\n    List<Integer> choices = Arrays.asList(1, 2); // Choose to climb 1 or 2 steps\n    int state = 0; // Start climbing from step 0\n    List<Integer> res = new ArrayList<>();\n    res.add(0); // Use res[0] to record the number of ways\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
    climbing_stairs_backtrack.cs
    /* Backtracking */\nvoid Backtrack(List<int> choices, int state, int n, List<int> res) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res[0]++;\n    // Iterate over all choices\n    foreach (int choice in choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n)\n            continue;\n        // Try: make a choice and update the state\n        Backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nint ClimbingStairsBacktrack(int n) {\n    List<int> choices = [1, 2]; // Choose to climb 1 or 2 steps\n    int state = 0; // Start climbing from step 0\n    List<int> res = [0]; // Use res[0] to record the number of ways\n    Backtrack(choices, state, n, res);\n    return res[0];\n}\n
    climbing_stairs_backtrack.go
    /* Backtracking */\nfunc backtrack(choices []int, state, n int, res []int) {\n    // When step n is reached, increase the number of ways by 1\n    if state == n {\n        res[0] = res[0] + 1\n    }\n    // Iterate over all choices\n    for _, choice := range choices {\n        // Prune: do not go beyond step n\n        if state+choice > n {\n            continue\n        }\n        // Try: make a choice and update the state\n        backtrack(choices, state+choice, n, res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfunc climbingStairsBacktrack(n int) int {\n    // Choose to climb 1 or 2 steps\n    choices := []int{1, 2}\n    // Start climbing from step 0\n    state := 0\n    res := make([]int, 1)\n    // Use res[0] to record the number of ways\n    res[0] = 0\n    backtrack(choices, state, n, res)\n    return res[0]\n}\n
    climbing_stairs_backtrack.swift
    /* Backtracking */\nfunc backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) {\n    // When step n is reached, increase the number of ways by 1\n    if state == n {\n        res[0] += 1\n    }\n    // Iterate over all choices\n    for choice in choices {\n        // Prune: do not go beyond step n\n        if state + choice > n {\n            continue\n        }\n        // Try: make a choice and update the state\n        backtrack(choices: choices, state: state + choice, n: n, res: &res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfunc climbingStairsBacktrack(n: Int) -> Int {\n    let choices = [1, 2] // Choose to climb 1 or 2 steps\n    let state = 0 // Start climbing from step 0\n    var res: [Int] = []\n    res.append(0) // Use res[0] to record the number of ways\n    backtrack(choices: choices, state: state, n: n, res: &res)\n    return res[0]\n}\n
    climbing_stairs_backtrack.js
    /* Backtracking */\nfunction backtrack(choices, state, n, res) {\n    // When step n is reached, increase the number of ways by 1\n    if (state === n) res.set(0, res.get(0) + 1);\n    // Iterate over all choices\n    for (const choice of choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n) continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfunction climbingStairsBacktrack(n) {\n    const choices = [1, 2]; // Choose to climb 1 or 2 steps\n    const state = 0; // Start climbing from step 0\n    const res = new Map();\n    res.set(0, 0); // Store the number of ways at key 0 in res\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
    climbing_stairs_backtrack.ts
    /* Backtracking */\nfunction backtrack(\n    choices: number[],\n    state: number,\n    n: number,\n    res: Map<0, any>\n): void {\n    // When step n is reached, increase the number of ways by 1\n    if (state === n) res.set(0, res.get(0) + 1);\n    // Iterate over all choices\n    for (const choice of choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n) continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfunction climbingStairsBacktrack(n: number): number {\n    const choices = [1, 2]; // Choose to climb 1 or 2 steps\n    const state = 0; // Start climbing from step 0\n    const res = new Map();\n    res.set(0, 0); // Store the number of ways at key 0 in res\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
    climbing_stairs_backtrack.dart
    /* Backtracking */\nvoid backtrack(List<int> choices, int state, int n, List<int> res) {\n  // When step n is reached, increase the number of ways by 1\n  if (state == n) {\n    res[0]++;\n  }\n  // Iterate over all choices\n  for (int choice in choices) {\n    // Prune: do not go beyond step n\n    if (state + choice > n) continue;\n    // Try: make a choice and update the state\n    backtrack(choices, state + choice, n, res);\n    // Backtrack\n  }\n}\n\n/* Climbing stairs: backtracking */\nint climbingStairsBacktrack(int n) {\n  List<int> choices = [1, 2]; // Choose to climb 1 or 2 steps\n  int state = 0; // Start climbing from step 0\n  List<int> res = [];\n  res.add(0); // Use res[0] to record the number of ways\n  backtrack(choices, state, n, res);\n  return res[0];\n}\n
    climbing_stairs_backtrack.rs
    /* Backtracking */\nfn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) {\n    // When step n is reached, increase the number of ways by 1\n    if state == n {\n        res[0] = res[0] + 1;\n    }\n    // Iterate over all choices\n    for &choice in choices {\n        // Prune: do not go beyond step n\n        if state + choice > n {\n            continue;\n        }\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfn climbing_stairs_backtrack(n: usize) -> i32 {\n    let choices = vec![1, 2]; // Choose to climb 1 or 2 steps\n    let state = 0; // Start climbing from step 0\n    let mut res = Vec::new();\n    res.push(0); // Use res[0] to record the number of ways\n    backtrack(&choices, state, n as i32, &mut res);\n    res[0]\n}\n
    climbing_stairs_backtrack.c
    /* Backtracking */\nvoid backtrack(int *choices, int state, int n, int *res, int len) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res[0]++;\n    // Iterate over all choices\n    for (int i = 0; i < len; i++) {\n        int choice = choices[i];\n        // Prune: do not go beyond step n\n        if (state + choice > n)\n            continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res, len);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nint climbingStairsBacktrack(int n) {\n    int choices[2] = {1, 2}; // Choose to climb 1 or 2 steps\n    int state = 0;           // Start climbing from step 0\n    int *res = (int *)malloc(sizeof(int));\n    *res = 0; // Use res[0] to record the number of ways\n    int len = sizeof(choices) / sizeof(int);\n    backtrack(choices, state, n, res, len);\n    int result = *res;\n    free(res);\n    return result;\n}\n
    climbing_stairs_backtrack.kt
    /* Backtracking */\nfun backtrack(\n    choices: MutableList<Int>,\n    state: Int,\n    n: Int,\n    res: MutableList<Int>\n) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res[0] = res[0] + 1\n    // Iterate over all choices\n    for (choice in choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n) continue\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfun climbingStairsBacktrack(n: Int): Int {\n    val choices = mutableListOf(1, 2) // Choose to climb 1 or 2 steps\n    val state = 0 // Start climbing from step 0\n    val res = mutableListOf<Int>()\n    res.add(0) // Use res[0] to record the number of ways\n    backtrack(choices, state, n, res)\n    return res[0]\n}\n
    climbing_stairs_backtrack.rb
    ### Backtracking ###\ndef backtrack(choices, state, n, res)\n  # When step n is reached, increase the number of ways by 1\n  res[0] += 1 if state == n\n  # Iterate over all choices\n  for choice in choices\n    # Prune: do not go beyond step n\n    next if state + choice > n\n\n    # Try: make a choice and update the state\n    backtrack(choices, state + choice, n, res)\n  end\n  # Backtrack\nend\n\n### Climbing stairs: backtracking ###\ndef climbing_stairs_backtrack(n)\n  choices = [1, 2] # Choose to climb 1 or 2 steps\n  state = 0 # Start climbing from step 0\n  res = [0] # Use res[0] to record the number of ways\n  backtrack(choices, state, n, res)\n  res.first\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1411-method-1-brute-force-search","level":2,"title":"14.1.1   Method 1: Brute-force search","text":"

    Backtracking usually does not explicitly decompose the problem. Instead, it treats a solution as a sequence of decisions and searches all possible solutions through trial and pruning.

    Let's analyze this problem through decomposition. Let \\(dp[i]\\) be the number of ways to reach step \\(i\\). The original problem is \\(dp[i]\\), and its subproblems include:

    \\[ dp[i-1], dp[i-2], \\dots, dp[2], dp[1] \\]

    Since each move climbs \\(1\\) or \\(2\\) steps, when we stand on step \\(i\\), we must previously have been on step \\(i - 1\\) or \\(i - 2\\). In other words, step \\(i\\) can only be reached from step \\(i -1\\) or \\(i - 2\\).

    This gives an important result: the number of ways to reach step \\(i - 1\\) plus the number of ways to reach step \\(i - 2\\) equals the number of ways to reach step \\(i\\). In symbols:

    \\[ dp[i] = dp[i-1] + dp[i-2] \\]

    Thus, the subproblems are linked by a recurrence: the solution to the original problem can be constructed from subproblem solutions. Figure 14-2 illustrates this relationship.

    Figure 14-2   Recurrence for the number of ways

    The recurrence gives a brute-force search solution. Starting at \\(dp[n]\\), recursively express a larger problem as the sum of two smaller ones until reaching \\(dp[1]\\) or \\(dp[2]\\). Their solutions are known: \\(dp[1] = 1\\) and \\(dp[2] = 2\\), meaning steps \\(1\\) and \\(2\\) can be reached in \\(1\\) and \\(2\\) ways, respectively.

    Like the standard backtracking code, the following code performs depth-first search, but is more concise:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs.py
    def dfs(i: int) -> int:\n    \"\"\"Search\"\"\"\n    # The values of dp[1] and dp[2] are known; return the corresponding value\n    if i == 1 or i == 2:\n        return i\n    # dp[i] = dp[i-1] + dp[i-2]\n    count = dfs(i - 1) + dfs(i - 2)\n    return count\n\ndef climbing_stairs_dfs(n: int) -> int:\n    \"\"\"Climbing stairs: search\"\"\"\n    return dfs(n)\n
    climbing_stairs_dfs.cpp
    /* Search */\nint dfs(int i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.java
    /* Search */\nint dfs(int i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.cs
    /* Search */\nint DFS(int i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = DFS(i - 1) + DFS(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nint ClimbingStairsDFS(int n) {\n    return DFS(n);\n}\n
    climbing_stairs_dfs.go
    /* Search */\nfunc dfs(i int) int {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    count := dfs(i-1) + dfs(i-2)\n    return count\n}\n\n/* Climbing stairs: search */\nfunc climbingStairsDFS(n int) int {\n    return dfs(n)\n}\n
    climbing_stairs_dfs.swift
    /* Search */\nfunc dfs(i: Int) -> Int {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i: i - 1) + dfs(i: i - 2)\n    return count\n}\n\n/* Climbing stairs: search */\nfunc climbingStairsDFS(n: Int) -> Int {\n    dfs(i: n)\n}\n
    climbing_stairs_dfs.js
    /* Search */\nfunction dfs(i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i === 1 || i === 2) return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nfunction climbingStairsDFS(n) {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.ts
    /* Search */\nfunction dfs(i: number): number {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i === 1 || i === 2) return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nfunction climbingStairsDFS(n: number): number {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.dart
    /* Search */\nint dfs(int i) {\n  // The values of dp[1] and dp[2] are known; return them\n  if (i == 1 || i == 2) return i;\n  // dp[i] = dp[i-1] + dp[i-2]\n  int count = dfs(i - 1) + dfs(i - 2);\n  return count;\n}\n\n/* Climbing stairs: search */\nint climbingStairsDFS(int n) {\n  return dfs(n);\n}\n
    climbing_stairs_dfs.rs
    /* Search */\nfn dfs(i: usize) -> i32 {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i as i32;\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i - 1) + dfs(i - 2);\n    count\n}\n\n/* Climbing stairs: search */\nfn climbing_stairs_dfs(n: usize) -> i32 {\n    dfs(n)\n}\n
    climbing_stairs_dfs.c
    /* Search */\nint dfs(int i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.kt
    /* Search */\nfun dfs(i: Int): Int {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2) return i\n    // dp[i] = dp[i-1] + dp[i-2]\n    val count = dfs(i - 1) + dfs(i - 2)\n    return count\n}\n\n/* Climbing stairs: search */\nfun climbingStairsDFS(n: Int): Int {\n    return dfs(n)\n}\n
    climbing_stairs_dfs.rb
    ### Search ###\ndef dfs(i)\n  # The values of dp[1] and dp[2] are known; return them\n  return i if i == 1 || i == 2\n  # dp[i] = dp[i-1] + dp[i-2]\n  dfs(i - 1) + dfs(i - 2)\nend\n\n### Climbing stairs: search ###\ndef climbing_stairs_dfs(n)\n  dfs(n)\nend\n
    Visualize execution

    View full screen >

    Figure 14-3 shows the recursion tree for brute-force search. For \\(dp[n]\\), its depth is \\(n\\), and the time complexity is \\(O(2^n)\\). Exponential growth is explosive, so a relatively large \\(n\\) leads to a very long wait.

    Figure 14-3   Recursion tree for climbing stairs

    As Figure 14-3 shows, overlapping subproblems cause the exponential time complexity. For example, \\(dp[9]\\) splits into \\(dp[8]\\) and \\(dp[7]\\), while \\(dp[8]\\) splits into \\(dp[7]\\) and \\(dp[6]\\). Both contain \\(dp[7]\\).

    These subproblems in turn contain smaller overlapping subproblems, and the pattern repeats at deeper levels of the recursion tree. Most of the computation is wasted on solving the same subproblems repeatedly.

    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1412-method-2-memoized-search","level":2,"title":"14.1.2   Method 2: Memoized search","text":"

    To improve efficiency, we want every overlapping subproblem to be solved only once. Introduce an array mem to record each solution and prune repeated subproblems during the search.

    1. The first time we calculate \\(dp[i]\\), store it in mem[i] for later use.
    2. When \\(dp[i]\\) is needed again, retrieve it directly from mem[i] instead of recomputing it.

    The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs_mem.py
    def dfs(i: int, mem: list[int]) -> int:\n    \"\"\"Memoized search\"\"\"\n    # The values of dp[1] and dp[2] are known; return the corresponding value\n    if i == 1 or i == 2:\n        return i\n    # If dp[i] has been recorded, return it directly\n    if mem[i] != -1:\n        return mem[i]\n    # dp[i] = dp[i-1] + dp[i-2]\n    count = dfs(i - 1, mem) + dfs(i - 2, mem)\n    # Record dp[i]\n    mem[i] = count\n    return count\n\ndef climbing_stairs_dfs_mem(n: int) -> int:\n    \"\"\"Climbing stairs: memoized search\"\"\"\n    # mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    mem = [-1] * (n + 1)\n    return dfs(n, mem)\n
    climbing_stairs_dfs_mem.cpp
    /* Memoized search */\nint dfs(int i, vector<int> &mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    vector<int> mem(n + 1, -1);\n    return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.java
    /* Memoized search */\nint dfs(int i, int[] mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    int[] mem = new int[n + 1];\n    Arrays.fill(mem, -1);\n    return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.cs
    /* Memoized search */\nint DFS(int i, int[] mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = DFS(i - 1, mem) + DFS(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nint ClimbingStairsDFSMem(int n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    int[] mem = new int[n + 1];\n    Array.Fill(mem, -1);\n    return DFS(n, mem);\n}\n
    climbing_stairs_dfs_mem.go
    /* Memoized search */\nfunc dfsMem(i int, mem []int) int {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // If dp[i] has been recorded, return it directly\n    if mem[i] != -1 {\n        return mem[i]\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    count := dfsMem(i-1, mem) + dfsMem(i-2, mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: memoized search */\nfunc climbingStairsDFSMem(n int) int {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    mem := make([]int, n+1)\n    for i := range mem {\n        mem[i] = -1\n    }\n    return dfsMem(n, mem)\n}\n
    climbing_stairs_dfs_mem.swift
    /* Memoized search */\nfunc dfs(i: Int, mem: inout [Int]) -> Int {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // If dp[i] has been recorded, return it directly\n    if mem[i] != -1 {\n        return mem[i]\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: memoized search */\nfunc climbingStairsDFSMem(n: Int) -> Int {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    var mem = Array(repeating: -1, count: n + 1)\n    return dfs(i: n, mem: &mem)\n}\n
    climbing_stairs_dfs_mem.js
    /* Memoized search */\nfunction dfs(i, mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i === 1 || i === 2) return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1) return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nfunction climbingStairsDFSMem(n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    const mem = new Array(n + 1).fill(-1);\n    return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.ts
    /* Memoized search */\nfunction dfs(i: number, mem: number[]): number {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i === 1 || i === 2) return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1) return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nfunction climbingStairsDFSMem(n: number): number {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    const mem = new Array(n + 1).fill(-1);\n    return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.dart
    /* Memoized search */\nint dfs(int i, List<int> mem) {\n  // The values of dp[1] and dp[2] are known; return them\n  if (i == 1 || i == 2) return i;\n  // If dp[i] has been recorded, return it directly\n  if (mem[i] != -1) return mem[i];\n  // dp[i] = dp[i-1] + dp[i-2]\n  int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n  // Record dp[i]\n  mem[i] = count;\n  return count;\n}\n\n/* Climbing stairs: memoized search */\nint climbingStairsDFSMem(int n) {\n  // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n  List<int> mem = List.filled(n + 1, -1);\n  return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.rs
    /* Memoized search */\nfn dfs(i: usize, mem: &mut [i32]) -> i32 {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i as i32;\n    }\n    // If dp[i] has been recorded, return it directly\n    if mem[i] != -1 {\n        return mem[i];\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    count\n}\n\n/* Climbing stairs: memoized search */\nfn climbing_stairs_dfs_mem(n: usize) -> i32 {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    let mut mem = vec![-1; n + 1];\n    dfs(n, &mut mem)\n}\n
    climbing_stairs_dfs_mem.c
    /* Memoized search */\nint dfs(int i, int *mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    int *mem = (int *)malloc((n + 1) * sizeof(int));\n    for (int i = 0; i <= n; i++) {\n        mem[i] = -1;\n    }\n    int result = dfs(n, mem);\n    free(mem);\n    return result;\n}\n
    climbing_stairs_dfs_mem.kt
    /* Memoized search */\nfun dfs(i: Int, mem: IntArray): Int {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2) return i\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1) return mem[i]\n    // dp[i] = dp[i-1] + dp[i-2]\n    val count = dfs(i - 1, mem) + dfs(i - 2, mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: memoized search */\nfun climbingStairsDFSMem(n: Int): Int {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    val mem = IntArray(n + 1)\n    mem.fill(-1)\n    return dfs(n, mem)\n}\n
    climbing_stairs_dfs_mem.rb
    ### Memoized search ###\ndef dfs(i, mem)\n  # The values of dp[1] and dp[2] are known; return them\n  return i if i == 1 || i == 2\n  # If dp[i] has been recorded, return it directly\n  return mem[i] if mem[i] != -1\n\n  # dp[i] = dp[i-1] + dp[i-2]\n  count = dfs(i - 1, mem) + dfs(i - 2, mem)\n  # Record dp[i]\n  mem[i] = count\nend\n\n### Climbing stairs: memoized search ###\ndef climbing_stairs_dfs_mem(n)\n  # mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n  mem = Array.new(n + 1, -1)\n  dfs(n, mem)\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-4, memoization makes every overlapping subproblem require only one computation, reducing the time complexity to \\(O(n)\\): a major improvement.

    Figure 14-4   Recursion tree for memoized search

    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1413-method-3-dynamic-programming","level":2,"title":"14.1.3   Method 3: Dynamic programming","text":"

    Memoized search is a top-down approach: start with the original problem (the root), recursively divide larger subproblems into smaller ones until reaching the smallest subproblems with known solutions (the leaves), then collect their solutions as recursion returns to construct the solution to the original problem.

    In contrast, dynamic programming is a bottom-up approach: start with the smallest subproblem solutions and iteratively build solutions to larger subproblems until the original problem is solved.

    Because this dynamic programming process does not backtrack, it needs only iterative loops, not recursion. In the following code, the array dp stores subproblem solutions, serving the same role as mem in memoized search:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py
    def climbing_stairs_dp(n: int) -> int:\n    \"\"\"Climbing stairs: dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return n\n    # Initialize the dp table to store subproblem solutions\n    dp = [0] * (n + 1)\n    # Initial states: set the solutions to the smallest subproblems\n    dp[1], dp[2] = 1, 2\n    # State transitions: solve progressively larger subproblems using smaller ones\n    for i in range(3, n + 1):\n        dp[i] = dp[i - 1] + dp[i - 2]\n    return dp[n]\n
    climbing_stairs_dp.cpp
    /* Climbing stairs: dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize the dp table to store subproblem solutions\n    vector<int> dp(n + 1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.java
    /* Climbing stairs: dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize the dp table to store subproblem solutions\n    int[] dp = new int[n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.cs
    /* Climbing stairs: dynamic programming */\nint ClimbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize the dp table to store subproblem solutions\n    int[] dp = new int[n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.go
    /* Climbing stairs: dynamic programming */\nfunc climbingStairsDP(n int) int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    // Initialize the dp table to store subproblem solutions\n    dp := make([]int, n+1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1\n    dp[2] = 2\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i] = dp[i-1] + dp[i-2]\n    }\n    return dp[n]\n}\n
    climbing_stairs_dp.swift
    /* Climbing stairs: dynamic programming */\nfunc climbingStairsDP(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    // Initialize the dp table to store subproblem solutions\n    var dp = Array(repeating: 0, count: n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1\n    dp[2] = 2\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3 ... n {\n        dp[i] = dp[i - 1] + dp[i - 2]\n    }\n    return dp[n]\n}\n
    climbing_stairs_dp.js
    /* Climbing stairs: dynamic programming */\nfunction climbingStairsDP(n) {\n    if (n === 1 || n === 2) return n;\n    // Initialize the dp table to store subproblem solutions\n    const dp = new Array(n + 1).fill(-1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.ts
    /* Climbing stairs: dynamic programming */\nfunction climbingStairsDP(n: number): number {\n    if (n === 1 || n === 2) return n;\n    // Initialize the dp table to store subproblem solutions\n    const dp = new Array(n + 1).fill(-1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.dart
    /* Climbing stairs: dynamic programming */\nint climbingStairsDP(int n) {\n  if (n == 1 || n == 2) return n;\n  // Initialize the dp table to store subproblem solutions\n  List<int> dp = List.filled(n + 1, 0);\n  // Initial states: set the solutions to the smallest subproblems\n  dp[1] = 1;\n  dp[2] = 2;\n  // State transitions: solve progressively larger subproblems using smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i] = dp[i - 1] + dp[i - 2];\n  }\n  return dp[n];\n}\n
    climbing_stairs_dp.rs
    /* Climbing stairs: dynamic programming */\nfn climbing_stairs_dp(n: usize) -> i32 {\n    // The values of dp[1] and dp[2] are known; return them\n    if n == 1 || n == 2 {\n        return n as i32;\n    }\n    // Initialize the dp table to store subproblem solutions\n    let mut dp = vec![-1; n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3..=n {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    dp[n]\n}\n
    climbing_stairs_dp.c
    /* Climbing stairs: dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize the dp table to store subproblem solutions\n    int *dp = (int *)malloc((n + 1) * sizeof(int));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    int result = dp[n];\n    free(dp);\n    return result;\n}\n
    climbing_stairs_dp.kt
    /* Climbing stairs: dynamic programming */\nfun climbingStairsDP(n: Int): Int {\n    if (n == 1 || n == 2) return n\n    // Initialize the dp table to store subproblem solutions\n    val dp = IntArray(n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1\n    dp[2] = 2\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (i in 3..n) {\n        dp[i] = dp[i - 1] + dp[i - 2]\n    }\n    return dp[n]\n}\n
    climbing_stairs_dp.rb
    ### Climbing stairs: dynamic programming ###\ndef climbing_stairs_dp(n)\n  return n  if n == 1 || n == 2\n\n  # Initialize the dp table to store subproblem solutions\n  dp = Array.new(n + 1, 0)\n  # Initial states: set the solutions to the smallest subproblems\n  dp[1], dp[2] = 1, 2\n  # State transitions: solve progressively larger subproblems using smaller ones\n  (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }\n\n  dp[n]\nend\n
    Visualize execution

    View full screen >

    Figure 14-5 traces the code's execution.

    Figure 14-5   Dynamic programming for climbing stairs

    Like backtracking, dynamic programming uses states to represent stages of solving a problem. Each state corresponds to a subproblem and its locally optimal solution. For climbing stairs, the state is the current step number \\(i\\).

    We can now summarize some common dynamic programming terminology.

    • The array dp is called the dp table, and \\(dp[i]\\) stores the solution to the subproblem for state \\(i\\).
    • The states for the smallest subproblems (steps \\(1\\) and \\(2\\)) are called the initial states.
    • The recurrence \\(dp[i] = dp[i-1] + dp[i-2]\\) is called the state transition equation.
    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1414-space-optimization","level":2,"title":"14.1.4   Space optimization","text":"

    You may have noticed that \\(dp[i]\\) depends only on \\(dp[i-1]\\) and \\(dp[i-2]\\), so we do not need an entire dp array storing every subproblem solution. Two variables updated as we proceed are enough. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py
    def climbing_stairs_dp_comp(n: int) -> int:\n    \"\"\"Climbing stairs: space-optimized dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return n\n    a, b = 1, 2\n    for _ in range(3, n + 1):\n        a, b = b, a + b\n    return b\n
    climbing_stairs_dp.cpp
    /* Climbing stairs: space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.java
    /* Climbing stairs: space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.cs
    /* Climbing stairs: space-optimized dynamic programming */\nint ClimbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.go
    /* Climbing stairs: space-optimized dynamic programming */\nfunc climbingStairsDPComp(n int) int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    a, b := 1, 2\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        a, b = b, a+b\n    }\n    return b\n}\n
    climbing_stairs_dp.swift
    /* Climbing stairs: space-optimized dynamic programming */\nfunc climbingStairsDPComp(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    var a = 1\n    var b = 2\n    for _ in 3 ... n {\n        (a, b) = (b, a + b)\n    }\n    return b\n}\n
    climbing_stairs_dp.js
    /* Climbing stairs: space-optimized dynamic programming */\nfunction climbingStairsDPComp(n) {\n    if (n === 1 || n === 2) return n;\n    let a = 1,\n        b = 2;\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.ts
    /* Climbing stairs: space-optimized dynamic programming */\nfunction climbingStairsDPComp(n: number): number {\n    if (n === 1 || n === 2) return n;\n    let a = 1,\n        b = 2;\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.dart
    /* Climbing stairs: space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n  if (n == 1 || n == 2) return n;\n  int a = 1, b = 2;\n  for (int i = 3; i <= n; i++) {\n    int tmp = b;\n    b = a + b;\n    a = tmp;\n  }\n  return b;\n}\n
    climbing_stairs_dp.rs
    /* Climbing stairs: space-optimized dynamic programming */\nfn climbing_stairs_dp_comp(n: usize) -> i32 {\n    if n == 1 || n == 2 {\n        return n as i32;\n    }\n    let (mut a, mut b) = (1, 2);\n    for _ in 3..=n {\n        let tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    b\n}\n
    climbing_stairs_dp.c
    /* Climbing stairs: space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.kt
    /* Climbing stairs: space-optimized dynamic programming */\nfun climbingStairsDPComp(n: Int): Int {\n    if (n == 1 || n == 2) return n\n    var a = 1\n    var b = 2\n    for (i in 3..n) {\n        val temp = b\n        b += a\n        a = temp\n    }\n    return b\n}\n
    climbing_stairs_dp.rb
    ### Climbing stairs: space-optimized dynamic programming ###\ndef climbing_stairs_dp_comp(n)\n  return n if n == 1 || n == 2\n\n  a, b = 1, 2\n  (3...(n + 1)).each { a, b = b, a + b }\n\n  b\nend\n
    Visualize execution

    View full screen >

    Removing the dp array reduces space complexity from \\(O(n)\\) to \\(O(1)\\).

    In dynamic programming, a state often depends on only a limited number of preceding states. We can keep just those needed, reducing the storage dimensions to save memory. This technique is called using rolling variables or a rolling array.

    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/","level":1,"title":"14.4   0-1 knapsack problem","text":"

    The knapsack problem is an excellent introduction to dynamic programming and one of its most common problem families. Variants include 0-1 knapsack, unbounded knapsack, and bounded knapsack.

    We will begin with the most common variant, 0-1 knapsack.

    Question

    Given \\(n\\) items, where item \\(i\\) has weight \\(wgt[i-1]\\) and value \\(val[i-1]\\), and a knapsack of capacity \\(cap\\), each item may be chosen only once. What is the maximum total value that fits within the capacity?

    As shown in Figure 14-17, item numbers \\(i\\) start at \\(1\\), while array indices start at \\(0\\). Item \\(i\\) therefore has weight \\(wgt[i-1]\\) and value \\(val[i-1]\\).

    Figure 14-17   Example input for 0-1 knapsack

    We can view 0-1 knapsack as \\(n\\) rounds of decisions, with two choices for each item: leave it out or put it in. It therefore fits the decision tree model.

    The objective is to maximize value within a capacity limit, making dynamic programming a promising approach.

    Step 1: Identify each decision, define the state, and establish the \\(dp\\) table

    Leaving an item out keeps the available capacity unchanged; putting it in reduces the capacity. The state therefore consists of the current item number \\(i\\) and capacity \\(c\\), denoted by \\([i, c]\\).

    State \\([i, c]\\) represents the subproblem of finding the maximum value obtainable from the first \\(i\\) items with capacity \\(c\\), denoted by \\(dp[i, c]\\).

    We seek \\(dp[n, cap]\\), so we need a two-dimensional \\(dp\\) table of size \\((n+1) \\times (cap+1)\\).

    Step 2: Identify optimal substructure and derive the state transition equation

    After deciding what to do with item \\(i\\), the remaining subproblem concerns the first \\(i-1\\) items. There are two cases.

    • Leave item \\(i\\) out: Capacity stays unchanged, and the remaining state is \\([i-1, c]\\).
    • Put item \\(i\\) in: Capacity decreases by \\(wgt[i-1]\\), value increases by \\(val[i-1]\\), and the remaining state is \\([i-1, c-wgt[i-1]]\\).

    This reveals the optimal substructure: the maximum value \\(dp[i, c]\\) is the larger value obtained by leaving item \\(i\\) out or putting item \\(i\\) in. The state transition equation is:

    \\[ dp[i, c] = \\max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) \\]

    If the current item's weight \\(wgt[i - 1]\\) exceeds the remaining capacity \\(c\\), the only option is to leave it out.

    Step 3: Determine the boundary conditions and evaluation order

    With no items or capacity \\(0\\), the maximum value is \\(0\\). Thus, the first column \\(dp[i, 0]\\) and first row \\(dp[0, c]\\) are both \\(0\\).

    The current state \\([i, c]\\) depends on \\([i-1, c]\\) above and \\([i-1, c-wgt[i-1]]\\) to the upper left, so two nested loops can traverse the \\(dp\\) table in increasing index order.

    We will now implement brute-force search, memoized search, and dynamic programming in that order.

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#1-method-1-brute-force-search","level":3,"title":"1.   Method 1: Brute-force search","text":"

    The search has the following elements.

    • Parameters: The state \\([i, c]\\).
    • Return value: The subproblem solution \\(dp[i, c]\\).
    • Base case: If there are no items left, \\(i = 0\\), or the remaining capacity is \\(0\\), stop recursion and return value \\(0\\).
    • Pruning: If the current item is heavier than the remaining capacity, only the choice to leave it out is allowed.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
    def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int:\n    \"\"\"0-1 knapsack: brute-force search\"\"\"\n    # If all items have been considered or no capacity remains, return value 0\n    if i == 0 or c == 0:\n        return 0\n    # If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c:\n        return knapsack_dfs(wgt, val, i - 1, c)\n    # Calculate the maximum values obtained by excluding and including item i\n    no = knapsack_dfs(wgt, val, i - 1, c)\n    yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n    # Return the larger value from the two choices\n    return max(no, yes)\n
    knapsack.cpp
    /* 0-1 knapsack: brute-force search */\nint knapsackDFS(vector<int> &wgt, vector<int> &val, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return max(no, yes);\n}\n
    knapsack.java
    /* 0-1 knapsack: brute-force search */\nint knapsackDFS(int[] wgt, int[] val, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return Math.max(no, yes);\n}\n
    knapsack.cs
    /* 0-1 knapsack: brute-force search */\nint KnapsackDFS(int[] weight, int[] val, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (weight[i - 1] > c) {\n        return KnapsackDFS(weight, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = KnapsackDFS(weight, val, i - 1, c);\n    int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return Math.Max(no, yes);\n}\n
    knapsack.go
    /* 0-1 knapsack: brute-force search */\nfunc knapsackDFS(wgt, val []int, i, c int) int {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i-1] > c {\n        return knapsackDFS(wgt, val, i-1, c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    no := knapsackDFS(wgt, val, i-1, c)\n    yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1]\n    // Return the larger value from the two choices\n    return int(math.Max(float64(no), float64(yes)))\n}\n
    knapsack.swift
    /* 0-1 knapsack: brute-force search */\nfunc knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c {\n        return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n    let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n    // Return the larger value from the two choices\n    return max(no, yes)\n}\n
    knapsack.js
    /* 0-1 knapsack: brute-force search */\nfunction knapsackDFS(wgt, val, i, c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    const no = knapsackDFS(wgt, val, i - 1, c);\n    const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return Math.max(no, yes);\n}\n
    knapsack.ts
    /* 0-1 knapsack: brute-force search */\nfunction knapsackDFS(\n    wgt: Array<number>,\n    val: Array<number>,\n    i: number,\n    c: number\n): number {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    const no = knapsackDFS(wgt, val, i - 1, c);\n    const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return Math.max(no, yes);\n}\n
    knapsack.dart
    /* 0-1 knapsack: brute-force search */\nint knapsackDFS(List<int> wgt, List<int> val, int i, int c) {\n  // If all items have been considered or no capacity remains, return value 0\n  if (i == 0 || c == 0) {\n    return 0;\n  }\n  // If the item exceeds the remaining capacity, it must be excluded\n  if (wgt[i - 1] > c) {\n    return knapsackDFS(wgt, val, i - 1, c);\n  }\n  // Calculate the maximum values obtained by excluding and including item i\n  int no = knapsackDFS(wgt, val, i - 1, c);\n  int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n  // Return the larger value from the two choices\n  return max(no, yes);\n}\n
    knapsack.rs
    /* 0-1 knapsack: brute-force search */\nfn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c as i32 {\n        return knapsack_dfs(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    let no = knapsack_dfs(wgt, val, i - 1, c);\n    let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n    // Return the larger value from the two choices\n    std::cmp::max(no, yes)\n}\n
    knapsack.c
    /* 0-1 knapsack: brute-force search */\nint knapsackDFS(int wgt[], int val[], int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return myMax(no, yes);\n}\n
    knapsack.kt
    /* 0-1 knapsack: brute-force search */\nfun knapsackDFS(\n    wgt: IntArray,\n    _val: IntArray,\n    i: Int,\n    c: Int\n): Int {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, _val, i - 1, c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    val no = knapsackDFS(wgt, _val, i - 1, c)\n    val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1]\n    // Return the larger value from the two choices\n    return max(no, yes)\n}\n
    knapsack.rb
    ### 0-1 knapsack: brute-force search ###\ndef knapsack_dfs(wgt, val, i, c)\n  # If all items have been considered or no capacity remains, return value 0\n  return 0 if i == 0 || c == 0\n  # If the item exceeds the remaining capacity, it must be excluded\n  return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c\n  # Calculate the maximum values obtained by excluding and including item i\n  no = knapsack_dfs(wgt, val, i - 1, c)\n  yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n  # Return the larger value from the two choices\n  [no, yes].max\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-18, each item creates two branches, excluding it or including it, so the time complexity is \\(O(2^n)\\).

    The recursion tree contains overlapping subproblems, such as \\(dp[1, 10]\\). Their number grows substantially with more items and larger capacity, especially when many items share the same weight.

    Figure 14-18   Brute-force recursion tree for 0-1 knapsack

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#2-method-2-memoized-search","level":3,"title":"2.   Method 2: Memoized search","text":"

    To solve each overlapping subproblem only once, use a memoization table mem, where mem[i][c] stores \\(dp[i, c]\\).

    With memoization, the time complexity depends on the number of subproblems, giving \\(O(n \\times cap)\\). The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
    def knapsack_dfs_mem(\n    wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int\n) -> int:\n    \"\"\"0-1 knapsack: memoized search\"\"\"\n    # If all items have been considered or no capacity remains, return value 0\n    if i == 0 or c == 0:\n        return 0\n    # If a result has already been recorded, return it directly\n    if mem[i][c] != -1:\n        return mem[i][c]\n    # If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c:\n        return knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n    # Calculate the maximum values obtained by excluding and including item i\n    no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n    yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n    # Record and return the larger value from the two choices\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n
    knapsack.cpp
    /* 0-1 knapsack: memoized search */\nint knapsackDFSMem(vector<int> &wgt, vector<int> &val, vector<vector<int>> &mem, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.java
    /* 0-1 knapsack: memoized search */\nint knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.cs
    /* 0-1 knapsack: memoized search */\nint KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (weight[i - 1] > c) {\n        return KnapsackDFSMem(weight, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = KnapsackDFSMem(weight, val, mem, i - 1, c);\n    int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = Math.Max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.go
    /* 0-1 knapsack: memoized search */\nfunc knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c]\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i-1] > c {\n        return knapsackDFSMem(wgt, val, mem, i-1, c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    no := knapsackDFSMem(wgt, val, mem, i-1, c)\n    yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1]\n    // Return the larger value from the two choices\n    mem[i][c] = int(math.Max(float64(no), float64(yes)))\n    return mem[i][c]\n}\n
    knapsack.swift
    /* 0-1 knapsack: memoized search */\nfunc knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c]\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c {\n        return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c)\n    let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n    // Record and return the larger value from the two choices\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n}\n
    knapsack.js
    /* 0-1 knapsack: memoized search */\nfunction knapsackDFSMem(wgt, val, mem, i, c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] !== -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    const yes =\n        knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.ts
    /* 0-1 knapsack: memoized search */\nfunction knapsackDFSMem(\n    wgt: Array<number>,\n    val: Array<number>,\n    mem: Array<Array<number>>,\n    i: number,\n    c: number\n): number {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] !== -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    const yes =\n        knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.dart
    /* 0-1 knapsack: memoized search */\nint knapsackDFSMem(\n  List<int> wgt,\n  List<int> val,\n  List<List<int>> mem,\n  int i,\n  int c,\n) {\n  // If all items have been considered or no capacity remains, return value 0\n  if (i == 0 || c == 0) {\n    return 0;\n  }\n  // If a result has already been recorded, return it directly\n  if (mem[i][c] != -1) {\n    return mem[i][c];\n  }\n  // If the item exceeds the remaining capacity, it must be excluded\n  if (wgt[i - 1] > c) {\n    return knapsackDFSMem(wgt, val, mem, i - 1, c);\n  }\n  // Calculate the maximum values obtained by excluding and including item i\n  int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n  int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n  // Record and return the larger value from the two choices\n  mem[i][c] = max(no, yes);\n  return mem[i][c];\n}\n
    knapsack.rs
    /* 0-1 knapsack: memoized search */\nfn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec<Vec<i32>>, i: usize, c: usize) -> i32 {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c as i32 {\n        return knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n    let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = std::cmp::max(no, yes);\n    mem[i][c]\n}\n
    knapsack.c
    /* 0-1 knapsack: memoized search */\nint knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = myMax(no, yes);\n    return mem[i][c];\n}\n
    knapsack.kt
    /* 0-1 knapsack: memoized search */\nfun knapsackDFSMem(\n    wgt: IntArray,\n    _val: IntArray,\n    mem: Array<IntArray>,\n    i: Int,\n    c: Int\n): Int {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c]\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, _val, mem, i - 1, c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    val no = knapsackDFSMem(wgt, _val, mem, i - 1, c)\n    val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1]\n    // Record and return the larger value from the two choices\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n}\n
    knapsack.rb
    ### 0-1 knapsack: memoized search ###\ndef knapsack_dfs_mem(wgt, val, mem, i, c)\n  # If all items have been considered or no capacity remains, return value 0\n  return 0 if i == 0 || c == 0\n  # If a result has already been recorded, return it directly\n  return mem[i][c] if mem[i][c] != -1\n  # If the item exceeds the remaining capacity, it must be excluded\n  return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c\n  # Calculate the maximum values obtained by excluding and including item i\n  no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n  yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n  # Record and return the larger value from the two choices\n  mem[i][c] = [no, yes].max\nend\n
    Visualize execution

    View full screen >

    Figure 14-19 shows the branches pruned by memoized search.

    Figure 14-19   Memoized recursion tree for 0-1 knapsack

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#3-method-3-dynamic-programming","level":3,"title":"3.   Method 3: Dynamic programming","text":"

    Dynamic programming essentially fills the \\(dp\\) table using state transitions, as shown in the code below:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
    def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"0-1 knapsack: dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize the dp table\n    dp = [[0] * (cap + 1) for _ in range(n + 1)]\n    # State transitions\n    for i in range(1, n + 1):\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            else:\n                # Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n    return dp[n][cap]\n
    knapsack.cpp
    /* 0-1 knapsack: dynamic programming */\nint knapsackDP(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize the dp table\n    vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    knapsack.java
    /* 0-1 knapsack: dynamic programming */\nint knapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize the dp table\n    int[][] dp = new int[n + 1][cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    knapsack.cs
    /* 0-1 knapsack: dynamic programming */\nint KnapsackDP(int[] weight, int[] val, int cap) {\n    int n = weight.Length;\n    // Initialize the dp table\n    int[,] dp = new int[n + 1, cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (weight[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i, c] = dp[i - 1, c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]);\n            }\n        }\n    }\n    return dp[n, cap];\n}\n
    knapsack.go
    /* 0-1 knapsack: dynamic programming */\nfunc knapsackDP(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize the dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, cap+1)\n    }\n    // State transitions\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i-1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    knapsack.swift
    /* 0-1 knapsack: dynamic programming */\nfunc knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n    // State transitions\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    knapsack.js
    /* 0-1 knapsack: dynamic programming */\nfunction knapsackDP(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array(n + 1)\n        .fill(0)\n        .map(() => Array(cap + 1).fill(0));\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    knapsack.ts
    /* 0-1 knapsack: dynamic programming */\nfunction knapsackDP(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    knapsack.dart
    /* 0-1 knapsack: dynamic programming */\nint knapsackDP(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0));\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If item i exceeds the remaining capacity, do not choose it\n        dp[i][c] = dp[i - 1][c];\n      } else {\n        // Take the larger value obtained by excluding and including item i\n        dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[n][cap];\n}\n
    knapsack.rs
    /* 0-1 knapsack: dynamic programming */\nfn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize the dp table\n    let mut dp = vec![vec![0; cap + 1]; n + 1];\n    // State transitions\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = std::cmp::max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1],\n                );\n            }\n        }\n    }\n    dp[n][cap]\n}\n
    knapsack.c
    /* 0-1 knapsack: dynamic programming */\nint knapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize the dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(cap + 1, sizeof(int));\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[n][cap];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
    knapsack.kt
    /* 0-1 knapsack: dynamic programming */\nfun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize the dp table\n    val dp = Array(n + 1) { IntArray(cap + 1) }\n    // State transitions\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    knapsack.rb
    ### 0-1 knapsack: dynamic programming ###\ndef knapsack_dp(wgt, val, cap)\n  n = wgt.length\n  # Initialize the dp table\n  dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n  # State transitions\n  for i in 1...(n + 1)\n    for c in 1...(cap + 1)\n      if wgt[i - 1] > c\n        # If item i exceeds the remaining capacity, do not choose it\n        dp[i][c] = dp[i - 1][c]\n      else\n        # Take the larger value obtained by excluding and including item i\n        dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[n][cap]\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-20, both time and space complexity are determined by the size of dp: \\(O(n \\times cap)\\).

    <1><2><3><4><5><6><7><8><9><10><11><12><13><14>

    Figure 14-20   Dynamic programming for 0-1 knapsack

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#4-space-optimization","level":3,"title":"4.   Space optimization","text":"

    Each state depends only on the previous row, so two rolling arrays reduce space complexity from \\(O(n \\times cap)\\) to \\(O(cap)\\).

    Can we go further and use just one array? Each state depends on entries directly above or to the upper left. With one array, when we begin processing row \\(i\\), it still holds the states from row \\(i-1\\).

    • With forward iteration, by the time we reach \\(dp[i, j]\\), the upper-left values \\(dp[i-1, 1]\\) ~ \\(dp[i-1, j-1]\\) may have been overwritten, making the transition incorrect.
    • With backward iteration, these values are not overwritten before they are needed, so transitions remain correct.

    Figure 14-21 shows one array changing from row \\(i = 1\\) to row \\(i = 2\\). Consider how forward and backward iteration differ.

    <1><2><3><4><5><6>

    Figure 14-21   Space-optimized dynamic programming for 0-1 knapsack

    In the implementation, simply remove the first dimension \\(i\\) from dp and reverse the inner loop:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
    def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"0-1 knapsack: space-optimized dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize the dp table\n    dp = [0] * (cap + 1)\n    # State transitions\n    for i in range(1, n + 1):\n        # Traverse in decreasing order\n        for c in range(cap, 0, -1):\n            if wgt[i - 1] > c:\n                # If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            else:\n                # Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n    return dp[cap]\n
    knapsack.cpp
    /* 0-1 knapsack: space-optimized dynamic programming */\nint knapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize the dp table\n    vector<int> dp(cap + 1, 0);\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.java
    /* 0-1 knapsack: space-optimized dynamic programming */\nint knapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize the dp table\n    int[] dp = new int[cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.cs
    /* 0-1 knapsack: space-optimized dynamic programming */\nint KnapsackDPComp(int[] weight, int[] val, int cap) {\n    int n = weight.Length;\n    // Initialize the dp table\n    int[] dp = new int[cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (int c = cap; c > 0; c--) {\n            if (weight[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.go
    /* 0-1 knapsack: space-optimized dynamic programming */\nfunc knapsackDPComp(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize the dp table\n    dp := make([]int, cap+1)\n    // State transitions\n    for i := 1; i <= n; i++ {\n        // Traverse in decreasing order\n        for c := cap; c >= 1; c-- {\n            if wgt[i-1] <= c {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[cap]\n}\n
    knapsack.swift
    /* 0-1 knapsack: space-optimized dynamic programming */\nfunc knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize the dp table\n    var dp = Array(repeating: 0, count: cap + 1)\n    // State transitions\n    for i in 1 ... n {\n        // Traverse in decreasing order\n        for c in (1 ... cap).reversed() {\n            if wgt[i - 1] <= c {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
    knapsack.js
    /* 0-1 knapsack: space-optimized dynamic programming */\nfunction knapsackDPComp(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array(cap + 1).fill(0);\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (let c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.ts
    /* 0-1 knapsack: space-optimized dynamic programming */\nfunction knapsackDPComp(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array(cap + 1).fill(0);\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (let c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.dart
    /* 0-1 knapsack: space-optimized dynamic programming */\nint knapsackDPComp(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize the dp table\n  List<int> dp = List.filled(cap + 1, 0);\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    // Traverse in decreasing order\n    for (int c = cap; c >= 1; c--) {\n      if (wgt[i - 1] <= c) {\n        // Take the larger value obtained by excluding and including item i\n        dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[cap];\n}\n
    knapsack.rs
    /* 0-1 knapsack: space-optimized dynamic programming */\nfn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize the dp table\n    let mut dp = vec![0; cap + 1];\n    // State transitions\n    for i in 1..=n {\n        // Traverse in decreasing order\n        for c in (1..=cap).rev() {\n            if wgt[i - 1] <= c as i32 {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    dp[cap]\n}\n
    knapsack.c
    /* 0-1 knapsack: space-optimized dynamic programming */\nint knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize the dp table\n    int *dp = calloc(cap + 1, sizeof(int));\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[cap];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    knapsack.kt
    /* 0-1 knapsack: space-optimized dynamic programming */\nfun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize the dp table\n    val dp = IntArray(cap + 1)\n    // State transitions\n    for (i in 1..n) {\n        // Traverse in decreasing order\n        for (c in cap downTo 1) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
    knapsack.rb
    ### 0-1 knapsack: space-optimized dynamic programming ###\ndef knapsack_dp_comp(wgt, val, cap)\n  n = wgt.length\n  # Initialize the dp table\n  dp = Array.new(cap + 1, 0)\n  # State transitions\n  for i in 1...(n + 1)\n    # Traverse in decreasing order\n    for c in cap.downto(1)\n      if wgt[i - 1] > c\n        # If item i exceeds the remaining capacity, do not choose it\n        dp[c] = dp[c]\n      else\n        # Take the larger value obtained by excluding and including item i\n        dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[cap]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/summary/","level":1,"title":"14.7   Summary","text":"","path":["Chapter 14.   Dynamic programming","14.7   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Dynamic programming decomposes problems and stores subproblem solutions to avoid repeated computation and improve efficiency.
    • Without a time limit, every dynamic programming problem can be solved by backtracking (brute-force search). However, the recursion tree contains many overlapping subproblems, making it very inefficient. Memoization stores computed solutions so that each overlapping subproblem is solved only once.
    • Memoized search is a top-down recursive approach, while the corresponding dynamic programming solution works bottom up, like filling a table. Since the current state depends only on certain nearby states, removing a dimension from the \\(dp\\) table can reduce space complexity.
    • Decomposing a problem into subproblems is a general algorithmic idea with different characteristics in divide and conquer, dynamic programming, and backtracking.
    • Dynamic programming problems have three key properties: overlapping subproblems, optimal substructure, and the no-aftereffect property.
    • A problem has optimal substructure if its optimal solution can be constructed from optimal solutions to subproblems.
    • The no-aftereffect property means a state's future depends only on that state, not on the history leading to it. Many combinatorial optimization problems lack this property and cannot be solved quickly with dynamic programming.

    Knapsack problems

    • Knapsack is one of the most typical dynamic programming problem families, with variants including 0-1, unbounded, and bounded knapsack.
    • In 0-1 knapsack, the state value is the maximum value obtainable from the first \\(i\\) items within capacity \\(c\\). The choices to exclude or include an item reveal optimal substructure and give the state transition equation. After space optimization, backward iteration is necessary because each state depends on entries above and to the upper left, which must not be overwritten prematurely.
    • Unbounded knapsack allows unlimited copies of each item, changing the transition for including an item. Because a state depends on entries directly above and to the left, space optimization requires forward iteration.
    • Coin change is a variant of unbounded knapsack. Minimizing the number of coins instead of maximizing value changes \\(\\max()\\) to \\(\\min()\\) in the transition. Requiring the target amount exactly, rather than staying within a capacity, calls for using \\(amt + 1\\) to represent an infeasible solution.
    • Coin change II counts combinations instead of minimizing coin count, so the transition replaces \\(\\min()\\) with addition.

    Edit distance

    • Edit distance (Levenshtein distance) measures similarity between strings. It is the minimum number of edits needed to turn one string into another, allowing insertion, deletion, and replacement.
    • The edit distance state is the minimum number of edits needed to transform the first \\(i\\) characters of \\(s\\) into the first \\(j\\) characters of \\(t\\). When \\(s[i-1] \\ne t[j-1]\\), insertion, deletion, and replacement each leave a corresponding subproblem, revealing optimal substructure and the state transition equation. When \\(s[i-1] = t[j-1]\\), no edit is needed for those characters.
    • Edit distance depends on states above, to the left, and to the upper left, so neither forward nor backward iteration alone works after space optimization. A temporary variable preserves the upper-left state, reducing the situation to one like unbounded knapsack and allowing forward iteration.
    ","path":["Chapter 14.   Dynamic programming","14.7   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/","level":1,"title":"14.5   Unbounded knapsack problem","text":"

    We will now solve another common knapsack variant, unbounded knapsack, and then examine a special case: coin change.

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1451-unbounded-knapsack","level":2,"title":"14.5.1   Unbounded knapsack","text":"

    Question

    Given \\(n\\) items, where item \\(i\\) has weight \\(wgt[i-1]\\) and value \\(val[i-1]\\), and a knapsack of capacity \\(cap\\), each item may be chosen repeatedly. What is the maximum value that fits within the capacity? An example is shown in Figure 14-22.

    Figure 14-22   Example input for unbounded knapsack

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach","level":3,"title":"1.   Dynamic programming approach","text":"

    Unbounded knapsack is very similar to 0-1 knapsack. The only difference is that there is no limit on how many times an item may be chosen.

    • In 0-1 knapsack, there is only one of each item, so after including item \\(i\\), we can choose only from the first \\(i-1\\) items.
    • In unbounded knapsack, each item has an unlimited supply, so after including item \\(i\\), we can still choose from the first \\(i\\) items.

    For unbounded knapsack, state \\([i, c]\\) has two cases.

    • Leave item \\(i\\) out: As in 0-1 knapsack, the remaining state is \\([i-1, c]\\).
    • Put item \\(i\\) in: Unlike 0-1 knapsack, the remaining state is \\([i, c-wgt[i-1]]\\).

    The state transition equation becomes:

    \\[ dp[i, c] = \\max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) \\]","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    Compared with 0-1 knapsack, only one \\(i-1\\) in the transition changes to \\(i\\); the rest of the code is identical:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py
    def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Unbounded knapsack: dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize the dp table\n    dp = [[0] * (cap + 1) for _ in range(n + 1)]\n    # State transitions\n    for i in range(1, n + 1):\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            else:\n                # Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n    return dp[n][cap]\n
    unbounded_knapsack.cpp
    /* Unbounded knapsack: dynamic programming */\nint unboundedKnapsackDP(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize the dp table\n    vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.java
    /* Unbounded knapsack: dynamic programming */\nint unboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize the dp table\n    int[][] dp = new int[n + 1][cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.cs
    /* Unbounded knapsack: dynamic programming */\nint UnboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.Length;\n    // Initialize the dp table\n    int[,] dp = new int[n + 1, cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i, c] = dp[i - 1, c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n, cap];\n}\n
    unbounded_knapsack.go
    /* Unbounded knapsack: dynamic programming */\nfunc unboundedKnapsackDP(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize the dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, cap+1)\n    }\n    // State transitions\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i-1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    unbounded_knapsack.swift
    /* Unbounded knapsack: dynamic programming */\nfunc unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n    // State transitions\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    unbounded_knapsack.js
    /* Unbounded knapsack: dynamic programming */\nfunction unboundedKnapsackDP(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.ts
    /* Unbounded knapsack: dynamic programming */\nfunction unboundedKnapsackDP(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.dart
    /* Unbounded knapsack: dynamic programming */\nint unboundedKnapsackDP(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0));\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If item i exceeds the remaining capacity, do not choose it\n        dp[i][c] = dp[i - 1][c];\n      } else {\n        // Take the larger value obtained by excluding and including item i\n        dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[n][cap];\n}\n
    unbounded_knapsack.rs
    /* Unbounded knapsack: dynamic programming */\nfn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize the dp table\n    let mut dp = vec![vec![0; cap + 1]; n + 1];\n    // State transitions\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.c
    /* Unbounded knapsack: dynamic programming */\nint unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize the dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(cap + 1, sizeof(int));\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[n][cap];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
    unbounded_knapsack.kt
    /* Unbounded knapsack: dynamic programming */\nfun unboundedKnapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize the dp table\n    val dp = Array(n + 1) { IntArray(cap + 1) }\n    // State transitions\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    unbounded_knapsack.rb
    ### Unbounded knapsack: dynamic programming ###\ndef unbounded_knapsack_dp(wgt, val, cap)\n  n = wgt.length\n  # Initialize the dp table\n  dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n  # State transitions\n  for i in 1...(n + 1)\n    for c in 1...(cap + 1)\n      if wgt[i - 1] > c\n        # If item i exceeds the remaining capacity, do not choose it\n        dp[i][c] = dp[i - 1][c]\n      else\n        # Take the larger value obtained by excluding and including item i\n        dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[n][cap]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization","level":3,"title":"3.   Space optimization","text":"

    The current state depends on states to its left and above, so after space optimization, each row of the \\(dp\\) table must be processed in forward order.

    This is the opposite of 0-1 knapsack. Use Figure 14-23 to understand the difference.

    <1><2><3><4><5><6>

    Figure 14-23   Space-optimized dynamic programming for unbounded knapsack

    The implementation simply removes the first dimension of dp:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py
    def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Unbounded knapsack: space-optimized dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize the dp table\n    dp = [0] * (cap + 1)\n    # State transitions\n    for i in range(1, n + 1):\n        # Traverse in increasing order\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            else:\n                # Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n    return dp[cap]\n
    unbounded_knapsack.cpp
    /* Unbounded knapsack: space-optimized dynamic programming */\nint unboundedKnapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize the dp table\n    vector<int> dp(cap + 1, 0);\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.java
    /* Unbounded knapsack: space-optimized dynamic programming */\nint unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize the dp table\n    int[] dp = new int[cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.cs
    /* Unbounded knapsack: space-optimized dynamic programming */\nint UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.Length;\n    // Initialize the dp table\n    int[] dp = new int[cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.go
    /* Unbounded knapsack: space-optimized dynamic programming */\nfunc unboundedKnapsackDPComp(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize the dp table\n    dp := make([]int, cap+1)\n    // State transitions\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[cap]\n}\n
    unbounded_knapsack.swift
    /* Unbounded knapsack: space-optimized dynamic programming */\nfunc unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize the dp table\n    var dp = Array(repeating: 0, count: cap + 1)\n    // State transitions\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
    unbounded_knapsack.js
    /* Unbounded knapsack: space-optimized dynamic programming */\nfunction unboundedKnapsackDPComp(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: cap + 1 }, () => 0);\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.ts
    /* Unbounded knapsack: space-optimized dynamic programming */\nfunction unboundedKnapsackDPComp(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: cap + 1 }, () => 0);\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.dart
    /* Unbounded knapsack: space-optimized dynamic programming */\nint unboundedKnapsackDPComp(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize the dp table\n  List<int> dp = List.filled(cap + 1, 0);\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If item i exceeds the remaining capacity, do not choose it\n        dp[c] = dp[c];\n      } else {\n        // Take the larger value obtained by excluding and including item i\n        dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[cap];\n}\n
    unbounded_knapsack.rs
    /* Unbounded knapsack: space-optimized dynamic programming */\nfn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize the dp table\n    let mut dp = vec![0; cap + 1];\n    // State transitions\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    dp[cap]\n}\n
    unbounded_knapsack.c
    /* Unbounded knapsack: space-optimized dynamic programming */\nint unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize the dp table\n    int *dp = calloc(cap + 1, sizeof(int));\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[cap];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    unbounded_knapsack.kt
    /* Unbounded knapsack: space-optimized dynamic programming */\nfun unboundedKnapsackDPComp(\n    wgt: IntArray,\n    _val: IntArray,\n    cap: Int\n): Int {\n    val n = wgt.size\n    // Initialize the dp table\n    val dp = IntArray(cap + 1)\n    // State transitions\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
    unbounded_knapsack.rb
    ### Unbounded knapsack: space-optimized dynamic programming ###\ndef unbounded_knapsack_dp_comp(wgt, val, cap)\n  n = wgt.length\n  # Initialize the dp table\n  dp = Array.new(cap + 1, 0)\n  # State transitions\n  for i in 1...(n + 1)\n    # Traverse in increasing order\n    for c in 1...(cap + 1)\n      if wgt[i -1] > c\n        # If item i exceeds the remaining capacity, do not choose it\n        dp[c] = dp[c]\n      else\n        # Take the larger value obtained by excluding and including item i\n        dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[cap]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1452-coin-change-problem","level":2,"title":"14.5.2   Coin change problem","text":"

    Knapsack represents a broad family of dynamic programming problems, with many variants such as coin change.

    Question

    Given \\(n\\) coin denominations, where denomination \\(i\\) has value \\(coins[i - 1]\\), and a target amount \\(amt\\), each denomination may be used repeatedly. What is the minimum number of coins needed to make the target? Return \\(-1\\) if it cannot be made. An example is shown in Figure 14-24.

    Figure 14-24   Example input for coin change

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach_1","level":3,"title":"1.   Dynamic programming approach","text":"

    Coin change can be viewed as a special case of unbounded knapsack, with the following connections and differences.

    • The two problems can be translated into one another: items correspond to coins, item weights to coin denominations, and knapsack capacity to the target amount.
    • The optimization objectives are opposite: unbounded knapsack maximizes item value, while coin change minimizes the number of coins.
    • Unbounded knapsack must stay within a capacity limit, whereas coin change must reach the target amount exactly.

    Step 1: Identify each decision, define the state, and establish the \\(dp\\) table

    State \\([i, a]\\) represents the subproblem of finding the minimum number of coins needed to make amount \\(a\\) using the first \\(i\\) denominations, denoted by \\(dp[i, a]\\).

    The two-dimensional \\(dp\\) table has size \\((n+1) \\times (amt+1)\\).

    Step 2: Identify optimal substructure and derive the state transition equation

    The state transition differs from unbounded knapsack in two ways.

    • We want a minimum, so replace \\(\\max()\\) with \\(\\min()\\).
    • We optimize the coin count rather than item value, so choosing a coin adds \\(+1\\).
    \\[ dp[i, a] = \\min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) \\]

    Step 3: Determine the boundary conditions and evaluation order

    A target amount of \\(0\\) requires \\(0\\) coins, so every entry \\(dp[i, 0]\\) in the first column is \\(0\\).

    With no coins, any target amount \\(> 0\\) is impossible. To let \\(\\min()\\) recognize and discard these infeasible solutions, represent them by \\(+ \\infty\\): set the first-row entries \\(dp[0, a]\\) to \\(+ \\infty\\).

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation_1","level":3,"title":"2.   Code implementation","text":"

    Most programming languages do not provide an integer value for \\(+ \\infty\\), so the maximum int value is used instead. However, the \\(+ 1\\) in the transition may then overflow.

    Instead, use \\(amt + 1\\) to represent an infeasible solution, since making \\(amt\\) requires at most \\(amt\\) coins. Before returning, check whether \\(dp[n, amt]\\) equals \\(amt + 1\\). If so, return \\(-1\\) to indicate that the target cannot be made. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py
    def coin_change_dp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: dynamic programming\"\"\"\n    n = len(coins)\n    MAX = amt + 1\n    # Initialize the dp table\n    dp = [[0] * (amt + 1) for _ in range(n + 1)]\n    # State transitions: first row and first column\n    for a in range(1, amt + 1):\n        dp[0][a] = MAX\n    # State transitions: remaining rows and columns\n    for i in range(1, n + 1):\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If the coin value exceeds the current amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            else:\n                # Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n    return dp[n][amt] if dp[n][amt] != MAX else -1\n
    coin_change.cpp
    /* Coin change: dynamic programming */\nint coinChangeDP(vector<int> &coins, int amt) {\n    int n = coins.size();\n    int MAX = amt + 1;\n    // Initialize the dp table\n    vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));\n    // State transitions: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
    coin_change.java
    /* Coin change: dynamic programming */\nint coinChangeDP(int[] coins, int amt) {\n    int n = coins.length;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int[][] dp = new int[n + 1][amt + 1];\n    // State transitions: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
    coin_change.cs
    /* Coin change: dynamic programming */\nint CoinChangeDP(int[] coins, int amt) {\n    int n = coins.Length;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int[,] dp = new int[n + 1, amt + 1];\n    // State transitions: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0, a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i, a] = dp[i - 1, a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n, amt] != MAX ? dp[n, amt] : -1;\n}\n
    coin_change.go
    /* Coin change: dynamic programming */\nfunc coinChangeDP(coins []int, amt int) int {\n    n := len(coins)\n    max := amt + 1\n    // Initialize the dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, amt+1)\n    }\n    // State transitions: first row and first column\n    for a := 1; a <= amt; a++ {\n        dp[0][a] = max\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i <= n; i++ {\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[i][a] = dp[i-1][a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1)))\n            }\n        }\n    }\n    if dp[n][amt] != max {\n        return dp[n][amt]\n    }\n    return -1\n}\n
    coin_change.swift
    /* Coin change: dynamic programming */\nfunc coinChangeDP(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    let MAX = amt + 1\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n    // State transitions: first row and first column\n    for a in 1 ... amt {\n        dp[0][a] = MAX\n    }\n    // State transitions: remaining rows and columns\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1\n}\n
    coin_change.js
    /* Coin change: dynamic programming */\nfunction coinChangeDP(coins, amt) {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // State transitions: first row and first column\n    for (let a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n
    coin_change.ts
    /* Coin change: dynamic programming */\nfunction coinChangeDP(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // State transitions: first row and first column\n    for (let a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n
    coin_change.dart
    /* Coin change: dynamic programming */\nint coinChangeDP(List<int> coins, int amt) {\n  int n = coins.length;\n  int MAX = amt + 1;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0));\n  // State transitions: first row and first column\n  for (int a = 1; a <= amt; a++) {\n    dp[0][a] = MAX;\n  }\n  // State transitions: remaining rows and columns\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If the coin exceeds the current amount a, do not choose coin i\n        dp[i][a] = dp[i - 1][a];\n      } else {\n        // Take the smaller result of not choosing and choosing coin i\n        dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n      }\n    }\n  }\n  return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
    coin_change.rs
    /* Coin change: dynamic programming */\nfn coin_change_dp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    let max = amt + 1;\n    // Initialize the dp table\n    let mut dp = vec![vec![0; amt + 1]; n + 1];\n    // State transitions: first row and first column\n    for a in 1..=amt {\n        dp[0][a] = max;\n    }\n    // State transitions: remaining rows and columns\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1);\n            }\n        }\n    }\n    if dp[n][amt] != max {\n        return dp[n][amt] as i32;\n    } else {\n        -1\n    }\n}\n
    coin_change.c
    /* Coin change: dynamic programming */\nint coinChangeDP(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(amt + 1, sizeof(int));\n    }\n    // State transitions: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    int res = dp[n][amt] != MAX ? dp[n][amt] : -1;\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
    coin_change.kt
    /* Coin change: dynamic programming */\nfun coinChangeDP(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    val MAX = amt + 1\n    // Initialize the dp table\n    val dp = Array(n + 1) { IntArray(amt + 1) }\n    // State transitions: first row and first column\n    for (a in 1..amt) {\n        dp[0][a] = MAX\n    }\n    // State transitions: remaining rows and columns\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If the coin value exceeds the current amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return if (dp[n][amt] != MAX) dp[n][amt] else -1\n}\n
    coin_change.rb
    ### Coin change: dynamic programming ###\ndef coin_change_dp(coins, amt)\n  n = coins.length\n  _MAX = amt + 1\n  # Initialize the dp table\n  dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n  # State transitions: first row and first column\n  (1...(amt + 1)).each { |a| dp[0][a] = _MAX }\n  # State transitions: remaining rows and columns\n  for i in 1...(n + 1)\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If the coin exceeds the current amount a, do not choose coin i\n        dp[i][a] = dp[i - 1][a]\n      else\n        # Take the smaller result of not choosing and choosing coin i\n        dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min\n      end\n    end\n  end\n  dp[n][amt] != _MAX ? dp[n][amt] : -1\nend\n
    Visualize execution

    View full screen >

    Figure 14-25 shows dynamic programming for coin change, which closely resembles unbounded knapsack.

    <1><2><3><4><5><6><7><8><9><10><11><12><13><14><15>

    Figure 14-25   Dynamic programming for coin change

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization_1","level":3,"title":"3.   Space optimization","text":"

    Space optimization is the same as for unbounded knapsack:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py
    def coin_change_dp_comp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: space-optimized dynamic programming\"\"\"\n    n = len(coins)\n    MAX = amt + 1\n    # Initialize the dp table\n    dp = [MAX] * (amt + 1)\n    dp[0] = 0\n    # State transitions\n    for i in range(1, n + 1):\n        # Traverse in increasing order\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If the coin value exceeds the current amount a, do not choose coin i\n                dp[a] = dp[a]\n            else:\n                # Take the smaller result of not choosing and choosing coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n    return dp[amt] if dp[amt] != MAX else -1\n
    coin_change.cpp
    /* Coin change: space-optimized dynamic programming */\nint coinChangeDPComp(vector<int> &coins, int amt) {\n    int n = coins.size();\n    int MAX = amt + 1;\n    // Initialize the dp table\n    vector<int> dp(amt + 1, MAX);\n    dp[0] = 0;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
    coin_change.java
    /* Coin change: space-optimized dynamic programming */\nint coinChangeDPComp(int[] coins, int amt) {\n    int n = coins.length;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int[] dp = new int[amt + 1];\n    Arrays.fill(dp, MAX);\n    dp[0] = 0;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
    coin_change.cs
    /* Coin change: space-optimized dynamic programming */\nint CoinChangeDPComp(int[] coins, int amt) {\n    int n = coins.Length;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int[] dp = new int[amt + 1];\n    Array.Fill(dp, MAX);\n    dp[0] = 0;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
    coin_change.go
    /* Coin change: dynamic programming */\nfunc coinChangeDPComp(coins []int, amt int) int {\n    n := len(coins)\n    max := amt + 1\n    // Initialize the dp table\n    dp := make([]int, amt+1)\n    for i := 1; i <= amt; i++ {\n        dp[i] = max\n    }\n    // State transitions\n    for i := 1; i <= n; i++ {\n        // Traverse in increasing order\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1)))\n            }\n        }\n    }\n    if dp[amt] != max {\n        return dp[amt]\n    }\n    return -1\n}\n
    coin_change.swift
    /* Coin change: space-optimized dynamic programming */\nfunc coinChangeDPComp(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    let MAX = amt + 1\n    // Initialize the dp table\n    var dp = Array(repeating: MAX, count: amt + 1)\n    dp[0] = 0\n    // State transitions\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1\n}\n
    coin_change.js
    /* Coin change: space-optimized dynamic programming */\nfunction coinChangeDPComp(coins, amt) {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize the dp table\n    const dp = Array.from({ length: amt + 1 }, () => MAX);\n    dp[0] = 0;\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] !== MAX ? dp[amt] : -1;\n}\n
    coin_change.ts
    /* Coin change: space-optimized dynamic programming */\nfunction coinChangeDPComp(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize the dp table\n    const dp = Array.from({ length: amt + 1 }, () => MAX);\n    dp[0] = 0;\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] !== MAX ? dp[amt] : -1;\n}\n
    coin_change.dart
    /* Coin change: space-optimized dynamic programming */\nint coinChangeDPComp(List<int> coins, int amt) {\n  int n = coins.length;\n  int MAX = amt + 1;\n  // Initialize the dp table\n  List<int> dp = List.filled(amt + 1, MAX);\n  dp[0] = 0;\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If the coin exceeds the current amount a, do not choose coin i\n        dp[a] = dp[a];\n      } else {\n        // Take the smaller result of not choosing and choosing coin i\n        dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n      }\n    }\n  }\n  return dp[amt] != MAX ? dp[amt] : -1;\n}\n
    coin_change.rs
    /* Coin change: space-optimized dynamic programming */\nfn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    let max = amt + 1;\n    // Initialize the dp table\n    let mut dp = vec![0; amt + 1];\n    dp.fill(max);\n    dp[0] = 0;\n    // State transitions\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1);\n            }\n        }\n    }\n    if dp[amt] != max {\n        return dp[amt] as i32;\n    } else {\n        -1\n    }\n}\n
    coin_change.c
    /* Coin change: space-optimized dynamic programming */\nint coinChangeDPComp(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int *dp = malloc((amt + 1) * sizeof(int));\n    for (int j = 1; j <= amt; j++) {\n        dp[j] = MAX;\n    } \n    dp[0] = 0;\n\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    int res = dp[amt] != MAX ? dp[amt] : -1;\n    // Free memory\n    free(dp);\n    return res;\n}\n
    coin_change.kt
    /* Coin change: space-optimized dynamic programming */\nfun coinChangeDPComp(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    val MAX = amt + 1\n    // Initialize the dp table\n    val dp = IntArray(amt + 1)\n    dp.fill(MAX)\n    dp[0] = 0\n    // State transitions\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If the coin value exceeds the current amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return if (dp[amt] != MAX) dp[amt] else -1\n}\n
    coin_change.rb
    ### Coin change: space-optimized dynamic programming ###\ndef coin_change_dp_comp(coins, amt)\n  n = coins.length\n  _MAX = amt + 1\n  # Initialize the dp table\n  dp = Array.new(amt + 1, _MAX)\n  dp[0] = 0\n  # State transitions\n  for i in 1...(n + 1)\n    # Traverse in increasing order\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If the coin exceeds the current amount a, do not choose coin i\n        dp[a] = dp[a]\n      else\n        # Take the smaller result of not choosing and choosing coin i\n        dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min\n      end\n    end\n  end\n  dp[amt] != _MAX ? dp[amt] : -1\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1453-coin-change-problem-ii","level":2,"title":"14.5.3   Coin change problem II","text":"

    Question

    Given \\(n\\) coin denominations, where denomination \\(i\\) has value \\(coins[i - 1]\\), and a target amount \\(amt\\), each denomination may be used repeatedly. How many combinations of coins make the target amount? An example is shown in Figure 14-26.

    Figure 14-26   Example input for coin change II

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach_2","level":3,"title":"1.   Dynamic programming approach","text":"

    This time, we count combinations, so the subproblem becomes the number of combinations that make amount \\(a\\) using the first \\(i\\) denominations. The \\(dp\\) table remains a two-dimensional matrix of size \\((n+1) \\times (amt + 1)\\).

    The number of combinations for the current state is the sum of those that exclude the current coin and those that include it. The state transition equation is:

    \\[ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] \\]

    A target of \\(0\\) can be made by choosing no coins, so initialize every first-column entry \\(dp[i, 0]\\) to \\(1\\). With no coins, any target \\(>0\\) is impossible, so the first-row entries \\(dp[0, a]\\) are \\(0\\).

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation_2","level":3,"title":"2.   Code implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py
    def coin_change_ii_dp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change II: dynamic programming\"\"\"\n    n = len(coins)\n    # Initialize the dp table\n    dp = [[0] * (amt + 1) for _ in range(n + 1)]\n    # Initialize the first column\n    for i in range(n + 1):\n        dp[i][0] = 1\n    # State transitions\n    for i in range(1, n + 1):\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If the coin value exceeds the current amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            else:\n                # Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n    return dp[n][amt]\n
    coin_change_ii.cpp
    /* Coin change II: dynamic programming */\nint coinChangeIIDP(vector<int> &coins, int amt) {\n    int n = coins.size();\n    // Initialize the dp table\n    vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));\n    // Initialize the first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
    coin_change_ii.java
    /* Coin change II: dynamic programming */\nint coinChangeIIDP(int[] coins, int amt) {\n    int n = coins.length;\n    // Initialize the dp table\n    int[][] dp = new int[n + 1][amt + 1];\n    // Initialize the first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
    coin_change_ii.cs
    /* Coin change II: dynamic programming */\nint CoinChangeIIDP(int[] coins, int amt) {\n    int n = coins.Length;\n    // Initialize the dp table\n    int[,] dp = new int[n + 1, amt + 1];\n    // Initialize the first column\n    for (int i = 0; i <= n; i++) {\n        dp[i, 0] = 1;\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i, a] = dp[i - 1, a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n, amt];\n}\n
    coin_change_ii.go
    /* Coin change II: dynamic programming */\nfunc coinChangeIIDP(coins []int, amt int) int {\n    n := len(coins)\n    // Initialize the dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, amt+1)\n    }\n    // Initialize the first column\n    for i := 0; i <= n; i++ {\n        dp[i][0] = 1\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i <= n; i++ {\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[i][a] = dp[i-1][a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
    coin_change_ii.swift
    /* Coin change II: dynamic programming */\nfunc coinChangeIIDP(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n    // Initialize the first column\n    for i in 0 ... n {\n        dp[i][0] = 1\n    }\n    // State transitions\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
    coin_change_ii.js
    /* Coin change II: dynamic programming */\nfunction coinChangeIIDP(coins, amt) {\n    const n = coins.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // Initialize the first column\n    for (let i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
    coin_change_ii.ts
    /* Coin change II: dynamic programming */\nfunction coinChangeIIDP(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // Initialize the first column\n    for (let i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
    coin_change_ii.dart
    /* Coin change II: dynamic programming */\nint coinChangeIIDP(List<int> coins, int amt) {\n  int n = coins.length;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0));\n  // Initialize the first column\n  for (int i = 0; i <= n; i++) {\n    dp[i][0] = 1;\n  }\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If the coin exceeds the current amount a, do not choose coin i\n        dp[i][a] = dp[i - 1][a];\n      } else {\n        // Add the numbers of ways without and with coin i\n        dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n      }\n    }\n  }\n  return dp[n][amt];\n}\n
    coin_change_ii.rs
    /* Coin change II: dynamic programming */\nfn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    // Initialize the dp table\n    let mut dp = vec![vec![0; amt + 1]; n + 1];\n    // Initialize the first column\n    for i in 0..=n {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize];\n            }\n        }\n    }\n    dp[n][amt]\n}\n
    coin_change_ii.c
    /* Coin change II: dynamic programming */\nint coinChangeIIDP(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    // Initialize the dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(amt + 1, sizeof(int));\n    }\n    // Initialize the first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    int res = dp[n][amt];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
    coin_change_ii.kt
    /* Coin change II: dynamic programming */\nfun coinChangeIIDP(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    // Initialize the dp table\n    val dp = Array(n + 1) { IntArray(amt + 1) }\n    // Initialize the first column\n    for (i in 0..n) {\n        dp[i][0] = 1\n    }\n    // State transitions\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If the coin value exceeds the current amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
    coin_change_ii.rb
    ### Coin change II: dynamic programming ###\ndef coin_change_ii_dp(coins, amt)\n  n = coins.length\n  # Initialize the dp table\n  dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n  # Initialize the first column\n  (0...(n + 1)).each { |i| dp[i][0] = 1 }\n  # State transitions\n  for i in 1...(n + 1)\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If the coin exceeds the current amount a, do not choose coin i\n        dp[i][a] = dp[i - 1][a]\n      else\n        # Add the numbers of ways without and with coin i\n        dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n      end\n    end\n  end\n  dp[n][amt]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization_2","level":3,"title":"3.   Space optimization","text":"

    The space optimization is the same: remove the coin dimension:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py
    def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change II: space-optimized dynamic programming\"\"\"\n    n = len(coins)\n    # Initialize the dp table\n    dp = [0] * (amt + 1)\n    dp[0] = 1\n    # State transitions\n    for i in range(1, n + 1):\n        # Traverse in increasing order\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If the coin value exceeds the current amount a, do not choose coin i\n                dp[a] = dp[a]\n            else:\n                # Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n    return dp[amt]\n
    coin_change_ii.cpp
    /* Coin change II: space-optimized dynamic programming */\nint coinChangeIIDPComp(vector<int> &coins, int amt) {\n    int n = coins.size();\n    // Initialize the dp table\n    vector<int> dp(amt + 1, 0);\n    dp[0] = 1;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.java
    /* Coin change II: space-optimized dynamic programming */\nint coinChangeIIDPComp(int[] coins, int amt) {\n    int n = coins.length;\n    // Initialize the dp table\n    int[] dp = new int[amt + 1];\n    dp[0] = 1;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.cs
    /* Coin change II: space-optimized dynamic programming */\nint CoinChangeIIDPComp(int[] coins, int amt) {\n    int n = coins.Length;\n    // Initialize the dp table\n    int[] dp = new int[amt + 1];\n    dp[0] = 1;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.go
    /* Coin change II: space-optimized dynamic programming */\nfunc coinChangeIIDPComp(coins []int, amt int) int {\n    n := len(coins)\n    // Initialize the dp table\n    dp := make([]int, amt+1)\n    dp[0] = 1\n    // State transitions\n    for i := 1; i <= n; i++ {\n        // Traverse in increasing order\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a-coins[i-1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
    coin_change_ii.swift
    /* Coin change II: space-optimized dynamic programming */\nfunc coinChangeIIDPComp(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    // Initialize the dp table\n    var dp = Array(repeating: 0, count: amt + 1)\n    dp[0] = 1\n    // State transitions\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
    coin_change_ii.js
    /* Coin change II: space-optimized dynamic programming */\nfunction coinChangeIIDPComp(coins, amt) {\n    const n = coins.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: amt + 1 }, () => 0);\n    dp[0] = 1;\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.ts
    /* Coin change II: space-optimized dynamic programming */\nfunction coinChangeIIDPComp(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: amt + 1 }, () => 0);\n    dp[0] = 1;\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.dart
    /* Coin change II: space-optimized dynamic programming */\nint coinChangeIIDPComp(List<int> coins, int amt) {\n  int n = coins.length;\n  // Initialize the dp table\n  List<int> dp = List.filled(amt + 1, 0);\n  dp[0] = 1;\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If the coin exceeds the current amount a, do not choose coin i\n        dp[a] = dp[a];\n      } else {\n        // Add the numbers of ways without and with coin i\n        dp[a] = dp[a] + dp[a - coins[i - 1]];\n      }\n    }\n  }\n  return dp[amt];\n}\n
    coin_change_ii.rs
    /* Coin change II: space-optimized dynamic programming */\nfn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    // Initialize the dp table\n    let mut dp = vec![0; amt + 1];\n    dp[0] = 1;\n    // State transitions\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1] as usize];\n            }\n        }\n    }\n    dp[amt]\n}\n
    coin_change_ii.c
    /* Coin change II: space-optimized dynamic programming */\nint coinChangeIIDPComp(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    // Initialize the dp table\n    int *dp = calloc(amt + 1, sizeof(int));\n    dp[0] = 1;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    int res = dp[amt];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    coin_change_ii.kt
    /* Coin change II: space-optimized dynamic programming */\nfun coinChangeIIDPComp(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    // Initialize the dp table\n    val dp = IntArray(amt + 1)\n    dp[0] = 1\n    // State transitions\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If the coin value exceeds the current amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
    coin_change_ii.rb
    ### Coin change II: space-optimized dynamic programming ###\ndef coin_change_ii_dp_comp(coins, amt)\n  n = coins.length\n  # Initialize the dp table\n  dp = Array.new(amt + 1, 0)\n  dp[0] = 1\n  # State transitions\n  for i in 1...(n + 1)\n    # Traverse in increasing order\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If the coin exceeds the current amount a, do not choose coin i\n        dp[a] = dp[a]\n      else\n        # Add the numbers of ways without and with coin i\n        dp[a] = dp[a] + dp[a - coins[i - 1]]\n      end\n    end\n  end\n  dp[amt]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_graph/","level":1,"title":"Chapter 9.   Graphs","text":"

    Abstract

    On the journey of life, we are like nodes joined by countless invisible edges.

    Every meeting and parting leaves a unique mark on this vast network.

    ","path":["Chapter 9.   Graphs"],"tags":[]},{"location":"chapter_graph/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 9.1   Graphs
    • 9.2   Basic graph operations
    • 9.3   Graph traversal
    • 9.4   Summary
    • 9.5   Exercises
    ","path":["Chapter 9.   Graphs"],"tags":[]},{"location":"chapter_graph/exercises/","level":1,"title":"9.5   Exercises","text":"","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#951-check-your-understanding","level":2,"title":"9.5.1   Check your understanding","text":"","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#1-representing-the-same-graph-in-two-ways","level":3,"title":"1.   Representing the same graph in two ways","text":"

    An undirected graph has four vertices A, B, C, D and edges A-B, A-C, B-C, C-D.

    1. Write its adjacency list.
    2. Fill in its adjacency matrix using only 0 and 1.
    3. Which representation lets us determine whether A and D are directly connected by inspecting just one stored entry?
    4. If a graph has many vertices but few edges, which representation usually uses less space?
    Reference answer
    1. The adjacency list is:

      A: B, C\nB: A, C\nC: A, B, D\nD: C\n
    2. The adjacency matrix is:

      A B C D A 0 1 1 0 B 1 0 1 0 C 1 1 0 1 D 0 0 1 0
    3. In the adjacency matrix, we can inspect row A, column D directly. This makes it well suited to checking whether any two vertices are directly connected.

    4. When there are many vertices but few edges, an adjacency list records only existing edges. It usually uses less space than an adjacency matrix, which reserves an entry for every pair of vertices.

    ","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#2-breadth-first-and-depth-first-traversal-orders","level":3,"title":"2.   Breadth-first and depth-first traversal orders","text":"

    An undirected graph has vertices A, B, C, D, E and edges A-B, A-C, B-D, C-D, D-E.

    Start at A. Whenever several unvisited neighbors are available, choose them in alphabetical order:

    1. Write the breadth-first traversal (BFS) order.
    2. Write the recursive depth-first traversal (DFS) order.
    3. Why must both traversals keep track of vertices already visited?
    Reference answer
    1. The BFS order is A, B, C, D, E. It first visits B and C, which are one edge away from A, and then the more distant D and E.

    2. The DFS order is A, B, D, C, E. It proceeds into an unvisited neighbor of the current vertex, first following A → B → D → C. When C has no new neighbors to visit, it returns to D and then visits E.

    3. The graph contains cycles, such as A-B-D-C-A. Without recording visited vertices, traversal could repeatedly follow a cycle through the same vertices and fail to terminate.

    ","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#3-can-one-bfs-visit-the-entire-graph","level":3,"title":"3.   Can one BFS visit the entire graph?","text":"

    An undirected graph has vertices A, B, C, D, E, F and only the edges A-B, B-C, D-E.

    1. Which vertices can a single BFS starting from A visit?
    2. Based on your answer, has this BFS visited every vertex in the graph? Why?
    3. Suppose we scan all vertices alphabetically and start a new BFS whenever we encounter an unvisited vertex. What are the starting vertices of these searches? How many separate connected parts, or connected components, does the graph have?
    Reference answer
    1. Starting from A reaches only A, B, C.

    2. Not every vertex is visited. D, E form another connected part, and F is an isolated vertex. None has a path to A, so they cannot be reached from A.

    3. The three BFS starting vertices are A, D, F, in order, visiting {A, B, C}, {D, E}, and {F}, respectively. Thus, the graph has three connected components.

    ","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#952-programming-exercises","level":2,"title":"9.5.2   Programming exercises","text":"","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#1-checking-for-a-path-in-an-undirected-graph","level":3,"title":"1.   Checking for a path in an undirected graph","text":"

    You are given an undirected graph with \\(n\\) vertices numbered from \\(0\\) to \\(n-1\\). Each entry [u, v] in the array edges represents an undirected edge between vertices u and v.

    You are also given a starting vertex source and an ending vertex destination. First build an adjacency list from edges, then use BFS or DFS to determine whether a path exists from source to destination. Return true if it does, or false otherwise. The graph may contain cycles and may be disconnected.

    Hints
    1. Add each undirected edge in both directions.
    2. The graph may contain cycles, so keep track of vertices already visited.
    3. Starting from source, return true if you reach destination. Return false if traversal ends without reaching it.

    LeetCode

    ","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/graph/","level":1,"title":"9.1   Graphs","text":"

    A graph is a nonlinear data structure made up of vertices and edges. We can represent a graph \\(G\\) abstractly using a set of vertices \\(V\\) and a set of edges \\(E\\). The following example describes a graph with 5 vertices and 7 edges.

    \\[ \\begin{aligned} V & = \\{ 1, 2, 3, 4, 5 \\} \\newline E & = \\{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \\} \\newline G & = \\{ V, E \\} \\newline \\end{aligned} \\]

    If we think of vertices as nodes and edges as references (pointers) connecting those nodes, a graph can be viewed as an extension of a linked list. As Figure 9-1 shows, network relationships (graphs) allow more freedom than linear relationships (linked lists) or divide-and-conquer relationships (trees), making them more complex.

    Figure 9-1   Relationships between linked lists, trees, and graphs

    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#911-common-graph-types-and-terminology","level":2,"title":"9.1.1   Common graph types and terminology","text":"

    Depending on whether their edges have a direction, graphs are classified as undirected graphs or directed graphs, as shown in Figure 9-2.

    • In an undirected graph, an edge represents a two-way connection between two vertices, such as a friendship on WeChat or QQ.
    • In a directed graph, edges have a direction: the edges \\(A \\rightarrow B\\) and \\(A \\leftarrow B\\) are independent of each other. Examples include following and being followed on Weibo or Douyin.

    Figure 9-2   Directed and undirected graphs

    Depending on whether all vertices are connected, graphs are classified as connected graphs or disconnected graphs, as shown in Figure 9-3.

    • In a connected graph, every other vertex can be reached from any starting vertex.
    • In a disconnected graph, at least one vertex cannot be reached from a given starting vertex.

    Figure 9-3   Connected and disconnected graphs

    We can also assign a weight to each edge to obtain a weighted graph, as shown in Figure 9-4. For example, mobile games such as Honor of Kings calculate an affinity score between players based on the time they spend playing together. A weighted graph can represent this network of affinities.

    Figure 9-4   Weighted and unweighted graphs

    The following terms are commonly used when discussing graphs.

    • Adjacency: Two vertices are adjacent if an edge connects them. In Figure 9-4, the vertices adjacent to vertex 1 are 2, 3, and 5.
    • Path: A sequence of edges followed from vertex A to vertex B is called a path from A to B. In Figure 9-4, the edge sequence 1-5-2-4 forms a path from vertex 1 to vertex 4.
    • Degree: The number of edges incident to a vertex. In a directed graph, the in-degree is the number of edges pointing to the vertex, and the out-degree is the number of edges pointing away from it.
    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#912-representing-a-graph","level":2,"title":"9.1.2   Representing a graph","text":"

    Two common ways to represent a graph are an adjacency matrix and an adjacency list. We will use undirected graphs in the following examples.

    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#1-adjacency-matrix","level":3,"title":"1.   Adjacency matrix","text":"

    For a graph with \\(n\\) vertices, an adjacency matrix represents the graph using an \\(n \\times n\\) matrix. Each row (or column) represents a vertex, and the matrix entries represent edges: \\(1\\) or \\(0\\) indicates whether an edge exists between two vertices.

    As shown in Figure 9-5, let \\(M\\) be the adjacency matrix and \\(V\\) the vertex list. The entry \\(M[i, j] = 1\\) means that an edge connects vertices \\(V[i]\\) and \\(V[j]\\), whereas \\(M[i, j] = 0\\) means that no edge connects them.

    Figure 9-5   Representing a graph with an adjacency matrix

    An adjacency matrix has the following properties.

    • In a simple graph, a vertex cannot connect to itself, so the entries on the main diagonal do not represent meaningful connections.
    • In an undirected graph, edges in the two directions are equivalent, so the adjacency matrix is symmetric about its main diagonal.
    • Replacing the \\(1\\) and \\(0\\) entries with weights allows the matrix to represent a weighted graph.

    With an adjacency matrix, we can access a matrix entry directly to retrieve an edge. Adding, removing, looking up, and modifying edges are therefore efficient, each taking \\(O(1)\\) time. However, the matrix requires \\(O(n^2)\\) space and uses a substantial amount of memory.

    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#2-adjacency-list","level":3,"title":"2.   Adjacency list","text":"

    An adjacency list represents a graph using \\(n\\) linked lists whose nodes represent vertices. The \\(i\\)-th linked list corresponds to vertex \\(i\\) and stores all of its adjacent vertices (the vertices connected to it). Figure 9-6 shows an example of a graph stored as an adjacency list.

    Figure 9-6   Representing a graph with an adjacency list

    An adjacency list stores only edges that actually exist. Since the total number of edges is usually much smaller than \\(n^2\\), it saves space. However, finding an edge requires traversing a linked list, so lookups are slower than with an adjacency matrix.

    As Figure 9-6 shows, an adjacency list closely resembles separate chaining in a hash table, so similar techniques can improve its efficiency. For example, a long linked list can be replaced with an AVL tree or a red-black tree to improve lookup time from \\(O(n)\\) to \\(O(\\log n)\\). Replacing it with a hash table can further reduce the time complexity to \\(O(1)\\).

    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#913-common-applications-of-graphs","level":2,"title":"9.1.3   Common applications of graphs","text":"

    As shown in Table 9-1, many real-world systems can be modeled as graphs, and their associated problems can be reduced to graph computation problems.

    Table 9-1   Common graphs in everyday life

    Vertices Edges Graph computation problem Social network Users Friendships Recommending potential friends Subway network Stations Connections between stations Recommending the shortest route Solar system Celestial bodies Gravitational interactions between bodies Calculating planetary orbits","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph_operations/","level":1,"title":"9.2   Basic graph operations","text":"

    Basic graph operations fall into two categories: operations on edges and operations on vertices. Their implementations differ depending on whether the graph is represented by an adjacency matrix or an adjacency list.

    ","path":["Chapter 9.   Graphs","9.2   Basic graph operations"],"tags":[]},{"location":"chapter_graph/graph_operations/#921-adjacency-matrix-implementation","level":2,"title":"9.2.1   Adjacency matrix implementation","text":"

    For an undirected graph with \\(n\\) vertices, the operations are implemented as shown in Figure 9-7.

    • Add or remove an edge: Modify the corresponding entry in the adjacency matrix directly, taking \\(O(1)\\) time. Since the graph is undirected, update the edge in both directions.
    • Add a vertex: Append one row and one column to the adjacency matrix and fill them with \\(0\\), taking \\(O(n)\\) time.
    • Remove a vertex: Delete the corresponding row and column from the adjacency matrix. The worst case occurs when deleting the first row and column: \\((n-1)^2\\) entries must be shifted up and to the left, taking \\(O(n^2)\\) time.
    • Initialize the graph: Given \\(n\\) vertices, initialize the vertex list vertices of length \\(n\\) in \\(O(n)\\) time, then initialize the \\(n \\times n\\) adjacency matrix adjMat in \\(O(n^2)\\) time.
    <1><2><3><4><5>

    Figure 9-7   Initializing an adjacency matrix and adding or removing edges and vertices

    The following code implements a graph using an adjacency matrix:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_matrix.py
    class GraphAdjMat:\n    \"\"\"Undirected graph class implemented with an adjacency matrix\"\"\"\n\n    def __init__(self, vertices: list[int], edges: list[list[int]]):\n        \"\"\"Constructor\"\"\"\n        # Vertex list: elements are vertex values, and indices are vertex indices\n        self.vertices: list[int] = []\n        # Adjacency matrix: row and column indices correspond to vertex indices\n        self.adj_mat: list[list[int]] = []\n        # Add a vertex\n        for val in vertices:\n            self.add_vertex(val)\n        # Add an edge\n        # Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for e in edges:\n            self.add_edge(e[0], e[1])\n\n    def size(self) -> int:\n        \"\"\"Get the number of vertices\"\"\"\n        return len(self.vertices)\n\n    def add_vertex(self, val: int):\n        \"\"\"Add a vertex\"\"\"\n        n = self.size()\n        # Append the new vertex value to the vertex list\n        self.vertices.append(val)\n        # Add a row to the adjacency matrix\n        new_row = [0] * n\n        self.adj_mat.append(new_row)\n        # Add a column to the adjacency matrix\n        for row in self.adj_mat:\n            row.append(0)\n\n    def remove_vertex(self, index: int):\n        \"\"\"Remove a vertex\"\"\"\n        if index >= self.size():\n            raise IndexError()\n        # Remove the vertex at index `index` from the vertex list\n        self.vertices.pop(index)\n        # Remove row `index` from the adjacency matrix\n        self.adj_mat.pop(index)\n        # Remove column `index` from the adjacency matrix\n        for row in self.adj_mat:\n            row.pop(index)\n\n    def add_edge(self, i: int, j: int):\n        \"\"\"Add an edge\"\"\"\n        # Parameters i and j are element indices in vertices\n        # Handle out-of-bounds or equal indices\n        if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:\n            raise IndexError()\n        # For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        self.adj_mat[i][j] = 1\n        self.adj_mat[j][i] = 1\n\n    def remove_edge(self, i: int, j: int):\n        \"\"\"Remove an edge\"\"\"\n        # Parameters i and j are element indices in vertices\n        # Handle out-of-bounds or equal indices\n        if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:\n            raise IndexError()\n        self.adj_mat[i][j] = 0\n        self.adj_mat[j][i] = 0\n\n    def print(self):\n        \"\"\"Print the adjacency matrix\"\"\"\n        print(\"Vertex list =\", self.vertices)\n        print(\"Adjacency matrix =\")\n        print_matrix(self.adj_mat)\n
    graph_adjacency_matrix.cpp
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    vector<int> vertices;       // Vertex list: elements are vertex values, and indices are vertex indices\n    vector<vector<int>> adjMat; // Adjacency matrix: row and column indices correspond to vertex indices\n\n  public:\n    /* Constructor */\n    GraphAdjMat(const vector<int> &vertices, const vector<vector<int>> &edges) {\n        // Add a vertex\n        for (int val : vertices) {\n            addVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (const vector<int> &edge : edges) {\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int size() const {\n        return vertices.size();\n    }\n\n    /* Add a vertex */\n    void addVertex(int val) {\n        int n = size();\n        // Append the new vertex value to the vertex list\n        vertices.push_back(val);\n        // Add a row to the adjacency matrix\n        adjMat.emplace_back(vector<int>(n, 0));\n        // Add a column to the adjacency matrix\n        for (vector<int> &row : adjMat) {\n            row.push_back(0);\n        }\n    }\n\n    /* Remove a vertex */\n    void removeVertex(int index) {\n        if (index >= size()) {\n            throw out_of_range(\"Invalid vertex\");\n        }\n        // Remove the vertex at index `index` from the vertex list\n        vertices.erase(vertices.begin() + index);\n        // Remove row `index` from the adjacency matrix\n        adjMat.erase(adjMat.begin() + index);\n        // Remove column `index` from the adjacency matrix\n        for (vector<int> &row : adjMat) {\n            row.erase(row.begin() + index);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    void addEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n            throw out_of_range(\"Invalid vertex\");\n        }\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat[i][j] = 1;\n        adjMat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    void removeEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n            throw out_of_range(\"Invalid vertex\");\n        }\n        adjMat[i][j] = 0;\n        adjMat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    void print() {\n        cout << \"Vertex list = \";\n        printVector(vertices);\n        cout << \"Adjacency matrix =\" << endl;\n        printVectorMatrix(adjMat);\n    }\n};\n
    graph_adjacency_matrix.java
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    List<Integer> vertices; // Vertex list: elements are vertex values, and indices are vertex indices\n    List<List<Integer>> adjMat; // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    public GraphAdjMat(int[] vertices, int[][] edges) {\n        this.vertices = new ArrayList<>();\n        this.adjMat = new ArrayList<>();\n        // Add a vertex\n        for (int val : vertices) {\n            addVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (int[] e : edges) {\n            addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    public int size() {\n        return vertices.size();\n    }\n\n    /* Add a vertex */\n    public void addVertex(int val) {\n        int n = size();\n        // Append the new vertex value to the vertex list\n        vertices.add(val);\n        // Add a row to the adjacency matrix\n        List<Integer> newRow = new ArrayList<>(n);\n        for (int j = 0; j < n; j++) {\n            newRow.add(0);\n        }\n        adjMat.add(newRow);\n        // Add a column to the adjacency matrix\n        for (List<Integer> row : adjMat) {\n            row.add(0);\n        }\n    }\n\n    /* Remove a vertex */\n    public void removeVertex(int index) {\n        if (index >= size())\n            throw new IndexOutOfBoundsException();\n        // Remove the vertex at index `index` from the vertex list\n        vertices.remove(index);\n        // Remove row `index` from the adjacency matrix\n        adjMat.remove(index);\n        // Remove column `index` from the adjacency matrix\n        for (List<Integer> row : adjMat) {\n            row.remove(index);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    public void addEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw new IndexOutOfBoundsException();\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat.get(i).set(j, 1);\n        adjMat.get(j).set(i, 1);\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    public void removeEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw new IndexOutOfBoundsException();\n        adjMat.get(i).set(j, 0);\n        adjMat.get(j).set(i, 0);\n    }\n\n    /* Print the adjacency matrix */\n    public void print() {\n        System.out.print(\"Vertex list = \");\n        System.out.println(vertices);\n        System.out.println(\"Adjacency matrix =\");\n        PrintUtil.printMatrix(adjMat);\n    }\n}\n
    graph_adjacency_matrix.cs
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    List<int> vertices;     // Vertex list: elements are vertex values, and indices are vertex indices\n    List<List<int>> adjMat; // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    public GraphAdjMat(int[] vertices, int[][] edges) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add a vertex\n        foreach (int val in vertices) {\n            AddVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        foreach (int[] e in edges) {\n            AddEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int Size() {\n        return vertices.Count;\n    }\n\n    /* Add a vertex */\n    public void AddVertex(int val) {\n        int n = Size();\n        // Append the new vertex value to the vertex list\n        vertices.Add(val);\n        // Add a row to the adjacency matrix\n        List<int> newRow = new(n);\n        for (int j = 0; j < n; j++) {\n            newRow.Add(0);\n        }\n        adjMat.Add(newRow);\n        // Add a column to the adjacency matrix\n        foreach (List<int> row in adjMat) {\n            row.Add(0);\n        }\n    }\n\n    /* Remove a vertex */\n    public void RemoveVertex(int index) {\n        if (index >= Size())\n            throw new IndexOutOfRangeException();\n        // Remove the vertex at index `index` from the vertex list\n        vertices.RemoveAt(index);\n        // Remove row `index` from the adjacency matrix\n        adjMat.RemoveAt(index);\n        // Remove column `index` from the adjacency matrix\n        foreach (List<int> row in adjMat) {\n            row.RemoveAt(index);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    public void AddEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j)\n            throw new IndexOutOfRangeException();\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat[i][j] = 1;\n        adjMat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    public void RemoveEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j)\n            throw new IndexOutOfRangeException();\n        adjMat[i][j] = 0;\n        adjMat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    public void Print() {\n        Console.Write(\"Vertex list = \");\n        PrintUtil.PrintList(vertices);\n        Console.WriteLine(\"Adjacency matrix =\");\n        PrintUtil.PrintMatrix(adjMat);\n    }\n}\n
    graph_adjacency_matrix.go
    /* Undirected graph type implemented with an adjacency matrix */\ntype graphAdjMat struct {\n    // Vertex list: elements are vertex values, and indices are vertex indices\n    vertices []int\n    // Adjacency matrix: row and column indices correspond to vertex indices\n    adjMat [][]int\n}\n\n/* Constructor */\nfunc newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat {\n    // Allocate the adjacency matrix for all vertices\n    n := len(vertices)\n    adjMat := make([][]int, n)\n    for i := range adjMat {\n        adjMat[i] = make([]int, n)\n    }\n    // Initialize the graph\n    g := &graphAdjMat{\n        vertices: vertices,\n        adjMat:   adjMat,\n    }\n    // Add all edges\n    // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n    for i := range edges {\n        g.addEdge(edges[i][0], edges[i][1])\n    }\n    return g\n}\n\n/* Get the number of vertices */\nfunc (g *graphAdjMat) size() int {\n    return len(g.vertices)\n}\n\n/* Add a vertex */\nfunc (g *graphAdjMat) addVertex(val int) {\n    n := g.size()\n    // Append the new vertex value to the vertex list\n    g.vertices = append(g.vertices, val)\n    // Add a row to the adjacency matrix\n    newRow := make([]int, n)\n    g.adjMat = append(g.adjMat, newRow)\n    // Add a column to the adjacency matrix\n    for i := range g.adjMat {\n        g.adjMat[i] = append(g.adjMat[i], 0)\n    }\n}\n\n/* Remove a vertex */\nfunc (g *graphAdjMat) removeVertex(index int) {\n    if index >= g.size() {\n        return\n    }\n    // Remove the vertex at index `index` from the vertex list\n    g.vertices = append(g.vertices[:index], g.vertices[index+1:]...)\n    // Remove row `index` from the adjacency matrix\n    g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...)\n    // Remove column `index` from the adjacency matrix\n    for i := range g.adjMat {\n        g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...)\n    }\n}\n\n/* Add an edge */\n// Parameters i and j are element indices in vertices\nfunc (g *graphAdjMat) addEdge(i, j int) {\n    // Check for out-of-bounds or equal indices (the constructed error is not returned)\n    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\n        fmt.Errorf(\"%s\", \"Index out of bounds\")\n    }\n    // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n    g.adjMat[i][j] = 1\n    g.adjMat[j][i] = 1\n}\n\n/* Remove an edge */\n// Parameters i and j are element indices in vertices\nfunc (g *graphAdjMat) removeEdge(i, j int) {\n    // Check for out-of-bounds or equal indices (the constructed error is not returned)\n    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\n        fmt.Errorf(\"%s\", \"Index out of bounds\")\n    }\n    g.adjMat[i][j] = 0\n    g.adjMat[j][i] = 0\n}\n\n/* Print the adjacency matrix */\nfunc (g *graphAdjMat) print() {\n    fmt.Printf(\"\\tVertex list = %v\\n\", g.vertices)\n    fmt.Printf(\"\\tAdjacency matrix = \\n\")\n    for i := range g.adjMat {\n        fmt.Printf(\"\\t\\t\\t%v\\n\", g.adjMat[i])\n    }\n}\n
    graph_adjacency_matrix.swift
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    private var vertices: [Int] // Vertex list: elements are vertex values, and indices are vertex indices\n    private var adjMat: [[Int]] // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    init(vertices: [Int], edges: [[Int]]) {\n        self.vertices = []\n        adjMat = []\n        // Add all vertices\n        for val in vertices {\n            addVertex(val: val)\n        }\n        // Add all edges\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for e in edges {\n            addEdge(i: e[0], j: e[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    func size() -> Int {\n        vertices.count\n    }\n\n    /* Add a vertex */\n    func addVertex(val: Int) {\n        let n = size()\n        // Append the new vertex value to the vertex list\n        vertices.append(val)\n        // Add a row to the adjacency matrix\n        let newRow = Array(repeating: 0, count: n)\n        adjMat.append(newRow)\n        // Add a column to the adjacency matrix\n        for i in adjMat.indices {\n            adjMat[i].append(0)\n        }\n    }\n\n    /* Remove a vertex */\n    func removeVertex(index: Int) {\n        if index >= size() {\n            fatalError(\"Invalid index\")\n        }\n        // Remove the vertex at index `index` from the vertex list\n        vertices.remove(at: index)\n        // Remove row `index` from the adjacency matrix\n        adjMat.remove(at: index)\n        // Remove column `index` from the adjacency matrix\n        for i in adjMat.indices {\n            adjMat[i].remove(at: index)\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    func addEdge(i: Int, j: Int) {\n        // Handle out-of-bounds or equal indices\n        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {\n            fatalError(\"Invalid index\")\n        }\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat[i][j] = 1\n        adjMat[j][i] = 1\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    func removeEdge(i: Int, j: Int) {\n        // Handle out-of-bounds or equal indices\n        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {\n            fatalError(\"Invalid index\")\n        }\n        adjMat[i][j] = 0\n        adjMat[j][i] = 0\n    }\n\n    /* Print the adjacency matrix */\n    func print() {\n        Swift.print(\"Vertex list = \", terminator: \"\")\n        Swift.print(vertices)\n        Swift.print(\"Adjacency matrix =\")\n        PrintUtil.printMatrix(matrix: adjMat)\n    }\n}\n
    graph_adjacency_matrix.js
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    vertices; // Vertex list: elements are vertex values, and indices are vertex indices\n    adjMat; // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    constructor(vertices, edges) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add a vertex\n        for (const val of vertices) {\n            this.addVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (const e of edges) {\n            this.addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size() {\n        return this.vertices.length;\n    }\n\n    /* Add a vertex */\n    addVertex(val) {\n        const n = this.size();\n        // Append the new vertex value to the vertex list\n        this.vertices.push(val);\n        // Add a row to the adjacency matrix\n        const newRow = [];\n        for (let j = 0; j < n; j++) {\n            newRow.push(0);\n        }\n        this.adjMat.push(newRow);\n        // Add a column to the adjacency matrix\n        for (const row of this.adjMat) {\n            row.push(0);\n        }\n    }\n\n    /* Remove a vertex */\n    removeVertex(index) {\n        if (index >= this.size()) {\n            throw new RangeError('Index out of bounds');\n        }\n        // Remove the vertex at index `index` from the vertex list\n        this.vertices.splice(index, 1);\n\n        // Remove row `index` from the adjacency matrix\n        this.adjMat.splice(index, 1);\n        // Remove column `index` from the adjacency matrix\n        for (const row of this.adjMat) {\n            row.splice(index, 1);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    addEdge(i, j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index out of bounds');\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about its main diagonal: (i, j) === (j, i)\n        this.adjMat[i][j] = 1;\n        this.adjMat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    removeEdge(i, j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index out of bounds');\n        }\n        this.adjMat[i][j] = 0;\n        this.adjMat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    print() {\n        console.log('Vertex list = ', this.vertices);\n        console.log('Adjacency matrix =', this.adjMat);\n    }\n}\n
    graph_adjacency_matrix.ts
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    vertices: number[]; // Vertex list: elements are vertex values, and indices are vertex indices\n    adjMat: number[][]; // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    constructor(vertices: number[], edges: number[][]) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add a vertex\n        for (const val of vertices) {\n            this.addVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (const e of edges) {\n            this.addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size(): number {\n        return this.vertices.length;\n    }\n\n    /* Add a vertex */\n    addVertex(val: number): void {\n        const n: number = this.size();\n        // Append the new vertex value to the vertex list\n        this.vertices.push(val);\n        // Add a row to the adjacency matrix\n        const newRow: number[] = [];\n        for (let j: number = 0; j < n; j++) {\n            newRow.push(0);\n        }\n        this.adjMat.push(newRow);\n        // Add a column to the adjacency matrix\n        for (const row of this.adjMat) {\n            row.push(0);\n        }\n    }\n\n    /* Remove a vertex */\n    removeVertex(index: number): void {\n        if (index >= this.size()) {\n            throw new RangeError('Index out of bounds');\n        }\n        // Remove the vertex at index `index` from the vertex list\n        this.vertices.splice(index, 1);\n\n        // Remove row `index` from the adjacency matrix\n        this.adjMat.splice(index, 1);\n        // Remove column `index` from the adjacency matrix\n        for (const row of this.adjMat) {\n            row.splice(index, 1);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    addEdge(i: number, j: number): void {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index out of bounds');\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about its main diagonal: (i, j) === (j, i)\n        this.adjMat[i][j] = 1;\n        this.adjMat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    removeEdge(i: number, j: number): void {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index out of bounds');\n        }\n        this.adjMat[i][j] = 0;\n        this.adjMat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    print(): void {\n        console.log('Vertex list = ', this.vertices);\n        console.log('Adjacency matrix =', this.adjMat);\n    }\n}\n
    graph_adjacency_matrix.dart
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n  List<int> vertices = []; // Vertex entries: each element is a vertex value, and its index is the vertex index\n  List<List<int>> adjMat = []; //Adjacency matrix: row and column indices correspond to vertex indices\n\n  /* Constructor */\n  GraphAdjMat(List<int> vertices, List<List<int>> edges) {\n    this.vertices = [];\n    this.adjMat = [];\n    // Add a vertex\n    for (int val in vertices) {\n      addVertex(val);\n    }\n    // Add an edge\n    // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n    for (List<int> e in edges) {\n      addEdge(e[0], e[1]);\n    }\n  }\n\n  /* Get the number of vertices */\n  int size() {\n    return vertices.length;\n  }\n\n  /* Add a vertex */\n  void addVertex(int val) {\n    int n = size();\n    // Append the new vertex value to the vertex list\n    vertices.add(val);\n    // Add a row to the adjacency matrix\n    List<int> newRow = List.filled(n, 0, growable: true);\n    adjMat.add(newRow);\n    // Add a column to the adjacency matrix\n    for (List<int> row in adjMat) {\n      row.add(0);\n    }\n  }\n\n  /* Remove a vertex */\n  void removeVertex(int index) {\n    if (index >= size()) {\n      throw IndexError;\n    }\n    // Remove the vertex at index `index` from the vertex list\n    vertices.removeAt(index);\n    // Remove row `index` from the adjacency matrix\n    adjMat.removeAt(index);\n    // Remove column `index` from the adjacency matrix\n    for (List<int> row in adjMat) {\n      row.removeAt(index);\n    }\n  }\n\n  /* Add an edge */\n  // Parameters i and j are element indices in vertices\n  void addEdge(int i, int j) {\n    // Handle out-of-bounds or equal indices\n    if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n      throw IndexError;\n    }\n    // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n    adjMat[i][j] = 1;\n    adjMat[j][i] = 1;\n  }\n\n  /* Remove an edge */\n  // Parameters i and j are element indices in vertices\n  void removeEdge(int i, int j) {\n    // Handle out-of-bounds or equal indices\n    if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n      throw IndexError;\n    }\n    adjMat[i][j] = 0;\n    adjMat[j][i] = 0;\n  }\n\n  /* Print the adjacency matrix */\n  void printAdjMat() {\n    print(\"Vertex list = $vertices\");\n    print(\"Adjacency matrix = \");\n    printMatrix(adjMat);\n  }\n}\n
    graph_adjacency_matrix.rs
    /* Undirected graph type implemented with an adjacency matrix */\npub struct GraphAdjMat {\n    // Vertex list: elements are vertex values, and indices are vertex indices\n    pub vertices: Vec<i32>,\n    // Adjacency matrix: row and column indices correspond to vertex indices\n    pub adj_mat: Vec<Vec<i32>>,\n}\n\nimpl GraphAdjMat {\n    /* Constructor */\n    pub fn new(vertices: Vec<i32>, edges: Vec<[usize; 2]>) -> Self {\n        let mut graph = GraphAdjMat {\n            vertices: vec![],\n            adj_mat: vec![],\n        };\n        // Add a vertex\n        for val in vertices {\n            graph.add_vertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for edge in edges {\n            graph.add_edge(edge[0], edge[1])\n        }\n\n        graph\n    }\n\n    /* Get the number of vertices */\n    pub fn size(&self) -> usize {\n        self.vertices.len()\n    }\n\n    /* Add a vertex */\n    pub fn add_vertex(&mut self, val: i32) {\n        let n = self.size();\n        // Append the new vertex value to the vertex list\n        self.vertices.push(val);\n        // Add a row to the adjacency matrix\n        self.adj_mat.push(vec![0; n]);\n        // Add a column to the adjacency matrix\n        for row in self.adj_mat.iter_mut() {\n            row.push(0);\n        }\n    }\n\n    /* Remove a vertex */\n    pub fn remove_vertex(&mut self, index: usize) {\n        if index >= self.size() {\n            panic!(\"Invalid vertex index\")\n        }\n        // Remove the vertex at index `index` from the vertex list\n        self.vertices.remove(index);\n        // Remove row `index` from the adjacency matrix\n        self.adj_mat.remove(index);\n        // Remove column `index` from the adjacency matrix\n        for row in self.adj_mat.iter_mut() {\n            row.remove(index);\n        }\n    }\n\n    /* Add an edge */\n    pub fn add_edge(&mut self, i: usize, j: usize) {\n        // Parameters i and j are element indices in vertices\n        // Handle out-of-bounds or equal indices\n        if i >= self.size() || j >= self.size() || i == j {\n            panic!(\"Invalid vertex index\")\n        }\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        self.adj_mat[i][j] = 1;\n        self.adj_mat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    pub fn remove_edge(&mut self, i: usize, j: usize) {\n        // Parameters i and j are element indices in vertices\n        // Handle out-of-bounds or equal indices\n        if i >= self.size() || j >= self.size() || i == j {\n            panic!(\"Invalid vertex index\")\n        }\n        self.adj_mat[i][j] = 0;\n        self.adj_mat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    pub fn print(&self) {\n        println!(\"Vertex list = {:?}\", self.vertices);\n        println!(\"Adjacency matrix =\");\n        println!(\"[\");\n        for row in &self.adj_mat {\n            println!(\"  {:?},\", row);\n        }\n        println!(\"]\")\n    }\n}\n
    graph_adjacency_matrix.c
    /* Undirected graph structure implemented with an adjacency matrix */\ntypedef struct {\n    int vertices[MAX_SIZE];\n    int adjMat[MAX_SIZE][MAX_SIZE];\n    int size;\n} GraphAdjMat;\n\n/* Constructor */\nGraphAdjMat *newGraphAdjMat() {\n    GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat));\n    graph->size = 0;\n    for (int i = 0; i < MAX_SIZE; i++) {\n        for (int j = 0; j < MAX_SIZE; j++) {\n            graph->adjMat[i][j] = 0;\n        }\n    }\n    return graph;\n}\n\n/* Destructor */\nvoid delGraphAdjMat(GraphAdjMat *graph) {\n    free(graph);\n}\n\n/* Add a vertex */\nvoid addVertex(GraphAdjMat *graph, int val) {\n    if (graph->size == MAX_SIZE) {\n        fprintf(stderr, \"The graph has reached its maximum number of vertices\\n\");\n        return;\n    }\n    // Add the vertex at index n and set row n and column n to zero\n    int n = graph->size;\n    graph->vertices[n] = val;\n    for (int i = 0; i <= n; i++) {\n        graph->adjMat[n][i] = graph->adjMat[i][n] = 0;\n    }\n    graph->size++;\n}\n\n/* Remove a vertex */\nvoid removeVertex(GraphAdjMat *graph, int index) {\n    if (index < 0 || index >= graph->size) {\n        fprintf(stderr, \"Vertex index out of bounds\\n\");\n        return;\n    }\n    // Remove the vertex at index `index` from the vertex list\n    for (int i = index; i < graph->size - 1; i++) {\n        graph->vertices[i] = graph->vertices[i + 1];\n    }\n    // Remove row `index` from the adjacency matrix\n    for (int i = index; i < graph->size - 1; i++) {\n        for (int j = 0; j < graph->size; j++) {\n            graph->adjMat[i][j] = graph->adjMat[i + 1][j];\n        }\n    }\n    // Remove column `index` from the adjacency matrix\n    for (int i = 0; i < graph->size; i++) {\n        for (int j = index; j < graph->size - 1; j++) {\n            graph->adjMat[i][j] = graph->adjMat[i][j + 1];\n        }\n    }\n    graph->size--;\n}\n\n/* Add an edge */\n// Parameters i and j are element indices in vertices\nvoid addEdge(GraphAdjMat *graph, int i, int j) {\n    if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {\n        fprintf(stderr, \"Edge endpoint indices are out of bounds or equal\\n\");\n        return;\n    }\n    graph->adjMat[i][j] = 1;\n    graph->adjMat[j][i] = 1;\n}\n\n/* Remove an edge */\n// Parameters i and j are element indices in vertices\nvoid removeEdge(GraphAdjMat *graph, int i, int j) {\n    if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {\n        fprintf(stderr, \"Edge endpoint indices are out of bounds or equal\\n\");\n        return;\n    }\n    graph->adjMat[i][j] = 0;\n    graph->adjMat[j][i] = 0;\n}\n\n/* Print the adjacency matrix */\nvoid printGraphAdjMat(GraphAdjMat *graph) {\n    printf(\"Vertex list = \");\n    printArray(graph->vertices, graph->size);\n    printf(\"Adjacency matrix =\\n\");\n    for (int i = 0; i < graph->size; i++) {\n        printArray(graph->adjMat[i], graph->size);\n    }\n}\n
    graph_adjacency_matrix.kt
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat(vertices: IntArray, edges: Array<IntArray>) {\n    val vertices = mutableListOf<Int>() // Vertex list: elements are vertex values, and indices are vertex indices\n    val adjMat = mutableListOf<MutableList<Int>>() // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    init {\n        // Add a vertex\n        for (vertex in vertices) {\n            addVertex(vertex)\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (edge in edges) {\n            addEdge(edge[0], edge[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    fun size(): Int {\n        return vertices.size\n    }\n\n    /* Add a vertex */\n    fun addVertex(_val: Int) {\n        val n = size()\n        // Append the new vertex value to the vertex list\n        vertices.add(_val)\n        // Add a row to the adjacency matrix\n        val newRow = mutableListOf<Int>()\n        for (j in 0..<n) {\n            newRow.add(0)\n        }\n        adjMat.add(newRow)\n        // Add a column to the adjacency matrix\n        for (row in adjMat) {\n            row.add(0)\n        }\n    }\n\n    /* Remove a vertex */\n    fun removeVertex(index: Int) {\n        if (index >= size())\n            throw IndexOutOfBoundsException()\n        // Remove the vertex at index `index` from the vertex list\n        vertices.removeAt(index)\n        // Remove row `index` from the adjacency matrix\n        adjMat.removeAt(index)\n        // Remove column `index` from the adjacency matrix\n        for (row in adjMat) {\n            row.removeAt(index)\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    fun addEdge(i: Int, j: Int) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw IndexOutOfBoundsException()\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat[i][j] = 1\n        adjMat[j][i] = 1\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    fun removeEdge(i: Int, j: Int) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw IndexOutOfBoundsException()\n        adjMat[i][j] = 0\n        adjMat[j][i] = 0\n    }\n\n    /* Print the adjacency matrix */\n    fun print() {\n        print(\"Vertex list = \")\n        println(vertices)\n        println(\"Adjacency matrix =\")\n        printMatrix(adjMat)\n    }\n}\n
    graph_adjacency_matrix.rb
    ### Undirected graph class implemented with an adjacency matrix ###\nclass GraphAdjMat\n  def initialize(vertices, edges)\n    ### Constructor ###\n    # Vertex list: elements are vertex values, and indices are vertex indices\n    @vertices = []\n    # Adjacency matrix: row and column indices correspond to vertex indices\n    @adj_mat = []\n    # Add a vertex\n    vertices.each { |val| add_vertex(val) }\n    # Add an edge\n    # Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n    edges.each { |e| add_edge(e[0], e[1]) }\n  end\n\n  ### Get the number of vertices ###\n  def size\n    @vertices.length\n  end\n\n  ### Add a vertex ###\n  def add_vertex(val)\n    n = size\n    # Append the new vertex value to the vertex list\n    @vertices << val\n    # Add a row to the adjacency matrix\n    new_row = Array.new(n, 0)\n    @adj_mat << new_row\n    # Add a column to the adjacency matrix\n    @adj_mat.each { |row| row << 0 }\n  end\n\n  ### Remove a vertex ###\n  def remove_vertex(index)\n    raise IndexError if index >= size\n\n    # Remove the vertex at index `index` from the vertex list\n    @vertices.delete_at(index)\n    # Remove row `index` from the adjacency matrix\n    @adj_mat.delete_at(index)\n    # Remove column `index` from the adjacency matrix\n    @adj_mat.each { |row| row.delete_at(index) }\n  end\n\n  ### Add an edge ###\n  def add_edge(i, j)\n    # Parameters i and j are element indices in vertices\n    # Handle out-of-bounds or equal indices\n    if i < 0 || j < 0 || i >= size || j >= size || i == j\n      raise IndexError\n    end\n    # For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n    @adj_mat[i][j] = 1\n    @adj_mat[j][i] = 1\n  end\n\n  ### Remove an edge ###\n  def remove_edge(i, j)\n    # Parameters i and j are element indices in vertices\n    # Handle out-of-bounds or equal indices\n    if i < 0 || j < 0 || i >= size || j >= size || i == j\n      raise IndexError\n    end\n    @adj_mat[i][j] = 0\n    @adj_mat[j][i] = 0\n  end\n\n  ### Print the adjacency matrix ###\n  def __print__\n    puts \"Vertex list = #{@vertices}\"\n    puts 'Adjacency matrix ='\n    print_matrix(@adj_mat)\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 9.   Graphs","9.2   Basic graph operations"],"tags":[]},{"location":"chapter_graph/graph_operations/#922-adjacency-list-implementation","level":2,"title":"9.2.2   Adjacency list implementation","text":"

    For an undirected graph with \\(n\\) vertices and \\(m\\) edges, the operations can be implemented as shown in Figure 9-8.

    • Add an edge: Append the edge to the linked list of the corresponding vertex, taking \\(O(1)\\) time. Since the graph is undirected, add the edge in both directions.
    • Remove an edge: Find and remove the specified edge in the corresponding vertex's linked list, taking \\(O(m)\\) time. For an undirected graph, remove the edge in both directions.
    • Add a vertex: Add a linked list to the adjacency list with the new vertex as its head node, taking \\(O(1)\\) time.
    • Remove a vertex: Traverse the entire adjacency list and remove every edge involving the specified vertex, taking \\(O(n + m)\\) time.
    • Initialize the graph: Create \\(n\\) vertices and \\(2m\\) edge entries in the adjacency list, taking \\(O(n + m)\\) time.
    <1><2><3><4><5>

    Figure 9-8   Initializing an adjacency list and adding or removing edges and vertices

    The following code implements an adjacency list, with these differences from Figure 9-8.

    • To make adding and removing vertices easier and simplify the code, we use lists (dynamic arrays) in place of linked lists.
    • A hash table stores the adjacency list. Each key is a vertex instance, and its value is the list (linked list) of adjacent vertices.

    We also use the Vertex class to represent vertices in the adjacency list. If we identified vertices by list indices, as in the adjacency matrix, deleting the vertex at index \\(i\\) would require traversing the entire adjacency list and subtracting \\(1\\) from every index greater than \\(i\\). This would be inefficient. When each vertex is a unique Vertex instance, deleting one vertex requires no changes to the other vertices.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_list.py
    class GraphAdjList:\n    \"\"\"Undirected graph class implemented with an adjacency list\"\"\"\n\n    def __init__(self, edges: list[list[Vertex]]):\n        \"\"\"Constructor\"\"\"\n        # Adjacency list: key is a vertex, value is the list of all its neighbors\n        self.adj_list = dict[Vertex, list[Vertex]]()\n        # Add all vertices and edges\n        for edge in edges:\n            self.add_vertex(edge[0])\n            self.add_vertex(edge[1])\n            self.add_edge(edge[0], edge[1])\n\n    def size(self) -> int:\n        \"\"\"Get the number of vertices\"\"\"\n        return len(self.adj_list)\n\n    def add_edge(self, vet1: Vertex, vet2: Vertex):\n        \"\"\"Add an edge\"\"\"\n        if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n            raise ValueError()\n        # Add edge vet1 - vet2\n        self.adj_list[vet1].append(vet2)\n        self.adj_list[vet2].append(vet1)\n\n    def remove_edge(self, vet1: Vertex, vet2: Vertex):\n        \"\"\"Remove an edge\"\"\"\n        if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n            raise ValueError()\n        # Remove edge vet1 - vet2\n        self.adj_list[vet1].remove(vet2)\n        self.adj_list[vet2].remove(vet1)\n\n    def add_vertex(self, vet: Vertex):\n        \"\"\"Add a vertex\"\"\"\n        if vet in self.adj_list:\n            return\n        # Add a new list to the adjacency list\n        self.adj_list[vet] = []\n\n    def remove_vertex(self, vet: Vertex):\n        \"\"\"Remove a vertex\"\"\"\n        if vet not in self.adj_list:\n            raise ValueError()\n        # Remove the list corresponding to vertex vet from the adjacency list\n        self.adj_list.pop(vet)\n        # Traverse the lists of the other vertices and remove all edges incident to vet\n        for vertex in self.adj_list:\n            if vet in self.adj_list[vertex]:\n                self.adj_list[vertex].remove(vet)\n\n    def print(self):\n        \"\"\"Print the adjacency list\"\"\"\n        print(\"Adjacency list =\")\n        for vertex in self.adj_list:\n            tmp = [v.val for v in self.adj_list[vertex]]\n            print(f\"{vertex.val}: {tmp},\")\n
    graph_adjacency_list.cpp
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n  public:\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    unordered_map<Vertex *, vector<Vertex *>> adjList;\n\n    /* Remove the specified vertex from the vector */\n    void remove(vector<Vertex *> &vec, Vertex *vet) {\n        for (int i = 0; i < vec.size(); i++) {\n            if (vec[i] == vet) {\n                vec.erase(vec.begin() + i);\n                break;\n            }\n        }\n    }\n\n    /* Constructor */\n    GraphAdjList(const vector<vector<Vertex *>> &edges) {\n        // Add all vertices and edges\n        for (const vector<Vertex *> &edge : edges) {\n            addVertex(edge[0]);\n            addVertex(edge[1]);\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int size() {\n        return adjList.size();\n    }\n\n    /* Add an edge */\n    void addEdge(Vertex *vet1, Vertex *vet2) {\n        if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n            throw invalid_argument(\"Invalid vertex\");\n        // Add edge vet1 - vet2\n        adjList[vet1].push_back(vet2);\n        adjList[vet2].push_back(vet1);\n    }\n\n    /* Remove an edge */\n    void removeEdge(Vertex *vet1, Vertex *vet2) {\n        if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n            throw invalid_argument(\"Invalid vertex\");\n        // Remove edge vet1 - vet2\n        remove(adjList[vet1], vet2);\n        remove(adjList[vet2], vet1);\n    }\n\n    /* Add a vertex */\n    void addVertex(Vertex *vet) {\n        if (adjList.count(vet))\n            return;\n        // Add a new list to the adjacency list\n        adjList[vet] = vector<Vertex *>();\n    }\n\n    /* Remove a vertex */\n    void removeVertex(Vertex *vet) {\n        if (!adjList.count(vet))\n            throw invalid_argument(\"Invalid vertex\");\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.erase(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (auto &adj : adjList) {\n            remove(adj.second, vet);\n        }\n    }\n\n    /* Print the adjacency list */\n    void print() {\n        cout << \"Adjacency list =\" << endl;\n        for (auto &adj : adjList) {\n            const auto &key = adj.first;\n            const auto &vec = adj.second;\n            cout << key->val << \": \";\n            printVector(vetsToVals(vec));\n        }\n    }\n};\n
    graph_adjacency_list.java
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    Map<Vertex, List<Vertex>> adjList;\n\n    /* Constructor */\n    public GraphAdjList(Vertex[][] edges) {\n        this.adjList = new HashMap<>();\n        // Add all vertices and edges\n        for (Vertex[] edge : edges) {\n            addVertex(edge[0]);\n            addVertex(edge[1]);\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    public int size() {\n        return adjList.size();\n    }\n\n    /* Add an edge */\n    public void addEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw new IllegalArgumentException();\n        // Add edge vet1 - vet2\n        adjList.get(vet1).add(vet2);\n        adjList.get(vet2).add(vet1);\n    }\n\n    /* Remove an edge */\n    public void removeEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw new IllegalArgumentException();\n        // Remove edge vet1 - vet2\n        adjList.get(vet1).remove(vet2);\n        adjList.get(vet2).remove(vet1);\n    }\n\n    /* Add a vertex */\n    public void addVertex(Vertex vet) {\n        if (adjList.containsKey(vet))\n            return;\n        // Add a new list to the adjacency list\n        adjList.put(vet, new ArrayList<>());\n    }\n\n    /* Remove a vertex */\n    public void removeVertex(Vertex vet) {\n        if (!adjList.containsKey(vet))\n            throw new IllegalArgumentException();\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.remove(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (List<Vertex> list : adjList.values()) {\n            list.remove(vet);\n        }\n    }\n\n    /* Print the adjacency list */\n    public void print() {\n        System.out.println(\"Adjacency list =\");\n        for (Map.Entry<Vertex, List<Vertex>> pair : adjList.entrySet()) {\n            List<Integer> tmp = new ArrayList<>();\n            for (Vertex vertex : pair.getValue())\n                tmp.add(vertex.val);\n            System.out.println(pair.getKey().val + \": \" + tmp + \",\");\n        }\n    }\n}\n
    graph_adjacency_list.cs
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    public Dictionary<Vertex, List<Vertex>> adjList;\n\n    /* Constructor */\n    public GraphAdjList(Vertex[][] edges) {\n        adjList = [];\n        // Add all vertices and edges\n        foreach (Vertex[] edge in edges) {\n            AddVertex(edge[0]);\n            AddVertex(edge[1]);\n            AddEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int Size() {\n        return adjList.Count;\n    }\n\n    /* Add an edge */\n    public void AddEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n            throw new InvalidOperationException();\n        // Add edge vet1 - vet2\n        adjList[vet1].Add(vet2);\n        adjList[vet2].Add(vet1);\n    }\n\n    /* Remove an edge */\n    public void RemoveEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n            throw new InvalidOperationException();\n        // Remove edge vet1 - vet2\n        adjList[vet1].Remove(vet2);\n        adjList[vet2].Remove(vet1);\n    }\n\n    /* Add a vertex */\n    public void AddVertex(Vertex vet) {\n        if (adjList.ContainsKey(vet))\n            return;\n        // Add a new list to the adjacency list\n        adjList.Add(vet, []);\n    }\n\n    /* Remove a vertex */\n    public void RemoveVertex(Vertex vet) {\n        if (!adjList.ContainsKey(vet))\n            throw new InvalidOperationException();\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.Remove(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        foreach (List<Vertex> list in adjList.Values) {\n            list.Remove(vet);\n        }\n    }\n\n    /* Print the adjacency list */\n    public void Print() {\n        Console.WriteLine(\"Adjacency list =\");\n        foreach (KeyValuePair<Vertex, List<Vertex>> pair in adjList) {\n            List<int> tmp = [];\n            foreach (Vertex vertex in pair.Value)\n                tmp.Add(vertex.val);\n            Console.WriteLine(pair.Key.val + \": [\" + string.Join(\", \", tmp) + \"],\");\n        }\n    }\n}\n
    graph_adjacency_list.go
    /* Undirected graph type implemented with an adjacency list */\ntype graphAdjList struct {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    adjList map[Vertex][]Vertex\n}\n\n/* Constructor */\nfunc newGraphAdjList(edges [][]Vertex) *graphAdjList {\n    g := &graphAdjList{\n        adjList: make(map[Vertex][]Vertex),\n    }\n    // Add all vertices and edges\n    for _, edge := range edges {\n        g.addVertex(edge[0])\n        g.addVertex(edge[1])\n        g.addEdge(edge[0], edge[1])\n    }\n    return g\n}\n\n/* Get the number of vertices */\nfunc (g *graphAdjList) size() int {\n    return len(g.adjList)\n}\n\n/* Add an edge */\nfunc (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) {\n    _, ok1 := g.adjList[vet1]\n    _, ok2 := g.adjList[vet2]\n    if !ok1 || !ok2 || vet1 == vet2 {\n        panic(\"error\")\n    }\n    // Add edge vet1 - vet2 by appending each vertex to the other vertex's neighbor list\n    g.adjList[vet1] = append(g.adjList[vet1], vet2)\n    g.adjList[vet2] = append(g.adjList[vet2], vet1)\n}\n\n/* Remove an edge */\nfunc (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) {\n    _, ok1 := g.adjList[vet1]\n    _, ok2 := g.adjList[vet2]\n    if !ok1 || !ok2 || vet1 == vet2 {\n        panic(\"error\")\n    }\n    // Remove edge vet1 - vet2\n    g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2)\n    g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1)\n}\n\n/* Add a vertex */\nfunc (g *graphAdjList) addVertex(vet Vertex) {\n    _, ok := g.adjList[vet]\n    if ok {\n        return\n    }\n    // Add a new list to the adjacency list\n    g.adjList[vet] = make([]Vertex, 0)\n}\n\n/* Remove a vertex */\nfunc (g *graphAdjList) removeVertex(vet Vertex) {\n    _, ok := g.adjList[vet]\n    if !ok {\n        panic(\"error\")\n    }\n    // Remove the list corresponding to vertex vet from the adjacency list\n    delete(g.adjList, vet)\n    // Traverse the lists of the other vertices and remove all edges incident to vet\n    for v, list := range g.adjList {\n        g.adjList[v] = DeleteSliceElms(list, vet)\n    }\n}\n\n/* Print the adjacency list */\nfunc (g *graphAdjList) print() {\n    var builder strings.Builder\n    fmt.Printf(\"Adjacency list = \\n\")\n    for k, v := range g.adjList {\n        builder.WriteString(\"\\t\\t\" + strconv.Itoa(k.Val) + \": \")\n        for _, vet := range v {\n            builder.WriteString(strconv.Itoa(vet.Val) + \" \")\n        }\n        fmt.Println(builder.String())\n        builder.Reset()\n    }\n}\n
    graph_adjacency_list.swift
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    public private(set) var adjList: [Vertex: [Vertex]]\n\n    /* Constructor */\n    public init(edges: [[Vertex]]) {\n        adjList = [:]\n        // Add all vertices and edges\n        for edge in edges {\n            addVertex(vet: edge[0])\n            addVertex(vet: edge[1])\n            addEdge(vet1: edge[0], vet2: edge[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    public func size() -> Int {\n        adjList.count\n    }\n\n    /* Add an edge */\n    public func addEdge(vet1: Vertex, vet2: Vertex) {\n        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n            fatalError(\"Invalid arguments\")\n        }\n        // Add edge vet1 - vet2\n        adjList[vet1]?.append(vet2)\n        adjList[vet2]?.append(vet1)\n    }\n\n    /* Remove an edge */\n    public func removeEdge(vet1: Vertex, vet2: Vertex) {\n        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n            fatalError(\"Invalid arguments\")\n        }\n        // Remove edge vet1 - vet2\n        adjList[vet1]?.removeAll { $0 == vet2 }\n        adjList[vet2]?.removeAll { $0 == vet1 }\n    }\n\n    /* Add a vertex */\n    public func addVertex(vet: Vertex) {\n        if adjList[vet] != nil {\n            return\n        }\n        // Add a new list to the adjacency list\n        adjList[vet] = []\n    }\n\n    /* Remove a vertex */\n    public func removeVertex(vet: Vertex) {\n        if adjList[vet] == nil {\n            fatalError(\"Invalid arguments\")\n        }\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.removeValue(forKey: vet)\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for key in adjList.keys {\n            adjList[key]?.removeAll { $0 == vet }\n        }\n    }\n\n    /* Print the adjacency list */\n    public func print() {\n        Swift.print(\"Adjacency list =\")\n        for (vertex, list) in adjList {\n            let list = list.map { $0.val }\n            Swift.print(\"\\(vertex.val): \\(list),\")\n        }\n    }\n}\n
    graph_adjacency_list.js
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    adjList;\n\n    /* Constructor */\n    constructor(edges) {\n        this.adjList = new Map();\n        // Add all vertices and edges\n        for (const edge of edges) {\n            this.addVertex(edge[0]);\n            this.addVertex(edge[1]);\n            this.addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size() {\n        return this.adjList.size;\n    }\n\n    /* Add an edge */\n    addEdge(vet1, vet2) {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2\n        ) {\n            throw new Error('Invalid argument');\n        }\n        // Add edge vet1 - vet2\n        this.adjList.get(vet1).push(vet2);\n        this.adjList.get(vet2).push(vet1);\n    }\n\n    /* Remove an edge */\n    removeEdge(vet1, vet2) {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2 ||\n            this.adjList.get(vet1).indexOf(vet2) === -1\n        ) {\n            throw new Error('Invalid argument');\n        }\n        // Remove edge vet1 - vet2\n        this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n        this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n    }\n\n    /* Add a vertex */\n    addVertex(vet) {\n        if (this.adjList.has(vet)) return;\n        // Add a new list to the adjacency list\n        this.adjList.set(vet, []);\n    }\n\n    /* Remove a vertex */\n    removeVertex(vet) {\n        if (!this.adjList.has(vet)) {\n            throw new Error('Invalid argument');\n        }\n        // Remove the list corresponding to vertex vet from the adjacency list\n        this.adjList.delete(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (const set of this.adjList.values()) {\n            const index = set.indexOf(vet);\n            if (index > -1) {\n                set.splice(index, 1);\n            }\n        }\n    }\n\n    /* Print the adjacency list */\n    print() {\n        console.log('Adjacency list =');\n        for (const [key, value] of this.adjList) {\n            const tmp = [];\n            for (const vertex of value) {\n                tmp.push(vertex.val);\n            }\n            console.log(key.val + ': ' + tmp.join());\n        }\n    }\n}\n
    graph_adjacency_list.ts
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    adjList: Map<Vertex, Vertex[]>;\n\n    /* Constructor */\n    constructor(edges: Vertex[][]) {\n        this.adjList = new Map();\n        // Add all vertices and edges\n        for (const edge of edges) {\n            this.addVertex(edge[0]);\n            this.addVertex(edge[1]);\n            this.addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size(): number {\n        return this.adjList.size;\n    }\n\n    /* Add an edge */\n    addEdge(vet1: Vertex, vet2: Vertex): void {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2\n        ) {\n            throw new Error('Invalid argument');\n        }\n        // Add edge vet1 - vet2\n        this.adjList.get(vet1).push(vet2);\n        this.adjList.get(vet2).push(vet1);\n    }\n\n    /* Remove an edge */\n    removeEdge(vet1: Vertex, vet2: Vertex): void {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2 ||\n            this.adjList.get(vet1).indexOf(vet2) === -1\n        ) {\n            throw new Error('Invalid argument');\n        }\n        // Remove edge vet1 - vet2\n        this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n        this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n    }\n\n    /* Add a vertex */\n    addVertex(vet: Vertex): void {\n        if (this.adjList.has(vet)) return;\n        // Add a new list to the adjacency list\n        this.adjList.set(vet, []);\n    }\n\n    /* Remove a vertex */\n    removeVertex(vet: Vertex): void {\n        if (!this.adjList.has(vet)) {\n            throw new Error('Invalid argument');\n        }\n        // Remove the list corresponding to vertex vet from the adjacency list\n        this.adjList.delete(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (const set of this.adjList.values()) {\n            const index: number = set.indexOf(vet);\n            if (index > -1) {\n                set.splice(index, 1);\n            }\n        }\n    }\n\n    /* Print the adjacency list */\n    print(): void {\n        console.log('Adjacency list =');\n        for (const [key, value] of this.adjList.entries()) {\n            const tmp = [];\n            for (const vertex of value) {\n                tmp.push(vertex.val);\n            }\n            console.log(key.val + ': ' + tmp.join());\n        }\n    }\n}\n
    graph_adjacency_list.dart
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n  // Adjacency list: key is a vertex, value is the list of all its neighbors\n  Map<Vertex, List<Vertex>> adjList = {};\n\n  /* Constructor */\n  GraphAdjList(List<List<Vertex>> edges) {\n    for (List<Vertex> edge in edges) {\n      addVertex(edge[0]);\n      addVertex(edge[1]);\n      addEdge(edge[0], edge[1]);\n    }\n  }\n\n  /* Get the number of vertices */\n  int size() {\n    return adjList.length;\n  }\n\n  /* Add an edge */\n  void addEdge(Vertex vet1, Vertex vet2) {\n    if (!adjList.containsKey(vet1) ||\n        !adjList.containsKey(vet2) ||\n        vet1 == vet2) {\n      throw ArgumentError;\n    }\n    // Add edge vet1 - vet2\n    adjList[vet1]!.add(vet2);\n    adjList[vet2]!.add(vet1);\n  }\n\n  /* Remove an edge */\n  void removeEdge(Vertex vet1, Vertex vet2) {\n    if (!adjList.containsKey(vet1) ||\n        !adjList.containsKey(vet2) ||\n        vet1 == vet2) {\n      throw ArgumentError;\n    }\n    // Remove edge vet1 - vet2\n    adjList[vet1]!.remove(vet2);\n    adjList[vet2]!.remove(vet1);\n  }\n\n  /* Add a vertex */\n  void addVertex(Vertex vet) {\n    if (adjList.containsKey(vet)) return;\n    // Add a new list to the adjacency list\n    adjList[vet] = [];\n  }\n\n  /* Remove a vertex */\n  void removeVertex(Vertex vet) {\n    if (!adjList.containsKey(vet)) {\n      throw ArgumentError;\n    }\n    // Remove the list corresponding to vertex vet from the adjacency list\n    adjList.remove(vet);\n    // Traverse the lists of the other vertices and remove all edges incident to vet\n    adjList.forEach((key, value) {\n      value.remove(vet);\n    });\n  }\n\n  /* Print the adjacency list */\n  void printAdjList() {\n    print(\"Adjacency list =\");\n    adjList.forEach((key, value) {\n      List<int> tmp = [];\n      for (Vertex vertex in value) {\n        tmp.add(vertex.val);\n      }\n      print(\"${key.val}: $tmp,\");\n    });\n  }\n}\n
    graph_adjacency_list.rs
    /* Undirected graph type implemented with an adjacency list */\npub struct GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    pub adj_list: HashMap<Vertex, Vec<Vertex>>, // A HashSet<Vertex> may be a better choice for the neighbor collection\n}\n\nimpl GraphAdjList {\n    /* Constructor */\n    pub fn new(edges: Vec<[Vertex; 2]>) -> Self {\n        let mut graph = GraphAdjList {\n            adj_list: HashMap::new(),\n        };\n        // Add all vertices and edges\n        for edge in edges {\n            graph.add_vertex(edge[0]);\n            graph.add_vertex(edge[1]);\n            graph.add_edge(edge[0], edge[1]);\n        }\n\n        graph\n    }\n\n    /* Get the number of vertices */\n    #[allow(unused)]\n    pub fn size(&self) -> usize {\n        self.adj_list.len()\n    }\n\n    /* Add an edge */\n    pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) {\n        if vet1 == vet2 {\n            panic!(\"Vertices must be distinct\");\n        }\n        // Add edge vet1 - vet2\n        self.adj_list.entry(vet1).or_default().push(vet2);\n        self.adj_list.entry(vet2).or_default().push(vet1);\n    }\n\n    /* Remove an edge */\n    #[allow(unused)]\n    pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) {\n        if vet1 == vet2 {\n            panic!(\"Vertices must be distinct\");\n        }\n        // Remove edge vet1 - vet2\n        self.adj_list\n            .entry(vet1)\n            .and_modify(|v| v.retain(|&e| e != vet2));\n        self.adj_list\n            .entry(vet2)\n            .and_modify(|v| v.retain(|&e| e != vet1));\n    }\n\n    /* Add a vertex */\n    pub fn add_vertex(&mut self, vet: Vertex) {\n        if self.adj_list.contains_key(&vet) {\n            return;\n        }\n        // Add a new neighbor list to the adjacency list\n        self.adj_list.insert(vet, vec![]);\n    }\n\n    /* Remove a vertex */\n    #[allow(unused)]\n    pub fn remove_vertex(&mut self, vet: Vertex) {\n        // Remove the neighbor list for vertex vet from the adjacency list\n        self.adj_list.remove(&vet);\n        // Traverse the other neighbor lists and remove every edge incident to vet\n        for list in self.adj_list.values_mut() {\n            list.retain(|&v| v != vet);\n        }\n    }\n\n    /* Print the adjacency list */\n    pub fn print(&self) {\n        println!(\"Adjacency list =\");\n        for (vertex, list) in &self.adj_list {\n            let list = list.iter().map(|vertex| vertex.val).collect::<Vec<i32>>();\n            println!(\"{}: {:?},\", vertex.val, list);\n        }\n    }\n}\n
    graph_adjacency_list.c
    /* Adjacency-list node structure */\ntypedef struct AdjListNode {\n    Vertex *vertex;           // Vertex\n    struct AdjListNode *next; // Next node\n} AdjListNode;\n\n/* Find the list node corresponding to a vertex */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n    for (int i = 0; i < graph->size; i++) {\n        if (graph->heads[i]->vertex == vet) {\n            return graph->heads[i];\n        }\n    }\n    return NULL;\n}\n\n/* Helper for adding an edge */\nvoid addEdgeHelper(AdjListNode *head, Vertex *vet) {\n    AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode));\n    node->vertex = vet;\n    // Insert at the front of the neighbor list\n    node->next = head->next;\n    head->next = node;\n}\n\n/* Helper for removing an edge */\nvoid removeEdgeHelper(AdjListNode *head, Vertex *vet) {\n    AdjListNode *pre = head;\n    AdjListNode *cur = head->next;\n    // Search the linked list for the node corresponding to vet\n    while (cur != NULL && cur->vertex != vet) {\n        pre = cur;\n        cur = cur->next;\n    }\n    if (cur == NULL)\n        return;\n    // Remove the node corresponding to vet from the linked list\n    pre->next = cur->next;\n    // Free memory\n    free(cur);\n}\n\n/* Undirected graph structure implemented with an adjacency list */\ntypedef struct {\n    AdjListNode *heads[MAX_SIZE]; // Array of list heads\n    int size;                     // Number of vertices\n} GraphAdjList;\n\n/* Constructor */\nGraphAdjList *newGraphAdjList() {\n    GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList));\n    if (!graph) {\n        return NULL;\n    }\n    graph->size = 0;\n    for (int i = 0; i < MAX_SIZE; i++) {\n        graph->heads[i] = NULL;\n    }\n    return graph;\n}\n\n/* Destructor */\nvoid delGraphAdjList(GraphAdjList *graph) {\n    for (int i = 0; i < graph->size; i++) {\n        AdjListNode *cur = graph->heads[i];\n        while (cur != NULL) {\n            AdjListNode *next = cur->next;\n            if (cur != graph->heads[i]) {\n                free(cur);\n            }\n            cur = next;\n        }\n        free(graph->heads[i]->vertex);\n        free(graph->heads[i]);\n    }\n    free(graph);\n}\n\n/* Find the list node corresponding to a vertex */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n    for (int i = 0; i < graph->size; i++) {\n        if (graph->heads[i]->vertex == vet) {\n            return graph->heads[i];\n        }\n    }\n    return NULL;\n}\n\n/* Add an edge */\nvoid addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n    AdjListNode *head1 = findNode(graph, vet1);\n    AdjListNode *head2 = findNode(graph, vet2);\n    assert(head1 != NULL && head2 != NULL && head1 != head2);\n    // Add edge vet1 - vet2\n    addEdgeHelper(head1, vet2);\n    addEdgeHelper(head2, vet1);\n}\n\n/* Remove an edge */\nvoid removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n    AdjListNode *head1 = findNode(graph, vet1);\n    AdjListNode *head2 = findNode(graph, vet2);\n    assert(head1 != NULL && head2 != NULL);\n    // Remove edge vet1 - vet2\n    removeEdgeHelper(head1, head2->vertex);\n    removeEdgeHelper(head2, head1->vertex);\n}\n\n/* Add a vertex */\nvoid addVertex(GraphAdjList *graph, Vertex *vet) {\n    assert(graph != NULL && graph->size < MAX_SIZE);\n    AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode));\n    head->vertex = vet;\n    head->next = NULL;\n    // Add a new list to the adjacency list\n    graph->heads[graph->size++] = head;\n}\n\n/* Remove a vertex */\nvoid removeVertex(GraphAdjList *graph, Vertex *vet) {\n    AdjListNode *node = findNode(graph, vet);\n    assert(node != NULL);\n    // Remove the list corresponding to vertex vet from the adjacency list\n    AdjListNode *cur = node, *pre = NULL;\n    while (cur) {\n        pre = cur;\n        cur = cur->next;\n        free(pre);\n    }\n    // Traverse the lists of the other vertices and remove all edges incident to vet\n    for (int i = 0; i < graph->size; i++) {\n        cur = graph->heads[i];\n        pre = NULL;\n        while (cur) {\n            pre = cur;\n            cur = cur->next;\n            if (cur && cur->vertex == vet) {\n                pre->next = cur->next;\n                free(cur);\n                break;\n            }\n        }\n    }\n    // Shift subsequent vertices forward to fill the gap\n    int i;\n    for (i = 0; i < graph->size; i++) {\n        if (graph->heads[i] == node)\n            break;\n    }\n    for (int j = i; j < graph->size - 1; j++) {\n        graph->heads[j] = graph->heads[j + 1];\n    }\n    graph->size--;\n    free(vet);\n}\n
    graph_adjacency_list.kt
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList(edges: Array<Array<Vertex?>>) {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    val adjList = HashMap<Vertex, MutableList<Vertex>>()\n\n    /* Constructor */\n    init {\n        // Add all vertices and edges\n        for (edge in edges) {\n            addVertex(edge[0]!!)\n            addVertex(edge[1]!!)\n            addEdge(edge[0]!!, edge[1]!!)\n        }\n    }\n\n    /* Get the number of vertices */\n    fun size(): Int {\n        return adjList.size\n    }\n\n    /* Add an edge */\n    fun addEdge(vet1: Vertex, vet2: Vertex) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw IllegalArgumentException()\n        // Add edge vet1 - vet2\n        adjList[vet1]?.add(vet2)\n        adjList[vet2]?.add(vet1)\n    }\n\n    /* Remove an edge */\n    fun removeEdge(vet1: Vertex, vet2: Vertex) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw IllegalArgumentException()\n        // Remove edge vet1 - vet2\n        adjList[vet1]?.remove(vet2)\n        adjList[vet2]?.remove(vet1)\n    }\n\n    /* Add a vertex */\n    fun addVertex(vet: Vertex) {\n        if (adjList.containsKey(vet))\n            return\n        // Add a new list to the adjacency list\n        adjList[vet] = mutableListOf()\n    }\n\n    /* Remove a vertex */\n    fun removeVertex(vet: Vertex) {\n        if (!adjList.containsKey(vet))\n            throw IllegalArgumentException()\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.remove(vet)\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (list in adjList.values) {\n            list.remove(vet)\n        }\n    }\n\n    /* Print the adjacency list */\n    fun print() {\n        println(\"Adjacency list =\")\n        for (pair in adjList.entries) {\n            val tmp = mutableListOf<Int>()\n            for (vertex in pair.value) {\n                tmp.add(vertex._val)\n            }\n            println(\"${pair.key._val}: $tmp,\")\n        }\n    }\n}\n
    graph_adjacency_list.rb
    ### Undirected graph class implemented with an adjacency list ###\nclass GraphAdjList\n  attr_reader :adj_list\n\n  ### Constructor ###\n  def initialize(edges)\n    # Adjacency list: key is a vertex, value is the list of all its neighbors\n    @adj_list = {}\n    # Add all vertices and edges\n    for edge in edges\n      add_vertex(edge[0])\n      add_vertex(edge[1])\n      add_edge(edge[0], edge[1])\n    end\n  end\n\n  ### Get the number of vertices ###\n  def size\n    @adj_list.length\n  end\n\n  ### Add an edge ###\n  def add_edge(vet1, vet2)\n    raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n    @adj_list[vet1] << vet2\n    @adj_list[vet2] << vet1\n  end\n\n  ### Remove an edge ###\n  def remove_edge(vet1, vet2)\n    raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n    # Remove edge vet1 - vet2\n    @adj_list[vet1].delete(vet2)\n    @adj_list[vet2].delete(vet1)\n  end\n\n  ### Add a vertex ###\n  def add_vertex(vet)\n    return if @adj_list.include?(vet)\n\n    # Add a new neighbor list to the adjacency list\n    @adj_list[vet] = []\n  end\n\n  ### Remove a vertex ###\n  def remove_vertex(vet)\n    raise ArgumentError unless @adj_list.include?(vet)\n\n    # Remove the neighbor list for vertex vet from the adjacency list\n    @adj_list.delete(vet)\n    # Traverse the other neighbor lists and remove all edges incident to vet\n    for vertex in @adj_list\n      @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet)\n    end\n  end\n\n  ### Print the adjacency list ###\n  def __print__\n    puts 'Adjacency list ='\n    for vertex in @adj_list\n      tmp = @adj_list[vertex.first].map { |v| v.val }\n      puts \"#{vertex.first.val}: #{tmp},\"\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 9.   Graphs","9.2   Basic graph operations"],"tags":[]},{"location":"chapter_graph/graph_operations/#923-comparing-efficiency","level":2,"title":"9.2.3   Comparing efficiency","text":"

    For a graph with \\(n\\) vertices and \\(m\\) edges, Table 9-2 compares the time and space efficiency of adjacency matrices and adjacency lists. Here, \"adjacency list (linked lists)\" refers to the implementation in this section, while \"adjacency list (hash tables)\" specifically refers to an implementation in which every linked list is replaced by a hash table.

    Table 9-2   Comparing adjacency matrices and adjacency lists

    Adjacency matrix Adjacency list (linked lists) Adjacency list (hash tables) Check adjacency \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) Add an edge \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) Remove an edge \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) Add a vertex \\(O(n)\\) \\(O(1)\\) \\(O(1)\\) Remove a vertex \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n)\\) Memory usage \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n + m)\\)

    Table 9-2 suggests that an adjacency list using hash tables is the most efficient in both time and space. In practice, however, edge operations in an adjacency matrix are faster: each requires only a single array access or assignment. Overall, an adjacency matrix trades space for time, while an adjacency list trades time for space.

    ","path":["Chapter 9.   Graphs","9.2   Basic graph operations"],"tags":[]},{"location":"chapter_graph/graph_traversal/","level":1,"title":"9.3   Graph traversal","text":"

    A tree represents one-to-many relationships, whereas a graph allows more freedom and can represent arbitrary many-to-many relationships. We can therefore view a tree as a special case of a graph. Accordingly, tree traversal is also a special case of graph traversal.

    Both graphs and trees use search algorithms for traversal. Graph traversal can likewise be divided into two types: breadth-first traversal and depth-first traversal.

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#931-breadth-first-traversal","level":2,"title":"9.3.1   Breadth-first traversal","text":"

    Breadth-first traversal visits vertices from near to far. Starting from a vertex, it always visits the nearest vertices first and expands outward layer by layer. In Figure 9-9, we start at the top-left vertex and visit all its adjacent vertices, then visit all the adjacent vertices of the next vertex, and so on until every vertex has been visited.

    Figure 9-9   Breadth-first traversal of a graph

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1-algorithm-implementation","level":3,"title":"1.   Algorithm implementation","text":"

    BFS is usually implemented with a queue, as shown in the code below. A queue's first-in, first-out behavior fits the near-to-far order of BFS.

    1. Enqueue the starting vertex startVet and begin the loop.
    2. In each iteration, dequeue the vertex at the front and record the visit, then enqueue all its adjacent vertices at the rear.
    3. Repeat step 2. until every vertex has been visited.

    To avoid visiting a vertex more than once, we use a hash set visited to track which vertices have already been visited.

    Tip

    A hash set can be viewed as a hash table that stores only key entries, with no value entries. Adding, removing, looking up, and modifying a key take \\(O(1)\\) time. Because every key is unique, hash sets are commonly used to remove duplicate data.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_bfs.py
    def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:\n    \"\"\"Breadth-first traversal\"\"\"\n    # Represent the graph with an adjacency list to access all neighbors of a given vertex\n    # Vertex traversal sequence\n    res = []\n    # Hash set recording visited vertices\n    visited = set[Vertex]([start_vet])\n    # Use a queue to implement BFS\n    que = deque[Vertex]([start_vet])\n    # Starting from start_vet, loop until all reachable vertices have been visited\n    while len(que) > 0:\n        vet = que.popleft()  # Dequeue the front vertex\n        res.append(vet)  # Record the visited vertex\n        # Traverse all neighbors of this vertex\n        for adj_vet in graph.adj_list[vet]:\n            if adj_vet in visited:\n                continue  # Skip vertices that have already been visited\n            que.append(adj_vet)  # Enqueue only unvisited vertices\n            visited.add(adj_vet)  # Mark this vertex as visited\n    # Return the vertex traversal sequence\n    return res\n
    graph_bfs.cpp
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nvector<Vertex *> graphBFS(GraphAdjList &graph, Vertex *startVet) {\n    // Vertex traversal sequence\n    vector<Vertex *> res;\n    // Hash set recording visited vertices\n    unordered_set<Vertex *> visited = {startVet};\n    // Use a queue to implement BFS\n    queue<Vertex *> que;\n    que.push(startVet);\n    // Starting from startVet, continue until all reachable vertices have been visited\n    while (!que.empty()) {\n        Vertex *vet = que.front();\n        que.pop();          // Dequeue the front vertex\n        res.push_back(vet); // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (auto adjVet : graph.adjList[vet]) {\n            if (visited.count(adjVet))\n                continue;            // Skip vertices that have already been visited\n            que.push(adjVet);        // Enqueue only unvisited vertices\n            visited.emplace(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.java
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nList<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = new ArrayList<>();\n    // Hash set recording visited vertices\n    Set<Vertex> visited = new HashSet<>();\n    visited.add(startVet);\n    // Use a queue to implement BFS\n    Queue<Vertex> que = new LinkedList<>();\n    que.offer(startVet);\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (!que.isEmpty()) {\n        Vertex vet = que.poll(); // Dequeue the front vertex\n        res.add(vet);            // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (Vertex adjVet : graph.adjList.get(vet)) {\n            if (visited.contains(adjVet))\n                continue;        // Skip vertices that have already been visited\n            que.offer(adjVet);   // Enqueue only unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.cs
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nList<Vertex> GraphBFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = [];\n    // Hash set recording visited vertices\n    HashSet<Vertex> visited = [startVet];\n    // Use a queue to implement BFS\n    Queue<Vertex> que = new();\n    que.Enqueue(startVet);\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (que.Count > 0) {\n        Vertex vet = que.Dequeue(); // Dequeue the front vertex\n        res.Add(vet);               // Record the visited vertex\n        foreach (Vertex adjVet in graph.adjList[vet]) {\n            if (visited.Contains(adjVet)) {\n                continue;          // Skip vertices that have already been visited\n            }\n            que.Enqueue(adjVet);   // Enqueue only unvisited vertices\n            visited.Add(adjVet);   // Mark this vertex as visited\n        }\n    }\n\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.go
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunc graphBFS(g *graphAdjList, startVet Vertex) []Vertex {\n    // Vertex traversal sequence\n    res := make([]Vertex, 0)\n    // Hash set recording visited vertices\n    visited := make(map[Vertex]struct{})\n    visited[startVet] = struct{}{}\n    // Use a queue for BFS, represented here by a slice\n    queue := make([]Vertex, 0)\n    queue = append(queue, startVet)\n    // Starting from startVet, loop until all reachable vertices have been visited\n    for len(queue) > 0 {\n        // Dequeue the front vertex\n        vet := queue[0]\n        queue = queue[1:]\n        // Record the visited vertex\n        res = append(res, vet)\n        // Traverse all neighbors of this vertex\n        for _, adjVet := range g.adjList[vet] {\n            _, isExist := visited[adjVet]\n            // Enqueue only unvisited vertices\n            if !isExist {\n                queue = append(queue, adjVet)\n                visited[adjVet] = struct{}{}\n            }\n        }\n    }\n    // Return the vertex traversal sequence\n    return res\n}\n
    graph_bfs.swift
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunc graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {\n    // Vertex traversal sequence\n    var res: [Vertex] = []\n    // Hash set recording visited vertices\n    var visited: Set<Vertex> = [startVet]\n    // Use a queue to implement BFS\n    var que: [Vertex] = [startVet]\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while !que.isEmpty {\n        let vet = que.removeFirst() // Dequeue the front vertex\n        res.append(vet) // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for adjVet in graph.adjList[vet] ?? [] {\n            if visited.contains(adjVet) {\n                continue // Skip vertices that have already been visited\n            }\n            que.append(adjVet) // Enqueue only unvisited vertices\n            visited.insert(adjVet) // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res\n}\n
    graph_bfs.js
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction graphBFS(graph, startVet) {\n    // Vertex traversal sequence\n    const res = [];\n    // Hash set recording visited vertices\n    const visited = new Set();\n    visited.add(startVet);\n    // Use a queue to implement BFS\n    const que = [startVet];\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (que.length) {\n        const vet = que.shift(); // Dequeue the front vertex\n        res.push(vet); // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (const adjVet of graph.adjList.get(vet) ?? []) {\n            if (visited.has(adjVet)) {\n                continue; // Skip vertices that have already been visited\n            }\n            que.push(adjVet); // Enqueue only unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.ts
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n    // Vertex traversal sequence\n    const res: Vertex[] = [];\n    // Hash set recording visited vertices\n    const visited: Set<Vertex> = new Set();\n    visited.add(startVet);\n    // Use a queue to implement BFS\n    const que = [startVet];\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (que.length) {\n        const vet = que.shift(); // Dequeue the front vertex\n        res.push(vet); // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (const adjVet of graph.adjList.get(vet) ?? []) {\n            if (visited.has(adjVet)) {\n                continue; // Skip vertices that have already been visited\n            }\n            que.push(adjVet); // Enqueue only unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.dart
    /* Breadth-first traversal */\nList<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {\n  // Represent the graph with an adjacency list to access all neighbors of a given vertex\n  // Vertex traversal sequence\n  List<Vertex> res = [];\n  // Hash set recording visited vertices\n  Set<Vertex> visited = {};\n  visited.add(startVet);\n  // Use a queue to implement BFS\n  Queue<Vertex> que = Queue();\n  que.add(startVet);\n  // Starting from startVet, loop until all reachable vertices have been visited\n  while (que.isNotEmpty) {\n    Vertex vet = que.removeFirst(); // Dequeue the front vertex\n    res.add(vet); // Record the visited vertex\n    // Traverse all neighbors of this vertex\n    for (Vertex adjVet in graph.adjList[vet]!) {\n      if (visited.contains(adjVet)) {\n        continue; // Skip vertices that have already been visited\n      }\n      que.add(adjVet); // Enqueue only unvisited vertices\n      visited.add(adjVet); // Mark this vertex as visited\n    }\n  }\n  // Return the vertex traversal sequence\n  return res;\n}\n
    graph_bfs.rs
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {\n    // Vertex traversal sequence\n    let mut res = vec![];\n    // Hash set recording visited vertices\n    let mut visited = HashSet::new();\n    visited.insert(start_vet);\n    // Use a queue to implement BFS\n    let mut que = VecDeque::new();\n    que.push_back(start_vet);\n    // Start at start_vet and loop until all reachable vertices have been visited\n    while let Some(vet) = que.pop_front() {\n        res.push(vet); // Record the visited vertex\n\n        // Traverse all neighbors of this vertex\n        if let Some(adj_vets) = graph.adj_list.get(&vet) {\n            for &adj_vet in adj_vets {\n                if visited.contains(&adj_vet) {\n                    continue; // Skip vertices that have already been visited\n                }\n                que.push_back(adj_vet); // Enqueue only unvisited vertices\n                visited.insert(adj_vet); // Mark this vertex as visited\n            }\n        }\n    }\n    // Return the vertex traversal sequence\n    res\n}\n
    graph_bfs.c
    /* Vertex queue structure */\ntypedef struct {\n    Vertex *vertices[MAX_SIZE];\n    int front, rear, size;\n} Queue;\n\n/* Constructor */\nQueue *newQueue() {\n    Queue *q = (Queue *)malloc(sizeof(Queue));\n    q->front = q->rear = q->size = 0;\n    return q;\n}\n\n/* Check whether the queue is empty */\nint isEmpty(Queue *q) {\n    return q->size == 0;\n}\n\n/* Enqueue operation */\nvoid enqueue(Queue *q, Vertex *vet) {\n    q->vertices[q->rear] = vet;\n    q->rear = (q->rear + 1) % MAX_SIZE;\n    q->size++;\n}\n\n/* Dequeue operation */\nVertex *dequeue(Queue *q) {\n    Vertex *vet = q->vertices[q->front];\n    q->front = (q->front + 1) % MAX_SIZE;\n    q->size--;\n    return vet;\n}\n\n/* Check whether a vertex has been visited */\nint isVisited(Vertex **visited, int size, Vertex *vet) {\n    // Search for the vertex with a linear scan in O(n) time\n    for (int i = 0; i < size; i++) {\n        if (visited[i] == vet)\n            return 1;\n    }\n    return 0;\n}\n\n/* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nvoid graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) {\n    // Use a queue to implement BFS\n    Queue *queue = newQueue();\n    enqueue(queue, startVet);\n    visited[(*visitedSize)++] = startVet;\n    // Starting from startVet, continue until all reachable vertices have been visited\n    while (!isEmpty(queue)) {\n        Vertex *vet = dequeue(queue); // Dequeue the front vertex\n        res[(*resSize)++] = vet;      // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        AdjListNode *node = findNode(graph, vet);\n        while (node != NULL) {\n            // Skip vertices that have already been visited\n            if (!isVisited(visited, *visitedSize, node->vertex)) {\n                enqueue(queue, node->vertex);             // Enqueue only unvisited vertices\n                visited[(*visitedSize)++] = node->vertex; // Mark this vertex as visited\n            }\n            node = node->next;\n        }\n    }\n    // Free memory\n    free(queue);\n}\n
    graph_bfs.kt
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList<Vertex?> {\n    // Vertex traversal sequence\n    val res = mutableListOf<Vertex?>()\n    // Hash set recording visited vertices\n    val visited = HashSet<Vertex>()\n    visited.add(startVet)\n    // Use a queue to implement BFS\n    val que = LinkedList<Vertex>()\n    que.offer(startVet)\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (!que.isEmpty()) {\n        val vet = que.poll() // Dequeue the front vertex\n        res.add(vet)         // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (adjVet in graph.adjList[vet]!!) {\n            if (visited.contains(adjVet))\n                continue        // Skip vertices that have already been visited\n            que.offer(adjVet)   // Enqueue only unvisited vertices\n            visited.add(adjVet) // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res\n}\n
    graph_bfs.rb
    ### Breadth-first traversal ###\ndef graph_bfs(graph, start_vet)\n  # Represent the graph with an adjacency list to access all neighbors of a given vertex\n  # Vertex traversal sequence\n  res = []\n  # Hash set recording visited vertices\n  visited = Set.new([start_vet])\n  # Use a queue to implement BFS\n  que = [start_vet]\n  # Start from start_vet and loop until all reachable vertices have been visited\n  while que.length > 0\n    vet = que.shift # Dequeue the front vertex\n    res << vet # Record the visited vertex\n    # Traverse all neighbors of this vertex\n    for adj_vet in graph.adj_list[vet]\n      next if visited.include?(adj_vet) # Skip vertices that have already been visited\n      que << adj_vet # Enqueue only unvisited vertices\n      visited.add(adj_vet) # Mark this vertex as visited\n    end\n  end\n  # Return the vertex traversal sequence\n  res\nend\n
    Visualize execution

    View full screen >

    The code is fairly abstract; compare it with Figure 9-10 to build your understanding.

    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 9-10   Steps in breadth-first traversal of a graph

    Is the breadth-first traversal order unique?

    No. Breadth-first traversal only requires vertices to be visited from near to far; vertices at the same distance may be visited in any order. In Figure 9-10, vertices \\(1\\) and \\(3\\) can be visited in either order, and vertices \\(2\\), \\(4\\), and \\(6\\) can also be visited in any order.

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2-complexity-analysis","level":3,"title":"2.   Complexity analysis","text":"

    Time complexity: Every vertex is enqueued and dequeued once, taking \\(O(|V|)\\) time. While traversing adjacent vertices, every edge is visited \\(2\\) times because the graph is undirected, taking \\(O(2|E|)\\) time. The total time is \\(O(|V| + |E|)\\).

    Space complexity: The list res, hash set visited, and queue que each contain at most \\(|V|\\) vertices, requiring \\(O(|V|)\\) space.

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#932-depth-first-traversal","level":2,"title":"9.3.2   Depth-first traversal","text":"

    Depth-first traversal follows a path as far as possible and returns when it can go no farther. In Figure 9-11, we start at the top-left vertex and move to an adjacent vertex of the current vertex, continuing until we reach a dead end. We then return and explore another path as far as possible, repeating this process until every vertex has been visited.

    Figure 9-11   Depth-first traversal of a graph

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1-algorithm-implementation_1","level":3,"title":"1.   Algorithm implementation","text":"

    This pattern of going as far as possible and then returning is usually implemented with recursion. As with breadth-first traversal, we use a hash set visited to track visited vertices and avoid visiting them again.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_dfs.py
    def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):\n    \"\"\"Depth-first traversal helper function\"\"\"\n    res.append(vet)  # Record the visited vertex\n    visited.add(vet)  # Mark this vertex as visited\n    # Traverse all neighbors of this vertex\n    for adjVet in graph.adj_list[vet]:\n        if adjVet in visited:\n            continue  # Skip vertices that have already been visited\n        # Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet)\n\ndef graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:\n    \"\"\"Depth-first traversal\"\"\"\n    # Represent the graph with an adjacency list to access all neighbors of a given vertex\n    # Vertex traversal sequence\n    res = []\n    # Hash set recording visited vertices\n    visited = set[Vertex]()\n    dfs(graph, visited, res, start_vet)\n    return res\n
    graph_dfs.cpp
    /* Depth-first traversal helper function */\nvoid dfs(GraphAdjList &graph, unordered_set<Vertex *> &visited, vector<Vertex *> &res, Vertex *vet) {\n    res.push_back(vet);   // Record the visited vertex\n    visited.emplace(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (Vertex *adjVet : graph.adjList[vet]) {\n        if (visited.count(adjVet))\n            continue; // Skip vertices that have already been visited\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nvector<Vertex *> graphDFS(GraphAdjList &graph, Vertex *startVet) {\n    // Vertex traversal sequence\n    vector<Vertex *> res;\n    // Hash set recording visited vertices\n    unordered_set<Vertex *> visited;\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.java
    /* Depth-first traversal helper function */\nvoid dfs(GraphAdjList graph, Set<Vertex> visited, List<Vertex> res, Vertex vet) {\n    res.add(vet);     // Record the visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (Vertex adjVet : graph.adjList.get(vet)) {\n        if (visited.contains(adjVet))\n            continue; // Skip vertices that have already been visited\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nList<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = new ArrayList<>();\n    // Hash set recording visited vertices\n    Set<Vertex> visited = new HashSet<>();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.cs
    /* Depth-first traversal helper function */\nvoid DFS(GraphAdjList graph, HashSet<Vertex> visited, List<Vertex> res, Vertex vet) {\n    res.Add(vet);     // Record the visited vertex\n    visited.Add(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    foreach (Vertex adjVet in graph.adjList[vet]) {\n        if (visited.Contains(adjVet)) {\n            continue; // Skip vertices that have already been visited                             \n        }\n        // Recursively visit the neighboring vertex\n        DFS(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nList<Vertex> GraphDFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = [];\n    // Hash set recording visited vertices\n    HashSet<Vertex> visited = [];\n    DFS(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.go
    /* Depth-first traversal helper function */\nfunc dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) {\n    // append returns an updated slice, which must be assigned back through the original slice pointer\n    *res = append(*res, vet)\n    visited[vet] = struct{}{}\n    // Traverse all neighbors of this vertex\n    for _, adjVet := range g.adjList[vet] {\n        _, isExist := visited[adjVet]\n        // Recursively visit the neighboring vertex\n        if !isExist {\n            dfs(g, visited, res, adjVet)\n        }\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunc graphDFS(g *graphAdjList, startVet Vertex) []Vertex {\n    // Vertex traversal sequence\n    res := make([]Vertex, 0)\n    // Hash set recording visited vertices\n    visited := make(map[Vertex]struct{})\n    dfs(g, visited, &res, startVet)\n    // Return the vertex traversal sequence\n    return res\n}\n
    graph_dfs.swift
    /* Depth-first traversal helper function */\nfunc dfs(graph: GraphAdjList, visited: inout Set<Vertex>, res: inout [Vertex], vet: Vertex) {\n    res.append(vet) // Record the visited vertex\n    visited.insert(vet) // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for adjVet in graph.adjList[vet] ?? [] {\n        if visited.contains(adjVet) {\n            continue // Skip vertices that have already been visited\n        }\n        // Recursively visit the neighboring vertex\n        dfs(graph: graph, visited: &visited, res: &res, vet: adjVet)\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunc graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {\n    // Vertex traversal sequence\n    var res: [Vertex] = []\n    // Hash set recording visited vertices\n    var visited: Set<Vertex> = []\n    dfs(graph: graph, visited: &visited, res: &res, vet: startVet)\n    return res\n}\n
    graph_dfs.js
    /* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction dfs(graph, visited, res, vet) {\n    res.push(vet); // Record the visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (const adjVet of graph.adjList.get(vet)) {\n        if (visited.has(adjVet)) {\n            continue; // Skip vertices that have already been visited\n        }\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction graphDFS(graph, startVet) {\n    // Vertex traversal sequence\n    const res = [];\n    // Hash set recording visited vertices\n    const visited = new Set();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.ts
    /* Depth-first traversal helper function */\nfunction dfs(\n    graph: GraphAdjList,\n    visited: Set<Vertex>,\n    res: Vertex[],\n    vet: Vertex\n): void {\n    res.push(vet); // Record the visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (const adjVet of graph.adjList.get(vet)) {\n        if (visited.has(adjVet)) {\n            continue; // Skip vertices that have already been visited\n        }\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n    // Vertex traversal sequence\n    const res: Vertex[] = [];\n    // Hash set recording visited vertices\n    const visited: Set<Vertex> = new Set();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.dart
    /* Depth-first traversal helper function */\nvoid dfs(\n  GraphAdjList graph,\n  Set<Vertex> visited,\n  List<Vertex> res,\n  Vertex vet,\n) {\n  res.add(vet); // Record the visited vertex\n  visited.add(vet); // Mark this vertex as visited\n  // Traverse all neighbors of this vertex\n  for (Vertex adjVet in graph.adjList[vet]!) {\n    if (visited.contains(adjVet)) {\n      continue; // Skip vertices that have already been visited\n    }\n    // Recursively visit the neighboring vertex\n    dfs(graph, visited, res, adjVet);\n  }\n}\n\n/* Depth-first traversal */\nList<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {\n  // Vertex traversal sequence\n  List<Vertex> res = [];\n  // Hash set recording visited vertices\n  Set<Vertex> visited = {};\n  dfs(graph, visited, res, startVet);\n  return res;\n}\n
    graph_dfs.rs
    /* Depth-first traversal helper function */\nfn dfs(graph: &GraphAdjList, visited: &mut HashSet<Vertex>, res: &mut Vec<Vertex>, vet: Vertex) {\n    res.push(vet); // Record the visited vertex\n    visited.insert(vet); // Mark this vertex as visited\n                         // Traverse all neighbors of this vertex\n    if let Some(adj_vets) = graph.adj_list.get(&vet) {\n        for &adj_vet in adj_vets {\n            if visited.contains(&adj_vet) {\n                continue; // Skip vertices that have already been visited\n            }\n            // Recursively visit the neighboring vertex\n            dfs(graph, visited, res, adj_vet);\n        }\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {\n    // Vertex traversal sequence\n    let mut res = vec![];\n    // Hash set recording visited vertices\n    let mut visited = HashSet::new();\n    dfs(&graph, &mut visited, &mut res, start_vet);\n\n    res\n}\n
    graph_dfs.c
    /* Check whether a vertex has been visited */\nint isVisited(Vertex **res, int size, Vertex *vet) {\n    // Search for the vertex with a linear scan in O(n) time\n    for (int i = 0; i < size; i++) {\n        if (res[i] == vet) {\n            return 1;\n        }\n    }\n    return 0;\n}\n\n/* Depth-first traversal helper function */\nvoid dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) {\n    // Record the visited vertex\n    res[(*resSize)++] = vet;\n    // Traverse all neighbors of this vertex\n    AdjListNode *node = findNode(graph, vet);\n    while (node != NULL) {\n        // Skip vertices that have already been visited\n        if (!isVisited(res, *resSize, node->vertex)) {\n            // Recursively visit the neighboring vertex\n            dfs(graph, res, resSize, node->vertex);\n        }\n        node = node->next;\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nvoid graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) {\n    dfs(graph, res, resSize, startVet);\n}\n
    graph_dfs.kt
    /* Depth-first traversal helper function */\nfun dfs(\n    graph: GraphAdjList,\n    visited: MutableSet<Vertex?>,\n    res: MutableList<Vertex?>,\n    vet: Vertex?\n) {\n    res.add(vet)     // Record the visited vertex\n    visited.add(vet) // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (adjVet in graph.adjList[vet]!!) {\n        if (visited.contains(adjVet))\n            continue  // Skip vertices that have already been visited\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet)\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList<Vertex?> {\n    // Vertex traversal sequence\n    val res = mutableListOf<Vertex?>()\n    // Hash set recording visited vertices\n    val visited = HashSet<Vertex?>()\n    dfs(graph, visited, res, startVet)\n    return res\n}\n
    graph_dfs.rb
    ### Depth-first traversal helper function ###\ndef dfs(graph, visited, res, vet)\n  res << vet # Record the visited vertex\n  visited.add(vet) # Mark this vertex as visited\n  # Traverse all neighbors of this vertex\n  for adj_vet in graph.adj_list[vet]\n    next if visited.include?(adj_vet) # Skip vertices that have already been visited\n    # Recursively visit the neighboring vertex\n    dfs(graph, visited, res, adj_vet)\n  end\nend\n\n### Depth-first traversal ###\ndef graph_dfs(graph, start_vet)\n  # Represent the graph with an adjacency list to access all neighbors of a given vertex\n  # Vertex traversal sequence\n  res = []\n  # Hash set recording visited vertices\n  visited = Set.new\n  dfs(graph, visited, res, start_vet)\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 9-12 shows the steps of depth-first traversal.

    • Straight dashed lines show recursive descent, where a new recursive call begins to visit another vertex.
    • Curved dashed lines show backtracking, where a recursive call has returned to the point from which it was made.

    To deepen your understanding, follow Figure 9-12 alongside the code and mentally simulate (or draw on paper) the entire DFS process, including when each recursive call begins and returns.

    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 9-12   Steps in depth-first traversal of a graph

    Is the depth-first traversal order unique?

    Like breadth-first traversal, depth-first traversal does not have a unique order. From any vertex, we can explore in any direction first: rearranging the order of adjacent vertices still produces a depth-first traversal.

    For example, in tree traversal, \"root \\(\\rightarrow\\) left \\(\\rightarrow\\) right\", \"left \\(\\rightarrow\\) root \\(\\rightarrow\\) right\", and \"left \\(\\rightarrow\\) right \\(\\rightarrow\\) root\" correspond to preorder, inorder, and postorder traversal. They use three different visiting priorities, yet all are depth-first traversals.

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2-complexity-analysis_1","level":3,"title":"2.   Complexity analysis","text":"

    Time complexity: Every vertex is visited \\(1\\) time, taking \\(O(|V|)\\) time, and every edge is visited \\(2\\) times, taking \\(O(2|E|)\\) time. The total time is \\(O(|V| + |E|)\\).

    Space complexity: The list res and hash set visited each contain at most \\(|V|\\) vertices, and the maximum recursion depth is \\(|V|\\), so the space complexity is \\(O(|V|)\\).

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/summary/","level":1,"title":"9.4   Summary","text":"","path":["Chapter 9.   Graphs","9.4   Summary"],"tags":[]},{"location":"chapter_graph/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • A graph consists of vertices and edges and can be represented using a vertex set and an edge set.
    • Network relationships (graphs) allow more freedom than linear relationships (linked lists) or divide-and-conquer relationships (trees), making them more complex.
    • Edges in a directed graph have a direction; all vertices in a connected graph are reachable; and each edge in a weighted graph has a weight.
    • An adjacency matrix represents a graph with a matrix. Each row (or column) represents a vertex, and entries represent edges, with \\(1\\) or \\(0\\) indicating whether an edge exists between two vertices. Adding, removing, looking up, and modifying edges are efficient, but the matrix uses substantial space.
    • An adjacency list represents a graph with multiple linked lists. The \\(i\\)-th linked list corresponds to vertex \\(i\\) and stores all of its adjacent vertices. It uses less space than an adjacency matrix, but finding an edge requires traversing a linked list, making lookups slower.
    • A long linked list in an adjacency list can be replaced with a red-black tree or a hash table to speed up lookups.
    • In terms of algorithm design, an adjacency matrix trades space for time, while an adjacency list trades time for space.
    • Graphs can model many real-world systems, such as social networks and subway networks.
    • A tree is a special case of a graph, and tree traversal is a special case of graph traversal.
    • Breadth-first graph traversal searches from near to far, expanding layer by layer. It is usually implemented with a queue.
    • Depth-first graph traversal follows a path as far as possible and backtracks when it can go no farther. It is often implemented with recursion.
    ","path":["Chapter 9.   Graphs","9.4   Summary"],"tags":[]},{"location":"chapter_graph/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Is a path defined as a sequence of vertices or a sequence of edges?

    Wikipedia's language editions give different definitions: the English edition defines a path as a sequence of edges, while the Chinese edition defines it as a sequence of vertices. The English wording is: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.

    In this book, a path is treated as a sequence of edges rather than vertices. This is because multiple edges can connect the same two vertices, in which case each edge corresponds to a path.

    Q: Can some vertices in a disconnected graph remain unvisited?

    In a disconnected graph, at least one vertex cannot be reached from a given starting vertex. Traversing the entire graph requires multiple starting points so that all connected components are visited.

    Q: Must the adjacent vertices in an adjacency list appear in a particular order?

    They may appear in any order. In practice, however, it can be useful to sort them by a specific rule, such as the order in which vertices were added or their values. This helps us quickly find a vertex with a particular extreme value.

    ","path":["Chapter 9.   Graphs","9.4   Summary"],"tags":[]},{"location":"chapter_greedy/","level":1,"title":"Chapter 15.   Greedy algorithms","text":"

    Abstract

    A sunflower turns toward the sun, always seeking the greatest opportunity to grow.

    Through a series of simple choices, a greedy strategy leads us step by step toward the best answer.

    ","path":["Chapter 15.   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 15.1   Greedy algorithms
    • 15.2   Fractional knapsack problem
    • 15.3   Maximum capacity problem
    • 15.4   Maximum product cutting problem
    • 15.5   Summary
    • 15.6   Exercises
    ","path":["Chapter 15.   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/exercises/","level":1,"title":"15.6   Exercises","text":"","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#1561-check-your-understanding","level":2,"title":"15.6.1   Check your understanding","text":"","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#1-is-choosing-the-largest-coin-always-best","level":3,"title":"1.   Is choosing the largest coin always best?","text":"

    The coin denominations are [1, 7, 10], and the target amount is 14.

    1. Follow the rule “always take the largest denomination that does not exceed the remaining amount,” and list the coins chosen.
    2. Is there a way to use fewer coins? If so, give one; if not, explain why.
    3. Can you conclude that this greedy strategy is correct for any set of coin denominations?
    Reference answer
    1. The greedy strategy chooses 10 + 1 + 1 + 1 + 1, using 5 coins in total.

    2. There is a solution with fewer coins: 7 + 7, which uses only 2.

    3. No. This counterexample shows that choosing the largest available denomination at each step does not always minimize the number of coins for arbitrary denominations. The largest immediate choice can rule out a better combination later.

    ","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#2-which-item-should-go-into-the-knapsack-first","level":3,"title":"2.   Which item should go into the knapsack first?","text":"

    A knapsack with a capacity of 4 kg can hold the following items. You may take part of an item, and the value obtained is proportional to the weight taken.

    • Item A weighs 4 kg and has a value of 20.
    • Item B weighs 3 kg and has a value of 18.
    1. What is each item's value per kilogram? Which item should you take first?
    2. If you fill the knapsack using the greedy strategy for fractional knapsack, what is the final value?
    3. When items can be divided and the knapsack limits total weight, should you compare total value or value per kilogram? Why?
    Reference answer
    1. A's value per kilogram is 20 ÷ 4 = 5, and B's is 18 ÷ 3 = 6. Take B first because it has the higher value per unit of weight.

    2. Take all of B first, using 3 kg of capacity and gaining a value of 18. With 1 kg of capacity left, take 1 kg of A for a value of 5. The final value is 18 + 5 = 23.

    3. The knapsack limits total weight, and items can be divided, so compare value per unit of weight. Although A has a higher total value, its value per kilogram is lower than B's. Taking all of A first would give a value of only 20.

    ","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#3-which-pointer-should-move-next","level":3,"title":"3.   Which pointer should move next?","text":"

    The wall heights are [1, 8, 6, 2, 5]. Use two pointers, initially at opposite ends, to find the maximum container capacity. Capacity equals the height of the shorter of the two walls multiplied by the difference between their indices.

    1. Initially, the left pointer is at index 0 and the right pointer is at index 4. What is the current capacity? Which pointer should move next?
    2. After making the move you chose in question 1, what are the two pointer indices? What is the capacity now, and which pointer should move next?
    3. For the current pair of walls, you could move the pointer at the shorter wall or the pointer at the taller wall. Which move still has a chance of producing a larger capacity? Why?
    Reference answer
    1. The current capacity is min(1, 5) × (4 - 0) = 4. The left wall is shorter, so move the left pointer.

    2. After moving the left pointer, the pointers are at indices 1 and 4. The current capacity is min(8, 5) × (4 - 1) = 15. The right wall is shorter, so move the right pointer next.

    3. Only moving the pointer at the shorter wall can still produce a larger capacity. Moving the pointer at the taller wall reduces the distance between the walls, while the container's height remains limited by the shorter wall that has not moved. That height can only stay the same or decrease, so the capacity cannot exceed its previous value. Moving the pointer at the shorter wall is the only way to have a chance of finding a taller wall.

    ","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#1562-programming-exercises","level":2,"title":"15.6.2   Programming exercises","text":"","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#1-fractional-knapsack","level":3,"title":"1.   Fractional knapsack","text":"

    You are given arrays wgt and val of equal length, where wgt[i] > 0 and val[i] >= 0, and a knapsack capacity cap >= 0. There is one of each item, but you may take just part of it. The value obtained is proportional to the fraction of the item's total weight that you take. Use a greedy algorithm to return the maximum total value the knapsack can hold, as a real number.

    Hints
    1. First calculate each item's value per unit of weight, val[i] / wgt[i]. Preserve the fractional part of the division result.
    2. Take items with a higher value per unit of weight first.
    3. If the remaining capacity is less than the current item's weight, take just enough of the item to fill the knapsack, then stop.
    ","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/","level":1,"title":"15.2   Fractional knapsack problem","text":"

    Question

    Given \\(n\\) items, where item \\(i\\) has weight \\(wgt[i-1]\\) and value \\(val[i-1]\\), and a knapsack with capacity \\(cap\\), each item may be selected only once. However, you may take a fraction of an item, with its value proportional to the weight taken. What is the maximum total value that fits within the knapsack's capacity? An example is shown in Figure 15-3.

    Figure 15-3   Example input for the fractional knapsack problem

    The fractional knapsack problem is very similar to the 0-1 knapsack problem. The state consists of the current item \\(i\\) and capacity \\(c\\), and the goal is to maximize value within the knapsack's capacity.

    The difference is that we may take only part of an item. As shown in Figure 15-4, we can divide items into arbitrary fractions and calculate their values in proportion to their weights.

    1. For item \\(i\\), the value per unit weight is \\(val[i-1] / wgt[i-1]\\), which we call its unit value.
    2. If we take a portion of item \\(i\\) weighing \\(w\\), the value added to the knapsack is \\(w \\times val[i-1] / wgt[i-1]\\).

    Figure 15-4   Value per unit weight of each item

    ","path":["Chapter 15.   Greedy algorithms","15.2   Fractional knapsack problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#1-choosing-a-greedy-strategy","level":3,"title":"1.   Choosing a greedy strategy","text":"

    Maximizing the total value in the knapsack essentially means maximizing the value obtained per unit weight. This leads to the greedy strategy shown in Figure 15-5.

    1. Sort the items by unit value in descending order.
    2. Iterate through the items, greedily choosing the item with the highest unit value at each step.
    3. If the remaining capacity is insufficient, take just enough of the current item to fill the knapsack.

    Figure 15-5   Greedy strategy for the fractional knapsack problem

    ","path":["Chapter 15.   Greedy algorithms","15.2   Fractional knapsack problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    We define an Item class to make it easier to sort items by unit value. We repeatedly make greedy choices, stopping and returning the result when the knapsack is full:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby fractional_knapsack.py
    class Item:\n    \"\"\"Item\"\"\"\n\n    def __init__(self, w: int, v: int):\n        self.w = w  # Item weight\n        self.v = v  # Item value\n\ndef fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Fractional knapsack: greedy algorithm\"\"\"\n    # Create a list of items, each with two attributes: weight and value\n    items = [Item(w, v) for w, v in zip(wgt, val)]\n    # Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort(key=lambda item: item.v / item.w, reverse=True)\n    # Repeatedly make greedy choices\n    res = 0\n    for item in items:\n        if item.w <= cap:\n            # If enough capacity remains, put the entire current item in the knapsack\n            res += item.v\n            cap -= item.w\n        else:\n            # If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (item.v / item.w) * cap\n            # No capacity remains, so exit the loop\n            break\n    return res\n
    fractional_knapsack.cpp
    /* Item */\nclass Item {\n  public:\n    int w; // Item weight\n    int v; // Item value\n\n    Item(int w, int v) : w(w), v(v) {\n    }\n};\n\n/* Fractional knapsack: greedy algorithm */\ndouble fractionalKnapsack(vector<int> &wgt, vector<int> &val, int cap) {\n    // Create a list of items, each with two attributes: weight and value\n    vector<Item> items;\n    for (int i = 0; i < wgt.size(); i++) {\n        items.push_back(Item(wgt[i], val[i]));\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; });\n    // Repeatedly make greedy choices\n    double res = 0;\n    for (auto &item : items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (double)item.v / item.w * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.java
    /* Item */\nclass Item {\n    int w; // Item weight\n    int v; // Item value\n\n    public Item(int w, int v) {\n        this.w = w;\n        this.v = v;\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\ndouble fractionalKnapsack(int[] wgt, int[] val, int cap) {\n    // Create a list of items, each with two attributes: weight and value\n    Item[] items = new Item[wgt.length];\n    for (int i = 0; i < wgt.length; i++) {\n        items[i] = new Item(wgt[i], val[i]);\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w)));\n    // Repeatedly make greedy choices\n    double res = 0;\n    for (Item item : items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (double) item.v / item.w * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.cs
    /* Item */\nclass Item(int w, int v) {\n    public int w = w; // Item weight\n    public int v = v; // Item value\n}\n\n/* Fractional knapsack: greedy algorithm */\ndouble FractionalKnapsack(int[] wgt, int[] val, int cap) {\n    // Create a list of items, each with two attributes: weight and value\n    Item[] items = new Item[wgt.Length];\n    for (int i = 0; i < wgt.Length; i++) {\n        items[i] = new Item(wgt[i], val[i]);\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w));\n    // Repeatedly make greedy choices\n    double res = 0;\n    foreach (Item item in items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (double)item.v / item.w * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.go
    /* Item */\ntype Item struct {\n    w int // Item weight\n    v int // Item value\n}\n\n/* Fractional knapsack: greedy algorithm */\nfunc fractionalKnapsack(wgt []int, val []int, cap int) float64 {\n    // Create a list of items, each with two attributes: weight and value\n    items := make([]Item, len(wgt))\n    for i := 0; i < len(wgt); i++ {\n        items[i] = Item{wgt[i], val[i]}\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    sort.Slice(items, func(i, j int) bool {\n        return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w)\n    })\n    // Repeatedly make greedy choices\n    res := 0.0\n    for _, item := range items {\n        if item.w <= cap {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += float64(item.v)\n            cap -= item.w\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += float64(item.v) / float64(item.w) * float64(cap)\n            // No capacity remains, so exit the loop\n            break\n        }\n    }\n    return res\n}\n
    fractional_knapsack.swift
    /* Item */\nclass Item {\n    var w: Int // Item weight\n    var v: Int // Item value\n\n    init(w: Int, v: Int) {\n        self.w = w\n        self.v = v\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\nfunc fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double {\n    // Create a list of items, each with two attributes: weight and value\n    var items = zip(wgt, val).map { Item(w: $0, v: $1) }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) }\n    // Repeatedly make greedy choices\n    var res = 0.0\n    var cap = cap\n    for item in items {\n        if item.w <= cap {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += Double(item.v)\n            cap -= item.w\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += Double(item.v) / Double(item.w) * Double(cap)\n            // No capacity remains, so exit the loop\n            break\n        }\n    }\n    return res\n}\n
    fractional_knapsack.js
    /* Item */\nclass Item {\n    constructor(w, v) {\n        this.w = w; // Item weight\n        this.v = v; // Item value\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\nfunction fractionalKnapsack(wgt, val, cap) {\n    // Create a list of items, each with two attributes: weight and value\n    const items = wgt.map((w, i) => new Item(w, val[i]));\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort((a, b) => b.v / b.w - a.v / a.w);\n    // Repeatedly make greedy choices\n    let res = 0;\n    for (const item of items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (item.v / item.w) * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.ts
    /* Item */\nclass Item {\n    w: number; // Item weight\n    v: number; // Item value\n\n    constructor(w: number, v: number) {\n        this.w = w;\n        this.v = v;\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\nfunction fractionalKnapsack(wgt: number[], val: number[], cap: number): number {\n    // Create a list of items, each with two attributes: weight and value\n    const items: Item[] = wgt.map((w, i) => new Item(w, val[i]));\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort((a, b) => b.v / b.w - a.v / a.w);\n    // Repeatedly make greedy choices\n    let res = 0;\n    for (const item of items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (item.v / item.w) * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.dart
    /* Item */\nclass Item {\n  int w; // Item weight\n  int v; // Item value\n\n  Item(this.w, this.v);\n}\n\n/* Fractional knapsack: greedy algorithm */\ndouble fractionalKnapsack(List<int> wgt, List<int> val, int cap) {\n  // Create a list of items, each with two attributes: weight and value\n  List<Item> items = List.generate(wgt.length, (i) => Item(wgt[i], val[i]));\n  // Sort by value per unit weight, item.v / item.w, in descending order\n  items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w));\n  // Repeatedly make greedy choices\n  double res = 0;\n  for (Item item in items) {\n    if (item.w <= cap) {\n      // If enough capacity remains, put the entire current item in the knapsack\n      res += item.v;\n      cap -= item.w;\n    } else {\n      // If the remaining capacity is insufficient, put part of the current item in the knapsack\n      res += item.v / item.w * cap;\n      // No capacity remains, so exit the loop\n      break;\n    }\n  }\n  return res;\n}\n
    fractional_knapsack.rs
    /* Item */\nstruct Item {\n    w: i32, // Item weight\n    v: i32, // Item value\n}\n\nimpl Item {\n    fn new(w: i32, v: i32) -> Self {\n        Self { w, v }\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\nfn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 {\n    // Create a list of items, each with two attributes: weight and value\n    let mut items = wgt\n        .iter()\n        .zip(val.iter())\n        .map(|(&w, &v)| Item::new(w, v))\n        .collect::<Vec<Item>>();\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort_by(|a, b| {\n        (b.v as f64 / b.w as f64)\n            .partial_cmp(&(a.v as f64 / a.w as f64))\n            .unwrap()\n    });\n    // Repeatedly make greedy choices\n    let mut res = 0.0;\n    for item in &items {\n        if item.w <= cap {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v as f64;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += item.v as f64 / item.w as f64 * cap as f64;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    res\n}\n
    fractional_knapsack.c
    /* Item */\ntypedef struct {\n    int w; // Item weight\n    int v; // Item value\n} Item;\n\n/* Fractional knapsack: greedy algorithm */\nfloat fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) {\n    // Create a list of items, each with two attributes: weight and value\n    Item *items = malloc(sizeof(Item) * itemCount);\n    for (int i = 0; i < itemCount; i++) {\n        items[i] = (Item){.w = wgt[i], .v = val[i]};\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity);\n    // Repeatedly make greedy choices\n    float res = 0.0;\n    for (int i = 0; i < itemCount; i++) {\n        if (items[i].w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += items[i].v;\n            cap -= items[i].w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (float)cap / items[i].w * items[i].v;\n            cap = 0;\n            break;\n        }\n    }\n    free(items);\n    return res;\n}\n
    fractional_knapsack.kt
    /* Item */\nclass Item(\n    val w: Int, // Item weight\n    val v: Int  // Item value\n)\n\n/* Fractional knapsack: greedy algorithm */\nfun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double {\n    // Create a list of items, each with two attributes: weight and value\n    var cap = c\n    val items = arrayOfNulls<Item>(wgt.size)\n    for (i in wgt.indices) {\n        items[i] = Item(wgt[i], _val[i])\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) }\n    // Repeatedly make greedy choices\n    var res = 0.0\n    for (item in items) {\n        if (item!!.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v\n            cap -= item.w\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += item.v.toDouble() / item.w * cap\n            // No capacity remains, so exit the loop\n            break\n        }\n    }\n    return res\n}\n
    fractional_knapsack.rb
    ### Item ###\nclass Item\n  attr_accessor :w # Item weight\n  attr_accessor :v # Item value\n\n  def initialize(w, v)\n    @w = w\n    @v = v\n  end\nend\n\n### Fractional knapsack: greedy algorithm ###\ndef fractional_knapsack(wgt, val, cap)\n  # Create a list of items, each with two attributes: weight and value\n  items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) }\n  # Sort by value per unit weight, item.v / item.w, in descending order\n  items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) }\n  # Repeatedly make greedy choices\n  res = 0\n  for item in items\n    if item.w <= cap\n      # If enough capacity remains, put the entire current item in the knapsack\n      res += item.v\n      cap -= item.w\n    else\n      # If the remaining capacity is insufficient, put part of the current item in the knapsack\n      res += (item.v.to_f / item.w) * cap\n      # No capacity remains, so exit the loop\n      break\n    end\n  end\n  res\nend\n
    Visualize execution

    View full screen >

    Built-in sorting algorithms typically take \\(O(n \\log n)\\) time and \\(O(\\log n)\\) or \\(O(n)\\) space, depending on the programming language's implementation.

    Apart from sorting, the worst case requires traversing the entire item list, so the time complexity is \\(O(n)\\), where \\(n\\) is the number of items.

    Initializing a list of Item objects requires \\(O(n)\\) space.

    ","path":["Chapter 15.   Greedy algorithms","15.2   Fractional knapsack problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#3-proof-of-correctness","level":3,"title":"3.   Proof of correctness","text":"

    We use proof by contradiction. Suppose item \\(x\\) has the highest unit value, and an algorithm produces a maximum value of res, but its solution does not include item \\(x\\).

    Now remove one unit of weight of any item from the knapsack and replace it with one unit of weight of item \\(x\\). Since \\(x\\) has the highest unit value, the total value after replacement must exceed res. This contradicts the assumption that res is optimal, showing that an optimal solution must include item \\(x\\).

    We can construct the same contradiction for other items in the solution. Thus, an item with a higher unit value is always a better choice, which establishes the greedy strategy.

    As shown in Figure 15-6, if we plot item weight on the horizontal axis and unit value on the vertical axis, the fractional knapsack problem becomes a problem of maximizing the area within a limited horizontal interval. This analogy provides a geometric view of why the greedy strategy works.

    Figure 15-6   Geometric representation of the fractional knapsack problem

    ","path":["Chapter 15.   Greedy algorithms","15.2   Fractional knapsack problem"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/","level":1,"title":"15.1   Greedy algorithms","text":"

    A greedy algorithm is a common approach to optimization problems. At each decision point, it chooses the option that looks best at that moment: it makes a locally optimal choice in the hope of reaching a globally optimal solution. Greedy algorithms are simple and efficient, with many practical applications.

    Both greedy algorithms and dynamic programming are commonly used for optimization. They share some features, such as relying on optimal substructure, but work differently.

    • Dynamic programming considers the current decision in light of all decisions from earlier stages, using solutions to earlier subproblems to build the solution to the current subproblem.
    • A greedy algorithm does not reconsider past decisions. It keeps moving forward, making greedy choices and shrinking the problem until it is solved.

    Let's explore how greedy algorithms work through the coin change problem, which you have already encountered in the section on the unbounded knapsack problem.

    Question

    Given \\(n\\) coin denominations, where denomination \\(i\\) has value \\(coins[i - 1]\\), and a target amount \\(amt\\), you may use each denomination any number of times. What is the minimum number of coins needed to make the target amount? Return \\(-1\\) if it cannot be made.

    The greedy strategy is shown in Figure 15-1. Given the remaining target amount, we greedily choose the largest coin that does not exceed it, repeating this step until we have made the target amount.

    Figure 15-1   Greedy strategy for coin change

    The implementation is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_greedy.py
    def coin_change_greedy(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: greedy algorithm\"\"\"\n    # Assume the coins list is sorted\n    i = len(coins) - 1\n    count = 0\n    # Repeatedly make greedy choices until no amount remains\n    while amt > 0:\n        # Find the largest coin no greater than the remaining amount\n        while i > 0 and coins[i] > amt:\n            i -= 1\n        # Choose coins[i]\n        amt -= coins[i]\n        count += 1\n    # Return -1 if no feasible solution was found\n    return count if amt == 0 else -1\n
    coin_change_greedy.cpp
    /* Coin change: greedy algorithm */\nint coinChangeGreedy(vector<int> &coins, int amt) {\n    // Assume the coins list is sorted\n    int i = coins.size() - 1;\n    int count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin that does not exceed the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.java
    /* Coin change: greedy algorithm */\nint coinChangeGreedy(int[] coins, int amt) {\n    // Assume the coins list is sorted\n    int i = coins.length - 1;\n    int count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.cs
    /* Coin change: greedy algorithm */\nint CoinChangeGreedy(int[] coins, int amt) {\n    // Assume the coins list is sorted\n    int i = coins.Length - 1;\n    int count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.go
    /* Coin change: greedy algorithm */\nfunc coinChangeGreedy(coins []int, amt int) int {\n    // Assume the coins list is sorted in ascending order\n    i := len(coins) - 1\n    count := 0\n    // Repeatedly make greedy choices until no amount remains\n    for amt > 0 {\n        // Find the largest coin no greater than the remaining amount\n        for i > 0 && coins[i] > amt {\n            i--\n        }\n        // Choose coins[i]\n        amt -= coins[i]\n        count++\n    }\n    // Return -1 if no feasible solution was found\n    if amt != 0 {\n        return -1\n    }\n    return count\n}\n
    coin_change_greedy.swift
    /* Coin change: greedy algorithm */\nfunc coinChangeGreedy(coins: [Int], amt: Int) -> Int {\n    // Assume the coins list is sorted in ascending order\n    var i = coins.count - 1\n    var count = 0\n    var amt = amt\n    // Repeatedly make greedy choices until no amount remains\n    while amt > 0 {\n        // Find the largest coin no greater than the remaining amount\n        while i > 0 && coins[i] > amt {\n            i -= 1\n        }\n        // Choose coins[i]\n        amt -= coins[i]\n        count += 1\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1\n}\n
    coin_change_greedy.js
    /* Coin change: greedy algorithm */\nfunction coinChangeGreedy(coins, amt) {\n    // Assume coins is sorted in ascending order\n    let i = coins.length - 1;\n    let count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt === 0 ? count : -1;\n}\n
    coin_change_greedy.ts
    /* Coin change: greedy algorithm */\nfunction coinChangeGreedy(coins: number[], amt: number): number {\n    // Assume coins is sorted in ascending order\n    let i = coins.length - 1;\n    let count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt === 0 ? count : -1;\n}\n
    coin_change_greedy.dart
    /* Coin change: greedy algorithm */\nint coinChangeGreedy(List<int> coins, int amt) {\n  // Assume the coins list is sorted in ascending order\n  int i = coins.length - 1;\n  int count = 0;\n  // Repeatedly make greedy choices until no amount remains\n  while (amt > 0) {\n    // Find the largest coin no greater than the remaining amount\n    while (i > 0 && coins[i] > amt) {\n      i--;\n    }\n    // Choose coins[i]\n    amt -= coins[i];\n    count++;\n  }\n  // Return -1 if no feasible solution was found\n  return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.rs
    /* Coin change: greedy algorithm */\nfn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 {\n    // Assume the coins list is sorted\n    let mut i = coins.len() - 1;\n    let mut count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while amt > 0 {\n        // Find the largest coin that does not exceed the remaining amount\n        while i > 0 && coins[i] > amt {\n            i -= 1;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count += 1;\n    }\n    // Return -1 if no feasible solution was found\n    if amt == 0 {\n        count\n    } else {\n        -1\n    }\n}\n
    coin_change_greedy.c
    /* Coin change: greedy algorithm */\nint coinChangeGreedy(int *coins, int size, int amt) {\n    // Assume the coins list is sorted\n    int i = size - 1;\n    int count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.kt
    /* Coin change: greedy algorithm */\nfun coinChangeGreedy(coins: IntArray, amt: Int): Int {\n    // Assume coins is sorted in ascending order\n    var am = amt\n    var i = coins.size - 1\n    var count = 0\n    // Repeatedly make greedy choices until no amount remains\n    while (am > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > am) {\n            i--\n        }\n        // Choose coins[i]\n        am -= coins[i]\n        count++\n    }\n    // Return -1 if no feasible solution was found\n    return if (am == 0) count else -1\n}\n
    coin_change_greedy.rb
    ### Coin change: greedy algorithm ###\ndef coin_change_greedy(coins, amt)\n  # Assume the coins list is sorted in ascending order\n  i = coins.length - 1\n  count = 0\n  # Repeatedly make greedy choices until no amount remains\n  while amt > 0\n    # Find the largest coin no greater than the remaining amount\n    while i > 0 && coins[i] > amt\n      i -= 1\n    end\n    # Choose coins[i]\n    amt -= coins[i]\n    count += 1\n  end\n  # Return -1 if no feasible solution was found\n  amt == 0 ? count : -1\nend\n
    Visualize execution

    View full screen >

    You might find yourself thinking, \"So clean!\" The greedy algorithm solves coin change in only about ten lines of code.

    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1511-strengths-and-limitations-of-greedy-algorithms","level":2,"title":"15.1.1   Strengths and limitations of greedy algorithms","text":"

    Greedy algorithms are straightforward, easy to implement, and usually efficient. In the code above, let \\(\\min(coins)\\) be the smallest coin denomination. At most \\(amt / \\min(coins)\\) greedy choices are made, giving a time complexity of \\(O(amt / \\min(coins))\\). This is an order of magnitude lower than the dynamic programming solution's \\(O(n \\times amt)\\) time complexity.

    However, for some sets of coin denominations, a greedy algorithm cannot find the optimal solution. Figure 15-2 illustrates two examples.

    • Successful case: \\(coins = [1, 5, 10, 20, 50, 100]\\). For these denominations, the greedy algorithm finds an optimal solution for any \\(amt\\).
    • Counterexample: \\(coins = [1, 20, 50]\\). For \\(amt = 60\\), the greedy algorithm produces \\(50 + 1 \\times 10\\), using \\(11\\) coins. Dynamic programming finds the optimal solution \\(20 + 20 + 20\\), using only \\(3\\) coins.
    • Counterexample: \\(coins = [1, 49, 50]\\). For \\(amt = 98\\), the greedy algorithm produces \\(50 + 1 \\times 48\\), using \\(49\\) coins. Dynamic programming finds the optimal solution \\(49 + 49\\), using only \\(2\\) coins.

    Figure 15-2   Examples where the greedy algorithm fails to find the optimal solution

    In other words, a greedy algorithm cannot guarantee a globally optimal solution to coin change and may produce a very poor solution. Dynamic programming is better suited to this problem.

    In general, greedy algorithms are useful in two situations.

    1. An optimal solution is guaranteed: A greedy algorithm is often the best choice because it is usually more efficient than backtracking or dynamic programming.
    2. A near-optimal solution can be found: A greedy algorithm can still be useful. For many complex problems, finding a globally optimal solution is very difficult, so finding a good suboptimal solution efficiently is valuable.
    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1512-properties-required-by-greedy-algorithms","level":2,"title":"15.1.2   Properties required by greedy algorithms","text":"

    What kinds of problems are suitable for greedy algorithms? More specifically, when can a greedy algorithm guarantee an optimal solution?

    Greedy algorithms have stricter requirements than dynamic programming. Two properties are particularly important.

    • Greedy-choice property: A greedy algorithm can guarantee an optimal solution only when locally optimal choices always lead to a globally optimal solution.
    • Optimal substructure: An optimal solution to the original problem contains optimal solutions to its subproblems.

    Optimal substructure was covered in the dynamic programming chapter, so we will not repeat that discussion here. Some problems have optimal substructure that is not immediately apparent, yet can still be solved greedily.

    We will focus on how to determine whether the greedy-choice property holds. Its description may sound simple, but proving it is difficult for many problems.

    For coin change, for example, it is easy to give counterexamples that disprove the greedy-choice property, but proving it is harder. If asked which conditions on the coin denominations allow a greedy solution, we may be able to give only a vague answer based on intuition or examples, rather than a rigorous mathematical proof.

    Quote

    One paper gives an \\(O(n^3)\\)-time algorithm for determining whether a set of coin denominations allows a greedy algorithm to find an optimal solution for every amount.

    Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.

    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1513-steps-for-solving-a-problem-greedily","level":2,"title":"15.1.3   Steps for solving a problem greedily","text":"

    Solving a greedy problem generally involves three steps.

    1. Analyze the problem: Understand its properties, including the state definition, optimization objective, and constraints. This step also appears in backtracking and dynamic programming.
    2. Choose a greedy strategy: Decide how to make a greedy choice at each step. Each choice should reduce the problem's size, eventually solving the whole problem.
    3. Prove correctness: Usually, we must establish the greedy-choice property and optimal substructure. This may require a mathematical proof, such as induction or proof by contradiction.

    Choosing the greedy strategy is the central step, but it can be difficult for the following reasons.

    • Greedy strategies vary greatly from problem to problem. For many problems, the strategy is straightforward and can be found with a little thought and experimentation. For more complex problems, it may be well hidden, testing our problem-solving experience and algorithmic skills.
    • Some greedy strategies are misleading. We may confidently devise a strategy, implement it, and submit the code, only to find that it fails some test cases. The strategy is only partially correct; the coin change problem above is a typical example.

    To ensure correctness, we should give the greedy strategy a rigorous mathematical proof, usually using contradiction or mathematical induction.

    However, such a proof can itself be difficult. When we do not know where to start, we often debug against test cases, gradually refining and checking the greedy strategy.

    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1514-typical-greedy-problems","level":2,"title":"15.1.4   Typical greedy problems","text":"

    Greedy algorithms often solve optimization problems with the greedy-choice property and optimal substructure. Some typical examples follow.

    • Coin change: For certain sets of coin denominations, a greedy algorithm always finds an optimal solution.
    • Interval scheduling: Given tasks that each occupy a time interval, the goal is to complete as many tasks as possible. Always choosing the available task that finishes earliest yields an optimal solution.
    • Fractional knapsack: Given items and a weight limit, choose items to maximize total value without exceeding the limit. Choosing the item with the highest value-to-weight ratio at each step can yield an optimal solution in some settings.
    • Stock trading: Given historical stock prices, you may buy and sell multiple times, but cannot buy again while holding stock until you sell it. The goal is to maximize profit.
    • Huffman coding: This greedy algorithm is used for lossless data compression. It builds a Huffman tree by repeatedly merging the two nodes with the lowest frequencies. The resulting tree has the minimum weighted path length (encoding length).
    • Dijkstra's algorithm: This greedy algorithm finds shortest paths from a given source vertex to every other vertex in a graph whose edge weights are all nonnegative.
    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/","level":1,"title":"15.3   Maximum capacity problem","text":"

    Question

    Given an array \\(ht\\), each element represents the height of a vertical wall. Any two walls, together with the space between them, form a container.

    The container's capacity is its height multiplied by its width (an area). Its height is determined by the shorter wall, and its width is the difference between the two walls' array indices.

    Choose two walls from the array to maximize the container's capacity, and return that maximum capacity. An example is shown in Figure 15-7.

    Figure 15-7   Example input for the maximum capacity problem

    A container is formed by two walls, so the state of this problem is the pair of wall indices, denoted by \\([i, j]\\).

    Capacity is height times width, with the height determined by the shorter wall and the width equal to the difference between the wall indices. Writing the capacity as \\(cap[i, j]\\), we have:

    \\[ cap[i, j] = \\min(ht[i], ht[j]) \\times (j - i) \\]

    For an array of length \\(n\\), there are \\(C_n^2 = \\frac{n(n - 1)}{2}\\) pairs of walls (possible states). The most direct approach is to enumerate all states and find the maximum capacity, taking \\(O(n^2)\\) time.

    ","path":["Chapter 15.   Greedy algorithms","15.3   Maximum capacity problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#1-choosing-a-greedy-strategy","level":3,"title":"1.   Choosing a greedy strategy","text":"

    A more efficient solution is possible. Consider the state \\([i, j]\\) in Figure 15-8, where \\(i < j\\) and \\(ht[i] < ht[j]\\): wall \\(i\\) is shorter and wall \\(j\\) is taller.

    Figure 15-8   Initial state

    As shown in Figure 15-9, moving the taller wall \\(j\\) inward toward the shorter wall \\(i\\) must reduce the capacity.

    Moving wall \\(j\\) inward always reduces the width \\(j-i\\). Since the shorter wall determines the height, the height can only stay the same (if \\(i\\) remains shorter) or decrease (if the new wall \\(j\\) is shorter).

    Figure 15-9   State after moving the taller wall inward

    Conversely, only moving the shorter wall \\(i\\) inward can potentially increase the capacity. Although the width must decrease, the height may increase if the new wall \\(i\\) is taller. Figure 15-10 shows an example where moving the shorter wall increases the area.

    Figure 15-10   State after moving the shorter wall inward

    This gives us a greedy strategy: initialize two pointers at opposite ends of the container. At each step, move the pointer at the shorter wall inward until the pointers meet.

    Figure 15-11 illustrates the greedy process.

    1. Initially, pointers \\(i\\) and \\(j\\) are at opposite ends of the array.
    2. Calculate the current capacity \\(cap[i, j]\\) and update the maximum capacity.
    3. Compare the heights of walls \\(i\\) and \\(j\\), and move the shorter wall inward by one position.
    4. Repeat steps 2. and 3. until \\(i\\) and \\(j\\) meet.
    <1><2><3><4><5><6><7><8><9>

    Figure 15-11   Greedy steps for the maximum capacity problem

    ","path":["Chapter 15.   Greedy algorithms","15.3   Maximum capacity problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    The loop runs at most \\(n\\) times, so the time complexity is \\(O(n)\\).

    The variables \\(i\\), \\(j\\), and \\(res\\) require constant extra space, so the space complexity is \\(O(1)\\).

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_capacity.py
    def max_capacity(ht: list[int]) -> int:\n    \"\"\"Maximum container capacity: greedy algorithm\"\"\"\n    # Initialize i and j at opposite ends of the array\n    i, j = 0, len(ht) - 1\n    # The initial maximum capacity is 0\n    res = 0\n    # Repeatedly make greedy choices until the two sides meet\n    while i < j:\n        # Update the maximum capacity\n        cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        # Move the shorter side inward\n        if ht[i] < ht[j]:\n            i += 1\n        else:\n            j -= 1\n    return res\n
    max_capacity.cpp
    /* Maximum container capacity: greedy algorithm */\nint maxCapacity(vector<int> &ht) {\n    // Initialize i and j at opposite ends of the array\n    int i = 0, j = ht.size() - 1;\n    // The initial maximum capacity is 0\n    int res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        int cap = min(ht[i], ht[j]) * (j - i);\n        res = max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
    max_capacity.java
    /* Maximum container capacity: greedy algorithm */\nint maxCapacity(int[] ht) {\n    // Initialize i and j at opposite ends of the array\n    int i = 0, j = ht.length - 1;\n    // The initial maximum capacity is 0\n    int res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        int cap = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
    max_capacity.cs
    /* Maximum container capacity: greedy algorithm */\nint MaxCapacity(int[] ht) {\n    // Initialize i and j at opposite ends of the array\n    int i = 0, j = ht.Length - 1;\n    // The initial maximum capacity is 0\n    int res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        int cap = Math.Min(ht[i], ht[j]) * (j - i);\n        res = Math.Max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
    max_capacity.go
    /* Maximum container capacity: greedy algorithm */\nfunc maxCapacity(ht []int) int {\n    // Initialize i and j at opposite ends of the array\n    i, j := 0, len(ht)-1\n    // The initial maximum capacity is 0\n    res := 0\n    // Repeatedly make greedy choices until the two sides meet\n    for i < j {\n        // Update the maximum capacity\n        capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i)\n        res = int(math.Max(float64(res), float64(capacity)))\n        // Move the shorter side inward\n        if ht[i] < ht[j] {\n            i++\n        } else {\n            j--\n        }\n    }\n    return res\n}\n
    max_capacity.swift
    /* Maximum container capacity: greedy algorithm */\nfunc maxCapacity(ht: [Int]) -> Int {\n    // Initialize i and j at opposite ends of the array\n    var i = ht.startIndex, j = ht.endIndex - 1\n    // The initial maximum capacity is 0\n    var res = 0\n    // Repeatedly make greedy choices until the two sides meet\n    while i < j {\n        // Update the maximum capacity\n        let cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        // Move the shorter side inward\n        if ht[i] < ht[j] {\n            i += 1\n        } else {\n            j -= 1\n        }\n    }\n    return res\n}\n
    max_capacity.js
    /* Maximum container capacity: greedy algorithm */\nfunction maxCapacity(ht) {\n    // Initialize i and j at opposite ends of the array\n    let i = 0,\n        j = ht.length - 1;\n    // The initial maximum capacity is 0\n    let res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        const cap = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    return res;\n}\n
    max_capacity.ts
    /* Maximum container capacity: greedy algorithm */\nfunction maxCapacity(ht: number[]): number {\n    // Initialize i and j at opposite ends of the array\n    let i = 0,\n        j = ht.length - 1;\n    // The initial maximum capacity is 0\n    let res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        const cap: number = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    return res;\n}\n
    max_capacity.dart
    /* Maximum container capacity: greedy algorithm */\nint maxCapacity(List<int> ht) {\n  // Initialize i and j at opposite ends of the array\n  int i = 0, j = ht.length - 1;\n  // The initial maximum capacity is 0\n  int res = 0;\n  // Repeatedly make greedy choices until the two sides meet\n  while (i < j) {\n    // Update the maximum capacity\n    int cap = min(ht[i], ht[j]) * (j - i);\n    res = max(res, cap);\n    // Move the shorter side inward\n    if (ht[i] < ht[j]) {\n      i++;\n    } else {\n      j--;\n    }\n  }\n  return res;\n}\n
    max_capacity.rs
    /* Maximum container capacity: greedy algorithm */\nfn max_capacity(ht: &[i32]) -> i32 {\n    // Initialize i and j at opposite ends of the array\n    let mut i = 0;\n    let mut j = ht.len() - 1;\n    // The initial maximum capacity is 0\n    let mut res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while i < j {\n        // Update the maximum capacity\n        let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32;\n        res = std::cmp::max(res, cap);\n        // Move the shorter side inward\n        if ht[i] < ht[j] {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    res\n}\n
    max_capacity.c
    /* Maximum container capacity: greedy algorithm */\nint maxCapacity(int ht[], int htLength) {\n    // Initialize i and j at opposite ends of the array\n    int i = 0;\n    int j = htLength - 1;\n    // The initial maximum capacity is 0\n    int res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        int capacity = myMin(ht[i], ht[j]) * (j - i);\n        res = myMax(res, capacity);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
    max_capacity.kt
    /* Maximum container capacity: greedy algorithm */\nfun maxCapacity(ht: IntArray): Int {\n    // Initialize i and j at opposite ends of the array\n    var i = 0\n    var j = ht.size - 1\n    // The initial maximum capacity is 0\n    var res = 0\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        val cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++\n        } else {\n            j--\n        }\n    }\n    return res\n}\n
    max_capacity.rb
    ### Maximum container capacity: greedy algorithm ###\ndef max_capacity(ht)\n  # Initialize i and j at opposite ends of the array\n  i, j = 0, ht.length - 1\n  # The initial maximum capacity is 0\n  res = 0\n\n  # Repeatedly make greedy choices until the two sides meet\n  while i < j\n    # Update the maximum capacity\n    cap = [ht[i], ht[j]].min * (j - i)\n    res = [res, cap].max\n    # Move the shorter side inward\n    if ht[i] < ht[j]\n      i += 1\n    else\n      j -= 1\n    end\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 15.   Greedy algorithms","15.3   Maximum capacity problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#3-proof-of-correctness","level":3,"title":"3.   Proof of correctness","text":"

    The greedy algorithm is faster than exhaustive search because each greedy choice skips some states.

    For example, in state \\(cap[i, j]\\), suppose \\(i\\) is the shorter wall and \\(j\\) the taller one. Moving the shorter wall \\(i\\) inward by one position skips the states shown in Figure 15-12. Their capacities will never be checked later.

    \\[ cap[i, i+1], cap[i, i+2], \\dots, cap[i, j-2], cap[i, j-1] \\]

    Figure 15-12   States skipped by moving the shorter wall

    Observe that the skipped states are precisely those obtained by moving the taller wall \\(j\\) inward. We have already shown that moving the taller wall inward must reduce capacity. None of these skipped states can therefore be optimal, and skipping them cannot cause us to miss the optimal solution.

    This shows that moving the shorter wall is safe, and that the greedy strategy is valid.

    ","path":["Chapter 15.   Greedy algorithms","15.3   Maximum capacity problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/","level":1,"title":"15.4   Maximum product cutting problem","text":"

    Question

    Given a positive integer \\(n\\), split it into a sum of at least two positive integers. What is the largest possible product of those integers? The problem is illustrated in Figure 15-13.

    Figure 15-13   Definition of the maximum product cutting problem

    Suppose we split \\(n\\) into \\(m\\) integers that will be factors in the product. Denote the \\(i\\)-th factor by \\(n_i\\), so that

    \\[ n = \\sum_{i=1}^{m}n_i \\]

    Our goal is to maximize the product of all these integer factors:

    \\[ \\max(\\prod_{i=1}^{m}n_i) \\]

    We need to determine how many parts \\(m\\) to create and what each \\(n_i\\) should be.

    ","path":["Chapter 15.   Greedy algorithms","15.4   Maximum product cutting problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#1-choosing-a-greedy-strategy","level":3,"title":"1.   Choosing a greedy strategy","text":"

    The product of two integers is often greater than their sum. Suppose we split off a factor of \\(2\\) from \\(n\\), giving the product \\(2(n-2)\\). Comparing this product with \\(n\\) gives:

    \\[ \\begin{aligned} 2(n-2) & \\geq n \\newline 2n - n - 4 & \\geq 0 \\newline n & \\geq 4 \\end{aligned} \\]

    As shown in Figure 15-14, when \\(n \\geq 4\\), splitting off a \\(2\\) increases the product. This suggests that every integer greater than or equal to \\(4\\) should be split further.

    Greedy strategy 1: If a factor \\(\\geq 4\\) appears in a split, divide it further. The final split should contain only the factors \\(1\\), \\(2\\), and \\(3\\).

    Figure 15-14   Splitting increases the product

    Next, consider which factor is best. Of \\(1\\), \\(2\\), and \\(3\\), clearly \\(1\\) is the worst: \\(1 \\times (n-1) < n\\) always holds, so splitting off a \\(1\\) reduces the product.

    As shown in Figure 15-15, when \\(n = 6\\), we have \\(3 \\times 3 > 2 \\times 2 \\times 2\\). This means that splitting into factors of \\(3\\) is better than splitting into factors of \\(2\\).

    Greedy strategy 2: A split should contain at most two factors of \\(2\\), because three \\(2\\)s can always be replaced with two \\(3\\)s to obtain a larger product.

    Figure 15-15   Optimal factors

    These observations lead to the following greedy strategy.

    1. Starting with \\(n\\), keep splitting off factors of \\(3\\) until the remainder is \\(0\\), \\(1\\), or \\(2\\).
    2. If the remainder is \\(0\\), then \\(n\\) is a multiple of \\(3\\), and no further action is needed.
    3. If the remainder is \\(2\\), keep it without splitting it further.
    4. If the remainder is \\(1\\), replace the last \\(3\\) and the remaining \\(1\\) with two \\(2\\)s, since \\(2 \\times 2 > 1 \\times 3\\).
    ","path":["Chapter 15.   Greedy algorithms","15.4   Maximum product cutting problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    As shown in Figure 15-16, we do not need a loop to split the integer. Floor division gives the number of \\(3\\)s, denoted by \\(a\\), and the modulo operation gives the remainder \\(b\\). Thus:

    \\[ n = 3 a + b \\]

    For the boundary cases \\(n \\leq 3\\), we must split off a \\(1\\), giving a product of \\(1 \\times (n - 1)\\).

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_product_cutting.py
    def max_product_cutting(n: int) -> int:\n    \"\"\"Maximum product after splitting: greedy algorithm\"\"\"\n    # When n <= 3, one part must be 1\n    if n <= 3:\n        return 1 * (n - 1)\n    # Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    a, b = n // 3, n % 3\n    if b == 1:\n        # When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return int(math.pow(3, a - 1)) * 2 * 2\n    if b == 2:\n        # When the remainder is 2, no adjustment is needed\n        return int(math.pow(3, a)) * 2\n    # When the remainder is 0, no adjustment is needed\n    return int(math.pow(3, a))\n
    max_product_cutting.cpp
    /* Maximum product after splitting: greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return (int)pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return (int)pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return (int)pow(3, a);\n}\n
    max_product_cutting.java
    /* Maximum product after splitting: greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return (int) Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return (int) Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return (int) Math.pow(3, a);\n}\n
    max_product_cutting.cs
    /* Maximum product after splitting: greedy algorithm */\nint MaxProductCutting(int n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return (int)Math.Pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return (int)Math.Pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return (int)Math.Pow(3, a);\n}\n
    max_product_cutting.go
    /* Maximum product after splitting: greedy algorithm */\nfunc maxProductCutting(n int) int {\n    // When n <= 3, one part must be 1\n    if n <= 3 {\n        return 1 * (n - 1)\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    a := n / 3\n    b := n % 3\n    if b == 1 {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return int(math.Pow(3, float64(a-1))) * 2 * 2\n    }\n    if b == 2 {\n        // When the remainder is 2, no adjustment is needed\n        return int(math.Pow(3, float64(a))) * 2\n    }\n    // When the remainder is 0, no adjustment is needed\n    return int(math.Pow(3, float64(a)))\n}\n
    max_product_cutting.swift
    /* Maximum product after splitting: greedy algorithm */\nfunc maxProductCutting(n: Int) -> Int {\n    // When n <= 3, one part must be 1\n    if n <= 3 {\n        return 1 * (n - 1)\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    let a = n / 3\n    let b = n % 3\n    if b == 1 {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return pow(3, a - 1) * 2 * 2\n    }\n    if b == 2 {\n        // When the remainder is 2, no adjustment is needed\n        return pow(3, a) * 2\n    }\n    // When the remainder is 0, no adjustment is needed\n    return pow(3, a)\n}\n
    max_product_cutting.js
    /* Maximum product after splitting: greedy algorithm */\nfunction maxProductCutting(n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    let a = Math.floor(n / 3);\n    let b = n % 3;\n    if (b === 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b === 2) {\n        // When the remainder is 2, no adjustment is needed\n        return Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return Math.pow(3, a);\n}\n
    max_product_cutting.ts
    /* Maximum product after splitting: greedy algorithm */\nfunction maxProductCutting(n: number): number {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    let a: number = Math.floor(n / 3);\n    let b: number = n % 3;\n    if (b === 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b === 2) {\n        // When the remainder is 2, no adjustment is needed\n        return Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return Math.pow(3, a);\n}\n
    max_product_cutting.dart
    /* Maximum product after splitting: greedy algorithm */\nint maxProductCutting(int n) {\n  // When n <= 3, one part must be 1\n  if (n <= 3) {\n    return 1 * (n - 1);\n  }\n  // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n  int a = n ~/ 3;\n  int b = n % 3;\n  if (b == 1) {\n    // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n    return (pow(3, a - 1) * 2 * 2).toInt();\n  }\n  if (b == 2) {\n    // When the remainder is 2, no adjustment is needed\n    return (pow(3, a) * 2).toInt();\n  }\n  // When the remainder is 0, no adjustment is needed\n  return pow(3, a).toInt();\n}\n
    max_product_cutting.rs
    /* Maximum product after splitting: greedy algorithm */\nfn max_product_cutting(n: i32) -> i32 {\n    // When n <= 3, one part must be 1\n    if n <= 3 {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    let a = n / 3;\n    let b = n % 3;\n    if b == 1 {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        3_i32.pow(a as u32 - 1) * 2 * 2\n    } else if b == 2 {\n        // When the remainder is 2, no adjustment is needed\n        3_i32.pow(a as u32) * 2\n    } else {\n        // When the remainder is 0, no adjustment is needed\n        3_i32.pow(a as u32)\n    }\n}\n
    max_product_cutting.c
    /* Maximum product after splitting: greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return pow(3, a);\n}\n
    max_product_cutting.kt
    /* Maximum product after splitting: greedy algorithm */\nfun maxProductCutting(n: Int): Int {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1)\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    val a = n / 3\n    val b = n % 3\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return 3.0.pow((a - 1)).toInt() * 2 * 2\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return 3.0.pow(a).toInt() * 2 * 2\n    }\n    // When the remainder is 0, no adjustment is needed\n    return 3.0.pow(a).toInt()\n}\n
    max_product_cutting.rb
    ### Maximum product after splitting: greedy algorithm ###\ndef max_product_cutting(n)\n  # When n <= 3, one part must be 1\n  return 1 * (n - 1) if n <= 3\n  # Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n  a, b = n / 3, n % 3\n  # When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n  return (3.pow(a - 1) * 2 * 2).to_i if b == 1\n  # When the remainder is 2, no adjustment is needed\n  return (3.pow(a) * 2).to_i if b == 2\n  # When the remainder is 0, no adjustment is needed\n  3.pow(a).to_i\nend\n
    Visualize execution

    View full screen >

    Figure 15-16   Calculating the maximum product

    The time complexity depends on how the programming language implements exponentiation. Python, for example, offers three common ways to compute powers.

    • The ** operator and the pow() function both have a time complexity of \\(O(\\log⁡ a)\\).
    • The math.pow() function calls the C library's pow() function internally, performing floating-point exponentiation in \\(O(1)\\) time.

    The variables \\(a\\) and \\(b\\) use constant extra space, so the space complexity is \\(O(1)\\).

    ","path":["Chapter 15.   Greedy algorithms","15.4   Maximum product cutting problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#3-proof-of-correctness","level":3,"title":"3.   Proof of correctness","text":"

    We use proof by contradiction, considering only \\(n \\geq 4\\).

    1. All factors are \\(\\leq 3\\): Suppose an optimal split contains a factor \\(x\\) that is \\(\\geq 4\\). We can split it further to obtain \\(2(x-2)\\), giving a greater (or equal) product. This contradicts the assumption.
    2. The split contains no \\(1\\): Suppose an optimal split contains a factor of \\(1\\). We can merge it into another factor to obtain a larger product, contradicting the assumption.
    3. The split contains at most two \\(2\\)s: Suppose an optimal split contains three \\(2\\)s. Replacing them with two \\(3\\)s gives a larger product, contradicting the assumption.
    ","path":["Chapter 15.   Greedy algorithms","15.4   Maximum product cutting problem"],"tags":[]},{"location":"chapter_greedy/summary/","level":1,"title":"15.5   Summary","text":"","path":["Chapter 15.   Greedy algorithms","15.5   Summary"],"tags":[]},{"location":"chapter_greedy/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Greedy algorithms are commonly used for optimization. At each decision point, they make a locally optimal choice in the hope of reaching a globally optimal solution.
    • A greedy algorithm repeatedly makes greedy choices, turning the problem into a smaller subproblem at each step until it is solved.
    • Greedy algorithms are easy to implement and efficient. Their time complexity is usually lower than that of dynamic programming.
    • For some coin denominations, a greedy algorithm is guaranteed to find an optimal coin change solution. For others, it may produce a very poor solution.
    • Problems suited to greedy algorithms have two properties: the greedy-choice property and optimal substructure. The greedy-choice property establishes that the strategy works.
    • Proving the greedy-choice property can be difficult for complex problems. Disproving it is often easier, as coin change illustrates.
    • Solving a greedy problem involves three main steps: analyze the problem, choose a greedy strategy, and prove correctness. Choosing the strategy is the central step, while proving correctness is often the hardest.
    • The fractional knapsack problem extends 0-1 knapsack by allowing fractions of items, making a greedy solution possible. The strategy can be proved correct by contradiction.
    • The maximum capacity problem can be solved by exhaustive search in \\(O(n^2)\\) time. A greedy strategy that moves the shorter wall inward at each step reduces this to \\(O(n)\\).
    • For the maximum product cutting problem, we derived two greedy strategies: continue splitting integers \\(\\geq 4\\), and favor factors of \\(3\\). The implementation uses exponentiation, so its time complexity depends on the exponentiation method, usually \\(O(1)\\) or \\(O(\\log n)\\).
    ","path":["Chapter 15.   Greedy algorithms","15.5   Summary"],"tags":[]},{"location":"chapter_hashing/","level":1,"title":"Chapter 6.   Hash tables","text":"

    Abstract

    In the world of computers, a hash table is like a knowledgeable librarian.

    Knowing how to calculate a call number lets the librarian find the desired book quickly.

    ","path":["Chapter 6.   Hash tables"],"tags":[]},{"location":"chapter_hashing/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 6.1   Hash tables
    • 6.2   Hash collisions
    • 6.3   Hash algorithms
    • 6.4   Summary
    • 6.5   Exercises
    ","path":["Chapter 6.   Hash tables"],"tags":[]},{"location":"chapter_hashing/exercises/","level":1,"title":"6.5   Exercises","text":"","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#651-check-your-understanding","level":2,"title":"6.5.1   Check your understanding","text":"","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#1-searching-after-a-hash-collision","level":3,"title":"1.   Searching after a hash collision","text":"

    A hash table has five buckets and uses the hash function \\(h(x)=x \\bmod 5\\). When elements collide, they are added in order to the list in that bucket. Insert [1, 6, 11, 7] in sequence:

    1. Write out the contents of buckets 0 through 4.
    2. When searching for 6, which bucket is visited first, and which elements are checked in order?
    3. Based on the bucket contents from question 1, did later elements overwrite earlier ones? Explain using this collision-handling method.
    Reference answer
    1. Since \\(1\\bmod5=6\\bmod5=11\\bmod5=1\\) and \\(7\\bmod5=2\\), the buckets contain:

      0: []\n1: [1, 6, 11]\n2: [7]\n3: []\n4: []\n
    2. Searching for 6 first visits bucket 1 and compares against 1 and then 6, finding the target on the second comparison.

    3. Equal hash values mean only that the elements land in the same bucket, not that the elements themselves are equal. Separate chaining keeps all colliding elements in the bucket and compares them one by one during a search, so 1, 6, and 11 do not overwrite one another.

    ","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#2-where-do-elements-go-when-a-hash-table-grows","level":3,"title":"2.   Where do elements go when a hash table grows?","text":"

    A hash table using separate chaining initially has five buckets and the hash function \\(h(x)=x\\bmod5\\). The keys [1, 6, 11] are all in bucket 1.

    The table is now expanded to seven buckets, and the hash function becomes \\(h(x)=x\\bmod7\\):

    1. Calculate the new bucket number for each of 1, 6, and 11.
    2. Which buckets contain elements after the expansion?
    3. Can the list in the old bucket 1 simply be copied unchanged into the new bucket 1? Explain using your results from questions 1 and 2.
    Reference answer
    1. The new bucket numbers are:

      • \\(1\\bmod7=1\\);
      • \\(6\\bmod7=6\\);
      • \\(11\\bmod7=4\\).
    2. Bucket 1 holds 1, bucket 4 holds 11, and bucket 6 holds 6. The three keys no longer share one bucket.

    3. The list cannot simply be copied unchanged. A bucket number is the remainder of the key divided by the number of buckets. When that number changes from 5 to 7, a key's bucket may change, so every key's position must be recalculated. If we copied the old bucket 1 unchanged, subsequent searches for 6 and 11 using the new formula would visit buckets 6 and 4, respectively, and fail to find them.

    ","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#3-can-we-still-find-11-after-deleting-6","level":3,"title":"3.   Can we still find 11 after deleting 6?","text":"

    A hash table has five positions with indices 0-4 and uses the hash function \\(h(x)=x\\bmod5\\). When a collision occurs, it starts at the computed index and searches to the right for the first empty position.

    Insert [1, 6, 11] in order:

    1. At which index is each number ultimately stored?
    2. Which indices are checked, in order, when searching for 11?
    3. Suppose deleting 6 marks its position as an empty slot that has never been used, and a search stops on reaching an empty slot. What happens when we search for 11 again? Is the result correct? If not, how can we avoid the problem?
    Reference answer
    1. The number 1 is stored at index 1. The number 6 also maps to index 1, so after the collision it is stored at index 2. The number 11 likewise starts at index 1, skips occupied indices 1 and 2, and is finally stored at index 3.

    2. Searching for 11 checks indices 1, 2, 3 in order and finds it at index 3.

    3. If index 2 is marked as an empty slot that has never been used, a search for 11 checks index 1 and then stops at index 2, incorrectly concluding that 11 is absent. Deletion should leave a deleted marker instead: a search continues to the next index when it encounters this marker, wrapping from index 4 to index 0. Later insertions can still reuse the position.

    ","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#652-programming-exercises","level":2,"title":"6.5.2   Programming exercises","text":"","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#1-comparing-the-characters-in-two-strings","level":3,"title":"1.   Comparing the characters in two strings","text":"

    Given two strings s and t containing only lowercase English letters, you may rearrange the characters in s freely, but may not add, delete, or replace any characters.

    Determine whether the rearrangement can produce t. Return true if it can, or false otherwise. Use a hash table to record the frequency of each letter; do not sort the characters in either string.

    Hints
    1. Strings of different lengths cannot contain the same characters with the same frequencies.
    2. Use a hash table to record each letter's count; increment the appropriate count by 1 while scanning s.
    3. Decrement the appropriate count by 1 while scanning t. The character frequencies match only if all counts end at 0.

    LeetCode

    ","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/","level":1,"title":"6.3   Hash algorithms","text":"

    The previous two sections explained how hash tables work and how collisions are resolved. Whether we use open addressing or separate chaining, these techniques keep the table working when collisions occur, but do not reduce how often collisions happen.

    Frequent collisions can severely degrade performance. In the separate-chaining table shown below, the ideal case distributes pairs evenly among buckets for the best lookup efficiency. In the worst case, all pairs occupy the same bucket, degrading the time complexity to \\(O(n)\\).

    Figure 6-8   Best and worst cases for hash collisions

    The hash function determines how key-value pairs are distributed. Recall its two steps: calculate a hash value, then take it modulo the array length:

    index = hash(key) % capacity\n

    For a fixed capacity, the hash algorithm hash() determines the output, and therefore the distribution of pairs in the table.

    To lower the likelihood of collisions, we should therefore focus on designing hash().

    ","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#631-goals-of-a-hash-algorithm","level":2,"title":"6.3.1   Goals of a hash algorithm","text":"

    To build a hash table that is both fast and reliable, its hash algorithm should have these properties.

    • Determinism: The same input must always produce the same output, ensuring reliable lookup.
    • Efficiency: Calculating a hash should be fast. Lower computational overhead makes the table more practical.
    • Uniform distribution: The algorithm should distribute pairs evenly across the table. More uniform distribution means fewer collisions.

    Hash algorithms also have many applications beyond hash tables.

    • Password storage: To protect passwords, systems usually store password hashes rather than plaintext passwords. When a user enters a password, the system hashes it and compares the result with the stored hash. A match means the password is accepted.
    • Data-integrity checking: A sender can calculate a hash of the data and send it alongside the data. The receiver recomputes the hash and compares it with the received hash. A match indicates that the data is intact.

    Cryptographic applications require stronger security properties to prevent reverse engineering, such as recovering an original password from its hash.

    • One-wayness: The hash should not reveal any information about the input data.
    • Collision resistance: Finding two different inputs with the same hash should be extremely difficult.
    • Avalanche effect: A small input change should produce a large, unpredictable output change.

    Uniform distribution and collision resistance are independent concepts. Uniform output does not guarantee collision resistance. For example, with random keys, key % 100 can produce uniform outputs. But this algorithm is too simple: all keys with the same last two digits give the same output. It is therefore easy to infer a usable key from a hash and crack a password.

    ","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#632-designing-a-hash-algorithm","level":2,"title":"6.3.2   Designing a hash algorithm","text":"

    Designing a hash algorithm is complex and involves many factors. For less demanding uses, however, we can devise simple algorithms.

    • Additive hashing: Add the ASCII codes of all input characters and use the sum as the hash.
    • Multiplicative hashing: Use multiplication to mix values, multiplying by a constant each round and accumulating character ASCII codes into the hash.
    • XOR hashing: Accumulate each input element into a hash using exclusive OR.
    • Rotational hashing: Accumulate each character's ASCII code into a hash, rotating the hash before each accumulation.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby simple_hash.py
    def add_hash(key: str) -> int:\n    \"\"\"Additive hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash += ord(c)\n    return hash % modulus\n\ndef mul_hash(key: str) -> int:\n    \"\"\"Multiplicative hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash = 31 * hash + ord(c)\n    return hash % modulus\n\ndef xor_hash(key: str) -> int:\n    \"\"\"XOR hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash ^= ord(c)\n    return hash % modulus\n\ndef rot_hash(key: str) -> int:\n    \"\"\"Rotating hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash = (hash << 4) ^ (hash >> 28) ^ ord(c)\n    return hash % modulus\n
    simple_hash.cpp
    /* Additive hash */\nint addHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = (hash + (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint mulHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = (31 * hash + (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint xorHash(string key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash ^= (int)c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotating hash */\nint rotHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n
    simple_hash.java
    /* Additive hash */\nint addHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = (hash + (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n\n/* Multiplicative hash */\nint mulHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = (31 * hash + (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n\n/* XOR hash */\nint xorHash(String key) {\n    int hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash ^= (int) c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotating hash */\nint rotHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n
    simple_hash.cs
    /* Additive hash */\nint AddHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = (hash + c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint MulHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = (31 * hash + c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint XorHash(string key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash ^= c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotating hash */\nint RotHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS;\n    }\n    return (int)hash;\n}\n
    simple_hash.go
    /* Additive hash */\nfunc addHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = (hash + int64(b)) % modulus\n    }\n    return int(hash)\n}\n\n/* Multiplicative hash */\nfunc mulHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = (31*hash + int64(b)) % modulus\n    }\n    return int(hash)\n}\n\n/* XOR hash */\nfunc xorHash(key string) int {\n    hash := 0\n    modulus := 1000000007\n    for _, b := range []byte(key) {\n        fmt.Println(int(b))\n        hash ^= int(b)\n        hash = (31*hash + int(b)) % modulus\n    }\n    return hash & modulus\n}\n\n/* Rotating hash */\nfunc rotHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus\n    }\n    return int(hash)\n}\n
    simple_hash.swift
    /* Additive hash */\nfunc addHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = (hash + Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n\n/* Multiplicative hash */\nfunc mulHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = (31 * hash + Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n\n/* XOR hash */\nfunc xorHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash ^= Int(scalar.value)\n        }\n    }\n    return hash & MODULUS\n}\n\n/* Rotating hash */\nfunc rotHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n
    simple_hash.js
    /* Additive hash */\nfunction addHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* Multiplicative hash */\nfunction mulHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* XOR hash */\nfunction xorHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash ^= c.charCodeAt(0);\n    }\n    return hash % MODULUS;\n}\n\n/* Rotating hash */\nfunction rotHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n
    simple_hash.ts
    /* Additive hash */\nfunction addHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* Multiplicative hash */\nfunction mulHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* XOR hash */\nfunction xorHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash ^= c.charCodeAt(0);\n    }\n    return hash % MODULUS;\n}\n\n/* Rotating hash */\nfunction rotHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n
    simple_hash.dart
    /* Additive hash */\nint addHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = (hash + key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n\n/* Multiplicative hash */\nint mulHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = (31 * hash + key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n\n/* XOR hash */\nint xorHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash ^= key.codeUnitAt(i);\n  }\n  return hash & MODULUS;\n}\n\n/* Rotating hash */\nint rotHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n
    simple_hash.rs
    /* Additive hash */\nfn add_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = (hash + c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n\n/* Multiplicative hash */\nfn mul_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = (31 * hash + c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n\n/* XOR hash */\nfn xor_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash ^= c as i64;\n    }\n\n    (hash & MODULUS) as i32\n}\n\n/* Rotating hash */\nfn rot_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n
    simple_hash.c
    /* Additive hash */\nint addHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = (hash + (unsigned char)key[i]) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint mulHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = (31 * hash + (unsigned char)key[i]) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint xorHash(char *key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n\n    for (int i = 0; i < strlen(key); i++) {\n        hash ^= (unsigned char)key[i];\n    }\n    return hash & MODULUS;\n}\n\n/* Rotating hash */\nint rotHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS;\n    }\n\n    return (int)hash;\n}\n
    simple_hash.kt
    /* Additive hash */\nfun addHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = (hash + c.code) % MODULUS\n    }\n    return hash.toInt()\n}\n\n/* Multiplicative hash */\nfun mulHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = (31 * hash + c.code) % MODULUS\n    }\n    return hash.toInt()\n}\n\n/* XOR hash */\nfun xorHash(key: String): Int {\n    var hash = 0\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = hash xor c.code\n    }\n    return hash and MODULUS\n}\n\n/* Rotating hash */\nfun rotHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS\n    }\n    return hash.toInt()\n}\n
    simple_hash.rb
    ### Additive hash ###\ndef add_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash += c.ord }\n\n  hash % modulus\nend\n\n### Multiplicative hash ###\ndef mul_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash = 31 * hash + c.ord }\n\n  hash % modulus\nend\n\n### XOR hash ###\ndef xor_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash ^= c.ord }\n\n  hash % modulus\nend\n\n### Rotating hash ###\ndef rot_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord }\n\n  hash % modulus\nend\n
    Visualize execution

    View full screen >

    Each algorithm ends by taking the result modulo the large prime \\(1000000007\\) to keep it within a suitable range. Why emphasize a prime modulus? What goes wrong with a composite modulus? This is an interesting question.

    The conclusion is that using a large prime modulus helps make hash values as uniformly distributed as possible. Because a prime shares no nontrivial factors with other numbers, it reduces periodic patterns introduced by the modulo operation and thus avoids collisions.

    For example, choose the composite modulus \\(9\\), which is divisible by \\(3\\). Every key divisible by \\(3\\) then maps to one of just three hash values: \\(0\\), \\(3\\), or \\(6\\).

    \\[ \\begin{aligned} \\text{modulus} & = 9 \\newline \\text{key} & = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} & = \\{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\\dots \\} \\end{aligned} \\]

    If the input keys follow this arithmetic progression, the hash values cluster, increasing collisions. Now replace the modulus with the prime \\(13\\). With no common factor between the key pattern and the modulus, the output becomes noticeably more uniform.

    \\[ \\begin{aligned} \\text{modulus} & = 13 \\newline \\text{key} & = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} & = \\{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \\dots \\} \\end{aligned} \\]

    If the keys are already uniformly random, either a prime or a composite modulus can produce uniformly distributed hashes. When the keys have a periodic pattern, however, a composite modulus is more likely to cause clustering.

    We therefore usually choose a prime modulus, preferably a large one, to suppress periodic patterns as much as possible and make the algorithm more robust.

    ","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#633-common-hash-algorithms","level":2,"title":"6.3.3   Common hash algorithms","text":"

    The simple algorithms above are fragile and fall well short of our design goals. Addition and XOR are commutative, for example, so additive and XOR hashing cannot distinguish strings containing the same characters in different orders. This can increase collisions and create security problems.

    In practice, standard algorithms such as MD5, SHA-1, SHA-2, and SHA-3 are commonly used. They map inputs of arbitrary length to fixed-length hash values.

    Over nearly a century, hash algorithms have continually evolved. Some researchers work to improve their performance, while others, along with hackers, search for security weaknesses. Table 6-2 lists commonly used algorithms.

    • MD5 and SHA-1 have been successfully attacked repeatedly and have been retired from security applications.
    • SHA-256, part of the SHA-2 family, is among the most secure hash algorithms. No successful attack has been reported, so it is widely used in security applications and protocols.
    • SHA-3 has lower implementation overhead and higher computational efficiency than SHA-2, but is not yet as widely used.

    Table 6-2   Common hash algorithms

    MD5 SHA-1 SHA-2 SHA-3 Introduced 1992 1995 2002 2008 Output length 128 bit 160 bit 256/512 bit 224/256/384/512 bit Hash collisions More frequent More frequent Rare Rare Security Low; successfully attacked Low; successfully attacked High High Applications Deprecated; still used for integrity checks Deprecated Cryptocurrency transaction verification, digital signatures, and more Can replace SHA-2","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#634-hash-values-of-data-structures","level":2,"title":"6.3.4   Hash values of data structures","text":"

    Hash-table keys can be integers, floating-point numbers, strings, and other types. Programming languages usually provide built-in hashing for these types to calculate bucket indices. In Python, for example, hash() calculates hash values for various types.

    • Integers and Booleans have hash values equal to their own values.
    • Hashing floating-point numbers and strings is more complex; interested readers can explore the details.
    • A tuple's hash combines the hashes of its individual elements into one value.
    • An object's hash is based on its memory address. Overriding its hash method allows hashing by content instead.

    Tip

    Built-in hash functions differ in their definitions and implementations across programming languages.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby built_in_hash.py
    num = 3\nhash_num = hash(num)\n# The hash of the integer 3 is 3\n\nbol = True\nhash_bol = hash(bol)\n# The hash of the Boolean True is 1\n\ndec = 3.14159\nhash_dec = hash(dec)\n# The hash of the floating-point number 3.14159 is 326484311674566659\n\nstr = \"Hello 算法\"\nhash_str = hash(str)\n# The hash of the string “Hello 算法” is 4617003410720528961\n\ntup = (12836, \"小哈\")\nhash_tup = hash(tup)\n# The hash of the tuple (12836, '小哈') is 1029005403108185979\n\nobj = ListNode(0)\nhash_obj = hash(obj)\n# The hash of the node object <ListNode object at 0x1058fd810> is 274267521\n
    built_in_hash.cpp
    int num = 3;\nsize_t hashNum = hash<int>()(num);\n// The hash of the integer 3 is 3\n\nbool bol = true;\nsize_t hashBol = hash<bool>()(bol);\n// The hash of the Boolean 1 is 1\n\ndouble dec = 3.14159;\nsize_t hashDec = hash<double>()(dec);\n// The hash of the floating-point number 3.14159 is 4614256650576692846\n\nstring str = \"Hello 算法\";\nsize_t hashStr = hash<string>()(str);\n// The hash of the string “Hello 算法” is 15466937326284535026\n\n// In C++, built-in std::hash() provides hashing only for basic data types\n// Hashing for arrays and objects must be implemented separately\n
    built_in_hash.java
    int num = 3;\nint hashNum = Integer.hashCode(num);\n// The hash of the integer 3 is 3\n\nboolean bol = true;\nint hashBol = Boolean.hashCode(bol);\n// The hash of the Boolean true is 1231\n\ndouble dec = 3.14159;\nint hashDec = Double.hashCode(dec);\n// The hash of the floating-point number 3.14159 is -1340954729\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode();\n// The hash of the string “Hello 算法” is -727081396\n\nObject[] arr = { 12836, \"小哈\" };\nint hashTup = Arrays.hashCode(arr);\n// The hash of the array [12836, 小哈] is 1151158\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode();\n// The hash of the node object utils.ListNode@7dc5e7b4 is 2110121908\n
    built_in_hash.cs
    int num = 3;\nint hashNum = num.GetHashCode();\n// The hash of the integer 3 is 3;\n\nbool bol = true;\nint hashBol = bol.GetHashCode();\n// The hash of the Boolean true is 1;\n\ndouble dec = 3.14159;\nint hashDec = dec.GetHashCode();\n// The hash of the floating-point number 3.14159 is -1340954729;\n\nstring str = \"Hello 算法\";\nint hashStr = str.GetHashCode();\n// The hash of the string “Hello 算法” is -586107568;\n\nobject[] arr = [12836, \"小哈\"];\nint hashTup = arr.GetHashCode();\n// The hash of the array [12836, 小哈] is 42931033;\n\nListNode obj = new(0);\nint hashObj = obj.GetHashCode();\n// The hash of the node object 0 is 39053774;\n
    built_in_hash.go
    // Go provides no built-in hash-code function\n
    built_in_hash.swift
    let num = 3\nlet hashNum = num.hashValue\n// The hash of the integer 3 is 9047044699613009734\n\nlet bol = true\nlet hashBol = bol.hashValue\n// The hash of the Boolean true is -4431640247352757451\n\nlet dec = 3.14159\nlet hashDec = dec.hashValue\n// The hash of the floating-point number 3.14159 is -2465384235396674631\n\nlet str = \"Hello 算法\"\nlet hashStr = str.hashValue\n// The hash of the string “Hello 算法” is -7850626797806988787\n\nlet arr = [AnyHashable(12836), AnyHashable(\"小哈\")]\nlet hashTup = arr.hashValue\n// The hash of the array [AnyHashable(12836), AnyHashable(\"小哈\")] is -2308633508154532996\n\nlet obj = ListNode(x: 0)\nlet hashObj = obj.hashValue\n// The hash of the node object utils.ListNode is -2434780518035996159\n
    built_in_hash.js
    // JavaScript provides no built-in hash-code function\n
    built_in_hash.ts
    // TypeScript provides no built-in hash-code function\n
    built_in_hash.dart
    int num = 3;\nint hashNum = num.hashCode;\n// The hash of the integer 3 is 34803\n\nbool bol = true;\nint hashBol = bol.hashCode;\n// The hash of the Boolean true is 1231\n\ndouble dec = 3.14159;\nint hashDec = dec.hashCode;\n// The hash of the floating-point number 3.14159 is 2570631074981783\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode;\n// The hash of the string “Hello 算法” is 468167534\n\nList arr = [12836, \"小哈\"];\nint hashArr = arr.hashCode;\n// The hash of the array [12836, 小哈] is 976512528\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode;\n// The hash of the node object Instance of 'ListNode' is 1033450432\n
    built_in_hash.rs
    use std::collections::hash_map::DefaultHasher;\nuse std::hash::{Hash, Hasher};\n\nlet num = 3;\nlet mut num_hasher = DefaultHasher::new();\nnum.hash(&mut num_hasher);\nlet hash_num = num_hasher.finish();\n// The hash of the integer 3 is 568126464209439262\n\nlet bol = true;\nlet mut bol_hasher = DefaultHasher::new();\nbol.hash(&mut bol_hasher);\nlet hash_bol = bol_hasher.finish();\n// The hash of the Boolean true is 4952851536318644461\n\nlet dec: f32 = 3.14159;\nlet mut dec_hasher = DefaultHasher::new();\ndec.to_bits().hash(&mut dec_hasher);\nlet hash_dec = dec_hasher.finish();\n// The hash of the floating-point number 3.14159 is 2566941990314602357\n\nlet str = \"Hello 算法\";\nlet mut str_hasher = DefaultHasher::new();\nstr.hash(&mut str_hasher);\nlet hash_str = str_hasher.finish();\n// The hash of the string “Hello 算法” is 16092673739211250988\n\nlet arr = (&12836, &\"小哈\");\nlet mut tup_hasher = DefaultHasher::new();\narr.hash(&mut tup_hasher);\nlet hash_tup = tup_hasher.finish();\n// The hash of the tuple (12836, \"小哈\") is 1885128010422702749\n\nlet node = ListNode::new(42);\nlet mut hasher = DefaultHasher::new();\nnode.borrow().val.hash(&mut hasher);\nlet hash = hasher.finish();\n// The hash of the node object RefCell { value: ListNode { val: 42, next: None } } is 15387811073369036852\n
    built_in_hash.c
    // C provides no built-in hash-code function\n
    built_in_hash.kt
    val num = 3\nval hashNum = num.hashCode()\n// The hash of the integer 3 is 3\n\nval bol = true\nval hashBol = bol.hashCode()\n// The hash of the Boolean true is 1231\n\nval dec = 3.14159\nval hashDec = dec.hashCode()\n// The hash of the floating-point number 3.14159 is -1340954729\n\nval str = \"Hello 算法\"\nval hashStr = str.hashCode()\n// The hash of the string “Hello 算法” is -727081396\n\nval arr = arrayOf<Any>(12836, \"小哈\")\nval hashTup = arr.hashCode()\n// The hash of the array [12836, 小哈] is 189568618\n\nval obj = ListNode(0)\nval hashObj = obj.hashCode()\n// The hash of the node object utils.ListNode@1d81eb93 is 495053715\n
    built_in_hash.rb
    num = 3\nhash_num = num.hash\n# The hash of the integer 3 is -4385856518450339636\n\nbol = true\nhash_bol = bol.hash\n# The hash of the Boolean true is -1617938112149317027\n\ndec = 3.14159\nhash_dec = dec.hash\n# The hash of the floating-point number 3.14159 is -1479186995943067893\n\nstr = \"Hello 算法\"\nhash_str = str.hash\n# The hash of the string “Hello 算法” is -4075943250025831763\n\ntup = [12836, '小哈']\nhash_tup = tup.hash\n# The hash of the tuple (12836, '小哈') is 1999544809202288822\n\nobj = ListNode.new(0)\nhash_obj = obj.hash\n# The hash of the node object #<ListNode:0x000078133140ab70> is 4302940560806366381\n
    Visualize execution

    View full screen >

    In many programming languages, only immutable objects can serve as hash-table keys. If a list, a dynamic array, were used as a key, changing its contents would change its hash, making the original value impossible to retrieve from the table.

    Custom objects, such as linked-list nodes, can be hashable even though their member variables are mutable. Their hash is usually based on their memory address, which stays the same even when their contents change.

    You may notice different hash values when running the program in different consoles. Each time the Python interpreter starts, it adds a random salt to its string hash function. This helps prevent HashDoS attacks and improves security.

    ","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_collision/","level":1,"title":"6.2   Hash collisions","text":"

    As discussed in the previous section, a hash function's input space is usually much larger than its output space, so collisions are theoretically unavoidable. For example, if the input space is all integers and the output space is limited to the array's capacity, multiple integers must map to the same bucket index.

    Hash collisions can produce incorrect lookup results and make a hash table unusable. One solution is to expand the table whenever a collision occurs until the collision disappears. This simple, brute-force approach works, but is inefficient because expansion requires substantial data movement and hash computation. We can improve efficiency with the following strategies.

    1. Improve the data structure so that it works correctly even when collisions occur.
    2. Expand only when necessary, when collisions become sufficiently frequent.

    The main structural approaches are “separate chaining” and “open addressing.”

    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#621-separate-chaining","level":2,"title":"6.2.1   Separate chaining","text":"

    In the original hash table, each bucket holds only one key-value pair. Separate chaining replaces this single element with a linked list. Each pair becomes a node, and all colliding pairs are stored in the same list. An example is shown below.

    Figure 6-5   A hash table using separate chaining

    The operations change as follows.

    • Lookup: Hash the input key to obtain a bucket index, access the list's head, and traverse the list comparing keys to find the target pair.
    • Insertion: Use the hash function to access the list's head, then add the node containing the key-value pair to the list.
    • Deletion: Use the hash function to locate the list, traverse it to find the target node, and remove that node.

    Separate chaining has the following limitations.

    • More space: Linked lists store node pointers and consume more memory than arrays.
    • Slower lookup: Finding an element requires a linear scan of the list.

    The code below gives a simple implementation. Keep two points in mind.

    • Lists, implemented as dynamic arrays, replace linked lists to simplify the code. The hash table is an array of buckets, each of which is a list.
    • The implementation includes expansion: when the load factor exceeds \\(\\frac{2}{3}\\), the table's capacity is doubled to \\(2\\) times its original size.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_chaining.py
    class HashMapChaining:\n    \"\"\"Hash table with separate chaining\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self.size = 0  # Number of key-value pairs\n        self.capacity = 4  # Hash table capacity\n        self.load_thres = 2.0 / 3.0  # Load factor threshold that triggers capacity growth\n        self.extend_ratio = 2  # Capacity growth factor\n        self.buckets = [[] for _ in range(self.capacity)]  # Bucket array\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        return key % self.capacity\n\n    def load_factor(self) -> float:\n        \"\"\"Load factor\"\"\"\n        return self.size / self.capacity\n\n    def get(self, key: int) -> str | None:\n        \"\"\"Lookup operation\"\"\"\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse the bucket; if key is found, return its corresponding val\n        for pair in bucket:\n            if pair.key == key:\n                return pair.val\n        # Return None if key is not found\n        return None\n\n    def put(self, key: int, val: str):\n        \"\"\"Insert operation\"\"\"\n        # Grow the capacity when the load factor exceeds the threshold\n        if self.load_factor() > self.load_thres:\n            self.extend()\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse the bucket; if the specified key is found, update its val and return\n        for pair in bucket:\n            if pair.key == key:\n                pair.val = val\n                return\n        # If key is absent, append the key-value pair at the end\n        pair = Pair(key, val)\n        bucket.append(pair)\n        self.size += 1\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse the bucket and remove the key-value pair\n        for pair in bucket:\n            if pair.key == key:\n                bucket.remove(pair)\n                self.size -= 1\n                break\n\n    def extend(self):\n        \"\"\"Grow the hash table capacity\"\"\"\n        # Keep a reference to the original hash table\n        buckets = self.buckets\n        # Initialize a new hash table with the expanded capacity\n        self.capacity *= self.extend_ratio\n        self.buckets = [[] for _ in range(self.capacity)]\n        self.size = 0\n        # Move the key-value pairs from the original hash table to the new one\n        for bucket in buckets:\n            for pair in bucket:\n                self.put(pair.key, pair.val)\n\n    def print(self):\n        \"\"\"Print the hash table\"\"\"\n        for bucket in self.buckets:\n            res = []\n            for pair in bucket:\n                res.append(str(pair.key) + \" -> \" + pair.val)\n            print(res)\n
    hash_map_chaining.cpp
    /* Hash table with separate chaining */\nclass HashMapChaining {\n  private:\n    int size;                       // Number of key-value pairs\n    int capacity;                   // Hash table capacity\n    double loadThres;               // Load factor threshold that triggers capacity growth\n    int extendRatio;                // Capacity growth factor\n    vector<vector<Pair *>> buckets; // Bucket array\n\n  public:\n    /* Constructor */\n    HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) {\n        buckets.resize(capacity);\n    }\n\n    /* Destructor */\n    ~HashMapChaining() {\n        for (auto &bucket : buckets) {\n            for (Pair *pair : bucket) {\n                // Free memory\n                delete pair;\n            }\n        }\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double)size / (double)capacity;\n    }\n\n    /* Lookup operation */\n    string get(int key) {\n        int index = hashFunc(key);\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (Pair *pair : buckets[index]) {\n            if (pair->key == key) {\n                return pair->val;\n            }\n        }\n        // Return an empty string if key is not found\n        return \"\";\n    }\n\n    /* Insert operation */\n    void put(int key, string val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        int index = hashFunc(key);\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (Pair *pair : buckets[index]) {\n            if (pair->key == key) {\n                pair->val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        buckets[index].push_back(new Pair(key, val));\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        auto &bucket = buckets[index];\n        // Traverse the bucket and remove the key-value pair\n        for (int i = 0; i < bucket.size(); i++) {\n            if (bucket[i]->key == key) {\n                Pair *tmp = bucket[i];\n                bucket.erase(bucket.begin() + i); // Remove the key-value pair from the list\n                delete tmp;                       // Free memory\n                size--;\n                return;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void extend() {\n        // Keep a copy of the original bucket array\n        vector<vector<Pair *>> bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets.clear();\n        buckets.resize(capacity);\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (auto &bucket : bucketsTmp) {\n            for (Pair *pair : bucket) {\n                put(pair->key, pair->val);\n                // Free memory\n                delete pair;\n            }\n        }\n    }\n\n    /* Print the hash table */\n    void print() {\n        for (auto &bucket : buckets) {\n            cout << \"[\";\n            for (Pair *pair : bucket) {\n                cout << pair->key << \" -> \" << pair->val << \", \";\n            }\n            cout << \"]\\n\";\n        }\n    }\n};\n
    hash_map_chaining.java
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    int size; // Number of key-value pairs\n    int capacity; // Hash table capacity\n    double loadThres; // Load factor threshold that triggers capacity growth\n    int extendRatio; // Capacity growth factor\n    List<List<Pair>> buckets; // Bucket array\n\n    /* Constructor */\n    public HashMapChaining() {\n        size = 0;\n        capacity = 4;\n        loadThres = 2.0 / 3.0;\n        extendRatio = 2;\n        buckets = new ArrayList<>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.add(new ArrayList<>());\n        }\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double) size / capacity;\n    }\n\n    /* Lookup operation */\n    String get(int key) {\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                return pair.val;\n            }\n        }\n        // Return null if key is not found\n        return null;\n    }\n\n    /* Insert operation */\n    void put(int key, String val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        Pair pair = new Pair(key, val);\n        bucket.add(pair);\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse the bucket and remove the key-value pair\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                bucket.remove(pair);\n                size--;\n                break;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void extend() {\n        // Keep a reference to the original hash table\n        List<List<Pair>> bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = new ArrayList<>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.add(new ArrayList<>());\n        }\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (List<Pair> bucket : bucketsTmp) {\n            for (Pair pair : bucket) {\n                put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    void print() {\n        for (List<Pair> bucket : buckets) {\n            List<String> res = new ArrayList<>();\n            for (Pair pair : bucket) {\n                res.add(pair.key + \" -> \" + pair.val);\n            }\n            System.out.println(res);\n        }\n    }\n}\n
    hash_map_chaining.cs
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    int size; // Number of key-value pairs\n    int capacity; // Hash table capacity\n    double loadThres; // Load factor threshold that triggers capacity growth\n    int extendRatio; // Capacity growth factor\n    List<List<Pair>> buckets; // Bucket array\n\n    /* Constructor */\n    public HashMapChaining() {\n        size = 0;\n        capacity = 4;\n        loadThres = 2.0 / 3.0;\n        extendRatio = 2;\n        buckets = new List<List<Pair>>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.Add([]);\n        }\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double LoadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Lookup operation */\n    public string? Get(int key) {\n        int index = HashFunc(key);\n        // Traverse the bucket; if key is found, return its corresponding val\n        foreach (Pair pair in buckets[index]) {\n            if (pair.key == key) {\n                return pair.val;\n            }\n        }\n        // Return null if key is not found\n        return null;\n    }\n\n    /* Insert operation */\n    public void Put(int key, string val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (LoadFactor() > loadThres) {\n            Extend();\n        }\n        int index = HashFunc(key);\n        // Traverse the bucket; if the specified key is found, update its val and return\n        foreach (Pair pair in buckets[index]) {\n            if (pair.key == key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        buckets[index].Add(new Pair(key, val));\n        size++;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        int index = HashFunc(key);\n        // Traverse the bucket and remove the key-value pair\n        foreach (Pair pair in buckets[index].ToList()) {\n            if (pair.key == key) {\n                buckets[index].Remove(pair);\n                size--;\n                break;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void Extend() {\n        // Keep a reference to the original hash table\n        List<List<Pair>> bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = new List<List<Pair>>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.Add([]);\n        }\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        foreach (List<Pair> bucket in bucketsTmp) {\n            foreach (Pair pair in bucket) {\n                Put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    public void Print() {\n        foreach (List<Pair> bucket in buckets) {\n            List<string> res = [];\n            foreach (Pair pair in bucket) {\n                res.Add(pair.key + \" -> \" + pair.val);\n            }\n            foreach (string kv in res) {\n                Console.WriteLine(kv);\n            }\n        }\n    }\n}\n
    hash_map_chaining.go
    /* Hash table with separate chaining */\ntype hashMapChaining struct {\n    size        int      // Number of key-value pairs\n    capacity    int      // Hash table capacity\n    loadThres   float64  // Load factor threshold that triggers capacity growth\n    extendRatio int      // Capacity growth factor\n    buckets     [][]pair // Bucket array\n}\n\n/* Constructor */\nfunc newHashMapChaining() *hashMapChaining {\n    buckets := make([][]pair, 4)\n    for i := 0; i < 4; i++ {\n        buckets[i] = make([]pair, 0)\n    }\n    return &hashMapChaining{\n        size:        0,\n        capacity:    4,\n        loadThres:   2.0 / 3.0,\n        extendRatio: 2,\n        buckets:     buckets,\n    }\n}\n\n/* Hash function */\nfunc (m *hashMapChaining) hashFunc(key int) int {\n    return key % m.capacity\n}\n\n/* Load factor */\nfunc (m *hashMapChaining) loadFactor() float64 {\n    return float64(m.size) / float64(m.capacity)\n}\n\n/* Lookup operation */\nfunc (m *hashMapChaining) get(key int) string {\n    idx := m.hashFunc(key)\n    bucket := m.buckets[idx]\n    // Traverse the bucket; if key is found, return its corresponding val\n    for _, p := range bucket {\n        if p.key == key {\n            return p.val\n        }\n    }\n    // If key is not found, return an empty string\n    return \"\"\n}\n\n/* Insert operation */\nfunc (m *hashMapChaining) put(key int, val string) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if m.loadFactor() > m.loadThres {\n        m.extend()\n    }\n    idx := m.hashFunc(key)\n    // Traverse the bucket; if the specified key is found, update its val and return\n    for i := range m.buckets[idx] {\n        if m.buckets[idx][i].key == key {\n            m.buckets[idx][i].val = val\n            return\n        }\n    }\n    // If key is absent, append the key-value pair at the end\n    p := pair{\n        key: key,\n        val: val,\n    }\n    m.buckets[idx] = append(m.buckets[idx], p)\n    m.size += 1\n}\n\n/* Remove operation */\nfunc (m *hashMapChaining) remove(key int) {\n    idx := m.hashFunc(key)\n    // Traverse the bucket and remove the key-value pair\n    for i, p := range m.buckets[idx] {\n        if p.key == key {\n            // Remove the element from the slice\n            m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...)\n            m.size -= 1\n            break\n        }\n    }\n}\n\n/* Grow the hash table capacity */\nfunc (m *hashMapChaining) extend() {\n    // Save a copy of the original hash table\n    tmpBuckets := make([][]pair, len(m.buckets))\n    for i := 0; i < len(m.buckets); i++ {\n        tmpBuckets[i] = make([]pair, len(m.buckets[i]))\n        copy(tmpBuckets[i], m.buckets[i])\n    }\n    // Initialize a new hash table with the expanded capacity\n    m.capacity *= m.extendRatio\n    m.buckets = make([][]pair, m.capacity)\n    for i := 0; i < m.capacity; i++ {\n        m.buckets[i] = make([]pair, 0)\n    }\n    m.size = 0\n    // Move the key-value pairs from the original hash table to the new one\n    for _, bucket := range tmpBuckets {\n        for _, p := range bucket {\n            m.put(p.key, p.val)\n        }\n    }\n}\n\n/* Print the hash table */\nfunc (m *hashMapChaining) print() {\n    var builder strings.Builder\n\n    for _, bucket := range m.buckets {\n        builder.WriteString(\"[\")\n        for _, p := range bucket {\n            builder.WriteString(strconv.Itoa(p.key) + \" -> \" + p.val + \" \")\n        }\n        builder.WriteString(\"]\")\n        fmt.Println(builder.String())\n        builder.Reset()\n    }\n}\n
    hash_map_chaining.swift
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    var loadThres: Double // Load factor threshold that triggers capacity growth\n    var extendRatio: Int // Capacity growth factor\n    var buckets: [[Pair]] // Bucket array\n\n    /* Constructor */\n    init() {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = Array(repeating: [], count: capacity)\n    }\n\n    /* Hash function */\n    func hashFunc(key: Int) -> Int {\n        key % capacity\n    }\n\n    /* Load factor */\n    func loadFactor() -> Double {\n        Double(size) / Double(capacity)\n    }\n\n    /* Lookup operation */\n    func get(key: Int) -> String? {\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse the bucket; if key is found, return its corresponding val\n        for pair in bucket {\n            if pair.key == key {\n                return pair.val\n            }\n        }\n        // Return nil if key is not found\n        return nil\n    }\n\n    /* Insert operation */\n    func put(key: Int, val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if loadFactor() > loadThres {\n            extend()\n        }\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for pair in bucket {\n            if pair.key == key {\n                pair.val = val\n                return\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        let pair = Pair(key: key, val: val)\n        buckets[index].append(pair)\n        size += 1\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse the bucket and remove the key-value pair\n        for (pairIndex, pair) in bucket.enumerated() {\n            if pair.key == key {\n                buckets[index].remove(at: pairIndex)\n                size -= 1\n                break\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    func extend() {\n        // Save the original bucket array\n        let bucketsTmp = buckets\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio\n        buckets = Array(repeating: [], count: capacity)\n        size = 0\n        // Move the key-value pairs from the original hash table to the new one\n        for bucket in bucketsTmp {\n            for pair in bucket {\n                put(key: pair.key, val: pair.val)\n            }\n        }\n    }\n\n    /* Print the hash table */\n    func print() {\n        for bucket in buckets {\n            let res = bucket.map { \"\\($0.key) -> \\($0.val)\" }\n            Swift.print(res)\n        }\n    }\n}\n
    hash_map_chaining.js
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    #size; // Number of key-value pairs\n    #capacity; // Hash table capacity\n    #loadThres; // Load factor threshold that triggers capacity growth\n    #extendRatio; // Capacity growth factor\n    #buckets; // Bucket array\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0;\n        this.#capacity = 4;\n        this.#loadThres = 2.0 / 3.0;\n        this.#extendRatio = 2;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor() {\n        return this.#size / this.#capacity;\n    }\n\n    /* Lookup operation */\n    get(key) {\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                return pair.val;\n            }\n        }\n        // Return null if key was not found\n        return null;\n    }\n\n    /* Insert operation */\n    put(key, val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        const pair = new Pair(key, val);\n        bucket.push(pair);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key) {\n        const index = this.#hashFunc(key);\n        let bucket = this.#buckets[index];\n        // Traverse the bucket and remove the key-value pair\n        for (let i = 0; i < bucket.length; i++) {\n            if (bucket[i].key === key) {\n                bucket.splice(i, 1);\n                this.#size--;\n                break;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    #extend() {\n        // Keep a reference to the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize a new hash table with the expanded capacity\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n        this.#size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (const bucket of bucketsTmp) {\n            for (const pair of bucket) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    print() {\n        for (const bucket of this.#buckets) {\n            let res = [];\n            for (const pair of bucket) {\n                res.push(pair.key + ' -> ' + pair.val);\n            }\n            console.log(res);\n        }\n    }\n}\n
    hash_map_chaining.ts
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    #size: number; // Number of key-value pairs\n    #capacity: number; // Hash table capacity\n    #loadThres: number; // Load factor threshold that triggers capacity growth\n    #extendRatio: number; // Capacity growth factor\n    #buckets: Pair[][]; // Bucket array\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0;\n        this.#capacity = 4;\n        this.#loadThres = 2.0 / 3.0;\n        this.#extendRatio = 2;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n    }\n\n    /* Hash function */\n    #hashFunc(key: number): number {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor(): number {\n        return this.#size / this.#capacity;\n    }\n\n    /* Lookup operation */\n    get(key: number): string | null {\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                return pair.val;\n            }\n        }\n        // Return null if key was not found\n        return null;\n    }\n\n    /* Insert operation */\n    put(key: number, val: string): void {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        const pair = new Pair(key, val);\n        bucket.push(pair);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key: number): void {\n        const index = this.#hashFunc(key);\n        let bucket = this.#buckets[index];\n        // Traverse the bucket and remove the key-value pair\n        for (let i = 0; i < bucket.length; i++) {\n            if (bucket[i].key === key) {\n                bucket.splice(i, 1);\n                this.#size--;\n                break;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    #extend(): void {\n        // Keep a reference to the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize a new hash table with the expanded capacity\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n        this.#size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (const bucket of bucketsTmp) {\n            for (const pair of bucket) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    print(): void {\n        for (const bucket of this.#buckets) {\n            let res = [];\n            for (const pair of bucket) {\n                res.push(pair.key + ' -> ' + pair.val);\n            }\n            console.log(res);\n        }\n    }\n}\n
    hash_map_chaining.dart
    /* Hash table with separate chaining */\nclass HashMapChaining {\n  late int size; // Number of key-value pairs\n  late int capacity; // Hash table capacity\n  late double loadThres; // Load factor threshold that triggers capacity growth\n  late int extendRatio; // Capacity growth factor\n  late List<List<Pair>> buckets; // Bucket array\n\n  /* Constructor */\n  HashMapChaining() {\n    size = 0;\n    capacity = 4;\n    loadThres = 2.0 / 3.0;\n    extendRatio = 2;\n    buckets = List.generate(capacity, (_) => []);\n  }\n\n  /* Hash function */\n  int hashFunc(int key) {\n    return key % capacity;\n  }\n\n  /* Load factor */\n  double loadFactor() {\n    return size / capacity;\n  }\n\n  /* Lookup operation */\n  String? get(int key) {\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse the bucket; if key is found, return its corresponding val\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        return pair.val;\n      }\n    }\n    // Return null if key was not found\n    return null;\n  }\n\n  /* Insert operation */\n  void put(int key, String val) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if (loadFactor() > loadThres) {\n      extend();\n    }\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse the bucket; if the specified key is found, update its val and return\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        pair.val = val;\n        return;\n      }\n    }\n    // If key is absent, append the key-value pair at the end\n    Pair pair = Pair(key, val);\n    bucket.add(pair);\n    size++;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse the bucket and remove the key-value pair\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        bucket.remove(pair);\n        size--;\n        break;\n      }\n    }\n  }\n\n  /* Grow the hash table capacity */\n  void extend() {\n    // Keep a reference to the original hash table\n    List<List<Pair>> bucketsTmp = buckets;\n    // Initialize a new hash table with the expanded capacity\n    capacity *= extendRatio;\n    buckets = List.generate(capacity, (_) => []);\n    size = 0;\n    // Move the key-value pairs from the original hash table to the new one\n    for (List<Pair> bucket in bucketsTmp) {\n      for (Pair pair in bucket) {\n        put(pair.key, pair.val);\n      }\n    }\n  }\n\n  /* Print the hash table */\n  void printHashMap() {\n    for (List<Pair> bucket in buckets) {\n      List<String> res = [];\n      for (Pair pair in bucket) {\n        res.add(\"${pair.key} -> ${pair.val}\");\n      }\n      print(res);\n    }\n  }\n}\n
    hash_map_chaining.rs
    /* Hash table with separate chaining */\nstruct HashMapChaining {\n    size: usize,\n    capacity: usize,\n    load_thres: f32,\n    extend_ratio: usize,\n    buckets: Vec<Vec<Pair>>,\n}\n\nimpl HashMapChaining {\n    /* Constructor */\n    fn new() -> Self {\n        Self {\n            size: 0,\n            capacity: 4,\n            load_thres: 2.0 / 3.0,\n            extend_ratio: 2,\n            buckets: vec![vec![]; 4],\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        key as usize % self.capacity\n    }\n\n    /* Load factor */\n    fn load_factor(&self) -> f32 {\n        self.size as f32 / self.capacity as f32\n    }\n\n    /* Remove operation */\n    fn remove(&mut self, key: i32) -> Option<String> {\n        let index = self.hash_func(key);\n\n        // Traverse the bucket and remove the key-value pair\n        for (i, p) in self.buckets[index].iter_mut().enumerate() {\n            if p.key == key {\n                let pair = self.buckets[index].remove(i);\n                self.size -= 1;\n                return Some(pair.val);\n            }\n        }\n\n        // Return None if key is not found\n        None\n    }\n\n    /* Grow the hash table capacity */\n    fn extend(&mut self) {\n        // Temporarily take ownership of the original buckets\n        let buckets_tmp = std::mem::take(&mut self.buckets);\n\n        // Initialize a new hash table with the expanded capacity\n        self.capacity *= self.extend_ratio;\n        self.buckets = vec![Vec::new(); self.capacity as usize];\n        self.size = 0;\n\n        // Move the key-value pairs from the original hash table to the new one\n        for bucket in buckets_tmp {\n            for pair in bucket {\n                self.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    fn print(&self) {\n        for bucket in &self.buckets {\n            let mut res = Vec::new();\n            for pair in bucket {\n                res.push(format!(\"{} -> {}\", pair.key, pair.val));\n            }\n            println!(\"{:?}\", res);\n        }\n    }\n\n    /* Insert operation */\n    fn put(&mut self, key: i32, val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if self.load_factor() > self.load_thres {\n            self.extend();\n        }\n\n        let index = self.hash_func(key);\n\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for pair in self.buckets[index].iter_mut() {\n            if pair.key == key {\n                pair.val = val;\n                return;\n            }\n        }\n\n        // If key is absent, append the key-value pair at the end\n        let pair = Pair { key, val };\n        self.buckets[index].push(pair);\n        self.size += 1;\n    }\n\n    /* Lookup operation */\n    fn get(&self, key: i32) -> Option<&str> {\n        let index = self.hash_func(key);\n\n        // Traverse the bucket; if key is found, return its corresponding val\n        for pair in self.buckets[index].iter() {\n            if pair.key == key {\n                return Some(&pair.val);\n            }\n        }\n\n        // Return None if key is not found\n        None\n    }\n}\n
    hash_map_chaining.c
    /* Linked list node */\ntypedef struct Node {\n    Pair *pair;\n    struct Node *next;\n} Node;\n\n/* Hash table with separate chaining */\ntypedef struct {\n    int size;         // Number of key-value pairs\n    int capacity;     // Hash table capacity\n    double loadThres; // Load factor threshold that triggers capacity growth\n    int extendRatio;  // Capacity growth factor\n    Node **buckets;   // Bucket array\n} HashMapChaining;\n\n/* Constructor */\nHashMapChaining *newHashMapChaining() {\n    HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining));\n    hashMap->size = 0;\n    hashMap->capacity = 4;\n    hashMap->loadThres = 2.0 / 3.0;\n    hashMap->extendRatio = 2;\n    hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));\n    for (int i = 0; i < hashMap->capacity; i++) {\n        hashMap->buckets[i] = NULL;\n    }\n    return hashMap;\n}\n\n/* Destructor */\nvoid delHashMapChaining(HashMapChaining *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Node *cur = hashMap->buckets[i];\n        while (cur) {\n            Node *tmp = cur;\n            cur = cur->next;\n            free(tmp->pair);\n            free(tmp);\n        }\n    }\n    free(hashMap->buckets);\n    free(hashMap);\n}\n\n/* Hash function */\nint hashFunc(HashMapChaining *hashMap, int key) {\n    return key % hashMap->capacity;\n}\n\n/* Load factor */\ndouble loadFactor(HashMapChaining *hashMap) {\n    return (double)hashMap->size / (double)hashMap->capacity;\n}\n\n/* Lookup operation */\nchar *get(HashMapChaining *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    // Traverse the bucket; if key is found, return its corresponding val\n    Node *cur = hashMap->buckets[index];\n    while (cur) {\n        if (cur->pair->key == key) {\n            return cur->pair->val;\n        }\n        cur = cur->next;\n    }\n    return \"\"; // Return an empty string if key is not found\n}\n\n/* Insert operation */\nvoid put(HashMapChaining *hashMap, int key, const char *val) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if (loadFactor(hashMap) > hashMap->loadThres) {\n        extend(hashMap);\n    }\n    int index = hashFunc(hashMap, key);\n    // Traverse the bucket; if the specified key is found, update its val and return\n    Node *cur = hashMap->buckets[index];\n    while (cur) {\n        if (cur->pair->key == key) {\n            strcpy(cur->pair->val, val); // If the specified key is found, update its val and return\n            return;\n        }\n        cur = cur->next;\n    }\n    // If key is absent, add the key-value pair at the head of the linked list\n    Pair *newPair = (Pair *)malloc(sizeof(Pair));\n    newPair->key = key;\n    strcpy(newPair->val, val);\n    Node *newNode = (Node *)malloc(sizeof(Node));\n    newNode->pair = newPair;\n    newNode->next = hashMap->buckets[index];\n    hashMap->buckets[index] = newNode;\n    hashMap->size++;\n}\n\n/* Grow the hash table capacity */\nvoid extend(HashMapChaining *hashMap) {\n    // Keep a reference to the original hash table\n    int oldCapacity = hashMap->capacity;\n    Node **oldBuckets = hashMap->buckets;\n    // Initialize a new hash table with the expanded capacity\n    hashMap->capacity *= hashMap->extendRatio;\n    hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));\n    for (int i = 0; i < hashMap->capacity; i++) {\n        hashMap->buckets[i] = NULL;\n    }\n    hashMap->size = 0;\n    // Move the key-value pairs from the original hash table to the new one\n    for (int i = 0; i < oldCapacity; i++) {\n        Node *cur = oldBuckets[i];\n        while (cur) {\n            put(hashMap, cur->pair->key, cur->pair->val);\n            Node *temp = cur;\n            cur = cur->next;\n            // Free memory\n            free(temp->pair);\n            free(temp);\n        }\n    }\n\n    free(oldBuckets);\n}\n\n/* Remove operation */\nvoid removeItem(HashMapChaining *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    Node *cur = hashMap->buckets[index];\n    Node *pre = NULL;\n    while (cur) {\n        if (cur->pair->key == key) {\n            // Remove the key-value pair from the list\n            if (pre) {\n                pre->next = cur->next;\n            } else {\n                hashMap->buckets[index] = cur->next;\n            }\n            // Free memory\n            free(cur->pair);\n            free(cur);\n            hashMap->size--;\n            return;\n        }\n        pre = cur;\n        cur = cur->next;\n    }\n}\n\n/* Print the hash table */\nvoid print(HashMapChaining *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Node *cur = hashMap->buckets[i];\n        printf(\"[\");\n        while (cur) {\n            printf(\"%d -> %s, \", cur->pair->key, cur->pair->val);\n            cur = cur->next;\n        }\n        printf(\"]\\n\");\n    }\n}\n
    hash_map_chaining.kt
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    val loadThres: Double // Load factor threshold that triggers capacity growth\n    val extendRatio: Int // Capacity growth factor\n    var buckets: MutableList<MutableList<Pair>> // Bucket array\n\n    /* Constructor */\n    init {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = mutableListOf()\n        for (i in 0..<capacity) {\n            buckets.add(mutableListOf())\n        }\n    }\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        return key % capacity\n    }\n\n    /* Load factor */\n    fun loadFactor(): Double {\n        return (size / capacity).toDouble()\n    }\n\n    /* Lookup operation */\n    fun get(key: Int): String? {\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (pair in bucket) {\n            if (pair.key == key) return pair._val\n        }\n        // Return null if key is not found\n        return null\n    }\n\n    /* Insert operation */\n    fun put(key: Int, _val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend()\n        }\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (pair in bucket) {\n            if (pair.key == key) {\n                pair._val = _val\n                return\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        val pair = Pair(key, _val)\n        bucket.add(pair)\n        size++\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse the bucket and remove the key-value pair\n        for (pair in bucket) {\n            if (pair.key == key) {\n                bucket.remove(pair)\n                size--\n                break\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    fun extend() {\n        // Keep a reference to the original hash table\n        val bucketsTmp = buckets\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio\n        // A mutable list has no fixed size\n        buckets = mutableListOf()\n        for (i in 0..<capacity) {\n            buckets.add(mutableListOf())\n        }\n        size = 0\n        // Move the key-value pairs from the original hash table to the new one\n        for (bucket in bucketsTmp) {\n            for (pair in bucket) {\n                put(pair.key, pair._val)\n            }\n        }\n    }\n\n    /* Print the hash table */\n    fun print() {\n        for (bucket in buckets) {\n            val res = mutableListOf<String>()\n            for (pair in bucket) {\n                val k = pair.key\n                val v = pair._val\n                res.add(\"$k -> $v\")\n            }\n            println(res)\n        }\n    }\n}\n
    hash_map_chaining.rb
    ### Hash table using chaining ###\nclass HashMapChaining\n  ### Constructor ###\n  def initialize\n    @size = 0 # Number of key-value pairs\n    @capacity = 4 # Hash table capacity\n    @load_thres = 2.0 / 3.0 # Load factor threshold that triggers capacity growth\n    @extend_ratio = 2 # Capacity growth factor\n    @buckets = Array.new(@capacity) { [] } # Bucket array\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    key % @capacity\n  end\n\n  ### Load factor ###\n  def load_factor\n    @size / @capacity\n  end\n\n  ### Lookup operation ###\n  def get(key)\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse the bucket; if key is found, return its corresponding val\n    for pair in bucket\n      return pair.val if pair.key == key\n    end\n    # Return nil if key is not found\n    nil\n  end\n\n  ### Insert operation ###\n  def put(key, val)\n    # Grow the capacity when the load factor exceeds the threshold\n    extend if load_factor > @load_thres\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse the bucket; if the specified key is found, update its val and return\n    for pair in bucket\n      if pair.key == key\n        pair.val = val\n        return\n      end\n    end\n    # If key is absent, append the key-value pair at the end\n    pair = Pair.new(key, val)\n    bucket << pair\n    @size += 1\n  end\n\n  ### Remove operation ###\n  def remove(key)\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse the bucket and remove the key-value pair\n    for pair in bucket\n      if pair.key == key\n        bucket.delete(pair)\n        @size -= 1\n        break\n      end\n    end\n  end\n\n  ### Grow the hash table capacity ###\n  def extend\n    # Temporarily store the original hash table\n    buckets = @buckets\n    # Initialize a new hash table with the expanded capacity\n    @capacity *= @extend_ratio\n    @buckets = Array.new(@capacity) { [] }\n    @size = 0\n    # Move the key-value pairs from the original hash table to the new one\n    for bucket in buckets\n      for pair in bucket\n        put(pair.key, pair.val)\n      end\n    end\n  end\n\n  ### Print the hash table ###\n  def print\n    for bucket in @buckets\n      res = []\n      for pair in bucket\n        res << \"#{pair.key} -> #{pair.val}\"\n      end\n      pp res\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    When a list becomes long, its \\(O(n)\\) lookup time is poor. Replacing it with an AVL tree or red-black tree improves lookup time to \\(O(\\log n)\\).

    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#622-open-addressing","level":2,"title":"6.2.2   Open addressing","text":"

    Open addressing handles collisions by probing multiple locations without introducing another data structure. Common strategies include linear probing, quadratic probing, and multiple hashing.

    We use linear probing to explain how an open-addressing hash table works.

    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#1-linear-probing","level":3,"title":"1.   Linear probing","text":"

    Linear probing searches with a fixed step size. Its operations differ from those of a basic hash table.

    • Insertion: Calculate the bucket index using the hash function. If the bucket is occupied, scan forward from that position with a fixed step size, usually \\(1\\), until an empty bucket is found, then insert the element.
    • Lookup: If a collision is encountered, scan forward with the same step size until the target is found, then return its value. An empty bucket means the target is absent, so return None.

    Figure 6-6 shows key-value pairs in an open-addressing table using linear probing. With this hash function, keys sharing the same last two digits map to the same bucket. Linear probing stores them successively in that bucket and the following buckets.

    Figure 6-6   Distribution of key-value pairs with linear probing

    However, linear probing is prone to clustering. The longer a run of occupied positions becomes, the more likely a collision is to occur within it, causing the cluster to grow further. This feedback loop eventually degrades insertion, deletion, lookup, and update performance.

    We cannot simply delete an element from an open-addressing table. Deletion would leave an empty bucket, None. A lookup stops when linear probing reaches such a bucket, making later elements in the probe sequence inaccessible and potentially reporting them as absent, as shown below.

    Figure 6-7   Lookup problems caused by deletion in open addressing

    To solve this, use lazy deletion: rather than simply removing the entry, mark its bucket with the constant TOMBSTONE. Both None and TOMBSTONE represent available buckets that can hold a new pair. Unlike None, however, a TOMBSTONE does not stop a search, because further pairs may lie beyond it.

    However, lazy deletion can accelerate performance degradation. Each deletion leaves a marker. As TOMBSTONE entries accumulate, searches may need to skip many of them before reaching the target, increasing search time.

    To address this, record the first TOMBSTONE encountered during probing and swap the target entry with it when found. On each lookup or insertion, this moves the entry closer to its ideal location—the start of the probe sequence—and improves lookup efficiency.

    The code below implements linear probing with lazy deletion. To use the table's space more fully, treat the array as circular: after reaching the end, wrap around to the beginning.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_open_addressing.py
    class HashMapOpenAddressing:\n    \"\"\"Hash table with open addressing\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self.size = 0  # Number of key-value pairs\n        self.capacity = 4  # Hash table capacity\n        self.load_thres = 2.0 / 3.0  # Load factor threshold that triggers capacity growth\n        self.extend_ratio = 2  # Capacity growth factor\n        self.buckets: list[Pair | None] = [None] * self.capacity  # Bucket array\n        self.TOMBSTONE = Pair(-1, \"-1\")  # Tombstone marker\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        return key % self.capacity\n\n    def load_factor(self) -> float:\n        \"\"\"Load factor\"\"\"\n        return self.size / self.capacity\n\n    def find_bucket(self, key: int) -> int:\n        \"\"\"Find the bucket index for key\"\"\"\n        index = self.hash_func(key)\n        first_tombstone = -1\n        # Probe linearly until an empty bucket is reached\n        while self.buckets[index] is not None:\n            # If key is found, return its bucket index\n            if self.buckets[index].key == key:\n                # If a tombstone was encountered earlier, move the key-value pair to that index\n                if first_tombstone != -1:\n                    self.buckets[first_tombstone] = self.buckets[index]\n                    self.buckets[index] = self.TOMBSTONE\n                    return first_tombstone  # Return the bucket index after the move\n                return index  # Return the bucket index\n            # Record the first tombstone encountered\n            if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE:\n                first_tombstone = index\n            # Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % self.capacity\n        # If key is absent, return the index for insertion\n        return index if first_tombstone == -1 else first_tombstone\n\n    def get(self, key: int) -> str:\n        \"\"\"Lookup operation\"\"\"\n        # Find the bucket index for key\n        index = self.find_bucket(key)\n        # If the key-value pair is found, return its val\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            return self.buckets[index].val\n        # Return None if the key-value pair is absent\n        return None\n\n    def put(self, key: int, val: str):\n        \"\"\"Insert operation\"\"\"\n        # Grow the capacity when the load factor exceeds the threshold\n        if self.load_factor() > self.load_thres:\n            self.extend()\n        # Find the bucket index for key\n        index = self.find_bucket(key)\n        # If the key-value pair is found, overwrite val and return\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            self.buckets[index].val = val\n            return\n        # If the key-value pair is absent, insert it\n        self.buckets[index] = Pair(key, val)\n        self.size += 1\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        # Find the bucket index for key\n        index = self.find_bucket(key)\n        # If the key-value pair is found, replace it with a tombstone\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            self.buckets[index] = self.TOMBSTONE\n            self.size -= 1\n\n    def extend(self):\n        \"\"\"Grow the hash table capacity\"\"\"\n        # Keep a reference to the original hash table\n        buckets_tmp = self.buckets\n        # Initialize a new hash table with the expanded capacity\n        self.capacity *= self.extend_ratio\n        self.buckets = [None] * self.capacity\n        self.size = 0\n        # Move the key-value pairs from the original hash table to the new one\n        for pair in buckets_tmp:\n            if pair not in [None, self.TOMBSTONE]:\n                self.put(pair.key, pair.val)\n\n    def print(self):\n        \"\"\"Print the hash table\"\"\"\n        for pair in self.buckets:\n            if pair is None:\n                print(\"None\")\n            elif pair is self.TOMBSTONE:\n                print(\"TOMBSTONE\")\n            else:\n                print(pair.key, \"->\", pair.val)\n
    hash_map_open_addressing.cpp
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n  private:\n    int size;                             // Number of key-value pairs\n    int capacity = 4;                     // Hash table capacity\n    const double loadThres = 2.0 / 3.0;     // Load factor threshold that triggers capacity growth\n    const int extendRatio = 2;            // Capacity growth factor\n    vector<Pair *> buckets;               // Bucket array\n    Pair *TOMBSTONE = new Pair(-1, \"-1\"); // Tombstone marker\n\n  public:\n    /* Constructor */\n    HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) {\n    }\n\n    /* Destructor */\n    ~HashMapOpenAddressing() {\n        for (Pair *pair : buckets) {\n            if (pair != nullptr && pair != TOMBSTONE) {\n                delete pair;\n            }\n        }\n        delete TOMBSTONE;\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Find the bucket index for key */\n    int findBucket(int key) {\n        int index = hashFunc(key);\n        int firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (buckets[index] != nullptr) {\n            // If key is found, return its bucket index\n            if (buckets[index]->key == key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    string get(int key) {\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, return its val\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            return buckets[index]->val;\n        }\n        // Return an empty string if the key-value pair does not exist\n        return \"\";\n    }\n\n    /* Insert operation */\n    void put(int key, string val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            buckets[index]->val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            delete buckets[index];\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void extend() {\n        // Keep a copy of the original bucket array\n        vector<Pair *> bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = vector<Pair *>(capacity, nullptr);\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (Pair *pair : bucketsTmp) {\n            if (pair != nullptr && pair != TOMBSTONE) {\n                put(pair->key, pair->val);\n                delete pair;\n            }\n        }\n    }\n\n    /* Print the hash table */\n    void print() {\n        for (Pair *pair : buckets) {\n            if (pair == nullptr) {\n                cout << \"nullptr\" << endl;\n            } else if (pair == TOMBSTONE) {\n                cout << \"TOMBSTONE\" << endl;\n            } else {\n                cout << pair->key << \" -> \" << pair->val << endl;\n            }\n        }\n    }\n};\n
    hash_map_open_addressing.java
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private int size; // Number of key-value pairs\n    private int capacity = 4; // Hash table capacity\n    private final double loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n    private final int extendRatio = 2; // Capacity growth factor\n    private Pair[] buckets; // Bucket array\n    private final Pair TOMBSTONE = new Pair(-1, \"-1\"); // Tombstone marker\n\n    /* Constructor */\n    public HashMapOpenAddressing() {\n        size = 0;\n        buckets = new Pair[capacity];\n    }\n\n    /* Hash function */\n    private int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    private double loadFactor() {\n        return (double) size / capacity;\n    }\n\n    /* Find the bucket index for key */\n    private int findBucket(int key) {\n        int index = hashFunc(key);\n        int firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (buckets[index] != null) {\n            // If key is found, return its bucket index\n            if (buckets[index].key == key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    public String get(int key) {\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, return its val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index].val;\n        }\n        // Return null if the key-value pair is absent\n        return null;\n    }\n\n    /* Insert operation */\n    public void put(int key, String val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index].val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    public void remove(int key) {\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    private void extend() {\n        // Keep a reference to the original hash table\n        Pair[] bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = new Pair[capacity];\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (Pair pair : bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    public void print() {\n        for (Pair pair : buckets) {\n            if (pair == null) {\n                System.out.println(\"null\");\n            } else if (pair == TOMBSTONE) {\n                System.out.println(\"TOMBSTONE\");\n            } else {\n                System.out.println(pair.key + \" -> \" + pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.cs
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    int size; // Number of key-value pairs\n    int capacity = 4; // Hash table capacity\n    double loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n    int extendRatio = 2; // Capacity growth factor\n    Pair[] buckets; // Bucket array\n    Pair TOMBSTONE = new(-1, \"-1\"); // Tombstone marker\n\n    /* Constructor */\n    public HashMapOpenAddressing() {\n        size = 0;\n        buckets = new Pair[capacity];\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double LoadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Find the bucket index for key */\n    int FindBucket(int key) {\n        int index = HashFunc(key);\n        int firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (buckets[index] != null) {\n            // If key is found, return its bucket index\n            if (buckets[index].key == key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    public string? Get(int key) {\n        // Find the bucket index for key\n        int index = FindBucket(key);\n        // If the key-value pair is found, return its val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index].val;\n        }\n        // Return null if the key-value pair is absent\n        return null;\n    }\n\n    /* Insert operation */\n    public void Put(int key, string val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (LoadFactor() > loadThres) {\n            Extend();\n        }\n        // Find the bucket index for key\n        int index = FindBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index].val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        // Find the bucket index for key\n        int index = FindBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void Extend() {\n        // Keep a reference to the original hash table\n        Pair[] bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = new Pair[capacity];\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        foreach (Pair pair in bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                Put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    public void Print() {\n        foreach (Pair pair in buckets) {\n            if (pair == null) {\n                Console.WriteLine(\"null\");\n            } else if (pair == TOMBSTONE) {\n                Console.WriteLine(\"TOMBSTONE\");\n            } else {\n                Console.WriteLine(pair.key + \" -> \" + pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.go
    /* Hash table with open addressing */\ntype hashMapOpenAddressing struct {\n    size        int     // Number of key-value pairs\n    capacity    int     // Hash table capacity\n    loadThres   float64 // Load factor threshold that triggers capacity growth\n    extendRatio int     // Capacity growth factor\n    buckets     []*pair // Bucket array\n    TOMBSTONE   *pair   // Tombstone marker\n}\n\n/* Constructor */\nfunc newHashMapOpenAddressing() *hashMapOpenAddressing {\n    return &hashMapOpenAddressing{\n        size:        0,\n        capacity:    4,\n        loadThres:   2.0 / 3.0,\n        extendRatio: 2,\n        buckets:     make([]*pair, 4),\n        TOMBSTONE:   &pair{-1, \"-1\"},\n    }\n}\n\n/* Hash function */\nfunc (h *hashMapOpenAddressing) hashFunc(key int) int {\n    return key % h.capacity // Calculate the hash value from the key\n}\n\n/* Load factor */\nfunc (h *hashMapOpenAddressing) loadFactor() float64 {\n    return float64(h.size) / float64(h.capacity) // Calculate the current load factor\n}\n\n/* Find the bucket index for key */\nfunc (h *hashMapOpenAddressing) findBucket(key int) int {\n    index := h.hashFunc(key) // Get the initial index\n    firstTombstone := -1     // Record the position of the first TOMBSTONE encountered\n    for h.buckets[index] != nil {\n        if h.buckets[index].key == key {\n            if firstTombstone != -1 {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                h.buckets[firstTombstone] = h.buckets[index]\n                h.buckets[index] = h.TOMBSTONE\n                return firstTombstone // Return the bucket index after the move\n            }\n            return index // Return the index found\n        }\n        if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE {\n            firstTombstone = index // Record the position of the first tombstone encountered\n        }\n        index = (index + 1) % h.capacity // Probe linearly, wrapping to the beginning after the end\n    }\n    // If key is absent, return the index for insertion\n    if firstTombstone != -1 {\n        return firstTombstone\n    }\n    return index\n}\n\n/* Lookup operation */\nfunc (h *hashMapOpenAddressing) get(key int) string {\n    index := h.findBucket(key) // Find the bucket index for key\n    if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE {\n        return h.buckets[index].val // If the key-value pair is found, return its val\n    }\n    return \"\" // If the key-value pair is absent, return \"\"\n}\n\n/* Insert operation */\nfunc (h *hashMapOpenAddressing) put(key int, val string) {\n    if h.loadFactor() > h.loadThres {\n        h.extend() // Grow the capacity when the load factor exceeds the threshold\n    }\n    index := h.findBucket(key) // Find the bucket index for key\n    if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE {\n        h.buckets[index] = &pair{key, val} // If the key-value pair is absent, insert it\n        h.size++\n    } else {\n        h.buckets[index].val = val // If the key-value pair is found, overwrite val\n    }\n}\n\n/* Remove operation */\nfunc (h *hashMapOpenAddressing) remove(key int) {\n    index := h.findBucket(key) // Find the bucket index for key\n    if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE {\n        h.buckets[index] = h.TOMBSTONE // If the key-value pair is found, replace it with a tombstone\n        h.size--\n    }\n}\n\n/* Grow the hash table capacity */\nfunc (h *hashMapOpenAddressing) extend() {\n    oldBuckets := h.buckets               // Keep a reference to the original hash table\n    h.capacity *= h.extendRatio           // Update the capacity\n    h.buckets = make([]*pair, h.capacity) // Initialize a new hash table with the expanded capacity\n    h.size = 0                            // Reset the size\n    // Move the key-value pairs from the original hash table to the new one\n    for _, pair := range oldBuckets {\n        if pair != nil && pair != h.TOMBSTONE {\n            h.put(pair.key, pair.val)\n        }\n    }\n}\n\n/* Print the hash table */\nfunc (h *hashMapOpenAddressing) print() {\n    for _, pair := range h.buckets {\n        if pair == nil {\n            fmt.Println(\"nil\")\n        } else if pair == h.TOMBSTONE {\n            fmt.Println(\"TOMBSTONE\")\n        } else {\n            fmt.Printf(\"%d -> %s\\n\", pair.key, pair.val)\n        }\n    }\n}\n
    hash_map_open_addressing.swift
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    var loadThres: Double // Load factor threshold that triggers capacity growth\n    var extendRatio: Int // Capacity growth factor\n    var buckets: [Pair?] // Bucket array\n    var TOMBSTONE: Pair // Tombstone marker\n\n    /* Constructor */\n    init() {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = Array(repeating: nil, count: capacity)\n        TOMBSTONE = Pair(key: -1, val: \"-1\")\n    }\n\n    /* Hash function */\n    func hashFunc(key: Int) -> Int {\n        key % capacity\n    }\n\n    /* Load factor */\n    func loadFactor() -> Double {\n        Double(size) / Double(capacity)\n    }\n\n    /* Find the bucket index for key */\n    func findBucket(key: Int) -> Int {\n        var index = hashFunc(key: key)\n        var firstTombstone = -1\n        // Probe linearly until an empty bucket is reached\n        while buckets[index] != nil {\n            // If key is found, return its bucket index\n            if buckets[index]!.key == key {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if firstTombstone != -1 {\n                    buckets[firstTombstone] = buckets[index]\n                    buckets[index] = TOMBSTONE\n                    return firstTombstone // Return the bucket index after the move\n                }\n                return index // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if firstTombstone == -1 && buckets[index] == TOMBSTONE {\n                firstTombstone = index\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone\n    }\n\n    /* Lookup operation */\n    func get(key: Int) -> String? {\n        // Find the bucket index for key\n        let index = findBucket(key: key)\n        // If the key-value pair is found, return its val\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            return buckets[index]!.val\n        }\n        // Return nil if the key-value pair is absent\n        return nil\n    }\n\n    /* Insert operation */\n    func put(key: Int, val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if loadFactor() > loadThres {\n            extend()\n        }\n        // Find the bucket index for key\n        let index = findBucket(key: key)\n        // If the key-value pair is found, overwrite val and return\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            buckets[index]!.val = val\n            return\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = Pair(key: key, val: val)\n        size += 1\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        // Find the bucket index for key\n        let index = findBucket(key: key)\n        // If the key-value pair is found, replace it with a tombstone\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            buckets[index] = TOMBSTONE\n            size -= 1\n        }\n    }\n\n    /* Grow the hash table capacity */\n    func extend() {\n        // Save the original bucket array\n        let bucketsTmp = buckets\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio\n        buckets = Array(repeating: nil, count: capacity)\n        size = 0\n        // Move the key-value pairs from the original hash table to the new one\n        for pair in bucketsTmp {\n            if let pair, pair != TOMBSTONE {\n                put(key: pair.key, val: pair.val)\n            }\n        }\n    }\n\n    /* Print the hash table */\n    func print() {\n        for pair in buckets {\n            if pair == nil {\n                Swift.print(\"null\")\n            } else if pair == TOMBSTONE {\n                Swift.print(\"TOMBSTONE\")\n            } else {\n                Swift.print(\"\\(pair!.key) -> \\(pair!.val)\")\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.js
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    #size; // Number of key-value pairs\n    #capacity; // Hash table capacity\n    #loadThres; // Load factor threshold that triggers capacity growth\n    #extendRatio; // Capacity growth factor\n    #buckets; // Bucket array\n    #TOMBSTONE; // Tombstone marker\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0; // Number of key-value pairs\n        this.#capacity = 4; // Hash table capacity\n        this.#loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n        this.#extendRatio = 2; // Capacity growth factor\n        this.#buckets = Array(this.#capacity).fill(null); // Bucket array\n        this.#TOMBSTONE = new Pair(-1, '-1'); // Tombstone marker\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor() {\n        return this.#size / this.#capacity;\n    }\n\n    /* Find the bucket index for key */\n    #findBucket(key) {\n        let index = this.#hashFunc(key);\n        let firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (this.#buckets[index] !== null) {\n            // If key is found, return its bucket index\n            if (this.#buckets[index].key === key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone !== -1) {\n                    this.#buckets[firstTombstone] = this.#buckets[index];\n                    this.#buckets[index] = this.#TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (\n                firstTombstone === -1 &&\n                this.#buckets[index] === this.#TOMBSTONE\n            ) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % this.#capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone === -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    get(key) {\n        // Find the bucket index for key\n        const index = this.#findBucket(key);\n        // If the key-value pair is found, return its val\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            return this.#buckets[index].val;\n        }\n        // Return null if the key-value pair does not exist\n        return null;\n    }\n\n    /* Insert operation */\n    put(key, val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        // Find the bucket index for key\n        const index = this.#findBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            this.#buckets[index].val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        this.#buckets[index] = new Pair(key, val);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key) {\n        // Find the bucket index for key\n        const index = this.#findBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            this.#buckets[index] = this.#TOMBSTONE;\n            this.#size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    #extend() {\n        // Keep a reference to the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize a new hash table with the expanded capacity\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = Array(this.#capacity).fill(null);\n        this.#size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (const pair of bucketsTmp) {\n            if (pair !== null && pair !== this.#TOMBSTONE) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    print() {\n        for (const pair of this.#buckets) {\n            if (pair === null) {\n                console.log('null');\n            } else if (pair === this.#TOMBSTONE) {\n                console.log('TOMBSTONE');\n            } else {\n                console.log(pair.key + ' -> ' + pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.ts
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private size: number; // Number of key-value pairs\n    private capacity: number; // Hash table capacity\n    private loadThres: number; // Load factor threshold that triggers capacity growth\n    private extendRatio: number; // Capacity growth factor\n    private buckets: Array<Pair | null>; // Bucket array\n    private TOMBSTONE: Pair; // Tombstone marker\n\n    /* Constructor */\n    constructor() {\n        this.size = 0; // Number of key-value pairs\n        this.capacity = 4; // Hash table capacity\n        this.loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n        this.extendRatio = 2; // Capacity growth factor\n        this.buckets = Array(this.capacity).fill(null); // Bucket array\n        this.TOMBSTONE = new Pair(-1, '-1'); // Tombstone marker\n    }\n\n    /* Hash function */\n    private hashFunc(key: number): number {\n        return key % this.capacity;\n    }\n\n    /* Load factor */\n    private loadFactor(): number {\n        return this.size / this.capacity;\n    }\n\n    /* Find the bucket index for key */\n    private findBucket(key: number): number {\n        let index = this.hashFunc(key);\n        let firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (this.buckets[index] !== null) {\n            // If key is found, return its bucket index\n            if (this.buckets[index]!.key === key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone !== -1) {\n                    this.buckets[firstTombstone] = this.buckets[index];\n                    this.buckets[index] = this.TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (\n                firstTombstone === -1 &&\n                this.buckets[index] === this.TOMBSTONE\n            ) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % this.capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone === -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    get(key: number): string | null {\n        // Find the bucket index for key\n        const index = this.findBucket(key);\n        // If the key-value pair is found, return its val\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            return this.buckets[index]!.val;\n        }\n        // Return null if the key-value pair does not exist\n        return null;\n    }\n\n    /* Insert operation */\n    put(key: number, val: string): void {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (this.loadFactor() > this.loadThres) {\n            this.extend();\n        }\n        // Find the bucket index for key\n        const index = this.findBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            this.buckets[index]!.val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        this.buckets[index] = new Pair(key, val);\n        this.size++;\n    }\n\n    /* Remove operation */\n    remove(key: number): void {\n        // Find the bucket index for key\n        const index = this.findBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            this.buckets[index] = this.TOMBSTONE;\n            this.size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    private extend(): void {\n        // Keep a reference to the original hash table\n        const bucketsTmp = this.buckets;\n        // Initialize a new hash table with the expanded capacity\n        this.capacity *= this.extendRatio;\n        this.buckets = Array(this.capacity).fill(null);\n        this.size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (const pair of bucketsTmp) {\n            if (pair !== null && pair !== this.TOMBSTONE) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    print(): void {\n        for (const pair of this.buckets) {\n            if (pair === null) {\n                console.log('null');\n            } else if (pair === this.TOMBSTONE) {\n                console.log('TOMBSTONE');\n            } else {\n                console.log(pair.key + ' -> ' + pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.dart
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n  late int _size; // Number of key-value pairs\n  int _capacity = 4; // Hash table capacity\n  double _loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n  int _extendRatio = 2; // Capacity growth factor\n  late List<Pair?> _buckets; // Bucket array\n  Pair _TOMBSTONE = Pair(-1, \"-1\"); // Tombstone marker\n\n  /* Constructor */\n  HashMapOpenAddressing() {\n    _size = 0;\n    _buckets = List.generate(_capacity, (index) => null);\n  }\n\n  /* Hash function */\n  int hashFunc(int key) {\n    return key % _capacity;\n  }\n\n  /* Load factor */\n  double loadFactor() {\n    return _size / _capacity;\n  }\n\n  /* Find the bucket index for key */\n  int findBucket(int key) {\n    int index = hashFunc(key);\n    int firstTombstone = -1;\n    // Probe linearly until an empty bucket is reached\n    while (_buckets[index] != null) {\n      // If key is found, return its bucket index\n      if (_buckets[index]!.key == key) {\n        // If a tombstone was encountered earlier, move the key-value pair to that index\n        if (firstTombstone != -1) {\n          _buckets[firstTombstone] = _buckets[index];\n          _buckets[index] = _TOMBSTONE;\n          return firstTombstone; // Return the bucket index after the move\n        }\n        return index; // Return the bucket index\n      }\n      // Record the first tombstone encountered\n      if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) {\n        firstTombstone = index;\n      }\n      // Calculate the bucket index, wrapping to the beginning after the end\n      index = (index + 1) % _capacity;\n    }\n    // If key is absent, return the index for insertion\n    return firstTombstone == -1 ? index : firstTombstone;\n  }\n\n  /* Lookup operation */\n  String? get(int key) {\n    // Find the bucket index for key\n    int index = findBucket(key);\n    // If the key-value pair is found, return its val\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      return _buckets[index]!.val;\n    }\n    // Return null if the key-value pair does not exist\n    return null;\n  }\n\n  /* Insert operation */\n  void put(int key, String val) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if (loadFactor() > _loadThres) {\n      extend();\n    }\n    // Find the bucket index for key\n    int index = findBucket(key);\n    // If the key-value pair is found, overwrite val and return\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      _buckets[index]!.val = val;\n      return;\n    }\n    // If the key-value pair is absent, insert it\n    _buckets[index] = new Pair(key, val);\n    _size++;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    // Find the bucket index for key\n    int index = findBucket(key);\n    // If the key-value pair is found, replace it with a tombstone\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      _buckets[index] = _TOMBSTONE;\n      _size--;\n    }\n  }\n\n  /* Grow the hash table capacity */\n  void extend() {\n    // Keep a reference to the original hash table\n    List<Pair?> bucketsTmp = _buckets;\n    // Initialize a new hash table with the expanded capacity\n    _capacity *= _extendRatio;\n    _buckets = List.generate(_capacity, (index) => null);\n    _size = 0;\n    // Move the key-value pairs from the original hash table to the new one\n    for (Pair? pair in bucketsTmp) {\n      if (pair != null && pair != _TOMBSTONE) {\n        put(pair.key, pair.val);\n      }\n    }\n  }\n\n  /* Print the hash table */\n  void printHashMap() {\n    for (Pair? pair in _buckets) {\n      if (pair == null) {\n        print(\"null\");\n      } else if (pair == _TOMBSTONE) {\n        print(\"TOMBSTONE\");\n      } else {\n        print(\"${pair.key} -> ${pair.val}\");\n      }\n    }\n  }\n}\n
    hash_map_open_addressing.rs
    /* Hash table with open addressing */\nstruct HashMapOpenAddressing {\n    size: usize,                // Number of key-value pairs\n    capacity: usize,            // Hash table capacity\n    load_thres: f64,            // Load factor threshold that triggers capacity growth\n    extend_ratio: usize,        // Capacity growth factor\n    buckets: Vec<Option<Pair>>, // Bucket array\n    TOMBSTONE: Option<Pair>,    // Tombstone marker\n}\n\nimpl HashMapOpenAddressing {\n    /* Constructor */\n    fn new() -> Self {\n        Self {\n            size: 0,\n            capacity: 4,\n            load_thres: 2.0 / 3.0,\n            extend_ratio: 2,\n            buckets: vec![None; 4],\n            TOMBSTONE: Some(Pair {\n                key: -1,\n                val: \"-1\".to_string(),\n            }),\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        (key % self.capacity as i32) as usize\n    }\n\n    /* Load factor */\n    fn load_factor(&self) -> f64 {\n        self.size as f64 / self.capacity as f64\n    }\n\n    /* Find the bucket index for key */\n    fn find_bucket(&mut self, key: i32) -> usize {\n        let mut index = self.hash_func(key);\n        let mut first_tombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while self.buckets[index].is_some() {\n            // If key is found, return the corresponding bucket index\n            if self.buckets[index].as_ref().unwrap().key == key {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if first_tombstone != -1 {\n                    self.buckets[first_tombstone as usize] = self.buckets[index].take();\n                    self.buckets[index] = self.TOMBSTONE.clone();\n                    return first_tombstone as usize; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE {\n                first_tombstone = index as i32;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % self.capacity;\n        }\n        // If key is absent, return the index for insertion\n        if first_tombstone == -1 {\n            index\n        } else {\n            first_tombstone as usize\n        }\n    }\n\n    /* Lookup operation */\n    fn get(&mut self, key: i32) -> Option<&str> {\n        // Find the bucket index for key\n        let index = self.find_bucket(key);\n        // If the key-value pair is found, return its val\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            return self.buckets[index].as_ref().map(|pair| &pair.val as &str);\n        }\n        // If the key-value pair does not exist, return None\n        None\n    }\n\n    /* Insert operation */\n    fn put(&mut self, key: i32, val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if self.load_factor() > self.load_thres {\n            self.extend();\n        }\n        // Find the bucket index for key\n        let index = self.find_bucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            self.buckets[index].as_mut().unwrap().val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        self.buckets[index] = Some(Pair { key, val });\n        self.size += 1;\n    }\n\n    /* Remove operation */\n    fn remove(&mut self, key: i32) {\n        // Find the bucket index for key\n        let index = self.find_bucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            self.buckets[index] = self.TOMBSTONE.clone();\n            self.size -= 1;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    fn extend(&mut self) {\n        // Temporarily save a copy of the original buckets\n        let buckets_tmp = self.buckets.clone();\n        // Initialize a new hash table with the expanded capacity\n        self.capacity *= self.extend_ratio;\n        self.buckets = vec![None; self.capacity];\n        self.size = 0;\n\n        // Move the key-value pairs from the original hash table to the new one\n        for pair in buckets_tmp {\n            if pair.is_none() || pair == self.TOMBSTONE {\n                continue;\n            }\n            let pair = pair.unwrap();\n\n            self.put(pair.key, pair.val);\n        }\n    }\n    /* Print the hash table */\n    fn print(&self) {\n        for pair in &self.buckets {\n            if pair.is_none() {\n                println!(\"null\");\n            } else if pair == &self.TOMBSTONE {\n                println!(\"TOMBSTONE\");\n            } else {\n                let pair = pair.as_ref().unwrap();\n                println!(\"{} -> {}\", pair.key, pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.c
    /* Hash table with open addressing */\ntypedef struct {\n    int size;         // Number of key-value pairs\n    int capacity;     // Hash table capacity\n    double loadThres; // Load factor threshold that triggers capacity growth\n    int extendRatio;  // Capacity growth factor\n    Pair **buckets;   // Bucket array\n    Pair *TOMBSTONE;  // Tombstone marker\n} HashMapOpenAddressing;\n\n/* Constructor */\nHashMapOpenAddressing *newHashMapOpenAddressing() {\n    HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing));\n    hashMap->size = 0;\n    hashMap->capacity = 4;\n    hashMap->loadThres = 2.0 / 3.0;\n    hashMap->extendRatio = 2;\n    hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));\n    hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair));\n    hashMap->TOMBSTONE->key = -1;\n    hashMap->TOMBSTONE->val = \"-1\";\n\n    return hashMap;\n}\n\n/* Destructor */\nvoid delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Pair *pair = hashMap->buckets[i];\n        if (pair != NULL && pair != hashMap->TOMBSTONE) {\n            free(pair->val);\n            free(pair);\n        }\n    }\n    free(hashMap->buckets);\n    free(hashMap->TOMBSTONE);\n    free(hashMap);\n}\n\n/* Hash function */\nint hashFunc(HashMapOpenAddressing *hashMap, int key) {\n    return key % hashMap->capacity;\n}\n\n/* Load factor */\ndouble loadFactor(HashMapOpenAddressing *hashMap) {\n    return (double)hashMap->size / (double)hashMap->capacity;\n}\n\n/* Find the bucket index for key */\nint findBucket(HashMapOpenAddressing *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    int firstTombstone = -1;\n    // Probe linearly until an empty bucket is reached\n    while (hashMap->buckets[index] != NULL) {\n        // If key is found, return its bucket index\n        if (hashMap->buckets[index]->key == key) {\n            // If a tombstone was encountered earlier, move the key-value pair to that index\n            if (firstTombstone != -1) {\n                hashMap->buckets[firstTombstone] = hashMap->buckets[index];\n                hashMap->buckets[index] = hashMap->TOMBSTONE;\n                return firstTombstone; // Return the bucket index after the move\n            }\n            return index; // Return the bucket index\n        }\n        // Record the first tombstone encountered\n        if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) {\n            firstTombstone = index;\n        }\n        // Calculate the bucket index, wrapping to the beginning after the end\n        index = (index + 1) % hashMap->capacity;\n    }\n    // If key is absent, return the index for insertion\n    return firstTombstone == -1 ? index : firstTombstone;\n}\n\n/* Lookup operation */\nchar *get(HashMapOpenAddressing *hashMap, int key) {\n    // Find the bucket index for key\n    int index = findBucket(hashMap, key);\n    // If the key-value pair is found, return its val\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        return hashMap->buckets[index]->val;\n    }\n    // Return an empty string if the key-value pair does not exist\n    return \"\";\n}\n\n/* Insert operation */\nvoid put(HashMapOpenAddressing *hashMap, int key, char *val) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if (loadFactor(hashMap) > hashMap->loadThres) {\n        extend(hashMap);\n    }\n    // Find the bucket index for key\n    int index = findBucket(hashMap, key);\n    // If the key-value pair is found, overwrite val and return\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        free(hashMap->buckets[index]->val);\n        hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1));\n        strcpy(hashMap->buckets[index]->val, val);\n        hashMap->buckets[index]->val[strlen(val)] = '\\0';\n        return;\n    }\n    // If the key-value pair is absent, insert it\n    Pair *pair = (Pair *)malloc(sizeof(Pair));\n    pair->key = key;\n    pair->val = (char *)malloc(sizeof(strlen(val) + 1));\n    strcpy(pair->val, val);\n    pair->val[strlen(val)] = '\\0';\n\n    hashMap->buckets[index] = pair;\n    hashMap->size++;\n}\n\n/* Remove operation */\nvoid removeItem(HashMapOpenAddressing *hashMap, int key) {\n    // Find the bucket index for key\n    int index = findBucket(hashMap, key);\n    // If the key-value pair is found, replace it with a tombstone\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        Pair *pair = hashMap->buckets[index];\n        free(pair->val);\n        free(pair);\n        hashMap->buckets[index] = hashMap->TOMBSTONE;\n        hashMap->size--;\n    }\n}\n\n/* Grow the hash table capacity */\nvoid extend(HashMapOpenAddressing *hashMap) {\n    // Keep a reference to the original hash table\n    Pair **bucketsTmp = hashMap->buckets;\n    int oldCapacity = hashMap->capacity;\n    // Initialize a new hash table with the expanded capacity\n    hashMap->capacity *= hashMap->extendRatio;\n    hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));\n    hashMap->size = 0;\n    // Move the key-value pairs from the original hash table to the new one\n    for (int i = 0; i < oldCapacity; i++) {\n        Pair *pair = bucketsTmp[i];\n        if (pair != NULL && pair != hashMap->TOMBSTONE) {\n            put(hashMap, pair->key, pair->val);\n            free(pair->val);\n            free(pair);\n        }\n    }\n    free(bucketsTmp);\n}\n\n/* Print the hash table */\nvoid print(HashMapOpenAddressing *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Pair *pair = hashMap->buckets[i];\n        if (pair == NULL) {\n            printf(\"NULL\\n\");\n        } else if (pair == hashMap->TOMBSTONE) {\n            printf(\"TOMBSTONE\\n\");\n        } else {\n            printf(\"%d -> %s\\n\", pair->key, pair->val);\n        }\n    }\n}\n
    hash_map_open_addressing.kt
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private var size: Int               // Number of key-value pairs\n    private var capacity: Int           // Hash table capacity\n    private val loadThres: Double       // Load factor threshold that triggers capacity growth\n    private val extendRatio: Int        // Capacity growth factor\n    private var buckets: Array<Pair?>   // Bucket array\n    private val TOMBSTONE: Pair         // Tombstone marker\n\n    /* Constructor */\n    init {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = arrayOfNulls(capacity)\n        TOMBSTONE = Pair(-1, \"-1\")\n    }\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        return key % capacity\n    }\n\n    /* Load factor */\n    fun loadFactor(): Double {\n        return (size / capacity).toDouble()\n    }\n\n    /* Find the bucket index for key */\n    fun findBucket(key: Int): Int {\n        var index = hashFunc(key)\n        var firstTombstone = -1\n        // Probe linearly until an empty bucket is reached\n        while (buckets[index] != null) {\n            // If key is found, return its bucket index\n            if (buckets[index]?.key == key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index]\n                    buckets[index] = TOMBSTONE\n                    return firstTombstone // Return the bucket index after the move\n                }\n                return index // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity\n        }\n        // If key is absent, return the index for insertion\n        return if (firstTombstone == -1) index else firstTombstone\n    }\n\n    /* Lookup operation */\n    fun get(key: Int): String? {\n        // Find the bucket index for key\n        val index = findBucket(key)\n        // If the key-value pair is found, return its val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index]?._val\n        }\n        // Return null if the key-value pair is absent\n        return null\n    }\n\n    /* Insert operation */\n    fun put(key: Int, _val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend()\n        }\n        // Find the bucket index for key\n        val index = findBucket(key)\n        // If the key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index]!!._val = _val\n            return\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = Pair(key, _val)\n        size++\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        // Find the bucket index for key\n        val index = findBucket(key)\n        // If the key-value pair is found, replace it with a tombstone\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE\n            size--\n        }\n    }\n\n    /* Grow the hash table capacity */\n    fun extend() {\n        // Keep a reference to the original hash table\n        val bucketsTmp = buckets\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio\n        buckets = arrayOfNulls(capacity)\n        size = 0\n        // Move the key-value pairs from the original hash table to the new one\n        for (pair in bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                put(pair.key, pair._val)\n            }\n        }\n    }\n\n    /* Print the hash table */\n    fun print() {\n        for (pair in buckets) {\n            if (pair == null) {\n                println(\"null\")\n            } else if (pair == TOMBSTONE) {\n                println(\"TOMESTOME\")\n            } else {\n                println(\"${pair.key} -> ${pair._val}\")\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.rb
    ### Hash table with open addressing ###\nclass HashMapOpenAddressing\n  TOMBSTONE = Pair.new(-1, '-1') # Tombstone marker\n\n  ### Constructor ###\n  def initialize\n    @size = 0 # Number of key-value pairs\n    @capacity = 4 # Hash table capacity\n    @load_thres = 2.0 / 3.0 # Load factor threshold that triggers capacity growth\n    @extend_ratio = 2 # Capacity growth factor\n    @buckets = Array.new(@capacity) # Bucket array\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    key % @capacity\n  end\n\n  ### Load factor ###\n  def load_factor\n    @size / @capacity\n  end\n\n  ### Find the bucket index for key ###\n  def find_bucket(key)\n    index = hash_func(key)\n    first_tombstone = -1\n    # Probe linearly until an empty bucket is reached\n    while !@buckets[index].nil?\n      # If key is found, return its bucket index\n      if @buckets[index].key == key\n        # If a tombstone was encountered earlier, move the key-value pair to that index\n        if first_tombstone != -1\n          @buckets[first_tombstone] = @buckets[index]\n          @buckets[index] = TOMBSTONE\n          return first_tombstone # Return the bucket index after the move\n        end\n        return index # Return the bucket index\n      end\n      # Record the first tombstone encountered\n      first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE\n      # Calculate the bucket index, wrapping to the beginning after the end\n      index = (index + 1) % @capacity\n    end\n    # If key is absent, return the index for insertion\n    first_tombstone == -1 ? index : first_tombstone\n  end\n\n  ### Lookup operation ###\n  def get(key)\n    # Find the bucket index for key\n    index = find_bucket(key)\n    # If the key-value pair is found, return its val\n    return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])\n    # Return nil if the key-value pair does not exist\n    nil\n  end\n\n  ### Insert operation ###\n  def put(key, val)\n    # Grow the capacity when the load factor exceeds the threshold\n    extend if load_factor > @load_thres\n    # Find the bucket index for key\n    index = find_bucket(key)\n    # If the key-value pair is found, overwrite val and return\n    unless [nil, TOMBSTONE].include?(@buckets[index])\n      @buckets[index].val = val\n      return\n    end\n    # If the key-value pair is absent, insert it\n    @buckets[index] = Pair.new(key, val)\n    @size += 1\n  end\n\n  ### Remove operation ###\n  def remove(key)\n    # Find the bucket index for key\n    index = find_bucket(key)\n    # If the key-value pair is found, replace it with a tombstone\n    unless [nil, TOMBSTONE].include?(@buckets[index])\n      @buckets[index] = TOMBSTONE\n      @size -= 1\n    end\n  end\n\n  ### Grow the hash table capacity ###\n  def extend\n    # Keep a reference to the original hash table\n    buckets_tmp = @buckets\n    # Initialize a new hash table with the expanded capacity\n    @capacity *= @extend_ratio\n    @buckets = Array.new(@capacity)\n    @size = 0\n    # Move the key-value pairs from the original hash table to the new one\n    for pair in buckets_tmp\n      put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)\n    end\n  end\n\n  ### Print the hash table ###\n  def print\n    for pair in @buckets\n      if pair.nil?\n        puts \"Nil\"\n      elsif pair == TOMBSTONE\n        puts \"TOMBSTONE\"\n      else\n        puts \"#{pair.key} -> #{pair.val}\"\n      end\n    end\n  end\nend\n
    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#2-quadratic-probing","level":3,"title":"2.   Quadratic probing","text":"

    Like linear probing, quadratic probing is a common open-addressing strategy. On a collision, it probes at distances determined by the square of the probe number—\\(1, 4, 9, \\dots\\)—rather than a fixed step size.

    Its main advantages are as follows.

    • Squared probe distances aim to reduce the clustering caused by linear probing.
    • Larger jumps help distribute entries more evenly while searching for empty positions.

    However, quadratic probing is not perfect.

    • Clustering still occurs: some positions are more likely to be occupied than others.
    • The growing squared distances may prevent probing the entire table, so an empty bucket may remain unreachable.
    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#3-multiple-hashing","level":3,"title":"3.   Multiple hashing","text":"

    As its name suggests, multiple hashing probes using several hash functions, \\(f_1(x)\\), \\(f_2(x)\\), \\(f_3(x)\\), \\(\\dots\\).

    • Insertion: If \\(f_1(x)\\) produces a collision, try \\(f_2(x)\\), and so on, inserting the element when an empty position is found.
    • Lookup: Try the hash functions in the same order and return when the target is found. If an empty position is reached or all functions have been tried, the target is absent; return None.

    Multiple hashing is less prone to clustering than linear probing, but computing several hash functions adds overhead.

    Tip

    All open-addressing strategies—linear probing, quadratic probing, and multiple hashing—share the restriction that elements cannot simply be deleted.

    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#623-choices-made-by-programming-languages","level":2,"title":"6.2.3   Choices made by programming languages","text":"

    Programming languages use different hash-table implementations. Here are a few examples.

    • Python uses open addressing. Its dict probes using pseudorandom values.
    • Java uses separate chaining. Since JDK 1.8, when a HashMap's underlying array has length at least 64 and a chain reaches length 8, the chain is converted to a red-black tree to improve lookup performance.
    • Since Go 1.24, the built-in map uses open addressing based on Swiss Tables by default. Go 1.23 and earlier use separate chaining, with up to 8 key-value pairs per bucket and linked overflow buckets when that capacity is exceeded.
    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_map/","level":1,"title":"6.1   Hash tables","text":"

    A hash table, also called a hash map, supports efficient lookup by mapping a key to a value. Given a key, it can retrieve the corresponding value in \\(O(1)\\) time.

    Suppose we have \\(n\\) students, each with a name and a student ID. To support the query “given a student ID, return the corresponding name,” use the hash table shown below.

    Figure 6-1   An abstract view of hash-table lookup

    Arrays and linked lists also support lookup. Table 6-1 compares their efficiency.

    • Insertion: append an element to the end of the array or linked list in \\(O(1)\\) time.
    • Lookup: because the array or list is unsorted, searching may require visiting every element, taking \\(O(n)\\) time.
    • Deletion: first find the element, then remove it from the array or list, taking \\(O(n)\\) time.

    Table 6-1   Comparison of element-lookup efficiency

    Array Linked list Hash table Find an element \\(O(n)\\) \\(O(n)\\) \\(O(1)\\) Insert an element \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) Delete an element \\(O(n)\\) \\(O(n)\\) \\(O(1)\\)

    Hash-table insertion, deletion, lookup, and updating all take \\(O(1)\\) time, making them highly efficient.

    ","path":["Chapter 6.   Hash tables","6.1   Hash tables"],"tags":[]},{"location":"chapter_hashing/hash_map/#611-common-hash-table-operations","level":2,"title":"6.1.1   Common hash-table operations","text":"

    Common operations include initialization, lookup, adding key-value pairs, and deleting pairs. Examples follow:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py
    # Initialize a hash table\nhmap: dict = {}\n\n# Insertion\n# Add key-value pairs (key, value) to the hash table\nhmap[12836] = \"小哈\"\nhmap[15937] = \"小啰\"\nhmap[16750] = \"小算\"\nhmap[13276] = \"小法\"\nhmap[10583] = \"小鸭\"\n\n# Lookup\n# Look up key in the hash table to obtain value\nname: str = hmap[15937]\n\n# Deletion\n# Remove a key-value pair (key, value) from the hash table\nhmap.pop(10583)\n
    hash_map.cpp
    /* Initialize a hash table */\nunordered_map<int, string> map;\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap[12836] = \"小哈\";\nmap[15937] = \"小啰\";\nmap[16750] = \"小算\";\nmap[13276] = \"小法\";\nmap[10583] = \"小鸭\";\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nstring name = map[15937];\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.erase(10583);\n
    hash_map.java
    /* Initialize a hash table */\nMap<Integer, String> map = new HashMap<>();\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap.put(12836, \"小哈\");\nmap.put(15937, \"小啰\");\nmap.put(16750, \"小算\");\nmap.put(13276, \"小法\");\nmap.put(10583, \"小鸭\");\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nString name = map.get(15937);\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.remove(10583);\n
    hash_map.cs
    /* Initialize a hash table */\nDictionary<int, string> map = new() {\n    /* Insertion */\n    // Add key-value pairs (key, value) to the hash table\n    { 12836, \"小哈\" },\n    { 15937, \"小啰\" },\n    { 16750, \"小算\" },\n    { 13276, \"小法\" },\n    { 10583, \"小鸭\" }\n};\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nstring name = map[15937];\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.Remove(10583);\n
    hash_map_test.go
    /* Initialize a hash table */\nhmap := make(map[int]string)\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nhmap[12836] = \"小哈\"\nhmap[15937] = \"小啰\"\nhmap[16750] = \"小算\"\nhmap[13276] = \"小法\"\nhmap[10583] = \"小鸭\"\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nname := hmap[15937]\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\ndelete(hmap, 10583)\n
    hash_map.swift
    /* Initialize a hash table */\nvar map: [Int: String] = [:]\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap[12836] = \"小哈\"\nmap[15937] = \"小啰\"\nmap[16750] = \"小算\"\nmap[13276] = \"小法\"\nmap[10583] = \"小鸭\"\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nlet name = map[15937]!\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.removeValue(forKey: 10583)\n
    hash_map.js
    /* Initialize a hash table */\nconst map = new Map();\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap.set(12836, '小哈');\nmap.set(15937, '小啰');\nmap.set(16750, '小算');\nmap.set(13276, '小法');\nmap.set(10583, '小鸭');\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nlet name = map.get(15937);\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.delete(10583);\n
    hash_map.ts
    /* Initialize a hash table */\nconst map = new Map<number, string>();\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap.set(12836, '小哈');\nmap.set(15937, '小啰');\nmap.set(16750, '小算');\nmap.set(13276, '小法');\nmap.set(10583, '小鸭');\nconsole.info('\\nAfter insertion, the hash table is\\nKey -> Value');\nconsole.info(map);\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nlet name = map.get(15937);\nconsole.info('\\nLooking up student ID 15937 gives the name ' + name);\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.delete(10583);\nconsole.info('\\nAfter deleting 10583, the hash table is\\nKey -> Value');\nconsole.info(map);\n
    hash_map.dart
    /* Initialize a hash table */\nMap<int, String> map = {};\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap[12836] = \"小哈\";\nmap[15937] = \"小啰\";\nmap[16750] = \"小算\";\nmap[13276] = \"小法\";\nmap[10583] = \"小鸭\";\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nString name = map[15937];\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.remove(10583);\n
    hash_map.rs
    use std::collections::HashMap;\n\n/* Initialize a hash table */\nlet mut map: HashMap<i32, String> = HashMap::new();\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap.insert(12836, \"小哈\".to_string());\nmap.insert(15937, \"小啰\".to_string());\nmap.insert(16750, \"小算\".to_string());\nmap.insert(13279, \"小法\".to_string());\nmap.insert(10583, \"小鸭\".to_string());\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nlet _name: Option<&String> = map.get(&15937);\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nlet _removed_value: Option<String> = map.remove(&10583);\n
    hash_map.c
    // C provides no built-in hash table\n
    hash_map.kt
    /* Initialize a hash table */\nval map = HashMap<Int,String>()\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap[12836] = \"小哈\"\nmap[15937] = \"小啰\"\nmap[16750] = \"小算\"\nmap[13276] = \"小法\"\nmap[10583] = \"小鸭\"\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nval name = map[15937]\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.remove(10583)\n
    hash_map.rb
    # Initialize a hash table\nhmap = {}\n\n# Insertion\n# Add key-value pairs (key, value) to the hash table\nhmap[12836] = \"小哈\"\nhmap[15937] = \"小啰\"\nhmap[16750] = \"小算\"\nhmap[13276] = \"小法\"\nhmap[10583] = \"小鸭\"\n\n# Lookup\n# Look up key in the hash table to obtain value\nname = hmap[15937]\n\n# Deletion\n# Remove a key-value pair (key, value) from the hash table\nhmap.delete(10583)\n
    Visualize execution

    View full screen >

    There are three common ways to traverse a hash table: visit key-value pairs, keys only, or values only. Examples follow:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py
    # Traverse the hash table\n# Traverse key-value pairs: key->value\nfor key, value in hmap.items():\n    print(key, \"->\", value)\n# Traverse keys only: key\nfor key in hmap.keys():\n    print(key)\n# Traverse values only: value\nfor value in hmap.values():\n    print(value)\n
    hash_map.cpp
    /* Traverse the hash table */\n// Traverse key-value pairs: key->value\nfor (auto kv: map) {\n    cout << kv.first << \" -> \" << kv.second << endl;\n}\n// Use an iterator to traverse key->value pairs\nfor (auto iter = map.begin(); iter != map.end(); iter++) {\n    cout << iter->first << \"->\" << iter->second << endl;\n}\n
    hash_map.java
    /* Traverse the hash table */\n// Traverse key-value pairs: key->value\nfor (Map.Entry <Integer, String> kv: map.entrySet()) {\n    System.out.println(kv.getKey() + \" -> \" + kv.getValue());\n}\n// Traverse keys only: key\nfor (int key: map.keySet()) {\n    System.out.println(key);\n}\n// Traverse values only: value\nfor (String val: map.values()) {\n    System.out.println(val);\n}\n
    hash_map.cs
    /* Traverse the hash table */\n// Traverse key-value pairs: Key->Value\nforeach (var kv in map) {\n    Console.WriteLine(kv.Key + \" -> \" + kv.Value);\n}\n// Traverse keys only: key\nforeach (int key in map.Keys) {\n    Console.WriteLine(key);\n}\n// Traverse values only: value\nforeach (string val in map.Values) {\n    Console.WriteLine(val);\n}\n
    hash_map_test.go
    /* Traverse the hash table */\n// Traverse key-value pairs: key->value\nfor key, value := range hmap {\n    fmt.Println(key, \"->\", value)\n}\n// Traverse keys only: key\nfor key := range hmap {\n    fmt.Println(key)\n}\n// Traverse values only: value\nfor _, value := range hmap {\n    fmt.Println(value)\n}\n
    hash_map.swift
    /* Traverse the hash table */\n// Traverse key-value pairs: Key->Value\nfor (key, value) in map {\n    print(\"\\(key) -> \\(value)\")\n}\n// Traverse keys only: Key\nfor key in map.keys {\n    print(key)\n}\n// Traverse values only: Value\nfor value in map.values {\n    print(value)\n}\n
    hash_map.js
    /* Traverse the hash table */\nconsole.info('\\nTraverse key-value pairs: Key->Value');\nfor (const [k, v] of map.entries()) {\n    console.info(k + ' -> ' + v);\n}\nconsole.info('\\nTraverse keys only: Key');\nfor (const k of map.keys()) {\n    console.info(k);\n}\nconsole.info('\\nTraverse values only: Value');\nfor (const v of map.values()) {\n    console.info(v);\n}\n
    hash_map.ts
    /* Traverse the hash table */\nconsole.info('\\nTraverse key-value pairs: Key->Value');\nfor (const [k, v] of map.entries()) {\n    console.info(k + ' -> ' + v);\n}\nconsole.info('\\nTraverse keys only: Key');\nfor (const k of map.keys()) {\n    console.info(k);\n}\nconsole.info('\\nTraverse values only: Value');\nfor (const v of map.values()) {\n    console.info(v);\n}\n
    hash_map.dart
    /* Traverse the hash table */\n// Traverse key-value pairs: Key->Value\nmap.forEach((key, value) {\n  print('$key -> $value');\n});\n\n// Traverse keys only: Key\nmap.keys.forEach((key) {\n  print(key);\n});\n\n// Traverse values only: Value\nmap.values.forEach((value) {\n  print(value);\n});\n
    hash_map.rs
    /* Traverse the hash table */\n// Traverse key-value pairs: Key->Value\nfor (key, value) in &map {\n    println!(\"{key} -> {value}\");\n}\n\n// Traverse keys only: Key\nfor key in map.keys() {\n    println!(\"{key}\");\n}\n\n// Traverse values only: Value\nfor value in map.values() {\n    println!(\"{value}\");\n}\n
    hash_map.c
    // C provides no built-in hash table\n
    hash_map.kt
    /* Traverse the hash table */\n// Traverse key-value pairs: key->value\nfor ((key, value) in map) {\n    println(\"$key -> $value\")\n}\n// Traverse keys only: key\nfor (key in map.keys) {\n    println(key)\n}\n// Traverse values only: value\nfor (_val in map.values) {\n    println(_val)\n}\n
    hash_map.rb
    # Traverse the hash table\n# Traverse key-value pairs: key->value\nhmap.entries.each { |key, value| puts \"#{key} -> #{value}\" }\n\n# Traverse keys only: key\nhmap.keys.each { |key| puts key }\n\n# Traverse values only: value\nhmap.values.each { |val| puts val }\n
    Visualize execution

    View full screen >

    ","path":["Chapter 6.   Hash tables","6.1   Hash tables"],"tags":[]},{"location":"chapter_hashing/hash_map/#612-a-simple-implementation","level":2,"title":"6.1.2   A simple implementation","text":"

    Start with the simplest case: implement a hash table using just one array. Each slot is called a bucket and holds one key-value pair. A lookup finds the bucket corresponding to key and retrieves its value.

    How do we locate the bucket for a key? A hash function maps a larger input space into a smaller output space. Here, the inputs are all possible keys, and the outputs are bucket indices. Given a key, the hash function tells us where its key-value pair is stored in the array.

    For an input key, the calculation has two steps.

    1. Use a hash algorithm, hash(), to calculate a hash value.
    2. Take that value modulo the number of buckets, the array length capacity, to obtain the bucket index index.
    index = hash(key) % capacity\n

    Use index to access the bucket and retrieve value.

    If capacity = 100 and hash(key) = key, the resulting hash function is key % 100. Figure 6-2 illustrates it with student IDs as keys and names as values.

    Figure 6-2   How a hash function works

    The code below implements a simple hash table. The Pair class groups a key and value into a key-value pair.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_hash_map.py
    class Pair:\n    \"\"\"Key-value pair\"\"\"\n\n    def __init__(self, key: int, val: str):\n        self.key = key\n        self.val = val\n\nclass ArrayHashMap:\n    \"\"\"Hash table implemented with an array\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        # Initialize an array containing 100 buckets\n        self.buckets: list[Pair | None] = [None] * 100\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        index = key % 100\n        return index\n\n    def get(self, key: int) -> str | None:\n        \"\"\"Lookup operation\"\"\"\n        index: int = self.hash_func(key)\n        pair: Pair = self.buckets[index]\n        if pair is None:\n            return None\n        return pair.val\n\n    def put(self, key: int, val: str):\n        \"\"\"Insert and update operations\"\"\"\n        pair = Pair(key, val)\n        index: int = self.hash_func(key)\n        self.buckets[index] = pair\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        index: int = self.hash_func(key)\n        # Set to None to mark removal\n        self.buckets[index] = None\n\n    def entry_set(self) -> list[Pair]:\n        \"\"\"Get all key-value pairs\"\"\"\n        result: list[Pair] = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair)\n        return result\n\n    def key_set(self) -> list[int]:\n        \"\"\"Get all keys\"\"\"\n        result = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair.key)\n        return result\n\n    def value_set(self) -> list[str]:\n        \"\"\"Get all values\"\"\"\n        result = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair.val)\n        return result\n\n    def print(self):\n        \"\"\"Print the hash table\"\"\"\n        for pair in self.buckets:\n            if pair is not None:\n                print(pair.key, \"->\", pair.val)\n
    array_hash_map.cpp
    /* Key-value pair */\nstruct Pair {\n  public:\n    int key;\n    string val;\n    Pair(int key, string val) {\n        this->key = key;\n        this->val = val;\n    }\n};\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n  private:\n    vector<Pair *> buckets;\n\n  public:\n    ArrayHashMap() {\n        // Initialize an array containing 100 buckets\n        buckets = vector<Pair *>(100);\n    }\n\n    ~ArrayHashMap() {\n        // Free memory\n        for (const auto &bucket : buckets) {\n            delete bucket;\n        }\n        buckets.clear();\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Lookup operation */\n    string get(int key) {\n        int index = hashFunc(key);\n        Pair *pair = buckets[index];\n        if (pair == nullptr)\n            return \"\";\n        return pair->val;\n    }\n\n    /* Insert operation */\n    void put(int key, string val) {\n        Pair *pair = new Pair(key, val);\n        int index = hashFunc(key);\n        buckets[index] = pair;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        // Free the memory and set the pointer to nullptr\n        delete buckets[index];\n        buckets[index] = nullptr;\n    }\n\n    /* Get all key-value pairs */\n    vector<Pair *> pairSet() {\n        vector<Pair *> pairSet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                pairSet.push_back(pair);\n            }\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    vector<int> keySet() {\n        vector<int> keySet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                keySet.push_back(pair->key);\n            }\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    vector<string> valueSet() {\n        vector<string> valueSet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                valueSet.push_back(pair->val);\n            }\n        }\n        return valueSet;\n    }\n\n    /* Print the hash table */\n    void print() {\n        for (Pair *kv : pairSet()) {\n            cout << kv->key << \" -> \" << kv->val << endl;\n        }\n    }\n};\n
    array_hash_map.java
    /* Key-value pair */\nclass Pair {\n    public int key;\n    public String val;\n\n    public Pair(int key, String val) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    private List<Pair> buckets;\n\n    public ArrayHashMap() {\n        // Initialize an array containing 100 buckets\n        buckets = new ArrayList<>();\n        for (int i = 0; i < 100; i++) {\n            buckets.add(null);\n        }\n    }\n\n    /* Hash function */\n    private int hashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Lookup operation */\n    public String get(int key) {\n        int index = hashFunc(key);\n        Pair pair = buckets.get(index);\n        if (pair == null)\n            return null;\n        return pair.val;\n    }\n\n    /* Insert operation */\n    public void put(int key, String val) {\n        Pair pair = new Pair(key, val);\n        int index = hashFunc(key);\n        buckets.set(index, pair);\n    }\n\n    /* Remove operation */\n    public void remove(int key) {\n        int index = hashFunc(key);\n        // Set the bucket to null to mark it as removed\n        buckets.set(index, null);\n    }\n\n    /* Get all key-value pairs */\n    public List<Pair> pairSet() {\n        List<Pair> pairSet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                pairSet.add(pair);\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    public List<Integer> keySet() {\n        List<Integer> keySet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                keySet.add(pair.key);\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    public List<String> valueSet() {\n        List<String> valueSet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                valueSet.add(pair.val);\n        }\n        return valueSet;\n    }\n\n    /* Print the hash table */\n    public void print() {\n        for (Pair kv : pairSet()) {\n            System.out.println(kv.key + \" -> \" + kv.val);\n        }\n    }\n}\n
    array_hash_map.cs
    /* Key-value pair: int->string */\nclass Pair(int key, string val) {\n    public int key = key;\n    public string val = val;\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    List<Pair?> buckets;\n    public ArrayHashMap() {\n        // Initialize an array containing 100 buckets\n        buckets = [];\n        for (int i = 0; i < 100; i++) {\n            buckets.Add(null);\n        }\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Lookup operation */\n    public string? Get(int key) {\n        int index = HashFunc(key);\n        Pair? pair = buckets[index];\n        if (pair == null) return null;\n        return pair.val;\n    }\n\n    /* Insert operation */\n    public void Put(int key, string val) {\n        Pair pair = new(key, val);\n        int index = HashFunc(key);\n        buckets[index] = pair;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        int index = HashFunc(key);\n        // Set the bucket to null to mark it as removed\n        buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    public List<Pair> PairSet() {\n        List<Pair> pairSet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                pairSet.Add(pair);\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    public List<int> KeySet() {\n        List<int> keySet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                keySet.Add(pair.key);\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    public List<string> ValueSet() {\n        List<string> valueSet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                valueSet.Add(pair.val);\n        }\n        return valueSet;\n    }\n\n    /* Print the hash table */\n    public void Print() {\n        foreach (Pair kv in PairSet()) {\n            Console.WriteLine(kv.key + \" -> \" + kv.val);\n        }\n    }\n}\n
    array_hash_map.go
    /* Key-value pair */\ntype pair struct {\n    key int\n    val string\n}\n\n/* Hash table implemented with an array */\ntype arrayHashMap struct {\n    buckets []*pair\n}\n\n/* Initialize the hash table */\nfunc newArrayHashMap() *arrayHashMap {\n    // Initialize an array containing 100 buckets\n    buckets := make([]*pair, 100)\n    return &arrayHashMap{buckets: buckets}\n}\n\n/* Hash function */\nfunc (a *arrayHashMap) hashFunc(key int) int {\n    index := key % 100\n    return index\n}\n\n/* Lookup operation */\nfunc (a *arrayHashMap) get(key int) string {\n    index := a.hashFunc(key)\n    pair := a.buckets[index]\n    if pair == nil {\n        return \"Not Found\"\n    }\n    return pair.val\n}\n\n/* Insert operation */\nfunc (a *arrayHashMap) put(key int, val string) {\n    pair := &pair{key: key, val: val}\n    index := a.hashFunc(key)\n    a.buckets[index] = pair\n}\n\n/* Remove operation */\nfunc (a *arrayHashMap) remove(key int) {\n    index := a.hashFunc(key)\n    // Set to nil to mark the entry as removed\n    a.buckets[index] = nil\n}\n\n/* Get all key-value pairs */\nfunc (a *arrayHashMap) pairSet() []*pair {\n    var pairs []*pair\n    for _, pair := range a.buckets {\n        if pair != nil {\n            pairs = append(pairs, pair)\n        }\n    }\n    return pairs\n}\n\n/* Get all keys */\nfunc (a *arrayHashMap) keySet() []int {\n    var keys []int\n    for _, pair := range a.buckets {\n        if pair != nil {\n            keys = append(keys, pair.key)\n        }\n    }\n    return keys\n}\n\n/* Get all values */\nfunc (a *arrayHashMap) valueSet() []string {\n    var values []string\n    for _, pair := range a.buckets {\n        if pair != nil {\n            values = append(values, pair.val)\n        }\n    }\n    return values\n}\n\n/* Print the hash table */\nfunc (a *arrayHashMap) print() {\n    for _, pair := range a.buckets {\n        if pair != nil {\n            fmt.Println(pair.key, \"->\", pair.val)\n        }\n    }\n}\n
    array_hash_map.swift
    /* Key-value pair */\nclass Pair: Equatable {\n    public var key: Int\n    public var val: String\n\n    public init(key: Int, val: String) {\n        self.key = key\n        self.val = val\n    }\n\n    public static func == (lhs: Pair, rhs: Pair) -> Bool {\n        lhs.key == rhs.key && lhs.val == rhs.val\n    }\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    private var buckets: [Pair?]\n\n    init() {\n        // Initialize an array containing 100 buckets\n        buckets = Array(repeating: nil, count: 100)\n    }\n\n    /* Hash function */\n    private func hashFunc(key: Int) -> Int {\n        let index = key % 100\n        return index\n    }\n\n    /* Lookup operation */\n    func get(key: Int) -> String? {\n        let index = hashFunc(key: key)\n        let pair = buckets[index]\n        return pair?.val\n    }\n\n    /* Insert operation */\n    func put(key: Int, val: String) {\n        let pair = Pair(key: key, val: val)\n        let index = hashFunc(key: key)\n        buckets[index] = pair\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        let index = hashFunc(key: key)\n        // Set to nil to mark the entry as removed\n        buckets[index] = nil\n    }\n\n    /* Get all key-value pairs */\n    func pairSet() -> [Pair] {\n        buckets.compactMap { $0 }\n    }\n\n    /* Get all keys */\n    func keySet() -> [Int] {\n        buckets.compactMap { $0?.key }\n    }\n\n    /* Get all values */\n    func valueSet() -> [String] {\n        buckets.compactMap { $0?.val }\n    }\n\n    /* Print the hash table */\n    func print() {\n        for pair in pairSet() {\n            Swift.print(\"\\(pair.key) -> \\(pair.val)\")\n        }\n    }\n}\n
    array_hash_map.js
    /* Key-value pair: Number -> String */\nclass Pair {\n    constructor(key, val) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    #buckets;\n    constructor() {\n        // Initialize an array containing 100 buckets\n        this.#buckets = new Array(100).fill(null);\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % 100;\n    }\n\n    /* Lookup operation */\n    get(key) {\n        let index = this.#hashFunc(key);\n        let pair = this.#buckets[index];\n        if (pair === null) return null;\n        return pair.val;\n    }\n\n    /* Insert operation */\n    set(key, val) {\n        let index = this.#hashFunc(key);\n        this.#buckets[index] = new Pair(key, val);\n    }\n\n    /* Remove operation */\n    delete(key) {\n        let index = this.#hashFunc(key);\n        // Set the bucket to null to mark it as deleted\n        this.#buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    entries() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i]);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all keys */\n    keys() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i].key);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all values */\n    values() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i].val);\n            }\n        }\n        return arr;\n    }\n\n    /* Print the hash table */\n    print() {\n        let pairSet = this.entries();\n        for (const pair of pairSet) {\n            console.info(`${pair.key} -> ${pair.val}`);\n        }\n    }\n}\n
    array_hash_map.ts
    /* Key-value pair: Number -> String */\nclass Pair {\n    public key: number;\n    public val: string;\n\n    constructor(key: number, val: string) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    private readonly buckets: (Pair | null)[];\n\n    constructor() {\n        // Initialize an array containing 100 buckets\n        this.buckets = new Array(100).fill(null);\n    }\n\n    /* Hash function */\n    private hashFunc(key: number): number {\n        return key % 100;\n    }\n\n    /* Lookup operation */\n    public get(key: number): string | null {\n        let index = this.hashFunc(key);\n        let pair = this.buckets[index];\n        if (pair === null) return null;\n        return pair.val;\n    }\n\n    /* Insert operation */\n    public set(key: number, val: string) {\n        let index = this.hashFunc(key);\n        this.buckets[index] = new Pair(key, val);\n    }\n\n    /* Remove operation */\n    public delete(key: number) {\n        let index = this.hashFunc(key);\n        // Set the bucket to null to mark it as deleted\n        this.buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    public entries(): (Pair | null)[] {\n        let arr: (Pair | null)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i]);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all keys */\n    public keys(): (number | undefined)[] {\n        let arr: (number | undefined)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i].key);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all values */\n    public values(): (string | undefined)[] {\n        let arr: (string | undefined)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i].val);\n            }\n        }\n        return arr;\n    }\n\n    /* Print the hash table */\n    public print() {\n        let pairSet = this.entries();\n        for (const pair of pairSet) {\n            console.info(`${pair.key} -> ${pair.val}`);\n        }\n    }\n}\n
    array_hash_map.dart
    /* Key-value pair */\nclass Pair {\n  int key;\n  String val;\n  Pair(this.key, this.val);\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n  late List<Pair?> _buckets;\n\n  ArrayHashMap() {\n    // Initialize an array containing 100 buckets\n    _buckets = List.filled(100, null);\n  }\n\n  /* Hash function */\n  int _hashFunc(int key) {\n    final int index = key % 100;\n    return index;\n  }\n\n  /* Lookup operation */\n  String? get(int key) {\n    final int index = _hashFunc(key);\n    final Pair? pair = _buckets[index];\n    if (pair == null) {\n      return null;\n    }\n    return pair.val;\n  }\n\n  /* Insert operation */\n  void put(int key, String val) {\n    final Pair pair = Pair(key, val);\n    final int index = _hashFunc(key);\n    _buckets[index] = pair;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    final int index = _hashFunc(key);\n    _buckets[index] = null;\n  }\n\n  /* Get all key-value pairs */\n  List<Pair> pairSet() {\n    List<Pair> pairSet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        pairSet.add(pair);\n      }\n    }\n    return pairSet;\n  }\n\n  /* Get all keys */\n  List<int> keySet() {\n    List<int> keySet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        keySet.add(pair.key);\n      }\n    }\n    return keySet;\n  }\n\n  /* Get all values */\n  List<String> values() {\n    List<String> valueSet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        valueSet.add(pair.val);\n      }\n    }\n    return valueSet;\n  }\n\n  /* Print the hash table */\n  void printHashMap() {\n    for (final Pair kv in pairSet()) {\n      print(\"${kv.key} -> ${kv.val}\");\n    }\n  }\n}\n
    array_hash_map.rs
    /* Key-value pair */\n#[derive(Debug, Clone, PartialEq)]\npub struct Pair {\n    pub key: i32,\n    pub val: String,\n}\n\n/* Hash table implemented with an array */\npub struct ArrayHashMap {\n    buckets: Vec<Option<Pair>>,\n}\n\nimpl ArrayHashMap {\n    pub fn new() -> ArrayHashMap {\n        // Initialize an array containing 100 buckets\n        Self {\n            buckets: vec![None; 100],\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        key as usize % 100\n    }\n\n    /* Lookup operation */\n    pub fn get(&self, key: i32) -> Option<&String> {\n        let index = self.hash_func(key);\n        self.buckets[index].as_ref().map(|pair| &pair.val)\n    }\n\n    /* Insert operation */\n    pub fn put(&mut self, key: i32, val: &str) {\n        let index = self.hash_func(key);\n        self.buckets[index] = Some(Pair {\n            key,\n            val: val.to_string(),\n        });\n    }\n\n    /* Remove operation */\n    pub fn remove(&mut self, key: i32) {\n        let index = self.hash_func(key);\n        // Set to None to mark removal\n        self.buckets[index] = None;\n    }\n\n    /* Get all key-value pairs */\n    pub fn entry_set(&self) -> Vec<&Pair> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref())\n            .collect()\n    }\n\n    /* Get all keys */\n    pub fn key_set(&self) -> Vec<&i32> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref().map(|pair| &pair.key))\n            .collect()\n    }\n\n    /* Get all values */\n    pub fn value_set(&self) -> Vec<&String> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref().map(|pair| &pair.val))\n            .collect()\n    }\n\n    /* Print the hash table */\n    pub fn print(&self) {\n        for pair in self.entry_set() {\n            println!(\"{} -> {}\", pair.key, pair.val);\n        }\n    }\n}\n
    array_hash_map.c
    /* Key-value pair: int -> string */\ntypedef struct {\n    int key;\n    char *val;\n} Pair;\n\n/* Hash table implemented with an array */\ntypedef struct {\n    Pair *buckets[MAX_SIZE];\n} ArrayHashMap;\n\n/* Constructor */\nArrayHashMap *newArrayHashMap() {\n    ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap));\n    for (int i=0; i < MAX_SIZE; i++) {\n        hmap->buckets[i] = NULL;\n    }\n    return hmap;\n}\n\n/* Destructor */\nvoid delArrayHashMap(ArrayHashMap *hmap) {\n    for (int i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            free(hmap->buckets[i]->val);\n            free(hmap->buckets[i]);\n        }\n    }\n    free(hmap);\n}\n\n/* Insert operation */\nvoid put(ArrayHashMap *hmap, const int key, const char *val) {\n    Pair *Pair = malloc(sizeof(Pair));\n    Pair->key = key;\n    Pair->val = malloc(strlen(val) + 1);\n    strcpy(Pair->val, val);\n\n    int index = hashFunc(key);\n    hmap->buckets[index] = Pair;\n}\n\n/* Remove operation */\nvoid removeItem(ArrayHashMap *hmap, const int key) {\n    int index = hashFunc(key);\n    free(hmap->buckets[index]->val);\n    free(hmap->buckets[index]);\n    hmap->buckets[index] = NULL;\n}\n\n/* Get all key-value pairs */\nvoid pairSet(ArrayHashMap *hmap, MapSet *set) {\n    Pair *entries;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count stored key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    entries = malloc(sizeof(Pair) * total);\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            entries[index].key = hmap->buckets[i]->key;\n            entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1);\n            strcpy(entries[index].val, hmap->buckets[i]->val);\n            index++;\n        }\n    }\n    set->set = entries;\n    set->len = total;\n}\n\n/* Get all keys */\nvoid keySet(ArrayHashMap *hmap, MapSet *set) {\n    int *keys;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count stored key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    keys = malloc(total * sizeof(int));\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            keys[index] = hmap->buckets[i]->key;\n            index++;\n        }\n    }\n    set->set = keys;\n    set->len = total;\n}\n\n/* Get all values */\nvoid valueSet(ArrayHashMap *hmap, MapSet *set) {\n    char **vals;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count stored key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    vals = malloc(total * sizeof(char *));\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            vals[index] = hmap->buckets[i]->val;\n            index++;\n        }\n    }\n    set->set = vals;\n    set->len = total;\n}\n\n/* Print the hash table */\nvoid print(ArrayHashMap *hmap) {\n    int i;\n    MapSet set;\n    pairSet(hmap, &set);\n    Pair *entries = (Pair *)set.set;\n    for (i = 0; i < set.len; i++) {\n        printf(\"%d -> %s\\n\", entries[i].key, entries[i].val);\n    }\n    free(set.set);\n}\n
    array_hash_map.kt
    /* Key-value pair */\nclass Pair(\n    var key: Int,\n    var _val: String\n)\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    // Initialize an array containing 100 buckets\n    private val buckets = arrayOfNulls<Pair>(100)\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        val index = key % 100\n        return index\n    }\n\n    /* Lookup operation */\n    fun get(key: Int): String? {\n        val index = hashFunc(key)\n        val pair = buckets[index] ?: return null\n        return pair._val\n    }\n\n    /* Insert operation */\n    fun put(key: Int, _val: String) {\n        val pair = Pair(key, _val)\n        val index = hashFunc(key)\n        buckets[index] = pair\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        val index = hashFunc(key)\n        // Set the bucket to null to mark it as removed\n        buckets[index] = null\n    }\n\n    /* Get all key-value pairs */\n    fun pairSet(): MutableList<Pair> {\n        val pairSet = mutableListOf<Pair>()\n        for (pair in buckets) {\n            if (pair != null)\n                pairSet.add(pair)\n        }\n        return pairSet\n    }\n\n    /* Get all keys */\n    fun keySet(): MutableList<Int> {\n        val keySet = mutableListOf<Int>()\n        for (pair in buckets) {\n            if (pair != null)\n                keySet.add(pair.key)\n        }\n        return keySet\n    }\n\n    /* Get all values */\n    fun valueSet(): MutableList<String> {\n        val valueSet = mutableListOf<String>()\n        for (pair in buckets) {\n            if (pair != null)\n                valueSet.add(pair._val)\n        }\n        return valueSet\n    }\n\n    /* Print the hash table */\n    fun print() {\n        for (kv in pairSet()) {\n            val key = kv.key\n            val _val = kv._val\n            println(\"$key -> $_val\")\n        }\n    }\n}\n
    array_hash_map.rb
    ### Key-value pair ###\nclass Pair\n  attr_accessor :key, :val\n\n  def initialize(key, val)\n    @key = key\n    @val = val\n  end\nend\n\n### Hash table implemented with an array ###\nclass ArrayHashMap\n  ### Constructor ###\n  def initialize\n    # Initialize an array containing 100 buckets\n    @buckets = Array.new(100)\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    index = key % 100\n  end\n\n  ### Lookup operation ###\n  def get(key)\n    index = hash_func(key)\n    pair = @buckets[index]\n\n    return if pair.nil?\n    pair.val\n  end\n\n  ### Insert operation ###\n  def put(key, val)\n    pair = Pair.new(key, val)\n    index = hash_func(key)\n    @buckets[index] = pair\n  end\n\n  ### Remove operation ###\n  def remove(key)\n    index = hash_func(key)\n    # Set the bucket to nil to indicate removal\n    @buckets[index] = nil\n  end\n\n  ### Get all key-value pairs ###\n  def entry_set\n    result = []\n    @buckets.each { |pair| result << pair unless pair.nil? }\n    result\n  end\n\n  ### Get all keys ###\n  def key_set\n    result = []\n    @buckets.each { |pair| result << pair.key unless pair.nil? }\n    result\n  end\n\n  ### Get all values ###\n  def value_set\n    result = []\n    @buckets.each { |pair| result << pair.val unless pair.nil? }\n    result\n  end\n\n  ### Print the hash table ###\n  def print\n    @buckets.each { |pair| puts \"#{pair.key} -> #{pair.val}\" unless pair.nil? }\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 6.   Hash tables","6.1   Hash tables"],"tags":[]},{"location":"chapter_hashing/hash_map/#613-collisions-and-expansion","level":2,"title":"6.1.3   Collisions and expansion","text":"

    A hash function maps the input space of all possible keys into the output space of array indices. The input space is usually far larger, so multiple inputs must theoretically map to the same output.

    For the function above, keys with the same last two digits produce the same output. Looking up student IDs 12836 and 20336 gives:

    12836 % 100 = 36\n20336 % 100 = 36\n

    As shown below, both IDs point to the same name, which is clearly incorrect. This situation, where different inputs produce the same output, is called a hash collision.

    Figure 6-3   An example of a hash collision

    A larger table capacity \\(n\\) makes it less likely that several keys share a bucket, reducing collisions. Expanding the hash table can therefore reduce hash collisions.

    In the example below, (136, A) and (236, D) collide before expansion but no longer collide afterward.

    Figure 6-4   Expanding a hash table

    As with array expansion, expanding a hash table requires moving every pair into a new table, which is time-consuming. Because capacity changes, the hash function must also recalculate every pair's position, adding further computation. Programming languages therefore usually reserve enough capacity to avoid frequent expansion.

    The load factor is the number of elements divided by the number of buckets. It measures how crowded the table is and often serves as the trigger for expansion. For example, in Java, a load factor above \\(0.75\\) causes the capacity to grow to \\(2\\) times its previous size.

    ","path":["Chapter 6.   Hash tables","6.1   Hash tables"],"tags":[]},{"location":"chapter_hashing/summary/","level":1,"title":"6.4   Summary","text":"","path":["Chapter 6.   Hash tables","6.4   Summary"],"tags":[]},{"location":"chapter_hashing/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Given a key, a hash table can retrieve its value in \\(O(1)\\) time, making lookup highly efficient.
    • Common operations include lookup, adding and deleting key-value pairs, and traversing the table.
    • A hash function maps a key to an array index, allowing access to the corresponding bucket and its value.
    • Two different keys may map to the same array index and cause incorrect lookup results. This is called a hash collision.
    • A larger capacity lowers the likelihood of collisions, so expanding a hash table can alleviate them. Like array expansion, hash-table expansion is expensive.
    • The load factor is the number of elements divided by the number of buckets. It indicates how crowded the table is and is commonly used to trigger expansion.
    • Separate chaining replaces a single entry with a linked list and stores all colliding entries in that list. Long lists slow lookup; converting them to red-black trees can improve efficiency.
    • Open addressing resolves collisions through repeated probing. Linear probing uses a fixed step size, is prone to clustering, and does not allow straightforward deletion. Multiple hashing uses several hash functions, reducing clustering but adding computational cost.
    • Programming languages use different implementations: Java's HashMap uses separate chaining, while Python's dict uses open addressing.
    • For hash tables, we want deterministic, efficient hashing with uniformly distributed outputs. Cryptographic hashing also requires collision resistance and an avalanche effect.
    • Hash algorithms often use a large prime modulus to promote uniform hash values and reduce collisions.
    • Common hash algorithms include MD5, SHA-1, SHA-2, and SHA-3. MD5 is often used for file-integrity checks, while SHA-2 is widely used in security applications and protocols.
    • Programming languages usually provide built-in hashing for data types to calculate bucket indices. In general, only immutable objects are hashable.
    ","path":["Chapter 6.   Hash tables","6.4   Summary"],"tags":[]},{"location":"chapter_hashing/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: When does a hash table have \\(O(n)\\) time complexity?

    When collisions are severe, its time complexity degrades to \\(O(n)\\). With a well-designed hash function, suitable capacity, and evenly distributed collisions, it is \\(O(1)\\). We usually assume \\(O(1)\\) time when using a language's built-in hash table.

    Q: Why not use the hash function \\(f(x) = x\\)? Wouldn't that avoid collisions?

    With \\(f(x) = x\\), each element has a unique bucket index, making the structure equivalent to an array. But the input space is usually far larger than the output space, the array's length, so the final hashing step usually takes a remainder modulo that length. A hash table maps a larger state space into a smaller one while providing \\(O(1)\\) lookup.

    Q: Hash tables are implemented with arrays, linked lists, and binary trees. How can they be faster than those structures?

    First, greater time efficiency comes at the cost of space efficiency: a significant portion of a hash table's memory is unused.

    Second, the improvement applies only to particular operations. If an array or linked list can perform an operation with the same time complexity, it is usually faster because computing a hash adds overhead and increases the constant factor.

    Finally, hash-table performance can degrade. For example, separate chaining searches within linked lists or red-black trees, and there is still a risk of degeneration to \\(O(n)\\) time.

    Q: Does multiple hashing also prevent direct deletion? Can a deleted slot be reused?

    Multiple hashing is a form of open addressing, and all open-addressing methods require deletion markers rather than simply emptying a slot. A marked slot can be reused. When inserting a new element, if the hash functions locate such a slot, the new element can occupy it. This preserves the probe sequence while making effective use of space.

    Q: Why can a collision occur during a lookup with linear probing?

    The hash function leads to a bucket and a key-value pair, but the stored key may not match the requested one. That indicates a collision. Linear probing then continues with its predefined step size until it finds the correct pair or determines that none exists.

    Q: Why does expanding a hash table reduce collisions?

    The final hashing step often takes the remainder modulo the array length \\(n\\) to produce a valid index. After expansion, \\(n\\) changes, so the index of a given key may change too. Keys that previously shared a bucket may be redistributed among several buckets, reducing collisions.

    Q: If fast access is the goal, why not just use an array?

    When keys are consecutive integers in a small range, an array is simple and efficient. But for other key types, such as strings, we need a hash function to map keys to array indices and a bucket array to store entries. That structure is a hash table.

    ","path":["Chapter 6.   Hash tables","6.4   Summary"],"tags":[]},{"location":"chapter_heap/","level":1,"title":"Chapter 8.   Heaps","text":"

    Abstract

    Heaps resemble mountain ranges, rising in layers of varied shapes.

    The peaks stand at different heights, yet the highest always catches the eye first.

    ","path":["Chapter 8.   Heaps"],"tags":[]},{"location":"chapter_heap/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 8.1   Heaps
    • 8.2   Building a heap
    • 8.3   The top-k problem
    • 8.4   Summary
    • 8.5   Exercises
    ","path":["Chapter 8.   Heaps"],"tags":[]},{"location":"chapter_heap/build_heap/","level":1,"title":"8.2   Building a heap","text":"

    Sometimes we want to construct a heap from all the elements of a list. This process is called “building a heap.”

    ","path":["Chapter 8.   Heaps","8.2   Building a heap"],"tags":[]},{"location":"chapter_heap/build_heap/#821-building-through-insertion","level":2,"title":"8.2.1   Building through insertion","text":"

    First create an empty heap, then traverse the list and insert each element: append it to the end of the heap, then sift it upward.

    Each insertion increases the heap's length by one. Since nodes are added to the binary tree from the top down, this constructs the heap “top down.”

    For \\(n\\) elements, each insertion takes \\(O(\\log{n})\\) time, so this method takes \\(O(n \\log n)\\) overall.

    ","path":["Chapter 8.   Heaps","8.2   Building a heap"],"tags":[]},{"location":"chapter_heap/build_heap/#822-building-by-sifting-nodes-down","level":2,"title":"8.2.2   Building by sifting nodes down","text":"

    We can build a heap more efficiently in two steps.

    1. Copy all list elements into the heap without changing their order. The heap property may not yet hold.
    2. Traverse the heap in reverse level order, sifting down each non-leaf node.

    After a node is sifted down, the subtree rooted at that node is a valid heap. Since the traversal is reversed, the heap is built “bottom up.”

    Reverse traversal ensures that the subtrees below the current node are already valid heaps, which is necessary for sifting down the current node to work correctly.

    Leaf nodes have no children and are already valid heaps, so they need no sifting. As the code below shows, the last non-leaf node is the parent of the last node. Start there and traverse backward, sifting each node down:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def __init__(self, nums: list[int]):\n    \"\"\"Constructor: build a heap from the input list\"\"\"\n    # Place the list elements in the heap without changing their order\n    self.max_heap = nums\n    # Sift down from every non-leaf node\n    for i in range(self.parent(self.size() - 1), -1, -1):\n        self.sift_down(i)\n
    my_heap.cpp
    /* Constructor: build a heap from the input list */\nMaxHeap(vector<int> nums) {\n    // Place the list elements in the heap without changing their order\n    maxHeap = nums;\n    // Sift down from every non-leaf node\n    for (int i = parent(size() - 1); i >= 0; i--) {\n        siftDown(i);\n    }\n}\n
    my_heap.java
    /* Constructor: build a heap from the input list */\nMaxHeap(List<Integer> nums) {\n    // Place the list elements in the heap without changing their order\n    maxHeap = new ArrayList<>(nums);\n    // Sift down from every non-leaf node\n    for (int i = parent(size() - 1); i >= 0; i--) {\n        siftDown(i);\n    }\n}\n
    my_heap.cs
    /* Constructor: build a heap from an input list */\nMaxHeap(IEnumerable<int> nums) {\n    // Place the list elements in the heap without changing their order\n    maxHeap = new List<int>(nums);\n    // Sift down from every non-leaf node\n    var size = Parent(this.Size() - 1);\n    for (int i = size; i >= 0; i--) {\n        SiftDown(i);\n    }\n}\n
    my_heap.go
    /* Constructor: build a heap from a slice */\nfunc newMaxHeap(nums []any) *maxHeap {\n    // Place the list elements in the heap without changing their order\n    h := &maxHeap{data: nums}\n    for i := h.parent(len(h.data) - 1); i >= 0; i-- {\n        // Sift down from every non-leaf node\n        h.siftDown(i)\n    }\n    return h\n}\n
    my_heap.swift
    /* Constructor: build a heap from the input list */\ninit(nums: [Int]) {\n    // Place the list elements in the heap without changing their order\n    maxHeap = nums\n    // Sift down from every non-leaf node\n    for i in (0 ... parent(i: size() - 1)).reversed() {\n        siftDown(i: i)\n    }\n}\n
    my_heap.js
    /* Constructor: create an empty heap or build a heap from the input list */\nconstructor(nums) {\n    // Place the list elements in the heap without changing their order\n    this.#maxHeap = nums === undefined ? [] : [...nums];\n    // Sift down from every non-leaf node\n    for (let i = this.#parent(this.size() - 1); i >= 0; i--) {\n        this.#siftDown(i);\n    }\n}\n
    my_heap.ts
    /* Constructor: create an empty heap or build a heap from the input list */\nconstructor(nums?: number[]) {\n    // Place the list elements in the heap without changing their order\n    this.maxHeap = nums === undefined ? [] : [...nums];\n    // Sift down from every non-leaf node\n    for (let i = this.parent(this.size() - 1); i >= 0; i--) {\n        this.siftDown(i);\n    }\n}\n
    my_heap.dart
    /* Constructor: build a heap from the input list */\nMaxHeap(List<int> nums) {\n  // Place the list elements in the heap without changing their order\n  _maxHeap = nums;\n  // Sift down from every non-leaf node\n  for (int i = _parent(size() - 1); i >= 0; i--) {\n    siftDown(i);\n  }\n}\n
    my_heap.rs
    /* Constructor: build a heap from the input list */\nfn new(nums: Vec<i32>) -> Self {\n    // Place the list elements in the heap without changing their order\n    let mut heap = MaxHeap { max_heap: nums };\n    // Sift down from every non-leaf node\n    for i in (0..=Self::parent(heap.size() - 1)).rev() {\n        heap.sift_down(i);\n    }\n    heap\n}\n
    my_heap.c
    /* Constructor: build a heap from the input array */\nMaxHeap *newMaxHeap(int nums[], int size) {\n    // Place all elements in the heap\n    MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap));\n    maxHeap->size = size;\n    memcpy(maxHeap->data, nums, size * sizeof(int));\n    for (int i = parent(maxHeap, size - 1); i >= 0; i--) {\n        // Sift down from every non-leaf node\n        siftDown(maxHeap, i);\n    }\n    return maxHeap;\n}\n
    my_heap.kt
    /* Max heap */\nclass MaxHeap(nums: MutableList<Int>?) {\n    // Use a resizable list instead of an array to avoid managing capacity growth\n    private val maxHeap = mutableListOf<Int>()\n\n    /* Constructor: build a heap from the input list */\n    init {\n        // Place the list elements in the heap without changing their order\n        maxHeap.addAll(nums!!)\n        // Sift down from every non-leaf node\n        for (i in parent(size() - 1) downTo 0) {\n            siftDown(i)\n        }\n    }\n\n    /* Get the left child index */\n    private fun left(i: Int): Int {\n        return 2 * i + 1\n    }\n\n    /* Get the right child index */\n    private fun right(i: Int): Int {\n        return 2 * i + 2\n    }\n\n    /* Get the parent index */\n    private fun parent(i: Int): Int {\n        return (i - 1) / 2 // Integer division (truncates toward zero in Kotlin)\n    }\n\n    /* Swap elements */\n    private fun swap(i: Int, j: Int) {\n        val temp = maxHeap[i]\n        maxHeap[i] = maxHeap[j]\n        maxHeap[j] = temp\n    }\n\n    /* Get the heap size */\n    fun size(): Int {\n        return maxHeap.size\n    }\n\n    /* Check whether the heap is empty */\n    fun isEmpty(): Boolean {\n        /* Check whether the heap is empty */\n        return size() == 0\n    }\n\n    /* Access the top element of the heap */\n    fun peek(): Int {\n        return maxHeap[0]\n    }\n\n    /* Push an element onto the heap */\n    fun push(_val: Int) {\n        // Add a node\n        maxHeap.add(_val)\n        // Sift up\n        siftUp(size() - 1)\n    }\n\n    /* Sift up starting from node i */\n    private fun siftUp(it: Int) {\n        // Kotlin function parameters are immutable, so create a temporary variable\n        var i = it\n        while (true) {\n            // Get the parent of node i\n            val p = parent(i)\n            // Stop sifting when the root has been passed or the node needs no repair\n            if (p < 0 || maxHeap[i] <= maxHeap[p]) break\n            // Swap the two nodes\n            swap(i, p)\n            // Continue sifting up\n            i = p\n        }\n    }\n\n    /* Pop an element from the heap */\n    fun pop(): Int {\n        // Handle an empty heap\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n        swap(0, size() - 1)\n        // Remove a node\n        val _val = maxHeap.removeAt(size() - 1)\n        // Sift down\n        siftDown(0)\n        // Return the top element of the heap\n        return _val\n    }\n\n    /* Sift down starting from node i */\n    private fun siftDown(it: Int) {\n        // Kotlin function parameters are immutable, so create a temporary variable\n        var i = it\n        while (true) {\n            // Find the node with the largest value among i, l, and r, and store its index in ma\n            val l = left(i)\n            val r = right(i)\n            var ma = i\n            if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l\n            if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r\n            // Stop if node i is largest or both child indices l and r are out of bounds\n            if (ma == i) break\n            // Swap the two nodes\n            swap(i, ma)\n            // Continue sifting down\n            i = ma\n        }\n    }\n\n    /* Print the heap (binary tree) */\n    fun print() {\n        val queue = PriorityQueue { a: Int, b: Int -> b - a }\n        queue.addAll(maxHeap)\n        printHeap(queue)\n    }\n}\n
    my_heap.rb
    ### Constructor: build a heap from the input list ###\ndef initialize(nums)\n  # Place the list elements in the heap without changing their order\n  @max_heap = nums\n  # Sift down from every non-leaf node\n  parent(size - 1).downto(0) do |i|\n    sift_down(i)\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.2   Building a heap"],"tags":[]},{"location":"chapter_heap/build_heap/#823-complexity-analysis","level":2,"title":"8.2.3   Complexity analysis","text":"

    Let us derive the time complexity of the second method.

    • A complete binary tree with \\(n\\) nodes has \\((n + 1) / 2\\) leaves, where \\(/\\) denotes integer division rounded down. Therefore, \\(n / 2\\) nodes need sifting.
    • A node can move at most as far as a leaf during sifting, so the maximum number of iterations is the tree height, \\(\\log n\\).

    Multiplying these gives a time complexity of \\(O(n \\log n)\\). However, this estimate is not tight, because it ignores the fact that there are far more nodes near the bottom of the tree than near the top.

    For a more accurate calculation, assume a perfect binary tree with \\(n\\) nodes and height \\(h\\). This simplifies the calculation without affecting the validity of the result.

    Figure 8-5   Number of nodes at each level of a perfect binary tree

    As shown above, the maximum number of sift-down iterations for a node equals its distance to a leaf, which is its height. Summing “number of nodes \\(\\times\\) node height” over all levels therefore gives the total number of sifting iterations across all nodes.

    \\[ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{(h-1)}\\times1 \\]

    To simplify this expression, use a standard series technique. First multiply \\(T(h)\\) by \\(2\\):

    \\[ \\begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{h-1}\\times1 \\newline 2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \\dots + 2^{h}\\times1 \\newline \\end{aligned} \\]

    Subtract the first expression, \\(T(h)\\), from the shifted expression, \\(2 T(h)\\):

    \\[ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \\dots + 2^{h-1} + 2^h \\]

    The powers of two in \\(T(h)\\) form a geometric series. Applying the sum formula gives:

    \\[ \\begin{aligned} T(h) & = 2 \\frac{1 - 2^h}{1 - 2} - h \\newline & = 2^{h+1} - h - 2 \\newline & = O(2^h) \\end{aligned} \\]

    A perfect binary tree of height \\(h\\) has \\(n = 2^{h+1} - 1\\) nodes, so \\(O(2^h) = O(n)\\). This shows that building a heap from an input list takes \\(O(n)\\) time and is highly efficient.

    ","path":["Chapter 8.   Heaps","8.2   Building a heap"],"tags":[]},{"location":"chapter_heap/exercises/","level":1,"title":"8.5   Exercises","text":"","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#851-check-your-understanding","level":2,"title":"8.5.1   Check your understanding","text":"","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#1-restoring-the-heap-after-inserting-10","level":3,"title":"1.   Restoring the heap after inserting 10","text":"

    The array [9, 7, 8, 3, 5] represents a max heap. Insert the number 10.

    1. After appending 10 to the array, what is its parent's value?
    2. Sift up from the new node. Write the array after each swap.
    3. What is the final root element? How many swaps were made?
    Reference answer
    1. The appended 10 has index 5, so its parent's index is \\(\\lfloor(5-1)/2\\rfloor=2\\), and its parent's value is 8.

    2. Since 10 is greater than 8, the first swap produces [9, 7, 10, 3, 5, 8]. Since 10 is also greater than its new parent, 9, the second swap produces [10, 7, 9, 3, 5, 8]. The value 10 has now reached the root, so sifting stops.

    3. The final root is 10, and there were two swaps.

    ","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#2-checking-parent-child-relationships-in-a-min-heap","level":3,"title":"2.   Checking parent-child relationships in a min heap","text":"

    The array [1, 4, 3, 7, 6, 2] represents a complete binary tree. A min heap requires each parent to be no greater than its children. For index \\(i\\), the left and right child indices are \\(2i+1\\) and \\(2i+2\\), respectively.

    1. What are the indices and values of the children of index 2?
    2. The node at index 2 has value 3. Does its relationship with its children violate the min-heap property? If so, which two elements should be swapped?
    3. Based on your answer to question 2, write the array after the swap if one is needed, or explain why no swap is necessary. Finally, check the other parent-child relationships.
    Reference answer
    1. The left child of index 2 is at index 5 and has value 2. Its right child would be at index 6, but the array has length 6, so that child does not exist.

    2. The parent value 3 exceeds the child value 2, violating the min-heap property. Swap the elements at indices 2 and 5.

    3. The resulting array is [1, 4, 2, 7, 6, 3]. Checking each relationship gives: 1 ≤ 4, 1 ≤ 2; 4 ≤ 7, 4 ≤ 6; 2 ≤ 3. Every parent is no greater than its children, so the min-heap property now holds.

    ","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#3-keeping-the-three-largest-numbers-with-a-min-heap","level":3,"title":"3.   Keeping the three largest numbers with a min heap","text":"

    To keep the three largest numbers from the stream [4, 1, 7, 3, 8], maintain a min heap of at most three elements.

    Insert the first three numbers in order. Once the heap is full, for each new number: if it is greater than the root, remove the root and insert the new number; otherwise, leave the heap unchanged.

    After reading each number, write the numbers retained in the heap and the root value. You need only give the retained numbers as a set, not their arrangement in the heap array.

    Reference answer

    The results after each number are:

    Number read Retained numbers Root 4 {4} 4 1 {1, 4} 1 7 {1, 4, 7} 1 3 {3, 4, 7} 3 8 {4, 7, 8} 4

    Once the heap is full, its root is the smallest retained number. Replace the root with the new number only when the new number is larger. The final set {4, 7, 8} contains exactly the three largest numbers.

    ","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#852-programming-exercises","level":2,"title":"8.5.2   Programming exercises","text":"","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#1-finding-the-kth-largest-element-in-an-array","level":3,"title":"1.   Finding the kth largest element in an array","text":"

    Given an integer array nums and an integer \\(k\\), where \\(1 \\le k \\le n\\) and \\(n\\) is the array length, return the element in position \\(k\\) when the array is ordered from largest to smallest.

    Count duplicate elements separately. For example, the second largest element of [5, 5, 2] is still 5. Use a min heap of at most \\(k\\) elements.

    Hints
    1. The kth largest element is the smallest of the k largest numbers.
    2. Insert each number into the min heap; if its size exceeds k, remove the minimum.
    3. When the traversal ends, the heap retains the k largest numbers, and its root is the answer.

    LeetCode

    ","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/heap/","level":1,"title":"8.1   Heaps","text":"

    A heap is a complete binary tree that satisfies a particular ordering condition. There are two main types, shown below.

    • Min heap: every node's value is \\(\\leq\\) the values of its children.
    • Max heap: every node's value is \\(\\geq\\) the values of its children.

    Figure 8-1   A min heap and a max heap

    As a special kind of complete binary tree, a heap has the following properties.

    • The lowest level is filled from left to right, and all other levels are full.
    • The root is the “top” of the heap, while the rightmost node on the lowest level is its “last” node.
    • In a max heap or min heap, the root holds the largest or smallest value, respectively.
    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#811-common-heap-operations","level":2,"title":"8.1.1   Common heap operations","text":"

    Many programming languages provide a priority queue, an abstract data structure whose elements are removed according to priority.

    Heaps are commonly used to implement priority queues. A max heap corresponds to a priority queue that removes elements from largest to smallest. From the user's perspective, the two can be treated as equivalent. This book therefore does not distinguish them strictly and uses the term “heap” for both.

    Table 8-1 lists common operations. Method names depend on the programming language.

    Table 8-1   Efficiency of heap operations

    Method Description Time complexity push() Insert an element \\(O(\\log n)\\) pop() Remove the root element \\(O(\\log n)\\) peek() Access the root: the maximum in a max heap or minimum in a min heap \\(O(1)\\) size() Get the number of elements \\(O(1)\\) isEmpty() Check whether the heap is empty \\(O(1)\\)

    In practice, use the heap or priority-queue class provided by the language.

    Just as a sorting algorithm can order values in ascending or descending order, we can switch between min heaps and max heaps by setting a flag or changing the Comparator:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap.py
    # Initialize a min heap\nmin_heap, flag = [], 1\n# Initialize a max heap\nmax_heap, flag = [], -1\n\n# Python's heapq module implements a min heap by default\n# Negate elements before insertion to reverse their order and implement a max heap\n# Here, flag = 1 gives a min heap and flag = -1 gives a max heap\n\n# Insert elements into the heap\nheapq.heappush(max_heap, flag * 1)\nheapq.heappush(max_heap, flag * 3)\nheapq.heappush(max_heap, flag * 2)\nheapq.heappush(max_heap, flag * 5)\nheapq.heappush(max_heap, flag * 4)\n\n# Access the root element\npeek: int = flag * max_heap[0] # 5\n\n# Remove root elements\n# Removed elements form a descending sequence\nval = flag * heapq.heappop(max_heap) # 5\nval = flag * heapq.heappop(max_heap) # 4\nval = flag * heapq.heappop(max_heap) # 3\nval = flag * heapq.heappop(max_heap) # 2\nval = flag * heapq.heappop(max_heap) # 1\n\n# Get the heap size\nsize: int = len(max_heap)\n\n# Check whether the heap is empty\nis_empty: bool = not max_heap\n\n# Build a heap from a list\nmin_heap: list[int] = [1, 3, 2, 5, 4]\nheapq.heapify(min_heap)\n
    heap.cpp
    /* Initialize a heap */\n// Initialize a min heap\npriority_queue<int, vector<int>, greater<int>> minHeap;\n// Initialize a max heap\npriority_queue<int, vector<int>, less<int>> maxHeap;\n\n/* Insert elements into the heap */\nmaxHeap.push(1);\nmaxHeap.push(3);\nmaxHeap.push(2);\nmaxHeap.push(5);\nmaxHeap.push(4);\n\n/* Access the root element */\nint peek = maxHeap.top(); // 5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\nmaxHeap.pop(); // 5\nmaxHeap.pop(); // 4\nmaxHeap.pop(); // 3\nmaxHeap.pop(); // 2\nmaxHeap.pop(); // 1\n\n/* Get the heap size */\nint size = maxHeap.size();\n\n/* Check whether the heap is empty */\nbool isEmpty = maxHeap.empty();\n\n/* Build a heap from a list */\nvector<int> input{1, 3, 2, 5, 4};\npriority_queue<int, vector<int>, greater<int>> minHeap(input.begin(), input.end());\n
    heap.java
    /* Initialize a heap */\n// Initialize a min heap\nQueue<Integer> minHeap = new PriorityQueue<>();\n// Initialize a max heap by changing the Comparator with a lambda expression\nQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);\n\n/* Insert elements into the heap */\nmaxHeap.offer(1);\nmaxHeap.offer(3);\nmaxHeap.offer(2);\nmaxHeap.offer(5);\nmaxHeap.offer(4);\n\n/* Access the root element */\nint peek = maxHeap.peek(); // 5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\npeek = maxHeap.poll(); // 5\npeek = maxHeap.poll(); // 4\npeek = maxHeap.poll(); // 3\npeek = maxHeap.poll(); // 2\npeek = maxHeap.poll(); // 1\n\n/* Get the heap size */\nint size = maxHeap.size();\n\n/* Check whether the heap is empty */\nboolean isEmpty = maxHeap.isEmpty();\n\n/* Build a heap from a list */\nminHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));\n
    heap.cs
    /* Initialize a heap */\n// Initialize a min heap\nPriorityQueue<int, int> minHeap = new();\n// Initialize a max heap by changing the Comparer with a lambda expression\nPriorityQueue<int, int> maxHeap = new(Comparer<int>.Create((x, y) => y.CompareTo(x)));\n\n/* Insert elements into the heap */\nmaxHeap.Enqueue(1, 1);\nmaxHeap.Enqueue(3, 3);\nmaxHeap.Enqueue(2, 2);\nmaxHeap.Enqueue(5, 5);\nmaxHeap.Enqueue(4, 4);\n\n/* Access the root element */\nint peek = maxHeap.Peek();//5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\npeek = maxHeap.Dequeue();  // 5\npeek = maxHeap.Dequeue();  // 4\npeek = maxHeap.Dequeue();  // 3\npeek = maxHeap.Dequeue();  // 2\npeek = maxHeap.Dequeue();  // 1\n\n/* Get the heap size */\nint size = maxHeap.Count;\n\n/* Check whether the heap is empty */\nbool isEmpty = maxHeap.Count == 0;\n\n/* Build a heap from a list */\nminHeap = new PriorityQueue<int, int>([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]);\n
    heap.go
    // In Go, implement heap.Interface to build an integer max heap\n// Implementing heap.Interface also requires implementing sort.Interface\ntype intHeap []any\n\n// Push implements the heap.Interface method for inserting an element\nfunc (h *intHeap) Push(x any) {\n    // Push and Pop use pointer receivers\n    // because they change the slice length as well as its contents.\n    *h = append(*h, x.(int))\n}\n\n// Pop implements the heap.Interface method used to remove the root element\nfunc (h *intHeap) Pop() any {\n    // The element to remove has been placed at the end\n    last := (*h)[len(*h)-1]\n    *h = (*h)[:len(*h)-1]\n    return last\n}\n\n// Len implements a sort.Interface method\nfunc (h *intHeap) Len() int {\n    return len(*h)\n}\n\n// Less implements a sort.Interface method\nfunc (h *intHeap) Less(i, j int) bool {\n    // For a min heap, change this to a less-than comparison\n    return (*h)[i].(int) > (*h)[j].(int)\n}\n\n// Swap implements a sort.Interface method\nfunc (h *intHeap) Swap(i, j int) {\n    (*h)[i], (*h)[j] = (*h)[j], (*h)[i]\n}\n\n// Top returns the root element\nfunc (h *intHeap) Top() any {\n    return (*h)[0]\n}\n\n/* Driver Code */\nfunc TestHeap(t *testing.T) {\n    /* Initialize a heap */\n    // Initialize a max heap\n    maxHeap := &intHeap{}\n    heap.Init(maxHeap)\n    /* Insert elements into the heap */\n    // Call the heap.Interface methods to add elements\n    heap.Push(maxHeap, 1)\n    heap.Push(maxHeap, 3)\n    heap.Push(maxHeap, 2)\n    heap.Push(maxHeap, 4)\n    heap.Push(maxHeap, 5)\n\n    /* Access the root element */\n    top := maxHeap.Top()\n    fmt.Printf(\"The root element is %d\\n\", top)\n\n    /* Remove root elements */\n    // Call the heap.Interface methods to remove elements\n    heap.Pop(maxHeap) // 5\n    heap.Pop(maxHeap) // 4\n    heap.Pop(maxHeap) // 3\n    heap.Pop(maxHeap) // 2\n    heap.Pop(maxHeap) // 1\n\n    /* Get the heap size */\n    size := len(*maxHeap)\n    fmt.Printf(\"The heap contains %d elements\\n\", size)\n\n    /* Check whether the heap is empty */\n    isEmpty := len(*maxHeap) == 0\n    fmt.Printf(\"Is the heap empty: %t\\n\", isEmpty)\n}\n
    heap.swift
    /* Initialize a heap */\n// Swift's Heap type supports both minimum and maximum operations and requires swift-collections\nvar heap = Heap<Int>()\n\n/* Insert elements into the heap */\nheap.insert(1)\nheap.insert(3)\nheap.insert(2)\nheap.insert(5)\nheap.insert(4)\n\n/* Access the root element */\nvar peek = heap.max()!\n\n/* Remove root elements */\npeek = heap.removeMax() // 5\npeek = heap.removeMax() // 4\npeek = heap.removeMax() // 3\npeek = heap.removeMax() // 2\npeek = heap.removeMax() // 1\n\n/* Get the heap size */\nlet size = heap.count\n\n/* Check whether the heap is empty */\nlet isEmpty = heap.isEmpty\n\n/* Build a heap from a list */\nlet heap2 = Heap([1, 3, 2, 5, 4])\n
    heap.js
    // JavaScript provides no built-in Heap class\n
    heap.ts
    // TypeScript provides no built-in Heap class\n
    heap.dart
    // Dart provides no built-in Heap class\n
    heap.rs
    use std::collections::BinaryHeap;\nuse std::cmp::Reverse;\n\n/* Initialize a heap */\n// Initialize a min heap\nlet mut min_heap = BinaryHeap::<Reverse<i32>>::new();\n// Initialize a max heap\nlet mut max_heap = BinaryHeap::new();\n\n/* Insert elements into the heap */\nmax_heap.push(1);\nmax_heap.push(3);\nmax_heap.push(2);\nmax_heap.push(5);\nmax_heap.push(4);\n\n/* Access the root element */\nlet peek = max_heap.peek().unwrap();  // 5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\nlet peek = max_heap.pop().unwrap();   // 5\nlet peek = max_heap.pop().unwrap();   // 4\nlet peek = max_heap.pop().unwrap();   // 3\nlet peek = max_heap.pop().unwrap();   // 2\nlet peek = max_heap.pop().unwrap();   // 1\n\n/* Get the heap size */\nlet size = max_heap.len();\n\n/* Check whether the heap is empty */\nlet is_empty = max_heap.is_empty();\n\n/* Build a heap from a list */\nlet min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]);\n
    heap.c
    // C provides no built-in Heap class\n
    heap.kt
    /* Initialize a heap */\n// Initialize a min heap\nvar minHeap = PriorityQueue<Int>()\n// Initialize a max heap by changing the Comparator with a lambda expression\nval maxHeap = PriorityQueue { a: Int, b: Int -> b - a }\n\n/* Insert elements into the heap */\nmaxHeap.offer(1)\nmaxHeap.offer(3)\nmaxHeap.offer(2)\nmaxHeap.offer(5)\nmaxHeap.offer(4)\n\n/* Access the root element */\nvar peek = maxHeap.peek() // 5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\npeek = maxHeap.poll() // 5\npeek = maxHeap.poll() // 4\npeek = maxHeap.poll() // 3\npeek = maxHeap.poll() // 2\npeek = maxHeap.poll() // 1\n\n/* Get the heap size */\nval size = maxHeap.size\n\n/* Check whether the heap is empty */\nval isEmpty = maxHeap.isEmpty()\n\n/* Build a heap from a list */\nminHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))\n
    heap.rb
    # Ruby provides no built-in Heap class\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#812-implementing-a-heap","level":2,"title":"8.1.2   Implementing a heap","text":"

    The following implementation is a max heap. To turn it into a min heap, reverse every ordering comparison, for example replacing \\(\\geq\\) with \\(\\leq\\). Interested readers can try this themselves.

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#1-storage-and-representation","level":3,"title":"1.   Storage and representation","text":"

    The binary-tree chapter showed that complete binary trees are well suited to array representation. Since a heap is a complete binary tree, we will store it in an array.

    In an array representation, elements store node values and indices identify positions in the tree. Index-mapping formulas take the place of node pointers.

    As shown below, a node at index \\(i\\) has its left child at \\(2i + 1\\), its right child at \\(2i + 2\\), and its parent at \\((i - 1) / 2\\), rounded down. An out-of-bounds index represents a missing node.

    Figure 8-2   Representing and storing a heap

    Wrap the index formulas in functions for convenient use:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def left(self, i: int) -> int:\n    \"\"\"Get the left child index\"\"\"\n    return 2 * i + 1\n\ndef right(self, i: int) -> int:\n    \"\"\"Get the right child index\"\"\"\n    return 2 * i + 2\n\ndef parent(self, i: int) -> int:\n    \"\"\"Get the parent index\"\"\"\n    return (i - 1) // 2  # Floor division\n
    my_heap.cpp
    /* Get the left child index */\nint left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nint right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nint parent(int i) {\n    return (i - 1) / 2; // Integer division truncates toward zero\n}\n
    my_heap.java
    /* Get the left child index */\nint left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nint right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nint parent(int i) {\n    return (i - 1) / 2; // Integer division (truncates toward zero in Java)\n}\n
    my_heap.cs
    /* Get the left child index */\nint Left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nint Right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nint Parent(int i) {\n    return (i - 1) / 2; // Integer division (truncates toward zero in C#)\n}\n
    my_heap.go
    /* Get the left child index */\nfunc (h *maxHeap) left(i int) int {\n    return 2*i + 1\n}\n\n/* Get the right child index */\nfunc (h *maxHeap) right(i int) int {\n    return 2*i + 2\n}\n\n/* Get the parent index */\nfunc (h *maxHeap) parent(i int) int {\n    // Integer division (truncates toward zero in Go)\n    return (i - 1) / 2\n}\n
    my_heap.swift
    /* Get the left child index */\nfunc left(i: Int) -> Int {\n    2 * i + 1\n}\n\n/* Get the right child index */\nfunc right(i: Int) -> Int {\n    2 * i + 2\n}\n\n/* Get the parent index */\nfunc parent(i: Int) -> Int {\n    (i - 1) / 2 // Integer division truncates toward zero\n}\n
    my_heap.js
    /* Get the left child index */\n#left(i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\n#right(i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\n#parent(i) {\n    return Math.floor((i - 1) / 2); // Floor division\n}\n
    my_heap.ts
    /* Get the left child index */\nleft(i: number): number {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nright(i: number): number {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nparent(i: number): number {\n    return Math.floor((i - 1) / 2); // Floor division\n}\n
    my_heap.dart
    /* Get the left child index */\nint _left(int i) {\n  return 2 * i + 1;\n}\n\n/* Get the right child index */\nint _right(int i) {\n  return 2 * i + 2;\n}\n\n/* Get the parent index */\nint _parent(int i) {\n  return (i - 1) ~/ 2; // Integer division\n}\n
    my_heap.rs
    /* Get the left child index */\nfn left(i: usize) -> usize {\n    2 * i + 1\n}\n\n/* Get the right child index */\nfn right(i: usize) -> usize {\n    2 * i + 2\n}\n\n/* Get the parent index */\nfn parent(i: usize) -> usize {\n    (i - 1) / 2 // Floor division\n}\n
    my_heap.c
    /* Get the left child index */\nint left(MaxHeap *maxHeap, int i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nint right(MaxHeap *maxHeap, int i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nint parent(MaxHeap *maxHeap, int i) {\n    return (i - 1) / 2; // Integer division truncates toward zero\n}\n
    my_heap.kt
    /* Get the left child index */\nfun left(i: Int): Int {\n    return 2 * i + 1\n}\n\n/* Get the right child index */\nfun right(i: Int): Int {\n    return 2 * i + 2\n}\n\n/* Get the parent index */\nfun parent(i: Int): Int {\n    return (i - 1) / 2 // Integer division (truncates toward zero in Kotlin)\n}\n
    my_heap.rb
    ### Get the left child index ###\ndef left(i)\n  2 * i + 1\nend\n\n### Get the right child index ###\ndef right(i)\n  2 * i + 2\nend\n\n### Get the parent index ###\ndef parent(i)\n  (i - 1) / 2     # Floor division\nend\n
    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#2-accessing-the-root","level":3,"title":"2.   Accessing the root","text":"

    The heap's root is the first element of the list:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def peek(self) -> int:\n    \"\"\"Access the top element of the heap\"\"\"\n    return self.max_heap[0]\n
    my_heap.cpp
    /* Access the top element of the heap */\nint peek() {\n    return maxHeap[0];\n}\n
    my_heap.java
    /* Access the top element of the heap */\nint peek() {\n    return maxHeap.get(0);\n}\n
    my_heap.cs
    /* Access the top element of the heap */\nint Peek() {\n    return maxHeap[0];\n}\n
    my_heap.go
    /* Access the top element of the heap */\nfunc (h *maxHeap) peek() any {\n    return h.data[0]\n}\n
    my_heap.swift
    /* Access the top element of the heap */\nfunc peek() -> Int {\n    maxHeap[0]\n}\n
    my_heap.js
    /* Access the top element of the heap */\npeek() {\n    return this.#maxHeap[0];\n}\n
    my_heap.ts
    /* Access the top element of the heap */\npeek(): number {\n    return this.maxHeap[0];\n}\n
    my_heap.dart
    /* Access the top element of the heap */\nint peek() {\n  return _maxHeap[0];\n}\n
    my_heap.rs
    /* Access the top element of the heap */\nfn peek(&self) -> Option<i32> {\n    self.max_heap.first().copied()\n}\n
    my_heap.c
    /* Access the top element of the heap */\nint peek(MaxHeap *maxHeap) {\n    return maxHeap->data[0];\n}\n
    my_heap.kt
    /* Access the top element of the heap */\nfun peek(): Int {\n    return maxHeap[0]\n}\n
    my_heap.rb
    ### Access the top element of the heap ###\ndef peek\n  @max_heap[0]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#3-inserting-an-element","level":3,"title":"3.   Inserting an element","text":"

    To insert val, first append it at the end of the heap. Since it may be larger than other elements, this can violate the heap property. We must restore the property along the path from the inserted node to the root, an operation called heapifying.

    Start at the inserted node and sift upward. As shown below, compare it with its parent and swap them if the inserted value is larger. Repeat upward until the root is reached or no swap is needed.

    <1><2><3><4><5><6><7><8><9>

    Figure 8-3   Steps for inserting into a heap

    With \\(n\\) nodes, the tree height is \\(O(\\log n)\\), so sifting takes at most \\(O(\\log n)\\) iterations. Insertion therefore takes \\(O(\\log n)\\) time. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def push(self, val: int):\n    \"\"\"Push an element onto the heap\"\"\"\n    # Add a node\n    self.max_heap.append(val)\n    # Sift up\n    self.sift_up(self.size() - 1)\n\ndef sift_up(self, i: int):\n    \"\"\"Sift up starting from node i\"\"\"\n    while True:\n        # Get the parent of node i\n        p = self.parent(i)\n        # Stop sifting when the root has been passed or the node needs no repair\n        if p < 0 or self.max_heap[i] <= self.max_heap[p]:\n            break\n        # Swap the two nodes\n        self.swap(i, p)\n        # Continue sifting up\n        i = p\n
    my_heap.cpp
    /* Push an element onto the heap */\nvoid push(int val) {\n    // Add a node\n    maxHeap.push_back(val);\n    // Sift up\n    siftUp(size() - 1);\n}\n\n/* Sift up starting from node i */\nvoid siftUp(int i) {\n    while (true) {\n        // Get the parent of node i\n        int p = parent(i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || maxHeap[i] <= maxHeap[p])\n            break;\n        // Swap the two nodes\n        swap(maxHeap[i], maxHeap[p]);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.java
    /* Push an element onto the heap */\nvoid push(int val) {\n    // Add a node\n    maxHeap.add(val);\n    // Sift up\n    siftUp(size() - 1);\n}\n\n/* Sift up starting from node i */\nvoid siftUp(int i) {\n    while (true) {\n        // Get the parent of node i\n        int p = parent(i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || maxHeap.get(i) <= maxHeap.get(p))\n            break;\n        // Swap the two nodes\n        swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.cs
    /* Push an element onto the heap */\nvoid Push(int val) {\n    // Add a node\n    maxHeap.Add(val);\n    // Sift up\n    SiftUp(Size() - 1);\n}\n\n/* Sift up starting from node i */\nvoid SiftUp(int i) {\n    while (true) {\n        // Get the parent of node i\n        int p = Parent(i);\n        // Stop sifting if the root has been passed or the node needs no adjustment\n        if (p < 0 || maxHeap[i] <= maxHeap[p])\n            break;\n        // Swap the two nodes\n        Swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.go
    /* Push an element onto the heap */\nfunc (h *maxHeap) push(val any) {\n    // Add a node\n    h.data = append(h.data, val)\n    // Sift up\n    h.siftUp(len(h.data) - 1)\n}\n\n/* Sift up starting from node i */\nfunc (h *maxHeap) siftUp(i int) {\n    for true {\n        // Get the parent of node i\n        p := h.parent(i)\n        // Stop sifting when the root has been passed or the node needs no repair\n        if p < 0 || h.data[i].(int) <= h.data[p].(int) {\n            break\n        }\n        // Swap the two nodes\n        h.swap(i, p)\n        // Continue sifting up\n        i = p\n    }\n}\n
    my_heap.swift
    /* Push an element onto the heap */\nfunc push(val: Int) {\n    // Add a node\n    maxHeap.append(val)\n    // Sift up\n    siftUp(i: size() - 1)\n}\n\n/* Sift up starting from node i */\nfunc siftUp(i: Int) {\n    var i = i\n    while true {\n        // Get the parent of node i\n        let p = parent(i: i)\n        // Stop when the parent index is invalid or the node already satisfies the heap order\n        if p < 0 || maxHeap[i] <= maxHeap[p] {\n            break\n        }\n        // Swap the two nodes\n        swap(i: i, j: p)\n        // Continue sifting up\n        i = p\n    }\n}\n
    my_heap.js
    /* Push an element onto the heap */\npush(val) {\n    // Add a node\n    this.#maxHeap.push(val);\n    // Sift up\n    this.#siftUp(this.size() - 1);\n}\n\n/* Sift up starting from node i */\n#siftUp(i) {\n    while (true) {\n        // Get the parent of node i\n        const p = this.#parent(i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break;\n        // Swap the two nodes\n        this.#swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.ts
    /* Push an element onto the heap */\npush(val: number): void {\n    // Add a node\n    this.maxHeap.push(val);\n    // Sift up\n    this.siftUp(this.size() - 1);\n}\n\n/* Sift up starting from node i */\nsiftUp(i: number): void {\n    while (true) {\n        // Get the parent of node i\n        const p = this.parent(i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break;\n        // Swap the two nodes\n        this.swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.dart
    /* Push an element onto the heap */\nvoid push(int val) {\n  // Add a node\n  _maxHeap.add(val);\n  // Sift up\n  siftUp(size() - 1);\n}\n\n/* Sift up starting from node i */\nvoid siftUp(int i) {\n  while (true) {\n    // Get the parent of node i\n    int p = _parent(i);\n    // Stop sifting when the root has been passed or the node needs no repair\n    if (p < 0 || _maxHeap[i] <= _maxHeap[p]) {\n      break;\n    }\n    // Swap the two nodes\n    _swap(i, p);\n    // Continue sifting up\n    i = p;\n  }\n}\n
    my_heap.rs
    /* Push an element onto the heap */\nfn push(&mut self, val: i32) {\n    // Add a node\n    self.max_heap.push(val);\n    // Sift up\n    self.sift_up(self.size() - 1);\n}\n\n/* Sift up starting from node i */\nfn sift_up(&mut self, mut i: usize) {\n    loop {\n        // Stop sifting up if node i is already the root\n        if i == 0 {\n            break;\n        }\n        // Get the parent of node i\n        let p = Self::parent(i);\n        // Stop sifting up when the node already satisfies the heap property\n        if self.max_heap[i] <= self.max_heap[p] {\n            break;\n        }\n        // Swap the two nodes\n        self.swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.c
    /* Push an element onto the heap */\nvoid push(MaxHeap *maxHeap, int val) {\n    // Normally, the number of nodes should remain below this limit\n    if (maxHeap->size == MAX_SIZE) {\n        printf(\"Heap is full!\");\n        return;\n    }\n    // Add a node\n    maxHeap->data[maxHeap->size] = val;\n    maxHeap->size++;\n\n    // Sift up\n    siftUp(maxHeap, maxHeap->size - 1);\n}\n\n/* Sift up starting from node i */\nvoid siftUp(MaxHeap *maxHeap, int i) {\n    while (true) {\n        // Get the parent of node i\n        int p = parent(maxHeap, i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) {\n            break;\n        }\n        // Swap the two nodes\n        swap(maxHeap, i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.kt
    /* Push an element onto the heap */\nfun push(_val: Int) {\n    // Add a node\n    maxHeap.add(_val)\n    // Sift up\n    siftUp(size() - 1)\n}\n\n/* Sift up starting from node i */\nfun siftUp(it: Int) {\n    // Kotlin function parameters are immutable, so create a temporary variable\n    var i = it\n    while (true) {\n        // Get the parent of node i\n        val p = parent(i)\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || maxHeap[i] <= maxHeap[p]) break\n        // Swap the two nodes\n        swap(i, p)\n        // Continue sifting up\n        i = p\n    }\n}\n
    my_heap.rb
    ### Push an element onto the heap ###\ndef push(val)\n  # Add a node\n  @max_heap << val\n  # Sift up\n  sift_up(size - 1)\nend\n\n### Sift up starting from node i ###\ndef sift_up(i)\n  loop do\n    # Get the parent of node i\n    p = parent(i)\n    # Stop sifting when the root has been passed or the node needs no repair\n    break if p < 0 || @max_heap[i] <= @max_heap[p]\n    # Swap the two nodes\n    swap(i, p)\n    # Continue sifting up\n    i = p\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#4-removing-the-root","level":3,"title":"4.   Removing the root","text":"

    The root is the first element of the list. Removing it directly would change every remaining index, making it difficult to restore the heap property. To minimize index changes, use the following steps.

    1. Swap the root with the last element, the rightmost leaf.
    2. Remove the last element from the list. Because of the swap, this is the original root.
    3. Starting at the root, sift downward.

    As shown below, sifting down proceeds in the opposite direction from sifting up. Compare the root with its two children and swap it with the larger child when needed. Continue downward until a leaf is reached or no swap is necessary.

    <1><2><3><4><5><6><7><8><9><10>

    Figure 8-4   Steps for removing the root of a heap

    Like insertion, removing the root takes \\(O(\\log n)\\) time. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def pop(self) -> int:\n    \"\"\"Pop an element from the heap\"\"\"\n    # Handle an empty heap\n    if self.is_empty():\n        raise IndexError(\"Heap is empty\")\n    # Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    self.swap(0, self.size() - 1)\n    # Remove a node\n    val = self.max_heap.pop()\n    # Sift down\n    self.sift_down(0)\n    # Return the top element of the heap\n    return val\n\ndef sift_down(self, i: int):\n    \"\"\"Sift down starting from node i\"\"\"\n    while True:\n        # Find the node with the largest value among i, l, and r, and store its index in ma\n        l, r, ma = self.left(i), self.right(i), i\n        if l < self.size() and self.max_heap[l] > self.max_heap[ma]:\n            ma = l\n        if r < self.size() and self.max_heap[r] > self.max_heap[ma]:\n            ma = r\n        # Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i:\n            break\n        # Swap the two nodes\n        self.swap(i, ma)\n        # Continue sifting down\n        i = ma\n
    my_heap.cpp
    /* Pop an element from the heap */\nvoid pop() {\n    // Handle an empty heap\n    if (isEmpty()) {\n        throw out_of_range(\"Heap is empty\");\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(maxHeap[0], maxHeap[size() - 1]);\n    // Remove a node\n    maxHeap.pop_back();\n    // Sift down\n    siftDown(0);\n}\n\n/* Sift down starting from node i */\nvoid siftDown(int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = left(i), r = right(i), ma = i;\n        if (l < size() && maxHeap[l] > maxHeap[ma])\n            ma = l;\n        if (r < size() && maxHeap[r] > maxHeap[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i)\n            break;\n        swap(maxHeap[i], maxHeap[ma]);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.java
    /* Pop an element from the heap */\nint pop() {\n    // Handle an empty heap\n    if (isEmpty())\n        throw new IndexOutOfBoundsException();\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(0, size() - 1);\n    // Remove a node\n    int val = maxHeap.remove(size() - 1);\n    // Sift down\n    siftDown(0);\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\nvoid siftDown(int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = left(i), r = right(i), ma = i;\n        if (l < size() && maxHeap.get(l) > maxHeap.get(ma))\n            ma = l;\n        if (r < size() && maxHeap.get(r) > maxHeap.get(ma))\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i)\n            break;\n        // Swap the two nodes\n        swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.cs
    /* Pop an element from the heap */\nint Pop() {\n    // Handle an empty heap\n    if (IsEmpty())\n        throw new IndexOutOfRangeException();\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    Swap(0, Size() - 1);\n    // Remove a node\n    int val = maxHeap.Last();\n    maxHeap.RemoveAt(Size() - 1);\n    // Sift down\n    SiftDown(0);\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\nvoid SiftDown(int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = Left(i), r = Right(i), ma = i;\n        if (l < Size() && maxHeap[l] > maxHeap[ma])\n            ma = l;\n        if (r < Size() && maxHeap[r] > maxHeap[ma])\n            ma = r;\n        // Stop sifting if node i has the largest value or has no children\n        if (ma == i) break;\n        // Swap the two nodes\n        Swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.go
    /* Pop an element from the heap */\nfunc (h *maxHeap) pop() any {\n    // Handle an empty heap\n    if h.isEmpty() {\n        fmt.Println(\"error\")\n        return nil\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    h.swap(0, h.size()-1)\n    // Remove a node\n    val := h.data[len(h.data)-1]\n    h.data = h.data[:len(h.data)-1]\n    // Sift down\n    h.siftDown(0)\n\n    // Return the top element of the heap\n    return val\n}\n\n/* Sift down starting from node i */\nfunc (h *maxHeap) siftDown(i int) {\n    for true {\n        // Find the node with the largest value among i, l, and r, and store its index in max\n        l, r, max := h.left(i), h.right(i), i\n        if l < h.size() && h.data[l].(int) > h.data[max].(int) {\n            max = l\n        }\n        if r < h.size() && h.data[r].(int) > h.data[max].(int) {\n            max = r\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if max == i {\n            break\n        }\n        // Swap the two nodes\n        h.swap(i, max)\n        // Continue sifting down\n        i = max\n    }\n}\n
    my_heap.swift
    /* Pop an element from the heap */\nfunc pop() -> Int {\n    // Handle an empty heap\n    if isEmpty() {\n        fatalError(\"Heap is empty\")\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(i: 0, j: size() - 1)\n    // Remove a node\n    let val = maxHeap.remove(at: size() - 1)\n    // Sift down\n    siftDown(i: 0)\n    // Return the top element of the heap\n    return val\n}\n\n/* Sift down starting from node i */\nfunc siftDown(i: Int) {\n    var i = i\n    while true {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = left(i: i)\n        let r = right(i: i)\n        var ma = i\n        if l < size(), maxHeap[l] > maxHeap[ma] {\n            ma = l\n        }\n        if r < size(), maxHeap[r] > maxHeap[ma] {\n            ma = r\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break\n        }\n        // Swap the two nodes\n        swap(i: i, j: ma)\n        // Continue sifting down\n        i = ma\n    }\n}\n
    my_heap.js
    /* Pop an element from the heap */\npop() {\n    // Handle an empty heap\n    if (this.isEmpty()) throw new Error('Heap is empty');\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    this.#swap(0, this.size() - 1);\n    // Remove a node\n    const val = this.#maxHeap.pop();\n    // Sift down\n    this.#siftDown(0);\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\n#siftDown(i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        const l = this.#left(i),\n            r = this.#right(i);\n        let ma = i;\n        if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l;\n        if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma === i) break;\n        // Swap the two nodes\n        this.#swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.ts
    /* Pop an element from the heap */\npop(): number {\n    // Handle an empty heap\n    if (this.isEmpty()) throw new RangeError('Heap is empty');\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    this.swap(0, this.size() - 1);\n    // Remove a node\n    const val = this.maxHeap.pop();\n    // Sift down\n    this.siftDown(0);\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\nsiftDown(i: number): void {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        const l = this.left(i),\n            r = this.right(i);\n        let ma = i;\n        if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l;\n        if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma === i) break;\n        // Swap the two nodes\n        this.swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.dart
    /* Pop an element from the heap */\nint pop() {\n  // Handle an empty heap\n  if (isEmpty()) throw Exception('Heap is empty');\n  // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n  _swap(0, size() - 1);\n  // Remove a node\n  int val = _maxHeap.removeLast();\n  // Sift down\n  siftDown(0);\n  // Return the top element of the heap\n  return val;\n}\n\n/* Sift down starting from node i */\nvoid siftDown(int i) {\n  while (true) {\n    // Find the node with the largest value among i, l, and r, and store its index in ma\n    int l = _left(i);\n    int r = _right(i);\n    int ma = i;\n    if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l;\n    if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r;\n    // Stop if node i is largest or both child indices l and r are out of bounds\n    if (ma == i) break;\n    // Swap the two nodes\n    _swap(i, ma);\n    // Continue sifting down\n    i = ma;\n  }\n}\n
    my_heap.rs
    /* Pop an element from the heap */\nfn pop(&mut self) -> i32 {\n    // Handle an empty heap\n    if self.is_empty() {\n        panic!(\"Index out of bounds\");\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    self.swap(0, self.size() - 1);\n    // Remove a node\n    let val = self.max_heap.pop().unwrap();\n    // Sift down\n    self.sift_down(0);\n    // Return the top element of the heap\n    val\n}\n\n/* Sift down starting from node i */\nfn sift_down(&mut self, mut i: usize) {\n    loop {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let (l, r, mut ma) = (Self::left(i), Self::right(i), i);\n        if l < self.size() && self.max_heap[l] > self.max_heap[ma] {\n            ma = l;\n        }\n        if r < self.size() && self.max_heap[r] > self.max_heap[ma] {\n            ma = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break;\n        }\n        // Swap the two nodes\n        self.swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.c
    /* Pop an element from the heap */\nint pop(MaxHeap *maxHeap) {\n    // Handle an empty heap\n    if (isEmpty(maxHeap)) {\n        printf(\"Heap is empty!\");\n        return INT_MAX;\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(maxHeap, 0, size(maxHeap) - 1);\n    // Remove a node\n    int val = maxHeap->data[maxHeap->size - 1];\n    maxHeap->size--;\n    // Sift down\n    siftDown(maxHeap, 0);\n\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\nvoid siftDown(MaxHeap *maxHeap, int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in max\n        int l = left(maxHeap, i);\n        int r = right(maxHeap, i);\n        int max = i;\n        if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) {\n            max = l;\n        }\n        if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) {\n            max = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (max == i) {\n            break;\n        }\n        // Swap the two nodes\n        swap(maxHeap, i, max);\n        // Continue sifting down\n        i = max;\n    }\n}\n
    my_heap.kt
    /* Pop an element from the heap */\nfun pop(): Int {\n    // Handle an empty heap\n    if (isEmpty()) throw IndexOutOfBoundsException()\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(0, size() - 1)\n    // Remove a node\n    val _val = maxHeap.removeAt(size() - 1)\n    // Sift down\n    siftDown(0)\n    // Return the top element of the heap\n    return _val\n}\n\n/* Sift down starting from node i */\nfun siftDown(it: Int) {\n    // Kotlin function parameters are immutable, so create a temporary variable\n    var i = it\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        val l = left(i)\n        val r = right(i)\n        var ma = i\n        if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l\n        if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i) break\n        // Swap the two nodes\n        swap(i, ma)\n        // Continue sifting down\n        i = ma\n    }\n}\n
    my_heap.rb
    ### Pop an element from the heap ###\ndef pop\n  # Handle an empty heap\n  raise IndexError, \"Heap is empty\" if is_empty?\n  # Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n  swap(0, size - 1)\n  # Remove a node\n  val = @max_heap.pop\n  # Sift down\n  sift_down(0)\n  # Return the top element of the heap\n  val\nend\n\n### Sift down starting from node i ###\ndef sift_down(i)\n  loop do\n    # Find the node with the largest value among i, l, and r, and store its index in ma\n    l, r, ma = left(i), right(i), i\n    ma = l if l < size && @max_heap[l] > @max_heap[ma]\n    ma = r if r < size && @max_heap[r] > @max_heap[ma]\n\n    # Stop if node i is largest or both child indices l and r are out of bounds\n    break if ma == i\n\n    # Swap the two nodes\n    swap(i, ma)\n    # Continue sifting down\n    i = ma\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#813-common-applications","level":2,"title":"8.1.3   Common applications","text":"
    • Priority queues: Heaps are usually the first choice for implementing priority queues. Enqueueing and dequeueing both take \\(O(\\log n)\\) time, and building a heap takes \\(O(n)\\).
    • Heap sort: Build a heap from a dataset, then repeatedly remove its root to obtain sorted data. In practice, a more elegant implementation is usually used; see the heap-sort section.
    • Finding the largest \\(k\\) elements: This is both a classic algorithm problem and a common application, such as selecting the 10 hottest news stories for Weibo's trending list or the 10 best-selling products.
    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/summary/","level":1,"title":"8.4   Summary","text":"","path":["Chapter 8.   Heaps","8.4   Summary"],"tags":[]},{"location":"chapter_heap/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • A heap is a complete binary tree. Depending on its ordering condition, it is either a max heap or a min heap. The root holds the largest or smallest element, respectively.
    • A priority queue removes elements according to their priority and is usually implemented with a heap.
    • Common heap operations and their time complexities include insertion in \\(O(\\log n)\\), removing the root in \\(O(\\log n)\\), and accessing the root in \\(O(1)\\).
    • Complete binary trees are well suited to array representation, so heaps are usually stored in arrays.
    • Sifting restores the heap property and is used during both insertion and removal.
    • Building a heap from \\(n\\) input elements can be optimized to \\(O(n)\\) time.
    • Top-k is a classic algorithm problem that a heap can solve efficiently in \\(O(n \\log k)\\) time.
    ","path":["Chapter 8.   Heaps","8.4   Summary"],"tags":[]},{"location":"chapter_heap/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Is the heap data structure the same concept as the heap in memory management?

    No; they simply share a name. In computer memory, the heap is used for dynamic memory allocation, allowing a running program to store data. A program can request heap memory for structures such as objects and arrays. When that data is no longer needed, the memory must be released to prevent leaks. Compared with stack memory, heap memory requires more careful management; misuse can lead to memory leaks, dangling pointers, and other problems.

    ","path":["Chapter 8.   Heaps","8.4   Summary"],"tags":[]},{"location":"chapter_heap/top_k/","level":1,"title":"8.3   The top-k problem","text":"

    Question

    Given an unsorted array nums of length \\(n\\), return its largest \\(k\\) elements.

    We first introduce two straightforward solutions, then a more efficient solution using a heap.

    ","path":["Chapter 8.   Heaps","8.3   The top-k problem"],"tags":[]},{"location":"chapter_heap/top_k/#831-method-1-repeated-selection","level":2,"title":"8.3.1   Method 1: Repeated selection","text":"

    As shown below, scan the array \\(k\\) times, selecting the \\(1\\)st, \\(2\\)nd, \\(\\dots\\), and \\(k\\)th largest elements in successive rounds. This takes \\(O(nk)\\) time.

    This method is suitable only when \\(k \\ll n\\). As \\(k\\) approaches \\(n\\), its time complexity approaches \\(O(n^2)\\), making it very slow.

    Figure 8-6   Finding the largest k elements by repeated scans

    Tip

    When \\(k = n\\), this produces a fully sorted sequence and is equivalent to selection sort.

    ","path":["Chapter 8.   Heaps","8.3   The top-k problem"],"tags":[]},{"location":"chapter_heap/top_k/#832-method-2-sorting","level":2,"title":"8.3.2   Method 2: Sorting","text":"

    As shown below, sort nums and return its rightmost \\(k\\) elements. This takes \\(O(n \\log n)\\) time.

    This method does more work than necessary: we need only the largest \\(k\\) elements, not a sorted order for all the others.

    Figure 8-7   Finding the largest k elements by sorting

    ","path":["Chapter 8.   Heaps","8.3   The top-k problem"],"tags":[]},{"location":"chapter_heap/top_k/#833-method-3-a-heap","level":2,"title":"8.3.3   Method 3: A heap","text":"

    A heap solves the top-k problem more efficiently, following the steps below.

    1. Initialize a min heap, whose root holds its smallest element.
    2. Insert the first \\(k\\) array elements into the heap one by one.
    3. Starting with element \\(k + 1\\), if the current element is larger than the root, remove the root and insert the current element.
    4. After the traversal, the heap contains the largest \\(k\\) elements.
    <1><2><3><4><5><6><7><8><9>

    Figure 8-8   Finding the largest k elements with a heap

    An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby top_k.py
    def top_k_heap(nums: list[int], k: int) -> list[int]:\n    \"\"\"Use a heap to find the k largest elements in an array\"\"\"\n    # Initialize a min heap\n    heap = []\n    # Push the first k array elements onto the heap\n    for i in range(k):\n        heapq.heappush(heap, nums[i])\n    # Starting with element k+1, keep the heap size at k\n    for i in range(k, len(nums)):\n        # If the current element is larger than the heap top, pop the top element and push the current element\n        if nums[i] > heap[0]:\n            heapq.heappop(heap)\n            heapq.heappush(heap, nums[i])\n    return heap\n
    top_k.cpp
    /* Use a heap to find the k largest elements in an array */\npriority_queue<int, vector<int>, greater<int>> topKHeap(vector<int> &nums, int k) {\n    // Initialize a min heap\n    priority_queue<int, vector<int>, greater<int>> heap;\n    // Push the first k array elements onto the heap\n    for (int i = 0; i < k; i++) {\n        heap.push(nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (int i = k; i < nums.size(); i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > heap.top()) {\n            heap.pop();\n            heap.push(nums[i]);\n        }\n    }\n    return heap;\n}\n
    top_k.java
    /* Use a heap to find the k largest elements in an array */\nQueue<Integer> topKHeap(int[] nums, int k) {\n    // Initialize a min heap\n    Queue<Integer> heap = new PriorityQueue<Integer>();\n    // Push the first k array elements onto the heap\n    for (int i = 0; i < k; i++) {\n        heap.offer(nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (int i = k; i < nums.length; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > heap.peek()) {\n            heap.poll();\n            heap.offer(nums[i]);\n        }\n    }\n    return heap;\n}\n
    top_k.cs
    /* Use a heap to find the k largest elements in an array */\nPriorityQueue<int, int> TopKHeap(int[] nums, int k) {\n    // Initialize a min heap\n    PriorityQueue<int, int> heap = new();\n    // Push the first k array elements onto the heap\n    for (int i = 0; i < k; i++) {\n        heap.Enqueue(nums[i], nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (int i = k; i < nums.Length; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > heap.Peek()) {\n            heap.Dequeue();\n            heap.Enqueue(nums[i], nums[i]);\n        }\n    }\n    return heap;\n}\n
    top_k.go
    /* Use a heap to find the k largest elements in an array */\nfunc topKHeap(nums []int, k int) *minHeap {\n    // Initialize a min heap\n    h := &minHeap{}\n    heap.Init(h)\n    // Push the first k array elements onto the heap\n    for i := 0; i < k; i++ {\n        heap.Push(h, nums[i])\n    }\n    // Starting with element k+1, keep the heap size at k\n    for i := k; i < len(nums); i++ {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if nums[i] > h.Top().(int) {\n            heap.Pop(h)\n            heap.Push(h, nums[i])\n        }\n    }\n    return h\n}\n
    top_k.swift
    /* Use a heap to find the k largest elements in an array */\nfunc topKHeap(nums: [Int], k: Int) -> [Int] {\n    // Build a heap from the first k elements and use it as a min heap\n    var heap = Heap(nums.prefix(k))\n    // Starting with element k+1, keep the heap size at k\n    for i in nums.indices.dropFirst(k) {\n        // If the current element exceeds the heap minimum, remove the minimum and insert the current element\n        if nums[i] > heap.min()! {\n            _ = heap.removeMin()\n            heap.insert(nums[i])\n        }\n    }\n    return heap.unordered\n}\n
    top_k.js
    /* Push an element onto the heap */\nfunction pushMinHeap(maxHeap, val) {\n    // Negate the value\n    maxHeap.push(-val);\n}\n\n/* Pop an element from the heap */\nfunction popMinHeap(maxHeap) {\n    // Negate the value\n    return -maxHeap.pop();\n}\n\n/* Access the top element of the heap */\nfunction peekMinHeap(maxHeap) {\n    // Negate the value\n    return -maxHeap.peek();\n}\n\n/* Get the elements in the heap */\nfunction getMinHeap(maxHeap) {\n    // Negate the value\n    return maxHeap.getMaxHeap().map((num) => -num);\n}\n\n/* Use a heap to find the k largest elements in an array */\nfunction topKHeap(nums, k) {\n    // Initialize a min heap\n    // Note: negate all values in the heap to simulate a min heap with a max heap\n    const maxHeap = new MaxHeap([]);\n    // Push the first k array elements onto the heap\n    for (let i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (let i = k; i < nums.length; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    // Return the elements in the heap\n    return getMinHeap(maxHeap);\n}\n
    top_k.ts
    /* Push an element onto the heap */\nfunction pushMinHeap(maxHeap: MaxHeap, val: number): void {\n    // Negate the value\n    maxHeap.push(-val);\n}\n\n/* Pop an element from the heap */\nfunction popMinHeap(maxHeap: MaxHeap): number {\n    // Negate the value\n    return -maxHeap.pop();\n}\n\n/* Access the top element of the heap */\nfunction peekMinHeap(maxHeap: MaxHeap): number {\n    // Negate the value\n    return -maxHeap.peek();\n}\n\n/* Get the elements in the heap */\nfunction getMinHeap(maxHeap: MaxHeap): number[] {\n    // Negate the value\n    return maxHeap.getMaxHeap().map((num: number) => -num);\n}\n\n/* Use a heap to find the k largest elements in an array */\nfunction topKHeap(nums: number[], k: number): number[] {\n    // Initialize a min heap\n    // Note: negate all values in the heap to simulate a min heap with a max heap\n    const maxHeap = new MaxHeap([]);\n    // Push the first k array elements onto the heap\n    for (let i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (let i = k; i < nums.length; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    // Return the elements in the heap\n    return getMinHeap(maxHeap);\n}\n
    top_k.dart
    /* Use a heap to find the k largest elements in an array */\nMinHeap topKHeap(List<int> nums, int k) {\n  // Initialize a min heap with the first k elements of the array\n  MinHeap heap = MinHeap(nums.sublist(0, k));\n  // Starting with element k+1, keep the heap size at k\n  for (int i = k; i < nums.length; i++) {\n    // If the current element is larger than the heap top, pop the top element and push the current element\n    if (nums[i] > heap.peek()) {\n      heap.pop();\n      heap.push(nums[i]);\n    }\n  }\n  return heap;\n}\n
    top_k.rs
    /* Use a heap to find the k largest elements in an array */\nfn top_k_heap(nums: Vec<i32>, k: usize) -> BinaryHeap<Reverse<i32>> {\n    // BinaryHeap is a max heap; Reverse reverses the comparison order to implement a min heap\n    let mut heap = BinaryHeap::<Reverse<i32>>::new();\n    // Push the first k array elements onto the heap\n    for &num in nums.iter().take(k) {\n        heap.push(Reverse(num));\n    }\n    // Starting with element k+1, keep the heap size at k\n    for &num in nums.iter().skip(k) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if num > heap.peek().unwrap().0 {\n            heap.pop();\n            heap.push(Reverse(num));\n        }\n    }\n    heap\n}\n
    top_k.c
    /* Push an element onto the heap */\nvoid pushMinHeap(MaxHeap *maxHeap, int val) {\n    // Negate the element\n    push(maxHeap, -val);\n}\n\n/* Pop an element from the heap */\nint popMinHeap(MaxHeap *maxHeap) {\n    // Negate the element\n    return -pop(maxHeap);\n}\n\n/* Access the top element of the heap */\nint peekMinHeap(MaxHeap *maxHeap) {\n    // Negate the element\n    return -peek(maxHeap);\n}\n\n/* Get the elements in the heap */\nint *getMinHeap(MaxHeap *maxHeap) {\n    // Negate every element in the heap and store the results in array res\n    int *res = (int *)malloc(maxHeap->size * sizeof(int));\n    for (int i = 0; i < maxHeap->size; i++) {\n        res[i] = -maxHeap->data[i];\n    }\n    return res;\n}\n\n/* Get the elements in the heap */\nint *getMinHeap(MaxHeap *maxHeap) {\n    // Negate every element in the heap and store the results in array res\n    int *res = (int *)malloc(maxHeap->size * sizeof(int));\n    for (int i = 0; i < maxHeap->size; i++) {\n        res[i] = -maxHeap->data[i];\n    }\n    return res;\n}\n\n// Use a heap to find the k largest elements in an array\nint *topKHeap(int *nums, int sizeNums, int k) {\n    // Initialize a min heap\n    // Note: negate every element to simulate a min heap with a max heap\n    int *empty = (int *)malloc(0);\n    MaxHeap *maxHeap = newMaxHeap(empty, 0);\n    // Push the first k array elements onto the heap\n    for (int i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (int i = k; i < sizeNums; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    int *res = getMinHeap(maxHeap);\n    // Free memory\n    delMaxHeap(maxHeap);\n    return res;\n}\n
    top_k.kt
    /* Use a heap to find the k largest elements in an array */\nfun topKHeap(nums: IntArray, k: Int): Queue<Int> {\n    // Initialize a min heap\n    val heap = PriorityQueue<Int>()\n    // Push the first k array elements onto the heap\n    for (i in 0..<k) {\n        heap.offer(nums[i])\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (i in k..<nums.size) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > heap.peek()) {\n            heap.poll()\n            heap.offer(nums[i])\n        }\n    }\n    return heap\n}\n
    top_k.rb
    ### Use a heap to find the k largest elements in an array ###\ndef top_k_heap(nums, k)\n  # Initialize a min heap\n  # Note: negate all values in the heap to simulate a min heap with a max heap\n  max_heap = MaxHeap.new([])\n\n  # Push the first k array elements onto the heap\n  for i in 0...k\n    push_min_heap(max_heap, nums[i])\n  end\n\n  # Starting with element k+1, keep the heap size at k\n  for i in k...nums.length\n    # If the current element is larger than the heap top, pop the top element and push the current element\n    if nums[i] > peek_min_heap(max_heap)\n      pop_min_heap(max_heap)\n      push_min_heap(max_heap, nums[i])\n    end\n  end\n\n  get_min_heap(max_heap)\nend\n
    Visualize execution

    View full screen >

    There are \\(n\\) rounds of heap insertion and removal to consider, and the heap has at most \\(k\\) elements, so the time complexity is \\(O(n \\log k)\\). This is highly efficient: when \\(k\\) is small, the complexity approaches \\(O(n)\\); when \\(k\\) is large, it does not exceed \\(O(n \\log n)\\).

    This approach also suits dynamic data streams. As new data arrives, we can maintain the heap to update the largest \\(k\\) elements continuously.

    ","path":["Chapter 8.   Heaps","8.3   The top-k problem"],"tags":[]},{"location":"chapter_hello_algo/","level":1,"title":"Foreword","text":"

    A few years ago, I shared a series of solutions to the “Coding Interviews” problems on LeetCode and received encouragement and support from many readers. In our conversations, one question came up more than any other: “How do I get started with algorithms?” Over time, I became deeply interested in this question.

    Jumping into practice problems with little preparation seems to be the most popular approach: simple, direct, and effective. Yet solving problems is rather like playing Minesweeper. Strong independent learners can clear the mines one by one, while those with a weak foundation may run into one setback after another and gradually retreat in frustration. Reading a textbook from cover to cover is another common approach. For job seekers, however, theses, applications, written tests, and interview preparation already consume most of their energy, making a thick textbook a formidable challenge.

    If you face similar difficulties, I am glad this book has found you. It is my answer to that question—perhaps not the optimal solution, but at least a constructive attempt. Although this book alone will not secure a job offer, it will guide you through the “knowledge map” of data structures and algorithms. It will show you the shapes, sizes, and locations of different “mines” and teach you ways to clear them. With these skills, I believe you can approach practice problems and technical reading more confidently and gradually build a complete body of knowledge.

    I wholeheartedly agree with Professor Feynman's words: “Knowledge isn't free. You have to pay attention.” In this sense, the book is not entirely “free.” To honor the precious attention you invest in it, I will devote as much of my own attention as I can to writing it.

    I am aware of the limits of my knowledge. Although the content has undergone considerable refinement, many errors surely remain. I sincerely welcome corrections and suggestions from teachers and fellow learners.

    Hello, algorithms!

    Computers have transformed the world. Their speed and remarkable programmability make them an ideal medium for executing algorithms and processing data. From the lifelike graphics of video games and the intelligent decisions of self-driving cars to AlphaGo's brilliant games and ChatGPT's natural interactions, these applications are all compelling expressions of algorithms running on computers.

    In fact, algorithms and data structures existed throughout the world long before computers. Early algorithms were relatively simple, such as ancient counting methods and steps for making tools. As civilization advanced, algorithms became more refined and complex. From exquisite craftsmanship and industrial products that boosted productivity to the scientific laws governing the universe, ingenious algorithmic ideas lie behind almost everything, whether ordinary or astonishing.

    Data structures are equally widespread. Systems ranging from social networks to subway routes can be modeled as “graphs.” Social organizations, from nations to families, often have the characteristics of “trees.” Winter clothing behaves like a “stack”: what goes on first comes off last. A tube of shuttlecocks is like a “queue,” with items inserted at one end and removed at the other. A dictionary resembles a “hash table,” allowing quick lookup of a desired entry.

    Through clear animated illustrations and runnable code examples, this book aims to help readers understand the core concepts of algorithms and data structures and implement them through programming. Building on that foundation, it seeks to reveal how algorithms take shape in our complex world and to show their beauty. I hope you find it helpful!

    ","path":["Foreword"],"tags":[]},{"location":"chapter_introduction/","level":1,"title":"Chapter 1.   Getting to know algorithms","text":"

    Abstract

    A young woman dances gracefully among the data, the melody of algorithms rippling through her skirt.

    She invites you to join her. Follow her steps into a world of algorithms filled with logic and beauty.

    ","path":["Chapter 1.   Getting to know algorithms"],"tags":[]},{"location":"chapter_introduction/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 1.1   Algorithms are everywhere
    • 1.2   What is an algorithm?
    • 1.3   Summary
    ","path":["Chapter 1.   Getting to know algorithms"],"tags":[]},{"location":"chapter_introduction/algorithms_are_everywhere/","level":1,"title":"1.1   Algorithms are everywhere","text":"

    When we hear the word “algorithm,” we naturally think of mathematics. In fact, many algorithms do not involve complex mathematics. Instead, they rely on basic logic that we encounter throughout everyday life.

    Before we formally explore algorithms, here is an interesting fact: you have already learned many algorithms without realizing it, and you routinely use them in everyday life. The following examples illustrate this point.

    Example 1: Looking up a word in a dictionary. In a Chinese dictionary, each character has a corresponding pinyin pronunciation, and entries are arranged alphabetically by pinyin. Suppose we want to find a character whose pinyin begins with \\(r\\). We would usually follow the process shown in Figure 1-1.

    1. Open the dictionary roughly halfway through and check the initial letter on that page. Suppose it is \\(m\\).
    2. Since \\(r\\) comes after \\(m\\) in the pinyin alphabet, rule out the first half of the dictionary and narrow the search to the second half.
    3. Repeat steps 1. and 2. until you find the pages whose pinyin entries begin with \\(r\\).
    <1><2><3><4><5>

    Figure 1-1   Steps for looking up a word in a dictionary

    This dictionary skill, learned in elementary school, is actually the well-known “binary search” algorithm. From a data structure perspective, we can view the dictionary as a sorted “array”; from an algorithm perspective, the sequence of lookup operations above is “binary search.”

    Example 2: Arranging playing cards. When playing cards, we need to arrange our hand from smallest to largest each round. The process is shown in Figure 1-2.

    1. Divide the cards into “sorted” and “unsorted” sections, initially treating the single leftmost card as sorted.
    2. Take a card from the unsorted section and insert it into the correct position in the sorted section. The two leftmost cards are now sorted.
    3. Repeat step 2., inserting one card from the unsorted section into the sorted section each round, until all the cards are sorted.

    Figure 1-2   Steps for sorting playing cards

    This method of arranging playing cards is essentially “insertion sort,” an algorithm that is very efficient for small datasets. Insertion sort appears in the library sorting functions of many programming languages.

    Example 3: Making change. Suppose we buy goods costing \\(69\\) yuan at a supermarket and pay the cashier \\(100\\) yuan. The cashier needs to give us \\(31\\) yuan in change and naturally reasons through the process shown in Figure 1-3.

    1. The available denominations smaller than \\(31\\) yuan are \\(1\\), \\(5\\), \\(10\\), and \\(20\\) yuan.
    2. Take the largest available denomination, \\(20\\) yuan, leaving \\(31 - 20 = 11\\) yuan.
    3. Take the largest denomination that fits the remaining amount, \\(10\\) yuan, leaving \\(11 - 10 = 1\\) yuan.
    4. Take the largest denomination that fits the remaining amount, \\(1\\) yuan, leaving \\(1 - 1 = 0\\) yuan.
    5. The change is complete: \\(20 + 10 + 1 = 31\\) yuan.

    Figure 1-3   The process of making change

    At each step above, we make the choice that looks best at the moment—using the largest possible denomination—and eventually obtain a valid way to make change. In terms of data structures and algorithms, this is a “greedy” algorithm.

    From cooking a meal to traveling between stars, solving almost any problem involves algorithms. Computers allow us to store data structures in memory through programming and write code that calls on CPUs and GPUs to execute algorithms. We can therefore bring real-world problems to computers and solve complex problems more efficiently.

    Tip

    If concepts such as data structures, algorithms, arrays, and binary search still seem unclear, keep reading. This book will guide you into the world of data structures and algorithms.

    ","path":["Chapter 1.   Getting to know algorithms","1.1   Algorithms are everywhere"],"tags":[]},{"location":"chapter_introduction/summary/","level":1,"title":"1.3   Summary","text":"","path":["Chapter 1.   Getting to know algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Algorithms are everywhere in daily life; they are not remote, inaccessible knowledge. In fact, we have already learned many algorithms without realizing it and use them to solve problems large and small.
    • Looking up a word in a dictionary follows the same principle as binary search. Binary search illustrates the important algorithmic idea of divide and conquer.
    • Arranging playing cards closely resembles insertion sort. Insertion sort is well suited to small datasets.
    • Making change is essentially a greedy algorithm: at each step, make the choice that looks best at the moment.
    • An algorithm is a set of instructions or steps that solves a specific problem within a finite time. A data structure is a way of organizing and storing data in a computer.
    • Data structures and algorithms are closely connected. Data structures provide the foundation for algorithms, while algorithms bring data structures to life.
    • We can compare data structures and algorithms to assembling building blocks: the blocks represent data; their shapes and connections represent data structures; and the assembly steps represent algorithms.
    ","path":["Chapter 1.   Getting to know algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: As a programmer, I have never used algorithms to solve problems in my daily work. Common algorithms are already built into programming languages, so I can simply use them. Does this mean the problems we encounter at work are not difficult enough to require algorithms?

    If specific job skills are like the techniques of a martial art, foundational subjects are more like the underlying strength that makes those techniques effective.

    I believe the purpose of learning algorithms—and other foundational subjects—is not to implement them from scratch at work. It is to use what we have learned to respond and make judgments professionally when solving problems, improving the overall quality of our work. For a simple example, every programming language has a built-in sorting function:

    • Without studying data structures and algorithms, we might pass any dataset to this sorting function. It runs smoothly and performs reasonably well, so nothing seems wrong.
    • After studying algorithms, however, we know that the built-in sorting function has a time complexity of \\(O(n \\log n)\\). If the data consists of integers with a fixed number of digits, such as student IDs, we can use the more efficient “radix sort” to reduce the time complexity to \\(O(nk)\\), where \\(k\\) is the number of digits. For large datasets, the time saved can create considerable value through lower costs, a better user experience, and so on.

    In engineering, optimal solutions are difficult to reach for many problems, and many others are solved only “well enough.” How difficult a problem appears depends both on its nature and on the knowledge of the person examining it. The more knowledge and experience we have, the more deeply we can analyze a problem and the more elegantly we can solve it.

    Q: Now that AI agents write almost all our code, do we still need to learn algorithms?

    To quote Mu Li: “An agent is an ability multiplier: it multiplies your ability by 10. It is not an adder that adds 10 to your ability.”

    If AI multiplies our ability, then that starting ability matters too. I think two kinds of ability are especially worth developing. The first is communication, particularly collaboration with AI: state the goal clearly, supply the necessary context, and correct the course promptly. Explaining a problem to AI also helps us clarify it for ourselves. The second is foundational knowledge. AI can fill in the details, but we need to understand the basic concepts ourselves to ask relevant questions, judge what is correct, and turn its output into useful results.

    One distinctive feature of AI is that, unlike earlier tools, it has no clear boundaries to its functionality. It is a little like water, taking different shapes in different people's hands. The stronger a person's abilities and the better their taste, the more pronounced AI's multiplying effect becomes.

    ","path":["Chapter 1.   Getting to know algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/","level":1,"title":"1.2   What is an algorithm?","text":"","path":["Chapter 1.   Getting to know algorithms","1.2   What is an algorithm?"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#121-definition-of-an-algorithm","level":2,"title":"1.2.1   Definition of an algorithm","text":"

    An algorithm is a set of instructions or steps that solves a specific problem within a finite time. It has the following properties.

    • The problem is well defined, with clear definitions of its inputs and outputs.
    • The algorithm is feasible: it can finish within a finite number of steps, using finite time and memory.
    • Every step has a definite meaning. Given the same inputs and execution conditions, the output is always the same.
    ","path":["Chapter 1.   Getting to know algorithms","1.2   What is an algorithm?"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#122-definition-of-a-data-structure","level":2,"title":"1.2.2   Definition of a data structure","text":"

    A data structure is a way of organizing and storing data. It encompasses the data itself, the relationships between data items, and the methods used to operate on them. Its design aims to achieve the following goals.

    • Use as little space as possible to conserve computer memory.
    • Make data operations as fast as possible, including access, insertion, deletion, and updating.
    • Provide a concise representation of data and its logical relationships so that algorithms can run efficiently.

    Designing a data structure involves tradeoffs. Improving one aspect often requires compromising another. Here are two examples.

    • Compared with arrays, linked lists make insertion and deletion easier, at the expense of data access speed.
    • Compared with linked lists, graphs provide richer information about logical relationships but require more memory.
    ","path":["Chapter 1.   Getting to know algorithms","1.2   What is an algorithm?"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#123-the-relationship-between-data-structures-and-algorithms","level":2,"title":"1.2.3   The relationship between data structures and algorithms","text":"

    As Figure 1-4 shows, data structures and algorithms are closely intertwined in three ways.

    • Data structures are the foundation of algorithms. They provide algorithms with data in a structured form and methods for operating on that data.
    • Algorithms bring data structures to life. A data structure by itself only stores information; it must be combined with an algorithm to solve a specific problem.
    • An algorithm can often be implemented using different data structures, but its efficiency may vary greatly. Choosing the right data structure is crucial.

    Figure 1-4   The relationship between data structures and algorithms

    Data structures and algorithms are like assembling the building blocks shown in Figure 1-5. A set of building blocks includes not only many pieces but also detailed assembly instructions. By following those instructions step by step, we can build an intricate model.

    Figure 1-5   Assembling building blocks

    Table 1-1 describes this analogy in detail.

    Table 1-1   Comparing data structures and algorithms to building blocks

    Data structures and algorithms Building blocks Input data Unassembled blocks Data structure How the blocks are organized, including their shapes, sizes, and connections Algorithm A sequence of steps for assembling the blocks into the desired form Output data The finished model

    Data structures and algorithms are independent of programming languages. This is why the book can provide implementations in multiple languages.

    A common shorthand

    In everyday discussions, we often shorten “data structures and algorithms” to “algorithms.” For example, the familiar algorithm problems on LeetCode actually test knowledge of both data structures and algorithms.

    ","path":["Chapter 1.   Getting to know algorithms","1.2   What is an algorithm?"],"tags":[]},{"location":"chapter_paperbook/","level":1,"title":"Print edition (Chinese)","text":"

    After a long period of refinement, the Simplified Chinese print edition of Hello Algo is finally here! A line of poetry captures how I feel:

    Chase the wind and moon without pause; beyond the open plains lie the mountains of spring.

    The following video introduces the print edition and shares some of my thoughts on:

    • The importance of learning data structures and algorithms.
    • Why I chose Python for the print edition.
    • What sharing knowledge means to me.

    I am new to making videos. Please show your support with a like, a coin, and a favorite—thank you!

    Here are some snapshots of the print edition:

    ","path":["Print edition (Chinese)"],"tags":[]},{"location":"chapter_paperbook/#strengths-and-limitations","level":2,"title":"Strengths and limitations","text":"

    Here are a few things about the print edition that may pleasantly surprise you:

    • Full-color printing makes the most of the book's animation-style illustrations.
    • Carefully selected paper reproduces colors faithfully while preserving the distinctive feel of a printed book.
    • Formatting is more standardized than in the web edition; for example, formulas in figures are italicized.
    • A fold-out mind map and bookmarks are included at no extra cost.
    • The print, web, and PDF editions have synchronized content, so you can switch freely between them.

    Tip

    Synchronizing the print and web editions is difficult, so there may be minor differences. Thank you for your understanding!

    Of course, there are also a few points to consider before buying:

    • The book uses Python, which may not be your main language. You can treat Python as pseudocode and focus on understanding the ideas.
    • Full-color printing greatly improves the reading experience for illustrations and code, but costs somewhat more than black-and-white printing.

    Tip

    “Print quality” and “price” are like “time efficiency” and “space efficiency” in algorithms: it is difficult to optimize both. In my view, print quality corresponds to time efficiency and deserves greater priority.

    ","path":["Print edition (Chinese)"],"tags":[]},{"location":"chapter_paperbook/#where-to-buy","level":2,"title":"Where to buy","text":"

    If you are interested in the Simplified Chinese print edition, consider getting a copy. We have arranged a 50% discount on the new book. Follow this link or scan the QR code below:

    ","path":["Print edition (Chinese)"],"tags":[]},{"location":"chapter_paperbook/#afterword","level":2,"title":"Afterword","text":"

    At first, I underestimated the work involved in publishing a print edition. I assumed that as long as the open-source project was well maintained, some form of automation could generate the printed book. Experience showed that producing a printed book differs greatly from updating an open-source project, and moving between the two requires considerable extra work.

    There is still a long road from a first draft to a manuscript ready for publication. It takes close collaboration and sustained refinement by the author and the publisher's planning, editorial, design, and marketing teams. My thanks go to Turing's commissioning editor Junhua Wang and everyone at Posts & Telecom Press and Turing Community who took part in publishing this book!

    I hope this book will help you!

    ","path":["Print edition (Chinese)"],"tags":[]},{"location":"chapter_preface/","level":1,"title":"Chapter 0.   Preface","text":"

    Abstract

    Algorithms are like a beautiful symphony, with each line of code flowing to its rhythm.

    May this book play softly in your mind, leaving a melody that is both distinctive and lasting.

    ","path":["Chapter 0.   Preface"],"tags":[]},{"location":"chapter_preface/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 0.1   About this book
    • 0.2   How to use this book
    • 0.3   Summary
    ","path":["Chapter 0.   Preface"],"tags":[]},{"location":"chapter_preface/about_the_book/","level":1,"title":"0.1   About this book","text":"

    This project aims to create an open-source, free, beginner-friendly introduction to data structures and algorithms.

    • Animated illustrations throughout the book make concepts clear and accessible, with a gentle learning curve that guides beginners through the landscape of data structures and algorithms.
    • Source code runs with one click, helping readers improve their programming skills through practice and understand how algorithms work and how data structures are implemented.
    • We encourage readers to learn together. Ask questions and share insights in the comments, and make progress through discussion.
    ","path":["Chapter 0.   Preface","0.1   About this book"],"tags":[]},{"location":"chapter_preface/about_the_book/#011-who-this-book-is-for","level":2,"title":"0.1.1   Who this book is for","text":"

    If you are new to algorithms, have never studied them, or have some experience with practice problems but only a hazy understanding of data structures and algorithms—sometimes feeling you understand, sometimes not—this book is written for you!

    If you have already solved a substantial number of problems and know most common problem types, this book can help you review and organize your knowledge. The repository's source code can serve as a “problem-solving toolkit” or an “algorithm dictionary.”

    If you are an expert in algorithms, we welcome your valuable suggestions and invite you to contribute to the book.

    Prerequisites

    You should have basic programming skills in at least one language and be able to read and write simple code.

    ","path":["Chapter 0.   Preface","0.1   About this book"],"tags":[]},{"location":"chapter_preface/about_the_book/#012-organization","level":2,"title":"0.1.2   Organization","text":"

    Figure 0-1 shows the book's main topics.

    • Complexity analysis: Criteria and methods for evaluating data structures and algorithms, including how to derive time and space complexity, common complexity classes, and examples.
    • Data structures: Basic data types and ways to classify data structures; definitions, strengths, weaknesses, common operations, variants, typical applications, and implementations of arrays, linked lists, stacks, queues, hash tables, trees, heaps, graphs, and more.
    • Algorithms: Definitions, strengths, weaknesses, efficiency, applications, solution steps, and example problems for searching, sorting, divide and conquer, backtracking, dynamic programming, greedy algorithms, and more.

    Figure 0-1   The book's main topics

    ","path":["Chapter 0.   Preface","0.1   About this book"],"tags":[]},{"location":"chapter_preface/about_the_book/#013-acknowledgments","level":2,"title":"0.1.3   Acknowledgments","text":"

    This book continues to improve through the collective efforts of many contributors in the open-source community. Thank you to everyone who has invested time and energy in writing it. In the order automatically generated by GitHub, they are: krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, rongyi, msk397, gvenusleo, khoaxuantu, rivertwilight, K3v123, gyt95, zhuoqinyue, yuelinxin, Zuoxun, mingXta, Phoenix0415, FangYuan33, GN-Yu, longsizhuo, pengchzn, QiLOL, Cathay-Chen, guowei-gong, xBLACKICEx, IsChristina, JoseHung, qualifier1024, hello-ikun, magentaqin, Guanngxu, thomasq0, sunshinesDL, L-Super, Transmigration-zhou, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, theNefelibatas, Shyam-Chen, sangxiaai, longranger2, codeberg-user, xiongsp, JeffersonHuang, prinpal, seven1240, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, SamJin98, hongyun-robot, nanlei, XiaChuerwu, yd-j, iron-irax, mgisr, steventimes, junminhong, heshuyue, danny900714, Nigh, Dr-XYZ, MolDuM, XC-Zero, reeswell, PXG-XPG, NI-SW, Horbin-Magician, Enlightenus, YangXuanyi, xjr7670, beatrix-chan, DullSword, qq909244296, iStig, boloboloda, hts0000, gledfish, fbigm, echo1937, jiaxianhua, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, linyejoe2, liuxjerry, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, ZhongGuanbin, hezhizhen, linzeyan, ZJKung, JTCPOWI, KawaiiAsh, luluxia, xb534, ztkuaikuai, yw-1021, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yanedie, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, lyl625760, lucaswangdev, llql1211, 0130w, shanghai-Jerry, EJackYang, Javesun99, eltociear, lipusheng, KNChiu, BlindTerran, ShiMaRing, lovelock, FreddieLi, FloranceYeh, fanchenggang, gltianwen, goerll, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, Asashishi, Asa0oo0o0o, fanenr, eagleanurag, akshiterate, 52coder, foursevenlove, KorsChen, hopkings2008, yang-le, realwujing, Evilrabbit520, Umer-Jahangir, Turing-1024-Lee, Suremotoo, paoxiaomooo, Chieko-Seren, Senrian, Allen-Scai, 19santosh99, ymmmas, Risuntsy, Richard-Zhang1019, RafaelCaso, qingpeng9802, primexiao, Urbaner3, codetypess, nidhoggfgg, MwumLi, CreatorMetaSky, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Kunchen-Luo, Keynman and KeiichiKasai.

    The code was reviewed by coderonion, curtishd, Gonglja, gvenusleo, hpstory, justin-tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon, and rongyi, listed alphabetically. Thank you for the time and effort you devoted to ensuring consistent, well-written code across languages.

    The English edition was reviewed by yuelinxin, K3v123, magentaqin, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, and thomasq0; the Japanese edition by eltociear; the Russian edition by И. А. Шевкун and Yuyan Huang; and the Traditional Chinese edition by Shyam-Chen and Dr-XYZ. Their contributions have made the book accessible to a wider readership. Thank you all.

    The tool for generating the book's ePub edition was developed by zhongfq. Thank you for giving readers a more flexible way to read.

    Many people helped me while I was writing this book.

    • Thank you to my mentor at work, Dr. Xi Li. During a long conversation, your encouragement to “get started” strengthened my resolve to write this book;
    • Thank you to my girlfriend Paopao, the book's first reader. Your many suggestions from a beginner's perspective have made it more accessible to newcomers;
    • Thank you to Tengbao, Qibao, and Feibao for giving the book a creative name that brings back fond memories of writing our first line of code, “Hello World!”;
    • Thank you to Xiaoquan for your professional help with intellectual property, which played an important role in improving this open-source book;
    • Thank you to Sutong for designing the beautiful cover and logo, and for patiently revising them many times to satisfy my perfectionism;
    • Thank you to @squidfunk for your typography advice and for developing the open-source documentation theme Material-for-MkDocs.

    While writing, I read many textbooks and articles on data structures and algorithms. These works provided excellent models and helped ensure the accuracy and quality of this book. My thanks go to all the teachers and predecessors whose outstanding contributions made this possible!

    The book advocates learning through both thought and hands-on practice, an approach deeply inspired by Dive into Deep Learning. I warmly recommend this excellent book to every reader.

    My heartfelt thanks to my parents. Your constant support and encouragement have given me the opportunity to do this wonderfully engaging work.

    ","path":["Chapter 0.   Preface","0.1   About this book"],"tags":[]},{"location":"chapter_preface/suggestions/","level":1,"title":"0.2   How to use this book","text":"

    Tip

    For the best reading experience, we recommend reading this section in full.

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#021-writing-conventions","level":2,"title":"0.2.1   Writing conventions","text":"
    • Sections with * after the title are optional and cover more difficult material. If your time is limited, you can skip them at first.
    • Technical terms appear in bold in the print and PDF editions or are underlined in the web edition, such as array. Remembering these terms will help you read technical literature.
    • Key points and summary statements appear in bold and deserve particular attention.
    • Words and phrases used in a specific sense appear in “quotation marks” to avoid ambiguity.
    • When terminology differs between programming languages, this book follows Python; for example, it uses None to represent a null value.
    • For a more compact layout, this book sometimes departs from a language's usual commenting conventions. Comments fall into three main categories: heading comments, explanatory comments, and multiline comments.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    \"\"\"Heading comment identifying a function, class, test case, and so on\"\"\"\n\n# Explanatory comment describing the code in detail\n\n\"\"\"\nMultiline\ncomment\n\"\"\"\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n// Multiline\n// comment\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    ### Heading comment identifying a function, class, test case, and so on ###\n\n# Explanatory comment describing the code in detail\n\n# Multiline\n# comment\n
    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#022-learn-efficiently-with-animated-illustrations","level":2,"title":"0.2.2   Learn efficiently with animated illustrations","text":"

    Compared with text, videos and images convey information in a denser, more structured form that is easier to understand. In this book, key and difficult concepts are presented mainly through animated illustrations, with text providing explanations and additional detail.

    When a passage includes an animated illustration like the one below, focus on the illustration and use the text to support it, drawing on both to understand the material.

    Figure 0-2   An example of an animated illustration

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#023-deepen-your-understanding-through-coding","level":2,"title":"0.2.3   Deepen your understanding through coding","text":"

    The accompanying code is hosted in the GitHub repository. As Figure 0-3 shows, the source code includes test cases and can be run with one click.

    If time permits, we recommend typing out the code yourself while referring to the examples. If your study time is limited, at least read and run all the code.

    Writing code often teaches us more than reading it. Hands-on practice is where real learning happens.

    Figure 0-3   An example of running code

    Getting ready to run the code involves three main steps.

    Step 1: Set up a local programming environment. Follow the tutorial in the appendix. If your environment is already set up, skip this step.

    Step 2: Clone or download the repository. Visit the GitHub repository. If you have Git installed, clone the repository with the following command:

    git clone https://github.com/krahets/hello-algo.git\n

    Alternatively, click “Download ZIP” in the location shown below to download the code as an archive, then extract it on your computer.

    Figure 0-4   Cloning the repository and downloading the code

    Step 3: Run the source code. As shown below, when a code block has a filename at the top, you can find the corresponding source file in the repository's en/codes folder. These files can be run with one click, saving unnecessary debugging time so you can focus on learning.

    Figure 0-5   A code block labeled with its source filename

    In addition to running code locally, the web edition supports visual execution of Python code through pythontutor. Click “Visualize execution” beneath a code block to expand the view and watch the algorithm run. You can also click “View full screen” for a larger display, as shown in Figure 0-6.

    Figure 0-6   Python code visualized in PythonTutor

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#024-learn-together-through-questions-and-discussion","level":2,"title":"0.2.4   Learn together through questions and discussion","text":"

    As you read, do not simply skip concepts you have not understood. You are welcome to ask questions in the comments. My fellow contributors and I will do our best to answer, usually within two days.

    As shown below, every section in the web edition has a comments area at the bottom. I hope you will read it regularly. Other readers' questions can reveal gaps in your own understanding and encourage deeper thought. I also hope you will generously answer their questions, share your insights, and help them make progress.

    Figure 0-7   An example of the comments area

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#025-a-learning-path-for-algorithms","level":2,"title":"0.2.5   A learning path for algorithms","text":"

    Broadly speaking, learning data structures and algorithms can be divided into three stages.

    1. Stage 1: Learn the fundamentals. Become familiar with the properties and uses of data structures, and study how different algorithms work, their steps, applications, and efficiency.
    2. Stage 2: Solve practice problems. We recommend starting with popular problems and solving at least 100 to become familiar with common algorithmic problems. When you first begin, forgetting what you have learned may be a challenge, but this is normal. Review problems according to the “Ebbinghaus forgetting curve”; after 3–5 rounds of repetition, they will usually be firmly in memory. See this GitHub repository for recommended problem lists and practice plans.
    3. Stage 3: Build a systematic understanding. Read algorithm articles, problem-solving frameworks, and textbooks to keep expanding your knowledge. In your practice, try more advanced strategies: group problems by topic, solve one problem in several ways, or apply one solution to several problems. You can find advice on these strategies in various communities.

    As Figure 0-8 shows, this book mainly covers “Stage 1,” helping you approach Stages 2 and 3 more efficiently.

    Figure 0-8   A learning path for algorithms

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/summary/","level":1,"title":"0.3   Summary","text":"","path":["Chapter 0.   Preface","0.3   Summary"],"tags":[]},{"location":"chapter_preface/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • This book is primarily for beginners in algorithms. If you already have some background, it can help you review your knowledge systematically, and its source code can serve as a toolkit for solving practice problems.
    • The book has three main parts—complexity analysis, data structures, and algorithms—covering most topics in the field.
    • For beginners, reading an introductory book early on is essential and can help avoid many detours.
    • The animated illustrations usually explain key or challenging concepts. Pay particular attention to them as you read.
    • Practice is the best way to learn programming. We strongly recommend running the source code and writing it out yourself.
    • Every section in the web edition has a comments area. You are welcome to share your questions and insights at any time.
    ","path":["Chapter 0.   Preface","0.3   Summary"],"tags":[]},{"location":"chapter_reference/","level":1,"title":"References","text":"

    [1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition).

    [2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition).

    [3] Robert Sedgewick, et al. Algorithms (4th Edition).

    [4] Weimin Yan. Data Structures (C Edition).

    [5] Junhui Deng. Data Structures (C++ Edition, 3rd Edition).

    [6] Mark Allen Weiss; translated by Yue Chen. Data Structures and Algorithm Analysis in Java (3rd Edition).

    [7] Jie Cheng. A Friendly Guide to Data Structures.

    [8] Zheng Wang. The Beauty of Data Structures and Algorithms.

    [9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).

    [10] Aston Zhang, et al. Dive into Deep Learning.

    ","path":["References"],"tags":[]},{"location":"chapter_searching/","level":1,"title":"Chapter 10.   Searching","text":"

    Abstract

    Searching is an adventure into the unknown. We may need to explore every corner of a mysterious space, or we may be able to pinpoint our target quickly.

    On this journey, every exploration may reveal an unexpected answer.

    ","path":["Chapter 10.   Searching"],"tags":[]},{"location":"chapter_searching/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 10.1   Binary search
    • 10.2   Finding an insertion point with binary search
    • 10.3   Finding boundaries with binary search
    • 10.4   Optimizing with hashing
    • 10.5   Search algorithms revisited
    • 10.6   Summary
    • 10.7   Exercises
    ","path":["Chapter 10.   Searching"],"tags":[]},{"location":"chapter_searching/binary_search/","level":1,"title":"10.1   Binary search","text":"

    Binary search is an efficient search algorithm based on divide and conquer. It uses the sorted order of the data to halve the search range each round, continuing until it finds the target element or the search interval becomes empty.

    Question

    Given an array nums of length \\(n\\), with distinct elements arranged in ascending order, find and return the index of target. If the array does not contain this element, return \\(-1\\). An example is shown below.

    Figure 10-1   Example data for binary search

    As shown below, initialize pointers \\(i = 0\\) and \\(j = n - 1\\) to the first and last elements, respectively. They represent the search interval \\([0, n - 1]\\). Square brackets denote a closed interval, which includes its endpoints.

    Next, repeat the following two steps.

    1. Calculate the midpoint index \\(m = \\lfloor {(i + j) / 2} \\rfloor\\), where \\(\\lfloor \\: \\rfloor\\) denotes rounding down.
    2. Compare nums[m] with target. There are three cases.
      1. If nums[m] < target, then target lies in \\([m + 1, j]\\), so set \\(i = m + 1\\).
      2. If nums[m] > target, then target lies in \\([i, m - 1]\\), so set \\(j = m - 1\\).
      3. If nums[m] = target, we have found target, so return index \\(m\\).

    If the array does not contain the target, the search interval eventually becomes empty. Return \\(-1\\) in this case.

    <1><2><3><4><5><6><7>

    Figure 10-2   The binary search process

    Because \\(i\\) and \\(j\\) are both of type int, \\(i + j\\) may exceed the range of int. To avoid integer overflow, we usually calculate the midpoint using \\(m = \\lfloor {i + (j - i) / 2} \\rfloor\\).

    The implementation is shown below:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py
    def binary_search(nums: list[int], target: int) -> int:\n    \"\"\"Binary search (closed interval)\"\"\"\n    # Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    i, j = 0, len(nums) - 1\n    # Loop until the search interval is empty (when i > j)\n    while i <= j:\n        # Python integers can grow arbitrarily large, subject to available memory, so integer overflow is not a concern\n        m = (i + j) // 2  # Calculate the midpoint index m\n        if nums[m] < target:\n            i = m + 1  # In this case, target must be in [m+1, j] if it exists\n        elif nums[m] > target:\n            j = m - 1  # In this case, target must be in [i, m-1] if it exists\n        else:\n            return m  # The target element was found; return its index\n    return -1  # The target element was not found; return -1\n
    binary_search.cpp
    /* Binary search (closed interval) */\nint binarySearch(vector<int> &nums, int target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    int i = 0, j = nums.size() - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.java
    /* Binary search (closed interval) */\nint binarySearch(int[] nums, int target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    int i = 0, j = nums.length - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.cs
    /* Binary search (closed interval) */\nint BinarySearch(int[] nums, int target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    int i = 0, j = nums.Length - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2;   // Calculate the midpoint index m\n        if (nums[m] < target)      // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else                       // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.go
    /* Binary search (closed interval) */\nfunc binarySearch(nums []int, target int) int {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    i, j := 0, len(nums)-1\n    // Loop until the search interval is empty (when i > j)\n    for i <= j {\n        m := i + (j-i)/2      // Calculate the midpoint index m\n        if nums[m] < target { // In this case, target must be in [m+1, j] if it exists\n            i = m + 1\n        } else if nums[m] > target { // In this case, target must be in [i, m-1] if it exists\n            j = m - 1\n        } else { // The target element was found; return its index\n            return m\n        }\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.swift
    /* Binary search (closed interval) */\nfunc binarySearch(nums: [Int], target: Int) -> Int {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    // Loop until the search interval is empty (when i > j)\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target { // In this case, target must be in [m+1, j] if it exists\n            i = m + 1\n        } else if nums[m] > target { // In this case, target must be in [i, m-1] if it exists\n            j = m - 1\n        } else { // The target element was found; return its index\n            return m\n        }\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.js
    /* Binary search (closed interval) */\nfunction binarySearch(nums, target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    let i = 0,\n        j = nums.length - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        // Calculate midpoint index m, using parseInt() to round down\n        const m = parseInt(i + (j - i) / 2);\n        if (nums[m] < target)\n            // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target)\n            // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else return m; // The target element was found; return its index\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.ts
    /* Binary search (closed interval) */\nfunction binarySearch(nums: number[], target: number): number {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    let i = 0,\n        j = nums.length - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        // Calculate the midpoint index m\n        const m = Math.floor(i + (j - i) / 2);\n        if (nums[m] < target) {\n            // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        } else if (nums[m] > target) {\n            // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        } else {\n            // The target element was found; return its index\n            return m;\n        }\n    }\n    return -1; // The target element was not found; return -1\n}\n
    binary_search.dart
    /* Binary search (closed interval) */\nint binarySearch(List<int> nums, int target) {\n  // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n  int i = 0, j = nums.length - 1;\n  // Loop until the search interval is empty (when i > j)\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      // In this case, target must be in [m+1, j] if it exists\n      i = m + 1;\n    } else if (nums[m] > target) {\n      // In this case, target must be in [i, m-1] if it exists\n      j = m - 1;\n    } else {\n      // The target element was found; return its index\n      return m;\n    }\n  }\n  // The target element was not found; return -1\n  return -1;\n}\n
    binary_search.rs
    /* Binary search (closed interval) */\nfn binary_search(nums: &[i32], target: i32) -> i32 {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    let mut i = 0;\n    let mut j = nums.len() as i32 - 1;\n    // Loop until the search interval is empty (when i > j)\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        } else if nums[m as usize] > target {\n            // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        } else {\n            // The target element was found; return its index\n            return m;\n        }\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.c
    /* Binary search (closed interval) */\nint binarySearch(int *nums, int len, int target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    int i = 0, j = len - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.kt
    /* Binary search (closed interval) */\nfun binarySearch(nums: IntArray, target: Int): Int {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    var i = 0\n    var j = nums.size - 1\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) // In this case, target must be in [m+1, j] if it exists\n            i = m + 1\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1\n        else  // The target element was found; return its index\n            return m\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.rb
    ### Binary search (closed interval) ###\ndef binary_search(nums, target)\n  # Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n  i, j = 0, nums.length - 1\n\n  # Loop until the search interval is empty (when i > j)\n  while i <= j\n    # Ruby integers can grow arbitrarily large, limited by available memory, so integer overflow is not a concern here\n    m = (i + j) / 2   # Calculate the midpoint index m\n\n    if nums[m] < target\n      i = m + 1 # In this case, target must be in [m+1, j] if it exists\n    elsif nums[m] > target\n      j = m - 1 # In this case, target must be in [i, m-1] if it exists\n    else\n      return m  # The target element was found; return its index\n    end\n  end\n\n  -1  # The target element was not found; return -1\nend\n
    Visualize execution

    View full screen >

    Time complexity is \\(O(\\log n)\\): the search interval is halved each round, so the loop runs \\(\\log_2 n\\) times.

    Space complexity is \\(O(1)\\): pointers \\(i\\) and \\(j\\) use constant space.

    ","path":["Chapter 10.   Searching","10.1   Binary search"],"tags":[]},{"location":"chapter_searching/binary_search/#1011-ways-to-represent-the-interval","level":2,"title":"10.1.1   Ways to represent the interval","text":"

    Besides the closed interval above, another common representation is a “left-closed, right-open” interval, written \\([0, n)\\). It includes the left endpoint but excludes the right endpoint. With this representation, \\([i, j)\\) is empty when \\(i = j\\).

    We can implement an equivalent binary search algorithm using this representation:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py
    def binary_search_lcro(nums: list[int], target: int) -> int:\n    \"\"\"Binary search (left-closed, right-open interval)\"\"\"\n    # Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    i, j = 0, len(nums)\n    # Loop until the search interval is empty (when i = j)\n    while i < j:\n        m = (i + j) // 2  # Calculate the midpoint index m\n        if nums[m] < target:\n            i = m + 1  # In this case, target must be in [m+1, j) if it exists\n        elif nums[m] > target:\n            j = m  # In this case, target must be in [i, m) if it exists\n        else:\n            return m  # The target element was found; return its index\n    return -1  # The target element was not found; return -1\n
    binary_search.cpp
    /* Binary search (left-closed, right-open interval) */\nint binarySearchLCRO(vector<int> &nums, int target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    int i = 0, j = nums.size();\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.java
    /* Binary search (left-closed, right-open interval) */\nint binarySearchLCRO(int[] nums, int target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    int i = 0, j = nums.length;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.cs
    /* Binary search (left-closed, right-open interval) */\nint BinarySearchLCRO(int[] nums, int target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    int i = 0, j = nums.Length;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2;   // Calculate the midpoint index m\n        if (nums[m] < target)      // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m;\n        else                       // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.go
    /* Binary search (left-closed, right-open interval) */\nfunc binarySearchLCRO(nums []int, target int) int {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    i, j := 0, len(nums)\n    // Loop until the search interval is empty (when i = j)\n    for i < j {\n        m := i + (j-i)/2      // Calculate the midpoint index m\n        if nums[m] < target { // In this case, target must be in [m+1, j) if it exists\n            i = m + 1\n        } else if nums[m] > target { // In this case, target must be in [i, m) if it exists\n            j = m\n        } else { // The target element was found; return its index\n            return m\n        }\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.swift
    /* Binary search (left-closed, right-open interval) */\nfunc binarySearchLCRO(nums: [Int], target: Int) -> Int {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    var i = nums.startIndex\n    var j = nums.endIndex\n    // Loop until the search interval is empty (when i = j)\n    while i < j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target { // In this case, target must be in [m+1, j) if it exists\n            i = m + 1\n        } else if nums[m] > target { // In this case, target must be in [i, m) if it exists\n            j = m\n        } else { // The target element was found; return its index\n            return m\n        }\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.js
    /* Binary search (left-closed, right-open interval) */\nfunction binarySearchLCRO(nums, target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    let i = 0,\n        j = nums.length;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        // Calculate midpoint index m, using parseInt() to round down\n        const m = parseInt(i + (j - i) / 2);\n        if (nums[m] < target)\n            // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target)\n            // In this case, target must be in [i, m) if it exists\n            j = m;\n        // The target element was found; return its index\n        else return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.ts
    /* Binary search (left-closed, right-open interval) */\nfunction binarySearchLCRO(nums: number[], target: number): number {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    let i = 0,\n        j = nums.length;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        // Calculate the midpoint index m\n        const m = Math.floor(i + (j - i) / 2);\n        if (nums[m] < target) {\n            // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        } else if (nums[m] > target) {\n            // In this case, target must be in [i, m) if it exists\n            j = m;\n        } else {\n            // The target element was found; return its index\n            return m;\n        }\n    }\n    return -1; // The target element was not found; return -1\n}\n
    binary_search.dart
    /* Binary search (left-closed, right-open interval) */\nint binarySearchLCRO(List<int> nums, int target) {\n  // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n  int i = 0, j = nums.length;\n  // Loop until the search interval is empty (when i = j)\n  while (i < j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      // In this case, target must be in [m+1, j) if it exists\n      i = m + 1;\n    } else if (nums[m] > target) {\n      // In this case, target must be in [i, m) if it exists\n      j = m;\n    } else {\n      // The target element was found; return its index\n      return m;\n    }\n  }\n  // The target element was not found; return -1\n  return -1;\n}\n
    binary_search.rs
    /* Binary search (left-closed, right-open interval) */\nfn binary_search_lcro(nums: &[i32], target: i32) -> i32 {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    let mut i = 0;\n    let mut j = nums.len() as i32;\n    // Loop until the search interval is empty (when i = j)\n    while i < j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        } else if nums[m as usize] > target {\n            // In this case, target must be in [i, m) if it exists\n            j = m;\n        } else {\n            // The target element was found; return its index\n            return m;\n        }\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.c
    /* Binary search (left-closed, right-open interval) */\nint binarySearchLCRO(int *nums, int len, int target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    int i = 0, j = len;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.kt
    /* Binary search (left-closed, right-open interval) */\nfun binarySearchLCRO(nums: IntArray, target: Int): Int {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    var i = 0\n    var j = nums.size\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) // In this case, target must be in [m+1, j) if it exists\n            i = m + 1\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m\n        else  // The target element was found; return its index\n            return m\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.rb
    ### Binary search (left-closed, right-open interval) ###\ndef binary_search_lcro(nums, target)\n  # Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n  i, j = 0, nums.length\n\n  # Loop until the search interval is empty (when i = j)\n  while i < j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # In this case, target must be in [m+1, j) if it exists\n    elsif nums[m] > target\n      j = m - 1 # In this case, target must be in [i, m) if it exists\n    else\n      return m  # The target element was found; return its index\n    end\n  end\n\n  -1  # The target element was not found; return -1\nend\n
    Visualize execution

    View full screen >

    As shown below, the two interval representations require different initialization, loop conditions, and operations for narrowing the interval.

    Since a closed interval includes both endpoints, the operations that narrow the interval through pointers \\(i\\) and \\(j\\) are symmetric. This makes mistakes less likely, so we generally recommend the closed-interval version.

    Figure 10-3   Two ways to define the interval

    ","path":["Chapter 10.   Searching","10.1   Binary search"],"tags":[]},{"location":"chapter_searching/binary_search/#1012-strengths-and-limitations","level":2,"title":"10.1.2   Strengths and limitations","text":"

    Binary search performs well in both time and space.

    • Binary search is time-efficient. Its logarithmic time complexity offers a significant advantage for large datasets. For example, when \\(n = 2^{20}\\), linear search requires \\(2^{20} = 1048576\\) iterations, whereas binary search needs only \\(\\log_2 2^{20} = 20\\).
    • Binary search requires no extra storage. It uses less space than search algorithms that rely on additional storage, such as hash lookup.

    However, binary search is not suitable in every situation, mainly for the following reasons.

    • Binary search requires sorted data. Sorting unsorted input solely to perform a binary search is not worthwhile, because sorting usually takes \\(O(n \\log n)\\) time, more than either linear or binary search. When insertions are frequent, preserving the array's order requires inserting each element at a specific position, an expensive \\(O(n)\\) operation.
    • Binary search is suitable only for arrays. It needs to jump between nonadjacent elements, which is inefficient in a linked list. It is therefore unsuitable for linked lists and data structures based on them.
    • Linear search performs better on small datasets. Each iteration of linear search needs only one comparison. Binary search needs one addition, one division, 1–3 comparisons, and one more addition or subtraction, totaling 4–6 elementary operations. When \\(n\\) is small, linear search can therefore be faster.
    ","path":["Chapter 10.   Searching","10.1   Binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/","level":1,"title":"10.3   Finding boundaries with binary search","text":"","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1031-finding-the-left-boundary","level":2,"title":"10.3.1   Finding the left boundary","text":"

    Question

    Given a sorted array nums of length \\(n\\) that may contain duplicate elements, return the index of the leftmost occurrence of target. If the array does not contain this element, return \\(-1\\).

    Recall the method for finding an insertion point with binary search: when the search finishes, \\(i\\) points to the leftmost target. Finding the insertion point therefore amounts to finding the index of the leftmost target.

    We can use the insertion-point function to find the left boundary. However, target may be absent from the array, leading to either of the following outcomes.

    • The insertion index \\(i\\) is out of bounds.
    • nums[i] is not equal to target.

    In either case, simply return \\(-1\\). The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py
    def binary_search_left_edge(nums: list[int], target: int) -> int:\n    \"\"\"Find the leftmost occurrence of target using binary search\"\"\"\n    # This is equivalent to finding the insertion point of target\n    i = binary_search_insertion(nums, target)\n    # If target was not found, return -1\n    if i == len(nums) or nums[i] != target:\n        return -1\n    # Target was found; return index i\n    return i\n
    binary_search_edge.cpp
    /* Find the leftmost occurrence of target using binary search */\nint binarySearchLeftEdge(vector<int> &nums, int target) {\n    // This is equivalent to finding the insertion point of target\n    int i = binarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i == nums.size() || nums[i] != target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.java
    /* Find the leftmost occurrence of target using binary search */\nint binarySearchLeftEdge(int[] nums, int target) {\n    // This is equivalent to finding the insertion point of target\n    int i = binary_search_insertion.binarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i == nums.length || nums[i] != target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.cs
    /* Find the leftmost occurrence of target using binary search */\nint BinarySearchLeftEdge(int[] nums, int target) {\n    // This is equivalent to finding the insertion point of target\n    int i = binary_search_insertion.BinarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i == nums.Length || nums[i] != target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.go
    /* Find the leftmost occurrence of target using binary search */\nfunc binarySearchLeftEdge(nums []int, target int) int {\n    // This is equivalent to finding the insertion point of target\n    i := binarySearchInsertion(nums, target)\n    // If target was not found, return -1\n    if i == len(nums) || nums[i] != target {\n        return -1\n    }\n    // Target was found; return index i\n    return i\n}\n
    binary_search_edge.swift
    /* Find the leftmost occurrence of target using binary search */\nfunc binarySearchLeftEdge(nums: [Int], target: Int) -> Int {\n    // This is equivalent to finding the insertion point of target\n    let i = binarySearchInsertion(nums: nums, target: target)\n    // If target was not found, return -1\n    if i == nums.endIndex || nums[i] != target {\n        return -1\n    }\n    // Target was found; return index i\n    return i\n}\n
    binary_search_edge.js
    /* Find the leftmost occurrence of target using binary search */\nfunction binarySearchLeftEdge(nums, target) {\n    // This is equivalent to finding the insertion point of target\n    const i = binarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i === nums.length || nums[i] !== target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.ts
    /* Find the leftmost occurrence of target using binary search */\nfunction binarySearchLeftEdge(nums: Array<number>, target: number): number {\n    // This is equivalent to finding the insertion point of target\n    const i = binarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i === nums.length || nums[i] !== target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.dart
    /* Find the leftmost occurrence of target using binary search */\nint binarySearchLeftEdge(List<int> nums, int target) {\n  // This is equivalent to finding the insertion point of target\n  int i = binarySearchInsertion(nums, target);\n  // If target was not found, return -1\n  if (i == nums.length || nums[i] != target) {\n    return -1;\n  }\n  // Target was found; return index i\n  return i;\n}\n
    binary_search_edge.rs
    /* Find the leftmost occurrence of target using binary search */\nfn binary_search_left_edge(nums: &[i32], target: i32) -> i32 {\n    // This is equivalent to finding the insertion point of target\n    let i = binary_search_insertion(nums, target);\n    // If target was not found, return -1\n    if i == nums.len() as i32 || nums[i as usize] != target {\n        return -1;\n    }\n    // Target was found; return index i\n    i\n}\n
    binary_search_edge.c
    /* Find the leftmost occurrence of target using binary search */\nint binarySearchLeftEdge(int *nums, int numSize, int target) {\n    // This is equivalent to finding the insertion point of target\n    int i = binarySearchInsertion(nums, numSize, target);\n    // If target was not found, return -1\n    if (i == numSize || nums[i] != target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.kt
    /* Find the leftmost occurrence of target using binary search */\nfun binarySearchLeftEdge(nums: IntArray, target: Int): Int {\n    // This is equivalent to finding the insertion point of target\n    val i = binarySearchInsertion(nums, target)\n    // If target was not found, return -1\n    if (i == nums.size || nums[i] != target) {\n        return -1\n    }\n    // Target was found; return index i\n    return i\n}\n
    binary_search_edge.rb
    ### Find the leftmost occurrence of target using binary search ###\ndef binary_search_left_edge(nums, target)\n  # This is equivalent to finding the insertion point of target\n  i = binary_search_insertion(nums, target)\n\n  # If target was not found, return -1\n  return -1 if i == nums.length || nums[i] != target\n\n  i # Target was found; return index i\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1032-finding-the-right-boundary","level":2,"title":"10.3.2   Finding the right boundary","text":"

    How can we find the rightmost target? The most direct approach is to modify the pointer update for nums[m] == target. We omit the code here; interested readers can implement it themselves.

    Here are two shortcuts.

    ","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1-reusing-the-left-boundary-search","level":3,"title":"1.   Reusing the left-boundary search","text":"

    We can use the function for finding the leftmost element to find the rightmost one: turn the search for the rightmost target into a search for the leftmost target + 1.

    As shown below, when the search finishes, pointer \\(i\\) points to the leftmost target + 1, if it exists, while \\(j\\) points to the rightmost target. We therefore return \\(j\\).

    Figure 10-7   Turning a right-boundary search into a left-boundary search

    The returned insertion point is \\(i\\), so subtract \\(1\\) to obtain \\(j\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py
    def binary_search_right_edge(nums: list[int], target: int) -> int:\n    \"\"\"Find the rightmost occurrence of target using binary search\"\"\"\n    # Reduce the problem to finding the leftmost insertion point of target + 1\n    i = binary_search_insertion(nums, target + 1)\n    # j is the candidate rightmost target index; i is the index of the first element greater than target, or len(nums)\n    j = i - 1\n    # If target was not found, return -1\n    if j == -1 or nums[j] != target:\n        return -1\n    # Target was found; return index j\n    return j\n
    binary_search_edge.cpp
    /* Find the rightmost occurrence of target using binary search */\nint binarySearchRightEdge(vector<int> &nums, int target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    int i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    int j = i - 1;\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.java
    /* Find the rightmost occurrence of target using binary search */\nint binarySearchRightEdge(int[] nums, int target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    int i = binary_search_insertion.binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    int j = i - 1;\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.cs
    /* Find the rightmost occurrence of target using binary search */\nint BinarySearchRightEdge(int[] nums, int target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1);\n    // j is the candidate index of the rightmost target; i is the insertion point after all elements <= target\n    int j = i - 1;\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.go
    /* Find the rightmost occurrence of target using binary search */\nfunc binarySearchRightEdge(nums []int, target int) int {\n    // For integer values, find the leftmost insertion point of target + 1 (assuming no overflow)\n    i := binarySearchInsertion(nums, target+1)\n    // i is the index of the first element greater than target (or len(nums)); j is the candidate index of the rightmost target\n    j := i - 1\n    // If target was not found, return -1\n    if j == -1 || nums[j] != target {\n        return -1\n    }\n    // Target was found; return index j\n    return j\n}\n
    binary_search_edge.swift
    /* Find the rightmost occurrence of target using binary search */\nfunc binarySearchRightEdge(nums: [Int], target: Int) -> Int {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    let i = binarySearchInsertion(nums: nums, target: target + 1)\n    // i is the first index with a value greater than target (or nums.endIndex); j is the candidate index of the rightmost target\n    let j = i - 1\n    // If target was not found, return -1\n    if j == -1 || nums[j] != target {\n        return -1\n    }\n    // Target was found; return index j\n    return j\n}\n
    binary_search_edge.js
    /* Find the rightmost occurrence of target using binary search */\nfunction binarySearchRightEdge(nums, target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    const i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    const j = i - 1;\n    // If target was not found, return -1\n    if (j === -1 || nums[j] !== target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.ts
    /* Find the rightmost occurrence of target using binary search */\nfunction binarySearchRightEdge(nums: Array<number>, target: number): number {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    const i = binarySearchInsertion(nums, target + 1);\n    // For integer elements, i is the first index greater than target (or the array length); j is the candidate rightmost target index\n    const j = i - 1;\n    // If target was not found, return -1\n    if (j === -1 || nums[j] !== target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.dart
    /* Find the rightmost occurrence of target using binary search */\nint binarySearchRightEdge(List<int> nums, int target) {\n  // Reduce the problem to finding the leftmost insertion point of target + 1\n  int i = binarySearchInsertion(nums, target + 1);\n  // i is the first index greater than target (or the array length); j is the candidate rightmost target index\n  int j = i - 1;\n  // If target was not found, return -1\n  if (j == -1 || nums[j] != target) {\n    return -1;\n  }\n  // Target was found; return index j\n  return j;\n}\n
    binary_search_edge.rs
    /* Find the rightmost occurrence of target using binary search */\nfn binary_search_right_edge(nums: &[i32], target: i32) -> i32 {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    let i = binary_search_insertion(nums, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    let j = i - 1;\n    // If target was not found, return -1\n    if j == -1 || nums[j as usize] != target {\n        return -1;\n    }\n    // Target was found; return index j\n    j\n}\n
    binary_search_edge.c
    /* Find the rightmost occurrence of target using binary search */\nint binarySearchRightEdge(int *nums, int numSize, int target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    int i = binarySearchInsertion(nums, numSize, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    int j = i - 1;\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.kt
    /* Find the rightmost occurrence of target using binary search */\nfun binarySearchRightEdge(nums: IntArray, target: Int): Int {\n    // For integer values, find the leftmost insertion point of target + 1 (assuming no overflow)\n    val i = binarySearchInsertion(nums, target + 1)\n    // i is the index of the first element greater than target (or the array length); j is the candidate rightmost target index\n    val j = i - 1\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1\n    }\n    // Target was found; return index j\n    return j\n}\n
    binary_search_edge.rb
    ### Find the rightmost occurrence of target using binary search ###\ndef binary_search_right_edge(nums, target)\n  # For integer elements, find the leftmost insertion point of target + 1\n  i = binary_search_insertion(nums, target + 1)\n\n  # i is the index of the first element greater than target (or the array length); j is the candidate rightmost target index\n  j = i - 1\n\n  # If target was not found, return -1\n  return -1 if j == -1 || nums[j] != target\n\n  j # Target was found; return index j\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#2-turning-a-boundary-search-into-an-element-search","level":3,"title":"2.   Turning a boundary search into an element search","text":"

    When the array does not contain target, \\(i\\) ultimately points to the first element greater than target, while \\(j\\) points to the rightmost element smaller than target.

    As shown below, we can therefore construct a value absent from the array and use it to find the left and right boundaries.

    • To find the leftmost target, search for target - 0.5 and return pointer \\(i\\).
    • To find the rightmost target, search for target + 0.5 and return pointer \\(j\\).

    Figure 10-8   Turning a boundary search into an element search

    The code is omitted here, but keep two points in mind.

    • The given array contains no fractional values, so we do not need to handle equality.
    • Since this method introduces fractional values, change the function's target variable to a floating-point type. No change is needed in Python.
    ","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/","level":1,"title":"10.2   Finding an insertion point with binary search","text":"

    Binary search can do more than find a target element. It also solves many variations of the problem, such as finding where to insert a target.

    ","path":["Chapter 10.   Searching","10.2   Finding an insertion point with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1021-when-there-are-no-duplicates","level":2,"title":"10.2.1   When there are no duplicates","text":"

    Question

    Given a sorted array nums of length \\(n\\) with no duplicate elements and a value target, insert target into nums while preserving its order. If target is already present, insert the new value to its left. Return the index of the newly inserted target, as illustrated below.

    Figure 10-4   Example data for finding an insertion point

    To reuse the binary search code from the previous section, we need to answer two questions.

    Question 1: If the array contains target, is the insertion index the index of that element?

    The problem asks us to insert target to the left of the equal element, so the new target takes the original one's position. Thus, if the array contains target, the insertion index is the index of that target.

    Question 2: If the array does not contain target, which index is the insertion point?

    Consider the search process more closely. Pointer \\(i\\) moves when nums[m] < target, so \\(i\\) approaches elements greater than or equal to target. Similarly, pointer \\(j\\) keeps approaching elements less than or equal to target.

    When the search ends, \\(i\\) points to the first element greater than target, and \\(j\\) points to the rightmost element smaller than target. Thus, when target is absent, the insertion index is \\(i\\). The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py
    def binary_search_insertion_simple(nums: list[int], target: int) -> int:\n    \"\"\"Find an insertion point using binary search (no duplicates)\"\"\"\n    i, j = 0, len(nums) - 1  # Initialize the closed interval [0, n-1]\n    while i <= j:\n        m = (i + j) // 2  # Calculate the midpoint index m\n        if nums[m] < target:\n            i = m + 1  # Target must be in [m+1, j] if it exists\n        elif nums[m] > target:\n            j = m - 1  # Target must be in [i, m-1] if it exists\n        else:\n            return m  # Target was found; return insertion point m\n    # Target was not found; return insertion point i\n    return i\n
    binary_search_insertion.cpp
    /* Find an insertion point using binary search (no duplicates) */\nint binarySearchInsertionSimple(vector<int> &nums, int target) {\n    int i = 0, j = nums.size() - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.java
    /* Find an insertion point using binary search (no duplicates) */\nint binarySearchInsertionSimple(int[] nums, int target) {\n    int i = 0, j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.cs
    /* Find an insertion point using binary search (no duplicates) */\nint BinarySearchInsertionSimple(int[] nums, int target) {\n    int i = 0, j = nums.Length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.go
    /* Find an insertion point using binary search (no duplicates) */\nfunc binarySearchInsertionSimple(nums []int, target int) int {\n    // Initialize the closed interval [0, n-1]\n    i, j := 0, len(nums)-1\n    for i <= j {\n        // Calculate the midpoint index m\n        m := i + (j-i)/2\n        if nums[m] < target {\n            // Target must be in [m+1, j] if it exists\n            i = m + 1\n        } else if nums[m] > target {\n            // Target must be in [i, m-1] if it exists\n            j = m - 1\n        } else {\n            // Target was found; return insertion point m\n            return m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i\n}\n
    binary_search_insertion.swift
    /* Find an insertion point using binary search (no duplicates) */\nfunc binarySearchInsertionSimple(nums: [Int], target: Int) -> Int {\n    // Initialize the closed interval [0, n-1]\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target {\n            i = m + 1 // Target must be in [m+1, j] if it exists\n        } else if nums[m] > target {\n            j = m - 1 // Target must be in [i, m-1] if it exists\n        } else {\n            return m // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i\n}\n
    binary_search_insertion.js
    /* Find an insertion point using binary search (no duplicates) */\nfunction binarySearchInsertionSimple(nums, target) {\n    let i = 0,\n        j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, using Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.ts
    /* Find an insertion point using binary search (no duplicates) */\nfunction binarySearchInsertionSimple(\n    nums: Array<number>,\n    target: number\n): number {\n    let i = 0,\n        j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, using Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.dart
    /* Find an insertion point using binary search (no duplicates) */\nint binarySearchInsertionSimple(List<int> nums, int target) {\n  int i = 0, j = nums.length - 1; // Initialize the closed interval [0, n-1]\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      i = m + 1; // Target must be in [m+1, j] if it exists\n    } else if (nums[m] > target) {\n      j = m - 1; // Target must be in [i, m-1] if it exists\n    } else {\n      return m; // Target was found; return insertion point m\n    }\n  }\n  // Target was not found; return insertion point i\n  return i;\n}\n
    binary_search_insertion.rs
    /* Find an insertion point using binary search (no duplicates) */\nfn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 {\n    let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize the closed interval [0, n-1]\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if nums[m as usize] > target {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m;\n        }\n    }\n    // Target was not found; return insertion point i\n    i\n}\n
    binary_search_insertion.c
    /* Find an insertion point using binary search (no duplicates) */\nint binarySearchInsertionSimple(int *nums, int numSize, int target) {\n    int i = 0, j = numSize - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.kt
    /* Find an insertion point using binary search (no duplicates) */\nfun binarySearchInsertionSimple(nums: IntArray, target: Int): Int {\n    var i = 0\n    var j = nums.size - 1 // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1 // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1 // Target must be in [i, m-1] if it exists\n        } else {\n            return m // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i\n}\n
    binary_search_insertion.rb
    ### Find an insertion point using binary search (no duplicates) ###\ndef binary_search_insertion_simple(nums, target)\n  # Initialize the closed interval [0, n-1]\n  i, j = 0, nums.length - 1\n\n  while i <= j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # Target must be in [m+1, j] if it exists\n    elsif nums[m] > target\n      j = m - 1 # Target must be in [i, m-1] if it exists\n    else\n      return m  # Target was found; return insertion point m\n    end\n  end\n\n  i # Target was not found; return insertion point i\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 10.   Searching","10.2   Finding an insertion point with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1022-when-duplicates-are-present","level":2,"title":"10.2.2   When duplicates are present","text":"

    Question

    Modify the previous problem to allow duplicate elements in the array. All other requirements remain the same.

    If the array contains multiple occurrences of target, ordinary binary search can return the index of one occurrence, but cannot determine how many more occurrences lie to its left or right.

    The target must be inserted as far left as possible, so we need the index of the leftmost target. One initial approach follows the steps below.

    1. Run binary search to find the index of any target, denoted by \\(k\\).
    2. Starting at index \\(k\\), scan leftward and return when the leftmost target is reached.

    Figure 10-5   Finding the insertion point among duplicates with a linear scan

    Although this works, it includes a linear search and therefore takes \\(O(n)\\) time. It is inefficient when the array contains many occurrences of target.

    Now consider extending binary search itself. As shown below, the overall process remains the same: each round calculates midpoint index \\(m\\) and compares target with nums[m]. The cases are as follows.

    • If nums[m] < target or nums[m] > target, we have not found target. Narrow the interval as in ordinary binary search, bringing pointers \\(i\\) and \\(j\\) closer to target.
    • If nums[m] == target, elements smaller than target lie in \\([i, m - 1]\\). Narrow the interval by setting \\(j = m - 1\\), bringing pointer \\(j\\) closer to elements smaller than target.

    When the loop finishes, \\(i\\) points to the leftmost target, and \\(j\\) points to the rightmost element smaller than target. Index \\(i\\) is therefore the insertion point.

    <1><2><3><4><5><6><7><8>

    Figure 10-6   Steps for finding an insertion point among duplicates

    In the code below, the branches for nums[m] > target and nums[m] == target perform the same operation, so they can be combined.

    Even so, we can leave them separate to make the logic clearer and easier to read.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py
    def binary_search_insertion(nums: list[int], target: int) -> int:\n    \"\"\"Find an insertion point using binary search (with duplicates)\"\"\"\n    i, j = 0, len(nums) - 1  # Initialize the closed interval [0, n-1]\n    while i <= j:\n        m = (i + j) // 2  # Calculate the midpoint index m\n        if nums[m] < target:\n            i = m + 1  # Target must be in [m+1, j] if it exists\n        elif nums[m] > target:\n            j = m - 1  # Target must be in [i, m-1] if it exists\n        else:\n            j = m - 1  # Continue in [i, m-1] to find the rightmost element smaller than target\n    # Return insertion point i\n    return i\n
    binary_search_insertion.cpp
    /* Find an insertion point using binary search (with duplicates) */\nint binarySearchInsertion(vector<int> &nums, int target) {\n    int i = 0, j = nums.size() - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue searching [i, m-1] for the insertion point before any equal elements\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.java
    /* Find an insertion point using binary search (with duplicates) */\nint binarySearchInsertion(int[] nums, int target) {\n    int i = 0, j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.cs
    /* Find an insertion point using binary search (with duplicates) */\nint BinarySearchInsertion(int[] nums, int target) {\n    int i = 0, j = nums.Length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.go
    /* Find an insertion point using binary search (with duplicates) */\nfunc binarySearchInsertion(nums []int, target int) int {\n    // Initialize the closed interval [0, n-1]\n    i, j := 0, len(nums)-1\n    for i <= j {\n        // Calculate the midpoint index m\n        m := i + (j-i)/2\n        if nums[m] < target {\n            // Target must be in [m+1, j] if it exists\n            i = m + 1\n        } else if nums[m] > target {\n            // Target must be in [i, m-1] if it exists\n            j = m - 1\n        } else {\n            // Continue in [i, m-1] to find the rightmost element smaller than target\n            j = m - 1\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
    binary_search_insertion.swift
    /* Find an insertion point using binary search (with duplicates) */\nfunc binarySearchInsertion(nums: [Int], target: Int) -> Int {\n    // Initialize the closed interval [0, n-1]\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target {\n            i = m + 1 // Target must be in [m+1, j] if it exists\n        } else if nums[m] > target {\n            j = m - 1 // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1 // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
    binary_search_insertion.js
    /* Find an insertion point using binary search (with duplicates) */\nfunction binarySearchInsertion(nums, target) {\n    let i = 0,\n        j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, using Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.ts
    /* Find an insertion point using binary search (with duplicates) */\nfunction binarySearchInsertion(nums: Array<number>, target: number): number {\n    let i = 0,\n        j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, using Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.dart
    /* Find an insertion point using binary search (with duplicates) */\nint binarySearchInsertion(List<int> nums, int target) {\n  int i = 0, j = nums.length - 1; // Initialize the closed interval [0, n-1]\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      i = m + 1; // Target must be in [m+1, j] if it exists\n    } else if (nums[m] > target) {\n      j = m - 1; // Target must be in [i, m-1] if it exists\n    } else {\n      j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n    }\n  }\n  // Return insertion point i\n  return i;\n}\n
    binary_search_insertion.rs
    /* Find an insertion point using binary search (with duplicates) */\npub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 {\n    let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize the closed interval [0, n-1]\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if nums[m as usize] > target {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue searching [i, m-1] for the insertion point before any equal elements\n        }\n    }\n    // Return insertion point i\n    i\n}\n
    binary_search_insertion.c
    /* Find an insertion point using binary search (with duplicates) */\nint binarySearchInsertion(int *nums, int numSize, int target) {\n    int i = 0, j = numSize - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.kt
    /* Find an insertion point using binary search (with duplicates) */\nfun binarySearchInsertion(nums: IntArray, target: Int): Int {\n    var i = 0\n    var j = nums.size - 1 // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1 // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1 // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1 // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
    binary_search_insertion.rb
    ### Find an insertion point using binary search (with duplicates) ###\ndef binary_search_insertion(nums, target)\n  # Initialize the closed interval [0, n-1]\n  i, j = 0, nums.length - 1\n\n  while i <= j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # Target must be in [m+1, j] if it exists\n    elsif nums[m] > target\n      j = m - 1 # Target must be in [i, m-1] if it exists\n    else\n      j = m - 1 # Continue in [i, m-1] to find the rightmost element smaller than target\n    end\n  end\n\n  i # Return insertion point i\nend\n
    Visualize execution

    View full screen >

    Tip

    All the code in this section uses closed intervals. Interested readers can implement versions using left-closed, right-open intervals.

    Viewed more broadly, binary search simply assigns a search goal to each of pointers \\(i\\) and \\(j\\). The goal may be a particular element, such as target, or a range of elements, such as those smaller than target.

    As the interval is repeatedly halved, pointers \\(i\\) and \\(j\\) approach their assigned goals. Eventually they either find the answer or stop after crossing a boundary.

    ","path":["Chapter 10.   Searching","10.2   Finding an insertion point with binary search"],"tags":[]},{"location":"chapter_searching/exercises/","level":1,"title":"10.7   Exercises","text":"","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#1071-check-your-understanding","level":2,"title":"10.7.1   Check your understanding","text":"","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#1-how-binary-search-narrows-the-search-interval","level":3,"title":"1.   How binary search narrows the search interval","text":"

    Search for 16 in the sorted array [2, 5, 8, 12, 16, 23, 38]. Use a closed interval [i, j] and the midpoint \\(m=i+(j-i)/2\\), rounded down.

    For each iteration, write (i, j, m), the midpoint element, and how the interval is narrowed next, until the target is found.

    Reference answer

    The search proceeds as follows:

    Iteration (i, j, m) Midpoint element Next step 1 (0, 6, 3) 12 12 < 16, so set i = 4 2 (4, 6, 5) 23 23 > 16, so set j = 4 3 (4, 4, 4) 16 Target found; return index 4

    Since the array is sorted, when the midpoint value is below the target, we can exclude the midpoint and everything to its left. When it is above the target, we can exclude the midpoint and everything to its right.

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#2-left-and-right-boundaries-of-repeated-elements","level":3,"title":"2.   Left and right boundaries of repeated elements","text":"

    Search for 2 in the array [1, 2, 2, 2, 4, 6]. A student uses binary search, finds the target at index 2, and immediately returns, saying: “Index 2 is the left boundary of the number 2.”

    1. Is the student correct? What are the left and right boundaries of 2? Explain.
    2. When finding the left boundary, which side should we continue searching if the midpoint element equals the target?
    3. Which side should we continue searching when finding the right boundary? State only the direction; you do not need to give the full search process.
    Reference answer
    1. The student is incorrect. Returning immediately upon finding a 2 guarantees only that some occurrence has been found, not the leftmost or rightmost one. Here, the left boundary is index 1 and the right boundary is index 3.

    2. When finding the left boundary, continue searching to the left even if the midpoint element equals 2. For example, with a closed interval, set j = m - 1.

    3. When finding the right boundary, continue searching to the right after the midpoint element equals 2. For example, set i = m + 1.

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#3-choosing-a-search-method-for-different-data","level":3,"title":"3.   Choosing a search method for different data","text":"

    Choose a suitable method from linear search, binary search, and a hash table for each situation, and explain:

    1. Repeatedly searching \\(10^7\\) integers that are already sorted and will not change, without building an additional data structure.
    2. Repeatedly checking whether a key exists in a data set with frequent insertions and deletions, with no need to preserve order or perform range queries.
    3. Searching for a value only once in an unsorted array.
    Reference answer
    1. Binary search: the data is sorted and static, giving \\(O(\\log n)\\) time without an additional data structure.
    2. A hash table: when the hash function distributes keys fairly evenly among buckets, insertion, deletion, and key lookup all have average time complexity \\(O(1)\\).
    3. Scan directly from beginning to end. For a single search, sorting or building a hash table would first require processing the entire array, without reducing the total work for this task.

      The choice depends on whether the data is sorted, whether additional structures are allowed, how many queries there are, and which operations are needed.

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#1072-programming-exercises","level":2,"title":"10.7.2   Programming exercises","text":"","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#1-binary-search-in-a-sorted-array","level":3,"title":"1.   Binary search in a sorted array","text":"

    Given an integer array nums in strictly increasing order and a target value target, find target using binary search. Return its array index if it exists, or -1 otherwise.

    Hints
    1. The initial interval has left = 0 and right = n - 1; it is nonempty while left <= right.
    2. Calculate the midpoint with mid = left + (right - left) // 2.
    3. If nums[mid] is less than target, move the left boundary to mid + 1; if nums[mid] is greater than target, move the right boundary to mid - 1; return immediately if they are equal.

    LeetCode

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#2-finding-an-insertion-position-in-a-sorted-array","level":3,"title":"2.   Finding an insertion position in a sorted array","text":"

    You are given an integer array nums in strictly increasing order and a target value target.

    If target is already in the array, return its index. Otherwise, return the position at which target can be inserted while preserving strictly increasing order. The answer may be 0 or the array length. Use binary search.

    Hints
    1. The answer may be 0 or the array length n.
    2. With a closed interval, if nums[mid] is greater than or equal to target, set right = mid - 1 to keep checking farther left; otherwise, set left = mid + 1.
    3. When the loop ends, left is the insertion position.

    LeetCode

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/","level":1,"title":"10.4   Optimizing with hashing","text":"

    In algorithm problems, we often reduce time complexity by replacing linear search with hash lookup. Let us explore this strategy through a problem.

    Question

    Given an integer array nums and a target value target, find two elements whose sum equals target and return their array indices. Any valid solution is acceptable.

    ","path":["Chapter 10.   Searching","10.4   Optimizing with hashing"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1041-linear-search-trading-time-for-space","level":2,"title":"10.4.1   Linear search: trading time for space","text":"

    Consider examining every possible pair directly. As shown below, use two nested loops and check whether the two integers sum to target each time. If they do, return their indices.

    Figure 10-9   Solving two sum with linear search

    The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py
    def two_sum_brute_force(nums: list[int], target: int) -> list[int]:\n    \"\"\"Method 1: brute-force enumeration\"\"\"\n    # Two nested loops give a time complexity of O(n^2)\n    for i in range(len(nums) - 1):\n        for j in range(i + 1, len(nums)):\n            if nums[i] + nums[j] == target:\n                return [i, j]\n    return []\n
    two_sum.cpp
    /* Method 1: brute-force enumeration */\nvector<int> twoSumBruteForce(vector<int> &nums, int target) {\n    int size = nums.size();\n    // Two nested loops give a time complexity of O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return {i, j};\n        }\n    }\n    return {};\n}\n
    two_sum.java
    /* Method 1: brute-force enumeration */\nint[] twoSumBruteForce(int[] nums, int target) {\n    int size = nums.length;\n    // Two nested loops give a time complexity of O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return new int[] { i, j };\n        }\n    }\n    return new int[0];\n}\n
    two_sum.cs
    /* Method 1: brute-force enumeration */\nint[] TwoSumBruteForce(int[] nums, int target) {\n    int size = nums.Length;\n    // Two nested loops give a time complexity of O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return [i, j];\n        }\n    }\n    return [];\n}\n
    two_sum.go
    /* Method 1: brute-force enumeration */\nfunc twoSumBruteForce(nums []int, target int) []int {\n    size := len(nums)\n    // Two nested loops give a time complexity of O(n^2)\n    for i := 0; i < size-1; i++ {\n        for j := i + 1; j < size; j++ {\n            if nums[i]+nums[j] == target {\n                return []int{i, j}\n            }\n        }\n    }\n    return nil\n}\n
    two_sum.swift
    /* Method 1: brute-force enumeration */\nfunc twoSumBruteForce(nums: [Int], target: Int) -> [Int] {\n    // Two nested loops give a time complexity of O(n^2)\n    for i in nums.indices.dropLast() {\n        for j in nums.indices.dropFirst(i + 1) {\n            if nums[i] + nums[j] == target {\n                return [i, j]\n            }\n        }\n    }\n    return [0]\n}\n
    two_sum.js
    /* Method 1: brute-force enumeration */\nfunction twoSumBruteForce(nums, target) {\n    const n = nums.length;\n    // Two nested loops give a time complexity of O(n^2)\n    for (let i = 0; i < n; i++) {\n        for (let j = i + 1; j < n; j++) {\n            if (nums[i] + nums[j] === target) {\n                return [i, j];\n            }\n        }\n    }\n    return [];\n}\n
    two_sum.ts
    /* Method 1: brute-force enumeration */\nfunction twoSumBruteForce(nums: number[], target: number): number[] {\n    const n = nums.length;\n    // Two nested loops give a time complexity of O(n^2)\n    for (let i = 0; i < n; i++) {\n        for (let j = i + 1; j < n; j++) {\n            if (nums[i] + nums[j] === target) {\n                return [i, j];\n            }\n        }\n    }\n    return [];\n}\n
    two_sum.dart
    /* Method 1: brute-force enumeration */\nList<int> twoSumBruteForce(List<int> nums, int target) {\n  int size = nums.length;\n  // Two nested loops give a time complexity of O(n^2)\n  for (var i = 0; i < size - 1; i++) {\n    for (var j = i + 1; j < size; j++) {\n      if (nums[i] + nums[j] == target) return [i, j];\n    }\n  }\n  return [0];\n}\n
    two_sum.rs
    /* Method 1: brute-force enumeration */\npub fn two_sum_brute_force(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {\n    let size = nums.len();\n    // Two nested loops give a time complexity of O(n^2)\n    for i in 0..size - 1 {\n        for j in i + 1..size {\n            if nums[i] + nums[j] == target {\n                return Some(vec![i as i32, j as i32]);\n            }\n        }\n    }\n    None\n}\n
    two_sum.c
    /* Method 1: brute-force enumeration */\nint *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) {\n    for (int i = 0; i < numsSize; ++i) {\n        for (int j = i + 1; j < numsSize; ++j) {\n            if (nums[i] + nums[j] == target) {\n                int *res = malloc(sizeof(int) * 2);\n                res[0] = i, res[1] = j;\n                *returnSize = 2;\n                return res;\n            }\n        }\n    }\n    *returnSize = 0;\n    return NULL;\n}\n
    two_sum.kt
    /* Method 1: brute-force enumeration */\nfun twoSumBruteForce(nums: IntArray, target: Int): IntArray {\n    val size = nums.size\n    // Two nested loops give a time complexity of O(n^2)\n    for (i in 0..<size - 1) {\n        for (j in i + 1..<size) {\n            if (nums[i] + nums[j] == target) return intArrayOf(i, j)\n        }\n    }\n    return IntArray(0)\n}\n
    two_sum.rb
    ### Method 1: brute-force enumeration ###\ndef two_sum_brute_force(nums, target)\n  # Two nested loops give a time complexity of O(n^2)\n  for i in 0...(nums.length - 1)\n    for j in (i + 1)...nums.length\n      return [i, j] if nums[i] + nums[j] == target\n    end\n  end\n\n  []\nend\n
    Visualize execution

    View full screen >

    This approach takes \\(O(n^2)\\) time and \\(O(1)\\) space, making it very slow for large datasets.

    ","path":["Chapter 10.   Searching","10.4   Optimizing with hashing"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1042-hash-lookup-trading-space-for-time","level":2,"title":"10.4.2   Hash lookup: trading space for time","text":"

    Now use a hash table whose keys are array elements and whose values are their indices. Iterate through the array, performing the steps below each round.

    1. Check whether target - nums[i] is in the hash table. If it is, return the indices of the two elements.
    2. Add the key-value pair consisting of nums[i] and index i to the hash table.
    <1><2><3>

    Figure 10-10   Solving two sum with an auxiliary hash table

    The implementation below requires only one loop:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py
    def two_sum_hash_table(nums: list[int], target: int) -> list[int]:\n    \"\"\"Method 2: auxiliary hash table\"\"\"\n    # The auxiliary hash table uses O(n) space\n    dic = {}\n    # A single loop with average O(1) hash operations gives an average time complexity of O(n)\n    for i in range(len(nums)):\n        if target - nums[i] in dic:\n            return [dic[target - nums[i]], i]\n        dic[nums[i]] = i\n    return []\n
    two_sum.cpp
    /* Method 2: auxiliary hash table */\nvector<int> twoSumHashTable(vector<int> &nums, int target) {\n    int size = nums.size();\n    // The auxiliary hash table uses O(n) space\n    unordered_map<int, int> dic;\n    // A single loop gives a time complexity of O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.find(target - nums[i]) != dic.end()) {\n            return {dic[target - nums[i]], i};\n        }\n        dic.emplace(nums[i], i);\n    }\n    return {};\n}\n
    two_sum.java
    /* Method 2: auxiliary hash table */\nint[] twoSumHashTable(int[] nums, int target) {\n    int size = nums.length;\n    // The auxiliary hash table uses O(n) space\n    Map<Integer, Integer> dic = new HashMap<>();\n    // A single loop gives a time complexity of O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.containsKey(target - nums[i])) {\n            return new int[] { dic.get(target - nums[i]), i };\n        }\n        dic.put(nums[i], i);\n    }\n    return new int[0];\n}\n
    two_sum.cs
    /* Method 2: auxiliary hash table */\nint[] TwoSumHashTable(int[] nums, int target) {\n    int size = nums.Length;\n    // The auxiliary hash table uses O(n) space\n    Dictionary<int, int> dic = [];\n    // A single loop gives a time complexity of O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.ContainsKey(target - nums[i])) {\n            return [dic[target - nums[i]], i];\n        }\n        dic.Add(nums[i], i);\n    }\n    return [];\n}\n
    two_sum.go
    /* Method 2: auxiliary hash table */\nfunc twoSumHashTable(nums []int, target int) []int {\n    // The auxiliary hash table uses O(n) space\n    hashTable := map[int]int{}\n    // A single loop gives a time complexity of O(n)\n    for idx, val := range nums {\n        if preIdx, ok := hashTable[target-val]; ok {\n            return []int{preIdx, idx}\n        }\n        hashTable[val] = idx\n    }\n    return nil\n}\n
    two_sum.swift
    /* Method 2: auxiliary hash table */\nfunc twoSumHashTable(nums: [Int], target: Int) -> [Int] {\n    // The auxiliary hash table uses O(n) space\n    var dic: [Int: Int] = [:]\n    // A single loop gives a time complexity of O(n)\n    for i in nums.indices {\n        if let j = dic[target - nums[i]] {\n            return [j, i]\n        }\n        dic[nums[i]] = i\n    }\n    return [0]\n}\n
    two_sum.js
    /* Method 2: auxiliary hash table */\nfunction twoSumHashTable(nums, target) {\n    // The auxiliary hash table uses O(n) space\n    let m = {};\n    // A single loop gives a time complexity of O(n)\n    for (let i = 0; i < nums.length; i++) {\n        if (m[target - nums[i]] !== undefined) {\n            return [m[target - nums[i]], i];\n        } else {\n            m[nums[i]] = i;\n        }\n    }\n    return [];\n}\n
    two_sum.ts
    /* Method 2: auxiliary hash table */\nfunction twoSumHashTable(nums: number[], target: number): number[] {\n    // The auxiliary hash table uses O(n) space\n    let m: Map<number, number> = new Map();\n    // A single loop gives a time complexity of O(n)\n    for (let i = 0; i < nums.length; i++) {\n        let index = m.get(target - nums[i]);\n        if (index !== undefined) {\n            return [index, i];\n        } else {\n            m.set(nums[i], i);\n        }\n    }\n    return [];\n}\n
    two_sum.dart
    /* Method 2: auxiliary hash table */\nList<int> twoSumHashTable(List<int> nums, int target) {\n  int size = nums.length;\n  // The auxiliary hash table uses O(n) space\n  Map<int, int> dic = HashMap();\n  // A single loop gives a time complexity of O(n)\n  for (var i = 0; i < size; i++) {\n    if (dic.containsKey(target - nums[i])) {\n      return [dic[target - nums[i]]!, i];\n    }\n    dic.putIfAbsent(nums[i], () => i);\n  }\n  return [0];\n}\n
    two_sum.rs
    /* Method 2: auxiliary hash table */\npub fn two_sum_hash_table(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {\n    // The auxiliary hash table uses O(n) space\n    let mut dic = HashMap::new();\n    // A single loop gives a time complexity of O(n)\n    for (i, num) in nums.iter().enumerate() {\n        match dic.get(&(target - num)) {\n            Some(v) => return Some(vec![*v as i32, i as i32]),\n            None => dic.insert(num, i as i32),\n        };\n    }\n    None\n}\n
    two_sum.c
    /* Hash table */\ntypedef struct {\n    int key;\n    int val;\n    UT_hash_handle hh; // Implemented using uthash.h\n} HashTable;\n\n/* Look up a key in the hash table */\nHashTable *find(HashTable *h, int key) {\n    HashTable *tmp;\n    HASH_FIND_INT(h, &key, tmp);\n    return tmp;\n}\n\n/* Insert an entry into the hash table */\nvoid insert(HashTable **h, int key, int val) {\n    HashTable *t = find(*h, key);\n    if (t == NULL) {\n        HashTable *tmp = malloc(sizeof(HashTable));\n        tmp->key = key, tmp->val = val;\n        HASH_ADD_INT(*h, key, tmp);\n    } else {\n        t->val = val;\n    }\n}\n\n/* Method 2: auxiliary hash table */\nint *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) {\n    HashTable *hashtable = NULL;\n    for (int i = 0; i < numsSize; i++) {\n        HashTable *t = find(hashtable, target - nums[i]);\n        if (t != NULL) {\n            int *res = malloc(sizeof(int) * 2);\n            res[0] = t->val, res[1] = i;\n            *returnSize = 2;\n            return res;\n        }\n        insert(&hashtable, nums[i], i);\n    }\n    *returnSize = 0;\n    return NULL;\n}\n
    two_sum.kt
    /* Method 2: auxiliary hash table */\nfun twoSumHashTable(nums: IntArray, target: Int): IntArray {\n    val size = nums.size\n    // The auxiliary hash table uses O(n) space\n    val dic = HashMap<Int, Int>()\n    // A single loop gives a time complexity of O(n)\n    for (i in 0..<size) {\n        if (dic.containsKey(target - nums[i])) {\n            return intArrayOf(dic[target - nums[i]]!!, i)\n        }\n        dic[nums[i]] = i\n    }\n    return IntArray(0)\n}\n
    two_sum.rb
    ### Method 2: auxiliary hash table ###\ndef two_sum_hash_table(nums, target)\n  # The auxiliary hash table uses O(n) space\n  dic = {}\n  # A single loop gives a time complexity of O(n)\n  for i in 0...nums.length\n    return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i])\n\n    dic[nums[i]] = i\n  end\n\n  []\nend\n
    Visualize execution

    View full screen >

    Hash lookup reduces the time complexity from \\(O(n^2)\\) to \\(O(n)\\), greatly improving efficiency.

    Maintaining an additional hash table requires \\(O(n)\\) space. Even so, this approach offers a better balance of time and space efficiency, making it the optimal solution to this problem.

    ","path":["Chapter 10.   Searching","10.4   Optimizing with hashing"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/","level":1,"title":"10.5   Search algorithms revisited","text":"

    A search algorithm finds one or more elements that satisfy specified conditions in a data structure, such as an array, linked list, tree, or graph.

    Search algorithms fall into two categories according to their approach.

    • Locate the target by traversing the data structure, as in traversals of arrays, linked lists, trees, and graphs.
    • Find elements efficiently by using the organization of the data or prior information about it, as in binary search, hash lookup, and binary search tree lookup.

    These ideas have all appeared in earlier chapters, so search algorithms are already familiar to us. In this section, we revisit them from a more systematic perspective.

    ","path":["Chapter 10.   Searching","10.5   Search algorithms revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1051-brute-force-search","level":2,"title":"10.5.1   Brute-force search","text":"

    Brute-force search locates a target by visiting the elements of a data structure one by one.

    • “Linear search” applies to linear data structures such as arrays and linked lists. Starting at one end, it visits elements in sequence until it finds the target or reaches the other end without finding it.
    • “Breadth-first search” and “depth-first search” are two strategies for traversing graphs and trees. Breadth-first search starts at an initial node and searches level by level, visiting nearby nodes before more distant ones. Depth-first search follows a path as far as possible, then backtracks and tries other paths until the whole structure has been traversed.

    Brute-force search is simple and broadly applicable. It requires neither preprocessing the data nor building an additional data structure.

    However, these algorithms take \\(O(n)\\) time, where \\(n\\) is the number of elements, so they perform poorly on large datasets.

    ","path":["Chapter 10.   Searching","10.5   Search algorithms revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1052-adaptive-search","level":2,"title":"10.5.2   Adaptive search","text":"

    Adaptive search uses properties of the data, such as its ordering, to optimize the search process and locate the target more efficiently.

    • “Binary search” uses sorted data to search efficiently and applies only to arrays.
    • “Hash lookup” uses a hash table to map lookup keys to target data as key-value pairs.
    • “Tree search” uses comparisons of node values in a particular tree structure, such as a binary search tree, to rule out nodes quickly and locate the target.

    These algorithms are efficient, achieving time complexities of \\(O(\\log n)\\) or even \\(O(1)\\).

    However, they often require preprocessing the data. Binary search requires sorting the array in advance; hash lookup and tree search require additional data structures, whose maintenance also costs time and space.

    Tip

    Adaptive search algorithms are often called lookup algorithms. Their main purpose is to retrieve target elements quickly from specific data structures.

    ","path":["Chapter 10.   Searching","10.5   Search algorithms revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1053-choosing-a-search-method","level":2,"title":"10.5.3   Choosing a search method","text":"

    Given a dataset of size \\(n\\), we can find a target using linear search, binary search, tree search, hash lookup, and other methods. Figure 10-11 illustrates how these methods work.

    Figure 10-11   Different search strategies

    Table 10-1 compares their efficiency and properties.

    Table 10-1   Efficiency of lookup algorithms

    Linear search Binary search Tree search Hash lookup Find an element \\(O(n)\\) \\(O(\\log n)\\) \\(O(\\log n)\\) \\(O(1)\\) Insert an element \\(O(1)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) Delete an element \\(O(n)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) Extra space \\(O(1)\\) \\(O(1)\\) \\(O(n)\\) \\(O(n)\\) Preprocessing / Sort \\(O(n \\log n)\\) Build a tree \\(O(n \\log n)\\) Build a hash table \\(O(n)\\) Data ordering Unsorted Sorted Sorted Unsorted

    The choice also depends on dataset size, required search performance, and how frequently the data is queried and updated.

    Linear search

    • It is broadly applicable and requires no preprocessing. If we need only one lookup, preprocessing for the other three methods takes longer than a linear search itself.
    • It suits small datasets, where time complexity has less influence on actual performance.
    • It suits frequently updated data because no additional data structure needs to be maintained.

    Binary search

    • It suits large datasets, with predictable performance and a worst-case time complexity of \\(O(\\log n)\\).
    • The dataset cannot be excessively large, because an array requires contiguous memory.
    • It is unsuitable for frequent insertions and deletions, since maintaining a sorted array is expensive.

    Hash lookup

    • It suits situations demanding very fast queries, with an average time complexity of \\(O(1)\\).
    • It is unsuitable when sorted data or range queries are needed, because a hash table does not preserve order.
    • It depends heavily on the hash function and collision-resolution strategy, creating a greater risk of performance degradation.
    • It is unsuitable for excessively large datasets, because a hash table needs extra space to minimize collisions and maintain good query performance.

    Tree search

    • It suits very large datasets, because tree nodes are stored at separate locations in memory.
    • It suits situations requiring sorted data or range queries.
    • Repeated insertions and deletions can make a binary search tree skewed, degrading its time complexity to \\(O(n)\\).
    • An AVL tree or red-black tree keeps operations at \\(O(\\log n)\\), but maintaining balance adds overhead.
    ","path":["Chapter 10.   Searching","10.5   Search algorithms revisited"],"tags":[]},{"location":"chapter_searching/summary/","level":1,"title":"10.6   Summary","text":"","path":["Chapter 10.   Searching","10.6   Summary"],"tags":[]},{"location":"chapter_searching/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Binary search relies on sorted data and repeatedly halves the search interval. It requires ordered input and applies only to arrays or data structures based on arrays.
    • Brute-force search locates data by traversing a data structure. Linear search suits arrays and linked lists; breadth-first and depth-first search suit graphs and trees. These algorithms are broadly applicable and require no preprocessing, but their \\(O(n)\\) time complexity is relatively high.
    • Hash lookup, tree search, and binary search efficiently locate targets in particular data structures. Their time complexities can be \\(O(\\log n)\\) or even \\(O(1)\\), but they usually rely on additional data structures.
    • In practice, choose a search method by considering dataset size, required search performance, query frequency, update frequency, and related factors.
    • Linear search suits small or frequently updated datasets; binary search suits large, sorted datasets; hash lookup suits fast queries without range-query requirements; tree search suits large, dynamic datasets that need ordering and range queries.
    • Replacing linear search with hash lookup is a common optimization that reduces lookup time from \\(O(n)\\) to \\(O(1)\\).
    ","path":["Chapter 10.   Searching","10.6   Summary"],"tags":[]},{"location":"chapter_sorting/","level":1,"title":"Chapter 11.   Sorting","text":"

    Abstract

    Sorting is like a magic key that turns chaos into order, helping us understand and process data more efficiently.

    From a simple ascending sequence to a complex arrangement by category, sorting reveals the harmonious beauty of data.

    ","path":["Chapter 11.   Sorting"],"tags":[]},{"location":"chapter_sorting/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 11.1   Sorting algorithms
    • 11.2   Selection sort
    • 11.3   Bubble sort
    • 11.4   Insertion sort
    • 11.5   Quick sort
    • 11.6   Merge sort
    • 11.7   Heap sort
    • 11.8   Bucket sort
    • 11.9   Counting sort
    • 11.10   Radix sort
    • 11.11   Summary
    • 11.12   Exercises
    ","path":["Chapter 11.   Sorting"],"tags":[]},{"location":"chapter_sorting/bubble_sort/","level":1,"title":"11.3   Bubble sort","text":"

    Bubble sort sorts by repeatedly comparing and swapping adjacent elements. The process resembles bubbles rising from the bottom to the top, which gives the algorithm its name.

    As shown below, we can simulate bubbling through swaps. Scan from the left end of the array to the right, comparing each pair of adjacent elements. If the left element is greater than the right, swap them. After the scan, the largest element has moved to the far right.

    <1><2><3><4><5><6><7>

    Figure 11-4   Simulating bubbling through element swaps

    ","path":["Chapter 11.   Sorting","11.3   Bubble sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1131-algorithm-steps","level":2,"title":"11.3.1   Algorithm steps","text":"

    For an array of length \\(n\\), bubble sort proceeds as shown below.

    1. First, perform a bubbling pass over all \\(n\\) elements, moving the largest element to its correct position.
    2. Next, perform a pass over the remaining \\(n - 1\\) elements, moving the second-largest element to its correct position.
    3. Continue in this way. After \\(n - 1\\) passes, the largest \\(n - 1\\) elements are all in their correct positions.
    4. The one remaining element must be the smallest and needs no further sorting. The array is now sorted.

    Figure 11-5   The bubble sort process

    An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py
    def bubble_sort(nums: list[int]):\n    \"\"\"Bubble sort\"\"\"\n    n = len(nums)\n    # Outer loop: the unsorted range is [0, i]\n    for i in range(n - 1, 0, -1):\n        # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                nums[j], nums[j + 1] = nums[j + 1], nums[j]\n
    bubble_sort.cpp
    /* Bubble sort */\nvoid bubbleSort(vector<int> &nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                // Use the std::swap() function here\n                swap(nums[j], nums[j + 1]);\n            }\n        }\n    }\n}\n
    bubble_sort.java
    /* Bubble sort */\nvoid bubbleSort(int[] nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
    bubble_sort.cs
    /* Bubble sort */\nvoid BubbleSort(int[] nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n            }\n        }\n    }\n}\n
    bubble_sort.go
    /* Bubble sort */\nfunc bubbleSort(nums []int) {\n    // Outer loop: the unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                nums[j], nums[j+1] = nums[j+1], nums[j]\n            }\n        }\n    }\n}\n
    bubble_sort.swift
    /* Bubble sort */\nfunc bubbleSort(nums: inout [Int]) {\n    // Outer loop: the unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swapAt(j, j + 1)\n            }\n        }\n    }\n}\n
    bubble_sort.js
    /* Bubble sort */\nfunction bubbleSort(nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
    bubble_sort.ts
    /* Bubble sort */\nfunction bubbleSort(nums: number[]): void {\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
    bubble_sort.dart
    /* Bubble sort */\nvoid bubbleSort(List<int> nums) {\n  // Outer loop: the unsorted range is [0, i]\n  for (int i = nums.length - 1; i > 0; i--) {\n    // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for (int j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n      }\n    }\n  }\n}\n
    bubble_sort.rs
    /* Bubble sort */\nfn bubble_sort(nums: &mut [i32]) {\n    // Outer loop: the unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swap(j, j + 1);\n            }\n        }\n    }\n}\n
    bubble_sort.c
    /* Bubble sort */\nvoid bubbleSort(int nums[], int size) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = size - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                int temp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = temp;\n            }\n        }\n    }\n}\n
    bubble_sort.kt
    /* Bubble sort */\nfun bubbleSort(nums: IntArray) {\n    // Outer loop: the unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n            }\n        }\n    }\n}\n
    bubble_sort.rb
    ### Bubble sort ###\ndef bubble_sort(nums)\n  n = nums.length\n  # Outer loop: the unsorted range is [0, i]\n  for i in (n - 1).downto(1)\n    # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        nums[j], nums[j + 1] = nums[j + 1], nums[j]\n      end\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.3   Bubble sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1132-improving-efficiency","level":2,"title":"11.3.2   Improving efficiency","text":"

    If a bubbling pass performs no swaps, the array is already sorted and we can return immediately. We can therefore add a variable flag to detect this situation and stop as soon as it occurs.

    With this optimization, the worst-case and average time complexities remain \\(O(n^2)\\). However, a fully sorted input achieves the best-case time complexity of \\(O(n)\\).

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py
    def bubble_sort_with_flag(nums: list[int]):\n    \"\"\"Bubble sort (flag optimization)\"\"\"\n    n = len(nums)\n    # Outer loop: the unsorted range is [0, i]\n    for i in range(n - 1, 0, -1):\n        flag = False  # Initialize the flag\n        # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                nums[j], nums[j + 1] = nums[j + 1], nums[j]\n                flag = True  # Record that a swap occurred\n        if not flag:\n            break  # Exit immediately if this pass made no swaps\n
    bubble_sort.cpp
    /* Bubble sort (flag optimization)*/\nvoid bubbleSortWithFlag(vector<int> &nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        bool flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                // Use the std::swap() function here\n                swap(nums[j], nums[j + 1]);\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if (!flag)\n            break; // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.java
    /* Bubble sort (flag optimization) */\nvoid bubbleSortWithFlag(int[] nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        boolean flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if (!flag)\n            break; // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.cs
    /* Bubble sort (flag optimization)*/\nvoid BubbleSortWithFlag(int[] nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        bool flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n                flag = true;  // Record that a swap occurred\n            }\n        }\n        if (!flag) break;     // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.go
    /* Bubble sort (flag optimization)*/\nfunc bubbleSortWithFlag(nums []int) {\n    // Outer loop: the unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        flag := false // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                nums[j], nums[j+1] = nums[j+1], nums[j]\n                flag = true // Record that a swap occurred\n            }\n        }\n        if flag == false { // Exit immediately if this pass made no swaps\n            break\n        }\n    }\n}\n
    bubble_sort.swift
    /* Bubble sort (flag optimization)*/\nfunc bubbleSortWithFlag(nums: inout [Int]) {\n    // Outer loop: the unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        var flag = false // Initialize the flag\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swapAt(j, j + 1)\n                flag = true // Record that a swap occurred\n            }\n        }\n        if !flag { // Exit immediately if this pass made no swaps\n            break\n        }\n    }\n}\n
    bubble_sort.js
    /* Bubble sort (flag optimization)*/\nfunction bubbleSortWithFlag(nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        let flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if (!flag) break; // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.ts
    /* Bubble sort (flag optimization)*/\nfunction bubbleSortWithFlag(nums: number[]): void {\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        let flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if (!flag) break; // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.dart
    /* Bubble sort (flag optimization)*/\nvoid bubbleSortWithFlag(List<int> nums) {\n  // Outer loop: the unsorted range is [0, i]\n  for (int i = nums.length - 1; i > 0; i--) {\n    bool flag = false; // Initialize the flag\n    // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for (int j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n        flag = true; // Record that a swap occurred\n      }\n    }\n    if (!flag) break; // Exit immediately if this pass made no swaps\n  }\n}\n
    bubble_sort.rs
    /* Bubble sort (flag optimization) */\nfn bubble_sort_with_flag(nums: &mut [i32]) {\n    // Outer loop: the unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        let mut flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swap(j, j + 1);\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if !flag {\n            break; // Exit immediately if this pass made no swaps\n        };\n    }\n}\n
    bubble_sort.c
    /* Bubble sort (flag optimization)*/\nvoid bubbleSortWithFlag(int nums[], int size) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = size - 1; i > 0; i--) {\n        bool flag = false;\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                int temp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = temp;\n                flag = true;\n            }\n        }\n        if (!flag)\n            break;\n    }\n}\n
    bubble_sort.kt
    /* Bubble sort (flag optimization) */\nfun bubbleSortWithFlag(nums: IntArray) {\n    // Outer loop: the unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        var flag = false // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n                flag = true // Record that a swap occurred\n            }\n        }\n        if (!flag) break // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.rb
    ### Bubble sort (flag optimization)###\ndef bubble_sort_with_flag(nums)\n  n = nums.length\n  # Outer loop: the unsorted range is [0, i]\n  for i in (n - 1).downto(1)\n    flag = false # Initialize the flag\n\n    # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        nums[j], nums[j + 1] = nums[j + 1], nums[j]\n        flag = true # Record that a swap occurred\n      end\n    end\n\n    break unless flag # Exit immediately if this pass made no swaps\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.3   Bubble sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1133-algorithm-properties","level":2,"title":"11.3.3   Algorithm properties","text":"
    • Time complexity: \\(O(n^2)\\); adaptive. Successive passes traverse \\(n - 1\\), \\(n - 2\\), \\(\\dots\\), \\(2\\), and \\(1\\) positions, totaling \\((n - 1) n / 2\\). With the flag optimization, the best-case time complexity is \\(O(n)\\).
    • Space complexity: \\(O(1)\\); in-place. Pointers \\(i\\) and \\(j\\) use constant extra space.
    • Stable. Equal elements are not swapped during a bubbling pass.
    ","path":["Chapter 11.   Sorting","11.3   Bubble sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/","level":1,"title":"11.8   Bucket sort","text":"

    The sorting algorithms discussed so far are “comparison-based”: they sort by comparing elements. Their worst-case time complexity has a lower bound of \\(\\Omega(n \\log n)\\). We now turn to several “non-comparison” sorting algorithms, which can achieve linear time complexity.

    Bucket sort is a classic application of divide and conquer. It creates ordered buckets, each corresponding to a range of values, and distributes the data evenly among them. It then sorts each bucket separately and combines the results in bucket order.

    ","path":["Chapter 11.   Sorting","11.8   Bucket sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1181-algorithm-steps","level":2,"title":"11.8.1   Algorithm steps","text":"

    Consider an array of length \\(n\\) containing floating-point values in \\([0, 1)\\). Bucket sort proceeds as shown below.

    1. Initialize \\(k\\) buckets and distribute the \\(n\\) elements among the \\(k\\) buckets.
    2. Sort each bucket separately, using the programming language's built-in sorting function here.
    3. Combine the results in ascending bucket order.

    Figure 11-13   The bucket sort process

    The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bucket_sort.py
    def bucket_sort(nums: list[float]):\n    \"\"\"Bucket sort\"\"\"\n    # Initialize k = n/2 buckets, expecting 2 elements per bucket\n    k = len(nums) // 2\n    buckets = [[] for _ in range(k)]\n    # 1. Distribute the array elements among the buckets\n    for num in nums:\n        # The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        i = int(num * k)\n        # Add num to bucket i\n        buckets[i].append(num)\n    # 2. Sort each bucket\n    for bucket in buckets:\n        # Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort()\n    # 3. Traverse the buckets to combine the results\n    i = 0\n    for bucket in buckets:\n        for num in bucket:\n            nums[i] = num\n            i += 1\n
    bucket_sort.cpp
    /* Bucket sort */\nvoid bucketSort(vector<float> &nums) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    int k = nums.size() / 2;\n    vector<vector<float>> buckets(k);\n    // 1. Distribute the array elements among the buckets\n    for (float num : nums) {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        int i = num * k;\n        // Add num to bucket i\n        buckets[i].push_back(num);\n    }\n    // 2. Sort each bucket\n    for (vector<float> &bucket : buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        sort(bucket.begin(), bucket.end());\n    }\n    // 3. Traverse the buckets to combine the results\n    int i = 0;\n    for (vector<float> &bucket : buckets) {\n        for (float num : bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
    bucket_sort.java
    /* Bucket sort */\nvoid bucketSort(float[] nums) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    int k = nums.length / 2;\n    List<List<Float>> buckets = new ArrayList<>();\n    for (int i = 0; i < k; i++) {\n        buckets.add(new ArrayList<>());\n    }\n    // 1. Distribute the array elements among the buckets\n    for (float num : nums) {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        int i = (int) (num * k);\n        // Add num to bucket i\n        buckets.get(i).add(num);\n    }\n    // 2. Sort each bucket\n    for (List<Float> bucket : buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        Collections.sort(bucket);\n    }\n    // 3. Traverse the buckets to combine the results\n    int i = 0;\n    for (List<Float> bucket : buckets) {\n        for (float num : bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
    bucket_sort.cs
    /* Bucket sort */\nvoid BucketSort(float[] nums) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    int k = nums.Length / 2;\n    List<List<float>> buckets = [];\n    for (int i = 0; i < k; i++) {\n        buckets.Add([]);\n    }\n    // 1. Distribute the array elements among the buckets\n    foreach (float num in nums) {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        int i = (int)(num * k);\n        // Add num to bucket i\n        buckets[i].Add(num);\n    }\n    // 2. Sort each bucket\n    foreach (List<float> bucket in buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.Sort();\n    }\n    // 3. Traverse the buckets to combine the results\n    int j = 0;\n    foreach (List<float> bucket in buckets) {\n        foreach (float num in bucket) {\n            nums[j++] = num;\n        }\n    }\n}\n
    bucket_sort.go
    /* Bucket sort */\nfunc bucketSort(nums []float64) {\n    // Initialize k = floor(n/2) buckets, expecting about 2 elements per bucket for uniformly distributed input\n    k := len(nums) / 2\n    buckets := make([][]float64, k)\n    for i := 0; i < k; i++ {\n        buckets[i] = make([]float64, 0)\n    }\n    // 1. Distribute the array elements among the buckets\n    for _, num := range nums {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        i := int(num * float64(k))\n        // Add num to bucket i\n        buckets[i] = append(buckets[i], num)\n    }\n    // 2. Sort each bucket\n    for i := 0; i < k; i++ {\n        // Use the standard slice-sorting function, or replace it with another sorting algorithm\n        sort.Float64s(buckets[i])\n    }\n    // 3. Traverse the buckets to combine the results\n    i := 0\n    for _, bucket := range buckets {\n        for _, num := range bucket {\n            nums[i] = num\n            i++\n        }\n    }\n}\n
    bucket_sort.swift
    /* Bucket sort */\nfunc bucketSort(nums: inout [Double]) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    let k = nums.count / 2\n    var buckets = (0 ..< k).map { _ in [Double]() }\n    // 1. Distribute the array elements among the buckets\n    for num in nums {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        let i = Int(num * Double(k))\n        // Add num to bucket i\n        buckets[i].append(num)\n    }\n    // 2. Sort each bucket\n    for i in buckets.indices {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        buckets[i].sort()\n    }\n    // 3. Traverse the buckets to combine the results\n    var i = nums.startIndex\n    for bucket in buckets {\n        for num in bucket {\n            nums[i] = num\n            i += 1\n        }\n    }\n}\n
    bucket_sort.js
    /* Bucket sort */\nfunction bucketSort(nums) {\n    // Initialize ceil(n/2) buckets, aiming for about 2 elements per bucket\n    const k = nums.length / 2;\n    const buckets = [];\n    for (let i = 0; i < k; i++) {\n        buckets.push([]);\n    }\n    // 1. Distribute the array elements among the buckets\n    for (const num of nums) {\n        // The input range is [0, 1); floor(num * k) maps values to indices from 0 to ceil(k)-1\n        const i = Math.floor(num * k);\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for (const bucket of buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort((a, b) => a - b);\n    }\n    // 3. Traverse the buckets to combine the results\n    let i = 0;\n    for (const bucket of buckets) {\n        for (const num of bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
    bucket_sort.ts
    /* Bucket sort */\nfunction bucketSort(nums: number[]): void {\n    // Set k = n/2 and create ceil(k) buckets, aiming for about 2 elements per bucket\n    const k = nums.length / 2;\n    const buckets: number[][] = [];\n    for (let i = 0; i < k; i++) {\n        buckets.push([]);\n    }\n    // 1. Distribute the array elements among the buckets\n    for (const num of nums) {\n        // The input range is [0, 1); floor(num * k) maps each value to an index in [0, ceil(k)-1]\n        const i = Math.floor(num * k);\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for (const bucket of buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort((a, b) => a - b);\n    }\n    // 3. Traverse the buckets to combine the results\n    let i = 0;\n    for (const bucket of buckets) {\n        for (const num of bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
    bucket_sort.dart
    /* Bucket sort */\nvoid bucketSort(List<double> nums) {\n  // Initialize k = floor(n/2) buckets, aiming for about 2 elements per bucket\n  int k = nums.length ~/ 2;\n  List<List<double>> buckets = List.generate(k, (index) => []);\n\n  // 1. Distribute the array elements among the buckets\n  for (double _num in nums) {\n    // The input range is [0, 1); use _num * k to map each value to an index in [0, k-1]\n    int i = (_num * k).toInt();\n    // Add _num to bucket i\n    buckets[i].add(_num);\n  }\n  // 2. Sort each bucket\n  for (List<double> bucket in buckets) {\n    bucket.sort();\n  }\n  // 3. Traverse the buckets to combine the results\n  int i = 0;\n  for (List<double> bucket in buckets) {\n    for (double _num in bucket) {\n      nums[i++] = _num;\n    }\n  }\n}\n
    bucket_sort.rs
    /* Bucket sort */\nfn bucket_sort(nums: &mut [f64]) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    let k = nums.len() / 2;\n    let mut buckets = vec![vec![]; k];\n    // 1. Distribute the array elements among the buckets\n    for &num in nums.iter() {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        let i = (num * k as f64) as usize;\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for bucket in &mut buckets {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort_by(|a, b| a.partial_cmp(b).unwrap());\n    }\n    // 3. Traverse the buckets to combine the results\n    let mut i = 0;\n    for bucket in buckets.iter() {\n        for &num in bucket.iter() {\n            nums[i] = num;\n            i += 1;\n        }\n    }\n}\n
    bucket_sort.c
    /* Bucket sort */\nvoid bucketSort(float nums[], int n) {\n    int k = n / 2;                                 // Initialize k = n/2 buckets\n    int *sizes = malloc(k * sizeof(int));          // Record the size of each bucket\n    float **buckets = malloc(k * sizeof(float *)); // Array of dynamic arrays (buckets)\n    // Preallocate enough space for each bucket\n    for (int i = 0; i < k; ++i) {\n        buckets[i] = (float *)malloc(n * sizeof(float));\n        sizes[i] = 0;\n    }\n    // 1. Distribute the array elements among the buckets\n    for (int i = 0; i < n; ++i) {\n        int idx = (int)(nums[i] * k);\n        buckets[idx][sizes[idx]++] = nums[i];\n    }\n    // 2. Sort each bucket\n    for (int i = 0; i < k; ++i) {\n        qsort(buckets[i], sizes[i], sizeof(float), compare);\n    }\n    // 3. Merge the sorted buckets\n    int idx = 0;\n    for (int i = 0; i < k; ++i) {\n        for (int j = 0; j < sizes[i]; ++j) {\n            nums[idx++] = buckets[i][j];\n        }\n        // Free memory\n        free(buckets[i]);\n    }\n}\n
    bucket_sort.kt
    /* Bucket sort */\nfun bucketSort(nums: FloatArray) {\n    // Initialize k = floor(n/2) buckets, expecting about 2 elements per bucket\n    val k = nums.size / 2\n    val buckets = mutableListOf<MutableList<Float>>()\n    for (i in 0..<k) {\n        buckets.add(mutableListOf())\n    }\n    // 1. Distribute the array elements among the buckets\n    for (num in nums) {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        val i = (num * k).toInt()\n        // Add num to bucket i\n        buckets[i].add(num)\n    }\n    // 2. Sort each bucket\n    for (bucket in buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort()\n    }\n    // 3. Traverse the buckets to combine the results\n    var i = 0\n    for (bucket in buckets) {\n        for (num in bucket) {\n            nums[i++] = num\n        }\n    }\n}\n
    bucket_sort.rb
    ### Bucket sort ###\ndef bucket_sort(nums)\n  # Initialize k = floor(n/2) buckets, aiming for about 2 elements per bucket\n  k = nums.length / 2\n  buckets = Array.new(k) { [] }\n\n  # 1. Distribute the array elements among the buckets\n  nums.each do |num|\n    # The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n    i = (num * k).to_i\n    # Add num to bucket i\n    buckets[i] << num\n  end\n\n  # 2. Sort each bucket\n  buckets.each do |bucket|\n    # Use the built-in sorting function, or replace it with another sorting algorithm\n    bucket.sort!\n  end\n\n  # 3. Traverse the buckets to combine the results\n  i = 0\n  buckets.each do |bucket|\n    bucket.each do |num|\n      nums[i] = num\n      i += 1\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.8   Bucket sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1182-algorithm-properties","level":2,"title":"11.8.2   Algorithm properties","text":"

    Bucket sort is useful for very large datasets. For example, suppose the input contains one million elements and memory limitations prevent loading all of them at once. We can divide them into 1,000 buckets, sort the buckets separately, and combine the results.

    • Time complexity: \\(O(n + k)\\). If the elements are evenly distributed, each bucket contains \\(\\frac{n}{k}\\) elements. If sorting one bucket takes \\(O(\\frac{n}{k} \\log\\frac{n}{k})\\) time, sorting all buckets takes \\(O(n \\log\\frac{n}{k})\\). When the number of buckets \\(k\\) is relatively large, this approaches \\(O(n)\\). Combining the results visits all buckets and elements, taking \\(O(n + k)\\) time. In the worst case, all data falls into one bucket, and sorting it takes \\(O(n^2)\\) time.
    • Space complexity: \\(O(n + k)\\); not in-place. Extra space is needed for \\(k\\) buckets and a total of \\(n\\) elements.
    • Whether bucket sort is stable depends on whether the algorithm used to sort within each bucket is stable.
    ","path":["Chapter 11.   Sorting","11.8   Bucket sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1183-distributing-elements-evenly","level":2,"title":"11.8.3   Distributing elements evenly","text":"

    Bucket sort can theoretically achieve \\(O(n)\\) time. The key is distributing elements evenly among the buckets, since real data is often unevenly distributed. Suppose we want to divide all products on Taobao evenly into 10 buckets by price. There are many products below 100 yuan but few above 1,000 yuan. Splitting the price range into 10 equal intervals would leave very different numbers of products in the buckets.

    To obtain an even distribution, we can first choose rough boundaries and divide the data into 3 buckets. Then split the more crowded buckets into 3 smaller buckets, repeating until all buckets contain roughly equal numbers of elements.

    As shown below, this effectively creates a recursion tree whose leaf values should be as balanced as possible. We need not use 3 buckets at every step; the subdivision can be chosen to suit the data.

    Figure 11-14   Recursively subdividing buckets

    If we know the probability distribution of product prices in advance, we can choose bucket boundaries according to that distribution. We do not necessarily need to measure the distribution explicitly; we can approximate it with a probability model suited to the data.

    In Figure 11-15, we assume prices follow a normal distribution. This lets us choose suitable price intervals and distribute products evenly among the buckets.

    Figure 11-15   Defining buckets from a probability distribution

    ","path":["Chapter 11.   Sorting","11.8   Bucket sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/","level":1,"title":"11.9   Counting sort","text":"

    Counting sort sorts by counting how often each element occurs. It is typically used for integer arrays.

    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1191-a-simple-implementation","level":2,"title":"11.9.1   A simple implementation","text":"

    Start with a simple example: an array nums of length \\(n\\) containing nonnegative integers. The overall process is shown below.

    1. Traverse the array to find its maximum value, denoted by \\(m\\), then create an auxiliary array counter of length \\(m + 1\\).
    2. Use counter to count occurrences of each number in nums. Here, counter[num] is the number of occurrences of num. Simply traverse nums and increment counter[num] by \\(1\\) for each current value num.
    3. Because the indices of counter are inherently ordered, the numbers are effectively already sorted. Traverse counter and write each number into nums in ascending order, repeating it according to its count.

    Figure 11-16   The counting sort process

    The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py
    def counting_sort_naive(nums: list[int]):\n    \"\"\"Counting sort\"\"\"\n    # Simple implementation; cannot be used to sort objects\n    # 1. Find the maximum array element m\n    m = max(nums)\n    # 2. Count the occurrences of each number\n    # counter[num] is the number of occurrences of num\n    counter = [0] * (m + 1)\n    for num in nums:\n        counter[num] += 1\n    # 3. Traverse counter and fill the original array nums with the elements\n    i = 0\n    for num in range(m + 1):\n        for _ in range(counter[num]):\n            nums[i] = num\n            i += 1\n
    counting_sort.cpp
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid countingSortNaive(vector<int> &nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int num : nums) {\n        m = max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    vector<int> counter(m + 1, 0);\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.java
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid countingSortNaive(int[] nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int num : nums) {\n        m = Math.max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int[] counter = new int[m + 1];\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.cs
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid CountingSortNaive(int[] nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    foreach (int num in nums) {\n        m = Math.Max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int[] counter = new int[m + 1];\n    foreach (int num in nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.go
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfunc countingSortNaive(nums []int) {\n    // 1. Find the maximum array element m\n    m := 0\n    for _, num := range nums {\n        if num > m {\n            m = num\n        }\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    counter := make([]int, m+1)\n    for _, num := range nums {\n        counter[num]++\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    for i, num := 0, 0; num < m+1; num++ {\n        for j := 0; j < counter[num]; j++ {\n            nums[i] = num\n            i++\n        }\n    }\n}\n
    counting_sort.swift
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfunc countingSortNaive(nums: inout [Int]) {\n    // 1. Find the maximum array element m\n    let m = nums.max()!\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    var counter = Array(repeating: 0, count: m + 1)\n    for num in nums {\n        counter[num] += 1\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    var i = 0\n    for num in 0 ..< m + 1 {\n        for _ in 0 ..< counter[num] {\n            nums[i] = num\n            i += 1\n        }\n    }\n}\n
    counting_sort.js
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfunction countingSortNaive(nums) {\n    // 1. Find the maximum array element m\n    let m = Math.max(...nums);\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    const counter = new Array(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    let i = 0;\n    for (let num = 0; num < m + 1; num++) {\n        for (let j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.ts
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfunction countingSortNaive(nums: number[]): void {\n    // 1. Find the maximum array element m\n    let m: number = Math.max(...nums);\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    const counter: number[] = new Array<number>(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    let i = 0;\n    for (let num = 0; num < m + 1; num++) {\n        for (let j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.dart
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid countingSortNaive(List<int> nums) {\n  // 1. Find the maximum array element m\n  int m = 0;\n  for (int _num in nums) {\n    m = max(m, _num);\n  }\n  // 2. Count the occurrences of each number\n  // counter[_num] is the number of occurrences of _num\n  List<int> counter = List.filled(m + 1, 0);\n  for (int _num in nums) {\n    counter[_num]++;\n  }\n  // 3. Traverse counter and fill the original array nums with the elements\n  int i = 0;\n  for (int _num = 0; _num < m + 1; _num++) {\n    for (int j = 0; j < counter[_num]; j++, i++) {\n      nums[i] = _num;\n    }\n  }\n}\n
    counting_sort.rs
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfn counting_sort_naive(nums: &mut [i32]) {\n    // 1. Find the maximum array element m\n    let m = *nums.iter().max().unwrap();\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    let mut counter = vec![0; m as usize + 1];\n    for &num in nums.iter() {\n        counter[num as usize] += 1;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    let mut i = 0;\n    for num in 0..m + 1 {\n        for _ in 0..counter[num as usize] {\n            nums[i] = num;\n            i += 1;\n        }\n    }\n}\n
    counting_sort.c
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid countingSortNaive(int nums[], int size) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > m) {\n            m = nums[i];\n        }\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int *counter = calloc(m + 1, sizeof(int));\n    for (int i = 0; i < size; i++) {\n        counter[nums[i]]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n    // 4. Free memory\n    free(counter);\n}\n
    counting_sort.kt
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfun countingSortNaive(nums: IntArray) {\n    // 1. Find the maximum array element m\n    var m = 0\n    for (num in nums) {\n        m = max(m, num)\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    val counter = IntArray(m + 1)\n    for (num in nums) {\n        counter[num]++\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    var i = 0\n    for (num in 0..<m + 1) {\n        var j = 0\n        while (j < counter[num]) {\n            nums[i] = num\n            j++\n            i++\n        }\n    }\n}\n
    counting_sort.rb
    ### Counting sort ###\ndef counting_sort_naive(nums)\n  # Simple implementation; cannot be used to sort objects\n  # 1. Find the maximum array element m\n  m = 0\n  nums.each { |num| m = [m, num].max }\n  # 2. Count the occurrences of each number\n  # counter[num] is the number of occurrences of num\n  counter = Array.new(m + 1, 0)\n  nums.each { |num| counter[num] += 1 }\n  # 3. Traverse counter and fill the original array nums with the elements\n  i = 0\n  for num in 0...(m + 1)\n    (0...counter[num]).each do\n      nums[i] = num\n      i += 1\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    How counting sort relates to bucket sort

    From a bucket-sort perspective, each index of counter is a bucket, and counting occurrences is equivalent to distributing elements into their corresponding buckets. Counting sort is essentially a special case of bucket sort for integer data.

    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1192-a-complete-implementation","level":2,"title":"11.9.2   A complete implementation","text":"

    You may have noticed that step 3. above fails when the input consists of objects. Suppose the inputs are product objects and we want to sort them by price, a member variable of the class. The algorithm above can produce only the sorted prices.

    How can we obtain the sorted original data? First, calculate the “prefix sums” of counter. The prefix sum prefix[i] is the sum of the array entries up to index i:

    \\[ \\text{prefix}[i] = \\sum_{j=0}^i \\text{counter[j]} \\]

    The prefix sum has a precise meaning: prefix[num] - 1 is the index of the last occurrence of num in the result array res. This tells us where each element belongs. Next, traverse the original array nums in reverse and perform two steps for each element num.

    1. Place num at index prefix[num] - 1 in res.
    2. Decrease prefix[num] by \\(1\\) to obtain the index for placing the next occurrence of num.

    After the traversal, res contains the sorted result. Finally, copy res over the original array nums. Figure 11-17 shows the complete process.

    <1><2><3><4><5><6><7><8>

    Figure 11-17   Steps of counting sort

    The implementation is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py
    def counting_sort(nums: list[int]):\n    \"\"\"Counting sort\"\"\"\n    # Full implementation; stable and adaptable to sorting objects\n    # 1. Find the maximum array element m\n    m = max(nums)\n    # 2. Count the occurrences of each number\n    # counter[num] is the number of occurrences of num\n    counter = [0] * (m + 1)\n    for num in nums:\n        counter[num] += 1\n    # 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    # That is, counter[num]-1 is the index of the last occurrence of num in res\n    for i in range(m):\n        counter[i + 1] += counter[i]\n    # 4. Traverse nums in reverse and place each element in the result array res\n    # Initialize array res to store the result\n    n = len(nums)\n    res = [0] * n\n    for i in range(n - 1, -1, -1):\n        num = nums[i]\n        res[counter[num] - 1] = num  # Place num at the corresponding index\n        counter[num] -= 1  # Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    # Overwrite the original array nums with the result array res\n    for i in range(n):\n        nums[i] = res[i]\n
    counting_sort.cpp
    /* Counting sort */\n// Stable implementation that can be adapted to sort objects\nvoid countingSort(vector<int> &nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int num : nums) {\n        m = max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    vector<int> counter(m + 1, 0);\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    int n = nums.size();\n    vector<int> res(n);\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--;              // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    nums = res;\n}\n
    counting_sort.java
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nvoid countingSort(int[] nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int num : nums) {\n        m = Math.max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int[] counter = new int[m + 1];\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    int n = nums.length;\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
    counting_sort.cs
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nvoid CountingSort(int[] nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    foreach (int num in nums) {\n        m = Math.Max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int[] counter = new int[m + 1];\n    foreach (int num in nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    int n = nums.Length;\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
    counting_sort.go
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nfunc countingSort(nums []int) {\n    // 1. Find the maximum array element m\n    m := 0\n    for _, num := range nums {\n        if num > m {\n            m = num\n        }\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    counter := make([]int, m+1)\n    for _, num := range nums {\n        counter[num]++\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for i := 0; i < m; i++ {\n        counter[i+1] += counter[i]\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    n := len(nums)\n    res := make([]int, n)\n    for i := n - 1; i >= 0; i-- {\n        num := nums[i]\n        // Place num at the corresponding index\n        res[counter[num]-1] = num\n        // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n        counter[num]--\n    }\n    // Overwrite the original array nums with the result array res\n    copy(nums, res)\n}\n
    counting_sort.swift
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nfunc countingSort(nums: inout [Int]) {\n    // 1. Find the maximum array element m\n    let m = nums.max()!\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    var counter = Array(repeating: 0, count: m + 1)\n    for num in nums {\n        counter[num] += 1\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for i in 0 ..< m {\n        counter[i + 1] += counter[i]\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    var res = Array(repeating: 0, count: nums.count)\n    for i in nums.indices.reversed() {\n        let num = nums[i]\n        res[counter[num] - 1] = num // Place num at the corresponding index\n        counter[num] -= 1 // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for i in nums.indices {\n        nums[i] = res[i]\n    }\n}\n
    counting_sort.js
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nfunction countingSort(nums) {\n    // 1. Find the maximum array element m\n    let m = Math.max(...nums);\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    const counter = new Array(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (let i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    const n = nums.length;\n    const res = new Array(n);\n    for (let i = n - 1; i >= 0; i--) {\n        const num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
    counting_sort.ts
    /* Counting sort */\n// Full stable implementation; this approach can be adapted to sort objects\nfunction countingSort(nums: number[]): void {\n    // 1. Find the maximum array element m\n    let m: number = Math.max(...nums);\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    const counter: number[] = new Array<number>(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (let i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    const n = nums.length;\n    const res: number[] = new Array<number>(n);\n    for (let i = n - 1; i >= 0; i--) {\n        const num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
    counting_sort.dart
    /* Counting sort */\n// Full stable implementation; this approach can be adapted to sort objects\nvoid countingSort(List<int> nums) {\n  // 1. Find the maximum array element m\n  int m = 0;\n  for (int _num in nums) {\n    m = max(m, _num);\n  }\n  // 2. Count the occurrences of each number\n  // counter[_num] is the number of occurrences of _num\n  List<int> counter = List.filled(m + 1, 0);\n  for (int _num in nums) {\n    counter[_num]++;\n  }\n  // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n  // That is, counter[_num]-1 is the index of the last occurrence of _num in res\n  for (int i = 0; i < m; i++) {\n    counter[i + 1] += counter[i];\n  }\n  // 4. Traverse nums in reverse and place each element in the result array res\n  // Initialize array res to store the result\n  int n = nums.length;\n  List<int> res = List.filled(n, 0);\n  for (int i = n - 1; i >= 0; i--) {\n    int _num = nums[i];\n    res[counter[_num] - 1] = _num; // Place _num at the corresponding index\n    counter[_num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of _num\n  }\n  // Overwrite the original array nums with the result array res\n  nums.setAll(0, res);\n}\n
    counting_sort.rs
    /* Counting sort */\n// Stable implementation that can be adapted to sort objects\nfn counting_sort(nums: &mut [i32]) {\n    // 1. Find the maximum array element m\n    let m = *nums.iter().max().unwrap() as usize;\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    let mut counter = vec![0; m + 1];\n    for &num in nums.iter() {\n        counter[num as usize] += 1;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for i in 0..m {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    let n = nums.len();\n    let mut res = vec![0; n];\n    for i in (0..n).rev() {\n        let num = nums[i];\n        res[counter[num as usize] - 1] = num; // Place num at the corresponding index\n        counter[num as usize] -= 1; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    nums.copy_from_slice(&res)\n}\n
    counting_sort.c
    /* Counting sort */\n// Full, stable implementation; can be adapted to sort objects\nvoid countingSort(int nums[], int size) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > m) {\n            m = nums[i];\n        }\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int *counter = calloc(m, sizeof(int));\n    for (int i = 0; i < size; i++) {\n        counter[nums[i]]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    int *res = malloc(sizeof(int) * size);\n    for (int i = size - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--;              // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    memcpy(nums, res, size * sizeof(int));\n    // 5. Free memory\n    free(res);\n    free(counter);\n}\n
    counting_sort.kt
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nfun countingSort(nums: IntArray) {\n    // 1. Find the maximum array element m\n    var m = 0\n    for (num in nums) {\n        m = max(m, num)\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    val counter = IntArray(m + 1)\n    for (num in nums) {\n        counter[num]++\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (i in 0..<m) {\n        counter[i + 1] += counter[i]\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    val n = nums.size\n    val res = IntArray(n)\n    for (i in n - 1 downTo 0) {\n        val num = nums[i]\n        res[counter[num] - 1] = num // Place num at the corresponding index\n        counter[num]-- // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (i in 0..<n) {\n        nums[i] = res[i]\n    }\n}\n
    counting_sort.rb
    ### Counting sort ###\ndef counting_sort(nums)\n  # Full, stable implementation; the approach can be adapted to sort objects\n  # 1. Find the maximum array element m\n  m = nums.max\n  # 2. Count the occurrences of each number\n  # counter[num] is the number of occurrences of num\n  counter = Array.new(m + 1, 0)\n  nums.each { |num| counter[num] += 1 }\n  # 3. Compute prefix sums of counter to convert occurrence counts into end positions\n  # That is, counter[num]-1 is the index of the last occurrence of num in res\n  (0...m).each { |i| counter[i + 1] += counter[i] }\n  # 4. Traverse nums in reverse and place each element in the result array res\n  # Initialize array res to store the result\n  n = nums.length\n  res = Array.new(n, 0)\n  (n - 1).downto(0).each do |i|\n    num = nums[i]\n    res[counter[num] - 1] = num # Place num at the corresponding index\n    counter[num] -= 1 # Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n  end\n  # Overwrite the original array nums with the result array res\n  (0...n).each { |i| nums[i] = res[i] }\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1193-algorithm-properties","level":2,"title":"11.9.3   Algorithm properties","text":"
    • Time complexity: \\(O(n + m)\\); not adaptive. Traversing nums and counter each takes linear time. Usually \\(n \\gg m\\), so the complexity approaches \\(O(n)\\).
    • Space complexity: \\(O(n + m)\\); not in-place. The algorithm uses arrays res and counter of lengths \\(n\\) and \\(m\\), respectively.
    • Stable. Since elements are placed into res from right to left, traversing nums in reverse preserves the relative order of equal elements. A forward traversal also produces a correctly sorted result, but it is not stable.
    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1194-limitations","level":2,"title":"11.9.4   Limitations","text":"

    Counting sort may seem ingenious: it sorts efficiently simply by counting occurrences. However, its prerequisites are relatively restrictive.

    Counting sort applies only to nonnegative integers. For other data types, the values must be convertible to nonnegative integers without changing their relative order. For example, if an integer array contains negative numbers, add a constant to every element to make them all positive, sort, and then reverse the transformation.

    Counting sort suits large datasets with a relatively small value range. In the example above, \\(m\\) must not be too large, or the algorithm will consume excessive space. When \\(n \\ll m\\), counting sort takes \\(O(m)\\) time and may be slower than an \\(O(n \\log n)\\) sorting algorithm.

    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/exercises/","level":1,"title":"11.12   Exercises","text":"","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#11121-check-your-understanding","level":2,"title":"11.12.1   Check your understanding","text":"","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#1-the-first-passes-of-selection-sort-and-bubble-sort","level":3,"title":"1.   The first passes of selection sort and bubble sort","text":"

    Given the array [4, 2, 5, 1, 3], sort in ascending order throughout this exercise.

    1. Simulate the first two passes of selection sort. Write the array after each pass and identify the positions whose final values are now fixed.
    2. Simulate the first pass of bubble sort. Write the array and the number of swaps, and identify the position whose final value is now fixed.
    Reference answer
    1. The first two passes are:

      Pass Array Explanation 1 [1, 2, 5, 4, 3] Swap the minimum element, 1, with the first element 2 [1, 2, 5, 4, 3] The value 2 is already at index 1, so no swap is needed

      The first two positions are now fixed. Subsequent passes need only continue selecting the minimum from [5, 4, 3].

    2. Compare adjacent elements in order: swap 4 and 2, leave 4 and 5 unchanged, swap 5 and 1, then swap 5 and 3. The result is [2, 4, 1, 3, 5], with three swaps. The maximum element, 5, has moved to the end, fixing the last position.

    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#2-does-the-order-of-equal-elements-change","level":3,"title":"2.   Does the order of equal elements change?","text":"

    In the array \\([2_a, 2_b, 1]\\), \\(2_a\\) and \\(2_b\\) have equal values; the subscripts mark their original order.

    1. Write the array after the first pass of selection sort. Has the relative order of \\(2_a\\) and \\(2_b\\) changed?
    2. Write the array after the first pass of bubble sort. Has the relative order of \\(2_a\\) and \\(2_b\\) changed?
    3. Based on your answers, explain how the two algorithms differ in preserving the original order of equal elements.
    Reference answer
    1. The first pass of selection sort selects the minimum element, 1, and swaps it with the first element, \\(2_a\\), producing \\([1, 2_b, 2_a]\\). The swap moves \\(2_a\\) after \\(2_b\\), changing their relative order.

    2. Bubble sort first compares \\(2_a\\) with \\(2_b\\) and does not swap them because they are equal. It then compares and swaps \\(2_b\\) and 1. After the first pass, the array is \\([2_a, 1, 2_b]\\). Since \\(2_a\\) still precedes \\(2_b\\), their relative order is unchanged.

    3. In this example, selection sort changes the original order of equal elements. Bubble sort swaps adjacent elements only when the left element is greater than the right one. Equal elements are never swapped with each other, so their original order is preserved.

    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#3-comparing-counting-sort-and-radix-sort","level":3,"title":"3.   Comparing counting sort and radix sort","text":"

    A school needs to sort many student IDs, each exactly eight digits long. Answer the following:

    1. How many passes does radix sort need when starting with the least significant digit?
    2. If counting sort treats each student ID directly as an integer, why would many counting entries go unused?
    3. Based on your answers, which would you choose for sorting many eight-digit student IDs: counting sort or radix sort? Why?
    Reference answer
    1. Each ID has eight digits, so there are eight passes, from the least to the most significant digit. Each pass groups digits only by values 0 through 9.

    2. Direct counting reserves an entry for every possible eight-digit value, but actual students use only a small fraction of them. Most counts would remain 0.

    3. Choose radix sort. It uses the fixed number of digits and the ten possible values per digit, requiring only eight stable grouping passes. Counting sort using each entire eight-digit ID as an integer index would reserve entries for many values that never occur.

    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#11122-programming-exercises","level":2,"title":"11.12.2   Programming exercises","text":"","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#1-sorting-an-array-with-merge-sort","level":3,"title":"1.   Sorting an array with merge sort","text":"

    Given an integer array nums, implement merge sort yourself to arrange its elements in nondecreasing order and return the result. Do not call the language's built-in sorting function.

    Hints
    1. An interval of length at most 1 is already sorted.
    2. Split the interval in half at the midpoint and recursively sort each half.
    3. Use two pointers to merge the sorted halves, then write the result back to the original array.

    LeetCode

    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#2-sorting-an-integer-array-with-counting-sort","level":3,"title":"2.   Sorting an integer array with counting sort","text":"

    You are given an integer array nums and a nonnegative integer \\(K\\). Every element lies between \\(0\\) and \\(K\\).

    Implement counting sort, write the elements back into nums in nondecreasing order, and return nums. Do not determine the order by comparing elements with one another, and do not use the language's built-in sorting function.

    Hints
    1. Since every element is between 0 and K, its value can directly index the counting array.
    2. First scan nums, incrementing the count at the corresponding position by 1.
    3. Then scan the counting array from 0 through K. If x occurs a given number of times, write that many consecutive copies of x into nums.
    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/heap_sort/","level":1,"title":"11.7   Heap sort","text":"

    Tip

    Before reading this section, make sure you have completed the chapter on heaps.

    Heap sort is an efficient sorting algorithm built on the heap data structure. We can implement it with the heap-building and removal operations we have already learned.

    1. Build a min heap from the input array. The smallest element is now at the root.
    2. Repeatedly remove the root and record the removed elements to obtain an ascending sequence.

    Although this works, it requires an extra array to store the removed elements, wasting space. In practice, we usually use a more elegant implementation.

    ","path":["Chapter 11.   Sorting","11.7   Heap sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1171-algorithm-steps","level":2,"title":"11.7.1   Algorithm steps","text":"

    For an array of length \\(n\\), heap sort proceeds as shown below.

    1. Build a max heap from the input array. The largest element is now at the root.
    2. Swap the root, the first element, with the last element of the heap. Reduce the heap's length by \\(1\\) and increase the number of sorted elements by \\(1\\).
    3. Starting at the root, sift down to restore the heap property.
    4. Repeat steps 2. and 3.. After \\(n - 1\\) rounds, the array is sorted.

    Tip

    In fact, removing the root of a heap also includes steps 2. and 3., with the additional step of removing an element from the array.

    <1><2><3><4><5><6><7><8><9><10><11><12>

    Figure 11-12   Steps of heap sort

    The implementation uses the same sift_down() function introduced in the heap chapter. Because the heap shrinks as maximum elements are extracted, add a length parameter \\(n\\) to specify its current valid length. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap_sort.py
    def sift_down(nums: list[int], n: int, i: int):\n    \"\"\"Sift down from node i in a heap of size n\"\"\"\n    while True:\n        # Find the node with the largest value among i, l, and r, and store its index in ma\n        l = 2 * i + 1\n        r = 2 * i + 2\n        ma = i\n        if l < n and nums[l] > nums[ma]:\n            ma = l\n        if r < n and nums[r] > nums[ma]:\n            ma = r\n        # Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i:\n            break\n        # Swap the two nodes\n        nums[i], nums[ma] = nums[ma], nums[i]\n        # Continue sifting down\n        i = ma\n\ndef heap_sort(nums: list[int]):\n    \"\"\"Heap sort\"\"\"\n    # Build the heap by sifting down from every non-leaf node\n    for i in range(len(nums) // 2 - 1, -1, -1):\n        sift_down(nums, len(nums), i)\n    # Extract the largest element from the heap, repeating for n-1 rounds\n    for i in range(len(nums) - 1, 0, -1):\n        # Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        nums[0], nums[i] = nums[i], nums[0]\n        # Sift down starting at the root\n        sift_down(nums, i, 0)\n
    heap_sort.cpp
    /* Sift down from node i in a heap of size n */\nvoid siftDown(vector<int> &nums, int n, int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i) {\n            break;\n        }\n        // Swap the two nodes\n        swap(nums[i], nums[ma]);\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(vector<int> &nums) {\n    // Build the heap by sifting down from every non-leaf node\n    for (int i = nums.size() / 2 - 1; i >= 0; --i) {\n        siftDown(nums, nums.size(), i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (int i = nums.size() - 1; i > 0; --i) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        swap(nums[0], nums[i]);\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.java
    /* Sift down from node i in a heap of size n */\nvoid siftDown(int[] nums, int n, int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i)\n            break;\n        // Swap the two nodes\n        int temp = nums[i];\n        nums[i] = nums[ma];\n        nums[ma] = temp;\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(int[] nums) {\n    // Build the heap by sifting down from every non-leaf node\n    for (int i = nums.length / 2 - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        int tmp = nums[0];\n        nums[0] = nums[i];\n        nums[i] = tmp;\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.cs
    /* Sift down from node i in a heap of size n */\nvoid SiftDown(int[] nums, int n, int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i)\n            break;\n        // Swap the two nodes\n        (nums[ma], nums[i]) = (nums[i], nums[ma]);\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid HeapSort(int[] nums) {\n    // Build the heap by sifting down from every non-leaf node\n    for (int i = nums.Length / 2 - 1; i >= 0; i--) {\n        SiftDown(nums, nums.Length, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        (nums[i], nums[0]) = (nums[0], nums[i]);\n        // Sift down starting at the root\n        SiftDown(nums, i, 0);\n    }\n}\n
    heap_sort.go
    /* Sift down from node i in a heap of size n */\nfunc siftDown(nums *[]int, n, i int) {\n    for true {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        l := 2*i + 1\n        r := 2*i + 2\n        ma := i\n        if l < n && (*nums)[l] > (*nums)[ma] {\n            ma = l\n        }\n        if r < n && (*nums)[r] > (*nums)[ma] {\n            ma = r\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break\n        }\n        // Swap the two nodes\n        (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i]\n        // Continue sifting down\n        i = ma\n    }\n}\n\n/* Heap sort */\nfunc heapSort(nums *[]int) {\n    // Build the heap by sifting down from every non-leaf node\n    for i := len(*nums)/2 - 1; i >= 0; i-- {\n        siftDown(nums, len(*nums), i)\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for i := len(*nums) - 1; i > 0; i-- {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0]\n        // Sift down starting at the root\n        siftDown(nums, i, 0)\n    }\n}\n
    heap_sort.swift
    /* Sift down from node i in a heap of size n */\nfunc siftDown(nums: inout [Int], n: Int, i: Int) {\n    var i = i\n    while true {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = 2 * i + 1\n        let r = 2 * i + 2\n        var ma = i\n        if l < n, nums[l] > nums[ma] {\n            ma = l\n        }\n        if r < n, nums[r] > nums[ma] {\n            ma = r\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break\n        }\n        // Swap the two nodes\n        nums.swapAt(i, ma)\n        // Continue sifting down\n        i = ma\n    }\n}\n\n/* Heap sort */\nfunc heapSort(nums: inout [Int]) {\n    // Build the heap by sifting down from every non-leaf node\n    for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) {\n        siftDown(nums: &nums, n: nums.count, i: i)\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for i in nums.indices.dropFirst().reversed() {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        nums.swapAt(0, i)\n        // Sift down starting at the root\n        siftDown(nums: &nums, n: i, i: 0)\n    }\n}\n
    heap_sort.js
    /* Sift down from node i in a heap of size n */\nfunction siftDown(nums, n, i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let ma = i;\n        if (l < n && nums[l] > nums[ma]) {\n            ma = l;\n        }\n        if (r < n && nums[r] > nums[ma]) {\n            ma = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma === i) {\n            break;\n        }\n        // Swap the two nodes\n        [nums[i], nums[ma]] = [nums[ma], nums[i]];\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfunction heapSort(nums) {\n    // Build the heap by sifting down from every non-leaf node\n    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        [nums[0], nums[i]] = [nums[i], nums[0]];\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.ts
    /* Sift down from node i in a heap of size n */\nfunction siftDown(nums: number[], n: number, i: number): void {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let ma = i;\n        if (l < n && nums[l] > nums[ma]) {\n            ma = l;\n        }\n        if (r < n && nums[r] > nums[ma]) {\n            ma = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma === i) {\n            break;\n        }\n        // Swap the two nodes\n        [nums[i], nums[ma]] = [nums[ma], nums[i]];\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfunction heapSort(nums: number[]): void {\n    // Build the heap by sifting down from every non-leaf node\n    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        [nums[0], nums[i]] = [nums[i], nums[0]];\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.dart
    /* Sift down from node i in a heap of size n */\nvoid siftDown(List<int> nums, int n, int i) {\n  while (true) {\n    // Find the node with the largest value among i, l, and r, and store its index in ma\n    int l = 2 * i + 1;\n    int r = 2 * i + 2;\n    int ma = i;\n    if (l < n && nums[l] > nums[ma]) ma = l;\n    if (r < n && nums[r] > nums[ma]) ma = r;\n    // Stop if node i is largest or both child indices l and r are out of bounds\n    if (ma == i) break;\n    // Swap the two nodes\n    int temp = nums[i];\n    nums[i] = nums[ma];\n    nums[ma] = temp;\n    // Continue sifting down\n    i = ma;\n  }\n}\n\n/* Heap sort */\nvoid heapSort(List<int> nums) {\n  // Build the heap by sifting down from every non-leaf node\n  for (int i = nums.length ~/ 2 - 1; i >= 0; i--) {\n    siftDown(nums, nums.length, i);\n  }\n  // Extract the largest element from the heap, repeating for n-1 rounds\n  for (int i = nums.length - 1; i > 0; i--) {\n    // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n    int tmp = nums[0];\n    nums[0] = nums[i];\n    nums[i] = tmp;\n    // Sift down starting at the root\n    siftDown(nums, i, 0);\n  }\n}\n
    heap_sort.rs
    /* Sift down from node i in a heap of size n */\nfn sift_down(nums: &mut [i32], n: usize, mut i: usize) {\n    loop {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let mut ma = i;\n        if l < n && nums[l] > nums[ma] {\n            ma = l;\n        }\n        if r < n && nums[r] > nums[ma] {\n            ma = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break;\n        }\n        // Swap the two nodes\n        nums.swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfn heap_sort(nums: &mut [i32]) {\n    // Build the heap by sifting down from every non-leaf node\n    for i in (0..nums.len() / 2).rev() {\n        sift_down(nums, nums.len(), i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for i in (1..nums.len()).rev() {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        nums.swap(0, i);\n        // Sift down starting at the root\n        sift_down(nums, i, 0);\n    }\n}\n
    heap_sort.c
    /* Sift down from node i in a heap of size n */\nvoid siftDown(int nums[], int n, int i) {\n    while (1) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i) {\n            break;\n        }\n        // Swap the two nodes\n        int temp = nums[i];\n        nums[i] = nums[ma];\n        nums[ma] = temp;\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(int nums[], int n) {\n    // Build the heap by sifting down from every non-leaf node\n    for (int i = n / 2 - 1; i >= 0; --i) {\n        siftDown(nums, n, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (int i = n - 1; i > 0; --i) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        int tmp = nums[0];\n        nums[0] = nums[i];\n        nums[i] = tmp;\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.kt
    /* Sift down from node i in a heap of size n */\nfun siftDown(nums: IntArray, n: Int, li: Int) {\n    var i = li\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        val l = 2 * i + 1\n        val r = 2 * i + 2\n        var ma = i\n        if (l < n && nums[l] > nums[ma]) \n            ma = l\n        if (r < n && nums[r] > nums[ma]) \n            ma = r\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i) \n            break\n        // Swap the two nodes\n        val temp = nums[i]\n        nums[i] = nums[ma]\n        nums[ma] = temp\n        // Continue sifting down\n        i = ma\n    }\n}\n\n/* Heap sort */\nfun heapSort(nums: IntArray) {\n    // Build the heap by sifting down from every non-leaf node\n    for (i in nums.size / 2 - 1 downTo 0) {\n        siftDown(nums, nums.size, i)\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (i in nums.size - 1 downTo 1) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        val temp = nums[0]\n        nums[0] = nums[i]\n        nums[i] = temp\n        // Sift down starting at the root\n        siftDown(nums, i, 0)\n    }\n}\n
    heap_sort.rb
    ### Sift down from node i in a heap of size n ###\ndef sift_down(nums, n, i)\n  while true\n    # Find the node with the largest value among i, l, and r, and store its index in ma\n    l = 2 * i + 1\n    r = 2 * i + 2\n    ma = i\n    ma = l if l < n && nums[l] > nums[ma]\n    ma = r if r < n && nums[r] > nums[ma]\n    # Stop if node i is largest or both child indices l and r are out of bounds\n    break if ma == i\n    # Swap the two nodes\n    nums[i], nums[ma] = nums[ma], nums[i]\n    # Continue sifting down\n    i = ma\n  end\nend\n\n### Heap sort ###\ndef heap_sort(nums)\n  # Build the heap by sifting down from every non-leaf node\n  (nums.length / 2 - 1).downto(0) do |i|\n    sift_down(nums, nums.length, i)\n  end\n  # Extract the largest element from the heap, repeating for n-1 rounds\n  (nums.length - 1).downto(1) do |i|\n    # Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n    nums[0], nums[i] = nums[i], nums[0]\n    # Sift down starting at the root\n    sift_down(nums, i, 0)\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.7   Heap sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1172-algorithm-properties","level":2,"title":"11.7.2   Algorithm properties","text":"
    • Time complexity: \\(O(n \\log n)\\); not adaptive. Building the heap takes \\(O(n)\\) time. Extracting its maximum takes \\(O(\\log n)\\) time and is repeated \\(n - 1\\) times.
    • Space complexity: \\(O(1)\\); in-place. A few pointer variables use \\(O(1)\\) space. Swapping and sifting take place within the original array.
    • Not stable. Swapping the root with the last element of the heap may change the relative order of equal elements.
    ","path":["Chapter 11.   Sorting","11.7   Heap sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/","level":1,"title":"11.4   Insertion sort","text":"

    Insertion sort is a simple sorting algorithm that works much like arranging a hand of playing cards.

    Choose a base element from the unsorted section, compare it with the elements in the sorted section to its left, and insert it into the correct position.

    Figure 11-6 shows how to insert an element into an array. Let the base element be base. Shift all elements between the target index and base one position to the right, then assign base to the target index.

    Figure 11-6   A single insertion operation

    ","path":["Chapter 11.   Sorting","11.4   Insertion sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1141-algorithm-steps","level":2,"title":"11.4.1   Algorithm steps","text":"

    The overall insertion sort process is shown below.

    1. Initially, the first element of the array is treated as sorted.
    2. Select the second element as base and insert it into the correct position. The first two elements are now sorted.
    3. Select the third element as base and insert it into the correct position. The first three elements are now sorted.
    4. Continue in this way. In the final round, select the last element as base and insert it into the correct position. All elements are now sorted.

    Figure 11-7   The insertion sort process

    An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby insertion_sort.py
    def insertion_sort(nums: list[int]):\n    \"\"\"Insertion sort\"\"\"\n    # Outer loop: the sorted range is [0, i-1]\n    for i in range(1, len(nums)):\n        base = nums[i]\n        j = i - 1\n        # Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while j >= 0 and nums[j] > base:\n            nums[j + 1] = nums[j]  # Shift nums[j] one position to the right\n            j -= 1\n        nums[j + 1] = base  # Place base at the correct position\n
    insertion_sort.cpp
    /* Insertion sort */\nvoid insertionSort(vector<int> &nums) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (int i = 1; i < nums.size(); i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = base; // Place base at the correct position\n    }\n}\n
    insertion_sort.java
    /* Insertion sort */\nvoid insertionSort(int[] nums) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (int i = 1; i < nums.length; i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = base;        // Place base at the correct position\n    }\n}\n
    insertion_sort.cs
    /* Insertion sort */\nvoid InsertionSort(int[] nums) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (int i = 1; i < nums.Length; i++) {\n        int bas = nums[i], j = i - 1;\n        // Inner loop: insert bas at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > bas) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = bas;         // Place bas at the correct position\n    }\n}\n
    insertion_sort.go
    /* Insertion sort */\nfunc insertionSort(nums []int) {\n    // Outer loop: the sorted range is [0, i-1]\n    for i := 1; i < len(nums); i++ {\n        base := nums[i]\n        j := i - 1\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        for j >= 0 && nums[j] > base {\n            nums[j+1] = nums[j] // Shift nums[j] one position to the right\n            j--\n        }\n        nums[j+1] = base // Place base at the correct position\n    }\n}\n
    insertion_sort.swift
    /* Insertion sort */\nfunc insertionSort(nums: inout [Int]) {\n    // Outer loop: the sorted range is [0, i-1]\n    for i in nums.indices.dropFirst() {\n        let base = nums[i]\n        var j = i - 1\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while j >= 0, nums[j] > base {\n            nums[j + 1] = nums[j] // Shift nums[j] one position to the right\n            j -= 1\n        }\n        nums[j + 1] = base // Place base at the correct position\n    }\n}\n
    insertion_sort.js
    /* Insertion sort */\nfunction insertionSort(nums) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (let i = 1; i < nums.length; i++) {\n        let base = nums[i],\n            j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = base; // Place base at the correct position\n    }\n}\n
    insertion_sort.ts
    /* Insertion sort */\nfunction insertionSort(nums: number[]): void {\n    // Outer loop: the sorted range is [0, i-1]\n    for (let i = 1; i < nums.length; i++) {\n        const base = nums[i];\n        let j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = base; // Place base at the correct position\n    }\n}\n
    insertion_sort.dart
    /* Insertion sort */\nvoid insertionSort(List<int> nums) {\n  // Outer loop: the sorted range is [0, i-1]\n  for (int i = 1; i < nums.length; i++) {\n    int base = nums[i], j = i - 1;\n    // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n    while (j >= 0 && nums[j] > base) {\n      nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n      j--;\n    }\n    nums[j + 1] = base; // Place base at the correct position\n  }\n}\n
    insertion_sort.rs
    /* Insertion sort */\nfn insertion_sort(nums: &mut [i32]) {\n    // Outer loop: the sorted range is [0, i-1]\n    for i in 1..nums.len() {\n        let (base, mut j) = (nums[i], (i - 1) as i32);\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while j >= 0 && nums[j as usize] > base {\n            nums[(j + 1) as usize] = nums[j as usize]; // Shift nums[j] one position to the right\n            j -= 1;\n        }\n        nums[(j + 1) as usize] = base; // Place base at the correct position\n    }\n}\n
    insertion_sort.c
    /* Insertion sort */\nvoid insertionSort(int nums[], int size) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (int i = 1; i < size; i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            // Shift nums[j] one position to the right\n            nums[j + 1] = nums[j];\n            j--;\n        }\n        // Place base at the correct position\n        nums[j + 1] = base;\n    }\n}\n
    insertion_sort.kt
    /* Insertion sort */\nfun insertionSort(nums: IntArray) {\n    //Outer loop: the sorted prefix grows to 1, 2, ..., n elements\n    for (i in nums.indices) {\n        val base = nums[i]\n        var j = i - 1\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j] // Shift nums[j] one position to the right\n            j--\n        }\n        nums[j + 1] = base        // Place base at the correct position\n    }\n}\n
    insertion_sort.rb
    ### Insertion sort ###\ndef insertion_sort(nums)\n  n = nums.length\n  # Outer loop: the sorted range is [0, i-1]\n  for i in 1...n\n    base = nums[i]\n    j = i - 1\n    # Inner loop: insert base at the correct position in the sorted range [0, i-1]\n    while j >= 0 && nums[j] > base\n      nums[j + 1] = nums[j] # Shift nums[j] one position to the right\n      j -= 1\n    end\n    nums[j + 1] = base # Place base at the correct position\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.4   Insertion sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1142-algorithm-properties","level":2,"title":"11.4.2   Algorithm properties","text":"
    • Time complexity: \\(O(n^2)\\); adaptive. In the worst case, the insertion operations require \\(n - 1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), and \\(1\\) iterations, totaling \\((n - 1) n / 2\\), so the time complexity is \\(O(n^2)\\). For ordered data, an insertion can stop early. A fully sorted input achieves the best-case time complexity of \\(O(n)\\).
    • Space complexity: \\(O(1)\\); in-place. Pointers \\(i\\) and \\(j\\) use constant extra space.
    • Stable. Each element is inserted to the right of equal elements, preserving their order.
    ","path":["Chapter 11.   Sorting","11.4   Insertion sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1143-why-use-insertion-sort","level":2,"title":"11.4.3   Why use insertion sort?","text":"

    Insertion sort takes \\(O(n^2)\\) time, while quick sort, which we will study shortly, takes \\(O(n \\log n)\\). Despite its higher time complexity, insertion sort is usually faster for small datasets.

    This resembles the comparison between linear and binary search. Algorithms such as quick sort, with \\(O(n \\log n)\\) time complexity, use divide and conquer and tend to involve more elementary operations. When the dataset is small, \\(n^2\\) and \\(n \\log n\\) are relatively close, so asymptotic complexity is not the dominant factor; the number of elementary operations per round matters more.

    In fact, the built-in sorting functions of many programming languages, such as Java, use insertion sort. The general approach is to use a divide-and-conquer algorithm such as quick sort for long arrays and insertion sort directly for short ones.

    Although bubble sort, selection sort, and insertion sort all take \\(O(n^2)\\) time, insertion sort is used much more often in practice, mainly for the following reasons.

    • Bubble sort swaps elements, using a temporary variable and three elementary operations. Insertion sort shifts elements by assignment, requiring just one operation. Bubble sort therefore usually has higher computational overhead.
    • Selection sort takes \\(O(n^2)\\) time in every case. For partially sorted data, insertion sort is usually more efficient than selection sort.
    • Selection sort is not stable and therefore cannot be used for multilevel sorting.
    ","path":["Chapter 11.   Sorting","11.4   Insertion sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/","level":1,"title":"11.6   Merge sort","text":"

    Merge sort is a divide-and-conquer sorting algorithm with the “division” and “merging” phases shown below.

    1. Division: recursively split the array at its midpoint, turning the problem of sorting a long array into problems of sorting shorter arrays.
    2. Merging: stop dividing when a subarray has length 1, then repeatedly merge two shorter sorted arrays into one longer sorted array until the process is complete.

    Figure 11-10   The division and merging phases of merge sort

    ","path":["Chapter 11.   Sorting","11.6   Merge sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1161-algorithm-steps","level":2,"title":"11.6.1   Algorithm steps","text":"

    As shown below, the division phase works from the top down, recursively splitting each array into two subarrays at its midpoint.

    1. Calculate the midpoint mid, then recursively divide the left subarray, [left, mid], and the right subarray, [mid + 1, right].
    2. Repeat step 1. recursively until each subarray has length 1.

    The merging phase works from the bottom up, combining the left and right subarrays into a sorted array. Merging begins with subarrays of length 1, so every subarray involved in this phase is already sorted.

    <1><2><3><4><5><6><7><8><9><10>

    Figure 11-11   Steps of merge sort

    Merge sort follows the same recursive order as a postorder traversal of a binary tree.

    • Postorder traversal: recurse into the left subtree, then the right subtree, and finally process the root.
    • Merge sort: recurse into the left subarray, then the right subarray, and finally merge them.

    The implementation follows. The interval to merge in nums is [left, right], while its corresponding interval in tmp is [0, right - left].

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby merge_sort.py
    def merge(nums: list[int], left: int, mid: int, right: int):\n    \"\"\"Merge the left and right subarrays\"\"\"\n    # The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    # Create a temporary array tmp to store the merged result\n    tmp = [0] * (right - left + 1)\n    # Initialize the starting indices of the left and right subarrays\n    i, j, k = left, mid + 1, 0\n    # While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while i <= mid and j <= right:\n        if nums[i] <= nums[j]:\n            tmp[k] = nums[i]\n            i += 1\n        else:\n            tmp[k] = nums[j]\n            j += 1\n        k += 1\n    # Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid:\n        tmp[k] = nums[i]\n        i += 1\n        k += 1\n    while j <= right:\n        tmp[k] = nums[j]\n        j += 1\n        k += 1\n    # Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for k in range(0, len(tmp)):\n        nums[left + k] = tmp[k]\n\ndef merge_sort(nums: list[int], left: int, right: int):\n    \"\"\"Merge sort\"\"\"\n    # Base case\n    if left >= right:\n        return  # Stop recursing when the subarray has at most one element\n    # Divide phase\n    mid = (left + right) // 2 # Calculate the midpoint\n    merge_sort(nums, left, mid)  # Recurse on the left subarray\n    merge_sort(nums, mid + 1, right)  # Recurse on the right subarray\n    # Merge phase\n    merge(nums, left, mid, right)\n
    merge_sort.cpp
    /* Merge the left and right subarrays */\nvoid merge(vector<int> &nums, int left, int mid, int right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    vector<int> tmp(right - left + 1);\n    // Initialize the starting indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.size(); k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid mergeSort(vector<int> &nums, int left, int right) {\n    // Base case\n    if (left >= right)\n        return; // Stop recursing when the subarray has at most one element\n    // Divide phase\n    int mid = left + (right - left) / 2;    // Calculate the midpoint\n    mergeSort(nums, left, mid);      // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.java
    /* Merge the left and right subarrays */\nvoid merge(int[] nums, int left, int mid, int right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    int[] tmp = new int[right - left + 1];\n    // Initialize the starting indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid mergeSort(int[] nums, int left, int right) {\n    // Base case\n    if (left >= right)\n        return; // Stop recursing when the subarray has at most one element\n    // Divide phase\n    int mid = left + (right - left) / 2; // Calculate the midpoint\n    mergeSort(nums, left, mid); // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.cs
    /* Merge the left and right subarrays */\nvoid Merge(int[] nums, int left, int mid, int right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    int[] tmp = new int[right - left + 1];\n    // Initialize the starting indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.Length; ++k) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid MergeSort(int[] nums, int left, int right) {\n    // Base case\n    if (left >= right) return;       // Stop recursing when the subarray has at most one element\n    // Divide phase\n    int mid = left + (right - left) / 2;    // Calculate the midpoint\n    MergeSort(nums, left, mid);      // Recurse on the left subarray\n    MergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    Merge(nums, left, mid, right);\n}\n
    merge_sort.go
    /* Merge the left and right subarrays */\nfunc merge(nums []int, left, mid, right int) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    tmp := make([]int, right-left+1)\n    // Initialize the starting indices of the left and right subarrays\n    i, j, k := left, mid+1, 0\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    for i <= mid && j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i]\n            i++\n        } else {\n            tmp[k] = nums[j]\n            j++\n        }\n        k++\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    for i <= mid {\n        tmp[k] = nums[i]\n        i++\n        k++\n    }\n    for j <= right {\n        tmp[k] = nums[j]\n        j++\n        k++\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for k := 0; k < len(tmp); k++ {\n        nums[left+k] = tmp[k]\n    }\n}\n\n/* Merge sort */\nfunc mergeSort(nums []int, left, right int) {\n    // Base case\n    if left >= right {\n        return\n    }\n    // Divide phase\n    mid := left + (right - left) / 2\n    mergeSort(nums, left, mid)\n    mergeSort(nums, mid+1, right)\n    // Merge phase\n    merge(nums, left, mid, right)\n}\n
    merge_sort.swift
    /* Merge the left and right subarrays */\nfunc merge(nums: inout [Int], left: Int, mid: Int, right: Int) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    var tmp = Array(repeating: 0, count: right - left + 1)\n    // Initialize the starting indices of the left and right subarrays\n    var i = left, j = mid + 1, k = 0\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while i <= mid, j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i]\n            i += 1\n        } else {\n            tmp[k] = nums[j]\n            j += 1\n        }\n        k += 1\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid {\n        tmp[k] = nums[i]\n        i += 1\n        k += 1\n    }\n    while j <= right {\n        tmp[k] = nums[j]\n        j += 1\n        k += 1\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for k in tmp.indices {\n        nums[left + k] = tmp[k]\n    }\n}\n\n/* Merge sort */\nfunc mergeSort(nums: inout [Int], left: Int, right: Int) {\n    // Base case\n    if left >= right { // Stop recursing when the subarray has at most one element\n        return\n    }\n    // Divide phase\n    let mid = left + (right - left) / 2 // Calculate the midpoint\n    mergeSort(nums: &nums, left: left, right: mid) // Recurse on the left subarray\n    mergeSort(nums: &nums, left: mid + 1, right: right) // Recurse on the right subarray\n    // Merge phase\n    merge(nums: &nums, left: left, mid: mid, right: right)\n}\n
    merge_sort.js
    /* Merge the left and right subarrays */\nfunction merge(nums, left, mid, right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    const tmp = new Array(right - left + 1);\n    // Initialize the starting indices of the left and right subarrays\n    let i = left,\n        j = mid + 1,\n        k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfunction mergeSort(nums, left, right) {\n    // Base case\n    if (left >= right) return; // Stop recursing when the subarray has at most one element\n    // Divide phase\n    let mid = Math.floor(left + (right - left) / 2); // Calculate the midpoint\n    mergeSort(nums, left, mid); // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.ts
    /* Merge the left and right subarrays */\nfunction merge(nums: number[], left: number, mid: number, right: number): void {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    const tmp = new Array(right - left + 1);\n    // Initialize the starting indices of the left and right subarrays\n    let i = left,\n        j = mid + 1,\n        k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfunction mergeSort(nums: number[], left: number, right: number): void {\n    // Base case\n    if (left >= right) return; // Stop recursing when the subarray has zero or one element\n    // Divide phase\n    let mid = Math.floor(left + (right - left) / 2); // Calculate the midpoint\n    mergeSort(nums, left, mid); // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.dart
    /* Merge the left and right subarrays */\nvoid merge(List<int> nums, int left, int mid, int right) {\n  // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n  // Create a temporary array tmp to store the merged result\n  List<int> tmp = List.filled(right - left + 1, 0);\n  // Initialize the starting indices of the left and right subarrays\n  int i = left, j = mid + 1, k = 0;\n  // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n  while (i <= mid && j <= right) {\n    if (nums[i] <= nums[j])\n      tmp[k++] = nums[i++];\n    else\n      tmp[k++] = nums[j++];\n  }\n  // Copy the remaining elements of the left and right subarrays into the temporary array\n  while (i <= mid) {\n    tmp[k++] = nums[i++];\n  }\n  while (j <= right) {\n    tmp[k++] = nums[j++];\n  }\n  // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n  for (k = 0; k < tmp.length; k++) {\n    nums[left + k] = tmp[k];\n  }\n}\n\n/* Merge sort */\nvoid mergeSort(List<int> nums, int left, int right) {\n  // Base case\n  if (left >= right) return; // Stop recursing when the subarray has at most one element\n  // Divide phase\n  int mid = left + (right - left) ~/ 2; // Calculate the midpoint\n  mergeSort(nums, left, mid); // Recurse on the left subarray\n  mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n  // Merge phase\n  merge(nums, left, mid, right);\n}\n
    merge_sort.rs
    /* Merge the left and right subarrays */\nfn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    let tmp_size = right - left + 1;\n    let mut tmp = vec![0; tmp_size];\n    // Initialize the starting indices of the left and right subarrays\n    let (mut i, mut j, mut k) = (left, mid + 1, 0);\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while i <= mid && j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i];\n            i += 1;\n        } else {\n            tmp[k] = nums[j];\n            j += 1;\n        }\n        k += 1;\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid {\n        tmp[k] = nums[i];\n        k += 1;\n        i += 1;\n    }\n    while j <= right {\n        tmp[k] = nums[j];\n        k += 1;\n        j += 1;\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for k in 0..tmp_size {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfn merge_sort(nums: &mut [i32], left: usize, right: usize) {\n    // Base case\n    if left >= right {\n        return; // Stop recursing when the subarray has at most one element\n    }\n\n    // Divide phase\n    let mid = left + (right - left) / 2; // Calculate the midpoint\n    merge_sort(nums, left, mid); // Recurse on the left subarray\n    merge_sort(nums, mid + 1, right); // Recurse on the right subarray\n\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.c
    /* Merge the left and right subarrays */\nvoid merge(int *nums, int left, int mid, int right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    int tmpSize = right - left + 1;\n    int *tmp = (int *)malloc(tmpSize * sizeof(int));\n    // Initialize the starting indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmpSize; ++k) {\n        nums[left + k] = tmp[k];\n    }\n    // Free memory\n    free(tmp);\n}\n\n/* Merge sort */\nvoid mergeSort(int *nums, int left, int right) {\n    // Base case\n    if (left >= right)\n        return; // Stop recursing when the subarray has at most one element\n    // Divide phase\n    int mid = left + (right - left) / 2;    // Calculate the midpoint\n    mergeSort(nums, left, mid);      // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.kt
    /* Merge the left and right subarrays */\nfun merge(nums: IntArray, left: Int, mid: Int, right: Int) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    val tmp = IntArray(right - left + 1)\n    // Initialize the starting indices of the left and right subarrays\n    var i = left\n    var j = mid + 1\n    var k = 0\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++]\n        else\n            tmp[k++] = nums[j++]\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++]\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++]\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (l in tmp.indices) {\n        nums[left + l] = tmp[l]\n    }\n}\n\n/* Merge sort */\nfun mergeSort(nums: IntArray, left: Int, right: Int) {\n    // Base case\n    if (left >= right) return  // Stop recursing when the subarray has at most one element\n    // Divide phase\n    val mid = left + (right - left) / 2 // Calculate the midpoint\n    mergeSort(nums, left, mid) // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right) // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right)\n}\n
    merge_sort.rb
    ### Merge the left and right subarrays ###\ndef merge(nums, left, mid, right)\n  # The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n  # Create a temporary array tmp to store the merged result\n  tmp = Array.new(right - left + 1, 0)\n  # Initialize the starting indices of the left and right subarrays\n  i, j, k = left, mid + 1, 0\n  # While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n  while i <= mid && j <= right\n    if nums[i] <= nums[j]\n      tmp[k] = nums[i]\n      i += 1\n    else\n      tmp[k] = nums[j]\n      j += 1\n    end\n    k += 1\n  end\n  # Copy the remaining elements of the left and right subarrays into the temporary array\n  while i <= mid\n    tmp[k] = nums[i]\n    i += 1\n    k += 1\n  end\n  while j <= right\n    tmp[k] = nums[j]\n    j += 1\n    k += 1\n  end\n  # Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n  (0...tmp.length).each do |k|\n    nums[left + k] = tmp[k]\n  end\nend\n\n### Merge sort ###\ndef merge_sort(nums, left, right)\n  # Base case\n  # Stop recursing when the subarray contains at most one element\n  return if left >= right\n  # Divide phase\n  mid = left + (right - left) / 2 # Calculate the midpoint\n  merge_sort(nums, left, mid) # Recurse on the left subarray\n  merge_sort(nums, mid + 1, right) # Recurse on the right subarray\n  # Merge phase\n  merge(nums, left, mid, right)\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.6   Merge sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1162-algorithm-properties","level":2,"title":"11.6.2   Algorithm properties","text":"
    • Time complexity: \\(O(n \\log n)\\); not adaptive. Division produces a recursion tree of height \\(\\log n\\). Merging performs a total of \\(n\\) operations at each level, giving \\(O(n \\log n)\\) overall.
    • Space complexity: \\(O(n)\\); not in-place. The recursion depth is \\(\\log n\\), requiring \\(O(\\log n)\\) space for stack frames. Merging uses an auxiliary array requiring \\(O(n)\\) extra space.
    • Stable. Merging preserves the order of equal elements.
    ","path":["Chapter 11.   Sorting","11.6   Merge sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1163-sorting-linked-lists","level":2,"title":"11.6.3   Sorting linked lists","text":"

    Merge sort has a significant advantage over other sorting algorithms for linked lists: it can reduce the space complexity of sorting a linked list to \\(O(1)\\).

    • Division: use iteration instead of recursion to divide the list, eliminating the space needed for recursive stack frames.
    • Merging: inserting and deleting linked-list nodes requires only changing references or pointers. Merging two short sorted lists into a longer one therefore needs no additional list.

    The implementation is fairly involved. Interested readers can consult further resources for details.

    ","path":["Chapter 11.   Sorting","11.6   Merge sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/","level":1,"title":"11.5   Quick sort","text":"

    Quick sort is an efficient, widely used sorting algorithm based on divide and conquer.

    Its core operation is “partitioning”: choose an array element as the “pivot,” move all smaller elements to its left, and move all larger elements to its right. The partitioning process is shown below.

    1. Choose the leftmost element as the pivot and initialize pointers i and j at the two ends of the array.
    2. In a loop, use i to find the first element greater than the pivot and j to find the first element smaller than it, then swap those elements.
    3. Repeat step 2. until i and j meet. Finally, swap the pivot into the boundary between the two subarrays.
    <1><2><3><4><5><6><7><8><9>

    Figure 11-8   Steps of partitioning

    After partitioning, the original array has three parts: the left subarray, the pivot, and the right subarray. Every element in the left subarray is \\(\\leq\\) the pivot, which is \\(\\leq\\) every element in the right subarray. We now need only sort the two subarrays.

    Quick sort's divide-and-conquer strategy

    Partitioning reduces the problem of sorting one long array to sorting two shorter arrays.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
    def partition(self, nums: list[int], left: int, right: int) -> int:\n    \"\"\"Two-pointer partitioning\"\"\"\n    # Use nums[left] as the pivot\n    i, j = left, right\n    while i < j:\n        while i < j and nums[j] >= nums[left]:\n            j -= 1  # Scan from right to left for the first element smaller than the pivot\n        while i < j and nums[i] <= nums[left]:\n            i += 1  # Scan from left to right for the first element larger than the pivot\n        # Swap the elements\n        nums[i], nums[j] = nums[j], nums[i]\n    # Swap the pivot into the boundary position between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i  # Return the pivot index\n
    quick_sort.cpp
    /* Two-pointer partitioning */\nint partition(vector<int> &nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;                // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;                // Scan from left to right for the first element larger than the pivot\n        swap(nums[i], nums[j]); // Swap these two elements\n    }\n    swap(nums[i], nums[left]);  // Swap the pivot into the boundary position between the two subarrays\n    return i;                   // Return the pivot index\n}\n
    quick_sort.java
    /* Swap the elements */\nvoid swap(int[] nums, int i, int j) {\n    int tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\nint partition(int[] nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left);  // Swap the pivot into the boundary position between the two subarrays\n    return i;             // Return the pivot index\n}\n
    quick_sort.cs
    /* Swap the elements */\nvoid Swap(int[] nums, int i, int j) {\n    (nums[j], nums[i]) = (nums[i], nums[j]);\n}\n\n/* Two-pointer partitioning */\nint Partition(int[] nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        Swap(nums, i, j); // Swap these two elements\n    }\n    Swap(nums, i, left);  // Swap the pivot into the boundary position between the two subarrays\n    return i;             // Return the pivot index\n}\n
    quick_sort.go
    /* Two-pointer partitioning */\nfunc (q *quickSort) partition(nums []int, left, right int) int {\n    // Use nums[left] as the pivot\n    i, j := left, right\n    for i < j {\n        for i < j && nums[j] >= nums[left] {\n            j-- // Scan from right to left for the first element smaller than the pivot\n        }\n        for i < j && nums[i] <= nums[left] {\n            i++ // Scan from left to right for the first element larger than the pivot\n        }\n        // Swap the elements\n        nums[i], nums[j] = nums[j], nums[i]\n    }\n    // Swap the pivot into the boundary position between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i // Return the pivot index\n}\n
    quick_sort.swift
    /* Two-pointer partitioning */\nfunc partition(nums: inout [Int], left: Int, right: Int) -> Int {\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while i < j {\n        while i < j, nums[j] >= nums[left] {\n            j -= 1 // Scan from right to left for the first element smaller than the pivot\n        }\n        while i < j, nums[i] <= nums[left] {\n            i += 1 // Scan from left to right for the first element larger than the pivot\n        }\n        nums.swapAt(i, j) // Swap these two elements\n    }\n    nums.swapAt(i, left) // Swap the pivot into the boundary position between the two subarrays\n    return i // Return the pivot index\n}\n
    quick_sort.js
    /* Swap the elements */\nswap(nums, i, j) {\n    let tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\npartition(nums, left, right) {\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j -= 1; // Scan from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i += 1; // Scan from left to right for the first element larger than the pivot\n        }\n        // Swap the elements\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i; // Return the pivot index\n}\n
    quick_sort.ts
    /* Swap the elements */\nswap(nums: number[], i: number, j: number): void {\n    let tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\npartition(nums: number[], left: number, right: number): number {\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j -= 1; // Scan from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i += 1; // Scan from left to right for the first element larger than the pivot\n        }\n        // Swap the elements\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i; // Return the pivot index\n}\n
    quick_sort.dart
    /* Swap the elements */\nvoid _swap(List<int> nums, int i, int j) {\n  int tmp = nums[i];\n  nums[i] = nums[j];\n  nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\nint _partition(List<int> nums, int left, int right) {\n  // Use nums[left] as the pivot\n  int i = left, j = right;\n  while (i < j) {\n    while (i < j && nums[j] >= nums[left]) j--; // Scan from right to left for the first element smaller than the pivot\n    while (i < j && nums[i] <= nums[left]) i++; // Scan from left to right for the first element larger than the pivot\n    _swap(nums, i, j); // Swap these two elements\n  }\n  _swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n  return i; // Return the pivot index\n}\n
    quick_sort.rs
    /* Two-pointer partitioning */\nfn partition(nums: &mut [i32], left: usize, right: usize) -> usize {\n    // Use nums[left] as the pivot\n    let (mut i, mut j) = (left, right);\n    while i < j {\n        while i < j && nums[j] >= nums[left] {\n            j -= 1; // Scan from right to left for the first element smaller than the pivot\n        }\n        while i < j && nums[i] <= nums[left] {\n            i += 1; // Scan from left to right for the first element larger than the pivot\n        }\n        nums.swap(i, j); // Swap these two elements\n    }\n    nums.swap(i, left); // Swap the pivot into the boundary position between the two subarrays\n    i // Return the pivot index\n}\n
    quick_sort.c
    /* Swap the elements */\nvoid swap(int nums[], int i, int j) {\n    int tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\nint partition(int nums[], int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j--; // Scan from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i++; // Scan from left to right for the first element larger than the pivot\n        }\n        // Swap these two elements\n        swap(nums, i, j);\n    }\n    // Swap the pivot into the boundary position between the two subarrays\n    swap(nums, i, left);\n    // Return the pivot index\n    return i;\n}\n
    quick_sort.kt
    /* Swap the elements */\nfun swap(nums: IntArray, i: Int, j: Int) {\n    val temp = nums[i]\n    nums[i] = nums[j]\n    nums[j] = temp\n}\n\n/* Two-pointer partitioning */\nfun partition(nums: IntArray, left: Int, right: Int): Int {\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--           // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++           // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j)  // Swap these two elements\n    }\n    swap(nums, i, left)   // Swap the pivot into the boundary position between the two subarrays\n    return i              // Return the pivot index\n}\n
    quick_sort.rb
    ### Two-pointer partitioning ###\ndef partition(nums, left, right)\n  # Use nums[left] as the pivot\n  i, j = left, right\n  while i < j\n    while i < j && nums[j] >= nums[left]\n      j -= 1 # Scan from right to left for the first element smaller than the pivot\n    end\n    while i < j && nums[i] <= nums[left]\n      i += 1 # Scan from left to right for the first element larger than the pivot\n    end\n    # Swap the elements\n    nums[i], nums[j] = nums[j], nums[i]\n  end\n  # Swap the pivot into the boundary position between the two subarrays\n  nums[i], nums[left] = nums[left], nums[i]\n  i # Return the pivot index\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1151-algorithm-steps","level":2,"title":"11.5.1   Algorithm steps","text":"

    The overall quick sort process is shown below.

    1. Partition the original array to obtain unsorted left and right subarrays.
    2. Recursively partition the left and right subarrays.
    3. Continue until each subarray has length 1, completing the sort.

    Figure 11-9   The quick sort process

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
    def quick_sort(self, nums: list[int], left: int, right: int):\n    \"\"\"Quick sort\"\"\"\n    # Stop recursing when the subarray has at most one element\n    if left >= right:\n        return\n    # Two-pointer partitioning\n    pivot = self.partition(nums, left, right)\n    # Recurse on the left and right subarrays\n    self.quick_sort(nums, left, pivot - 1)\n    self.quick_sort(nums, pivot + 1, right)\n
    quick_sort.cpp
    /* Quick sort */\nvoid quickSort(vector<int> &nums, int left, int right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right)\n        return;\n    // Two-pointer partitioning\n    int pivot = partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.java
    /* Quick sort */\nvoid quickSort(int[] nums, int left, int right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right)\n        return;\n    // Two-pointer partitioning\n    int pivot = partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.cs
    /* Quick sort */\nvoid QuickSort(int[] nums, int left, int right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right)\n        return;\n    // Two-pointer partitioning\n    int pivot = Partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    QuickSort(nums, left, pivot - 1);\n    QuickSort(nums, pivot + 1, right);\n}\n
    quick_sort.go
    /* Quick sort */\nfunc (q *quickSort) quickSort(nums []int, left, right int) {\n    // Stop recursing when the subarray has at most one element\n    if left >= right {\n        return\n    }\n    // Two-pointer partitioning\n    pivot := q.partition(nums, left, right)\n    // Recurse on the left and right subarrays\n    q.quickSort(nums, left, pivot-1)\n    q.quickSort(nums, pivot+1, right)\n}\n
    quick_sort.swift
    /* Quick sort */\nfunc quickSort(nums: inout [Int], left: Int, right: Int) {\n    // Stop recursing when the subarray has at most one element\n    if left >= right {\n        return\n    }\n    // Two-pointer partitioning\n    let pivot = partition(nums: &nums, left: left, right: right)\n    // Recurse on the left and right subarrays\n    quickSort(nums: &nums, left: left, right: pivot - 1)\n    quickSort(nums: &nums, left: pivot + 1, right: right)\n}\n
    quick_sort.js
    /* Quick sort */\nquickSort(nums, left, right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right) return;\n    // Two-pointer partitioning\n    const pivot = this.partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    this.quickSort(nums, left, pivot - 1);\n    this.quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.ts
    /* Quick sort */\nquickSort(nums: number[], left: number, right: number): void {\n    // Stop recursing when the subarray has zero or one element\n    if (left >= right) {\n        return;\n    }\n    // Two-pointer partitioning\n    const pivot = this.partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    this.quickSort(nums, left, pivot - 1);\n    this.quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.dart
    /* Quick sort */\nvoid quickSort(List<int> nums, int left, int right) {\n  // Stop recursing when the subarray has at most one element\n  if (left >= right) return;\n  // Two-pointer partitioning\n  int pivot = _partition(nums, left, right);\n  // Recurse on the left and right subarrays\n  quickSort(nums, left, pivot - 1);\n  quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.rs
    /* Quick sort */\npub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) {\n    // Stop recursing when the subarray has at most one element\n    if left >= right {\n        return;\n    }\n    // Two-pointer partitioning\n    let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n    // Recurse on the left and right subarrays\n    Self::quick_sort(left, pivot - 1, nums);\n    Self::quick_sort(pivot + 1, right, nums);\n}\n
    quick_sort.c
    /* Quick sort */\nvoid quickSort(int nums[], int left, int right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right) {\n        return;\n    }\n    // Two-pointer partitioning\n    int pivot = partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.kt
    /* Quick sort */\nfun quickSort(nums: IntArray, left: Int, right: Int) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right) return\n    // Two-pointer partitioning\n    val pivot = partition(nums, left, right)\n    // Recurse on the left and right subarrays\n    quickSort(nums, left, pivot - 1)\n    quickSort(nums, pivot + 1, right)\n}\n
    quick_sort.rb
    ### Quick sort ###\ndef quick_sort(nums, left, right)\n  # Continue while the subarray contains more than one element\n  if left < right\n    # Two-pointer partitioning\n    pivot = partition(nums, left, right)\n    # Recurse on the left and right subarrays\n    quick_sort(nums, left, pivot - 1)\n    quick_sort(nums, pivot + 1, right)\n  end\n  nums\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1152-algorithm-properties","level":2,"title":"11.5.2   Algorithm properties","text":"
    • Time complexity: \\(O(n \\log n)\\); not adaptive. On average, partitioning produces \\(\\log n\\) recursive levels, with \\(n\\) loop iterations in total at each level, giving \\(O(n \\log n)\\) time. In the worst case, each partition splits an array of length \\(n\\) into subarrays of lengths \\(0\\) and \\(n - 1\\). The recursion reaches \\(n\\) levels, each involving up to \\(n\\) iterations, for \\(O(n^2)\\) time overall.
    • Space complexity: \\(O(n)\\); in-place. A fully reverse-sorted input reaches the worst-case recursion depth of \\(n\\), requiring \\(O(n)\\) space for stack frames. Sorting takes place in the original array without an auxiliary array.
    • Not stable. In the final partitioning step, the pivot may be swapped to the right of an equal element.
    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1153-why-is-quick-sort-quick","level":2,"title":"11.5.3   Why is quick sort quick?","text":"

    As its name suggests, quick sort has an efficiency advantage. Although its average time complexity matches that of merge sort and heap sort, it is usually faster, mainly for the following reasons.

    • The worst case is unlikely. Quick sort's worst-case time complexity is \\(O(n^2)\\), making its performance less predictable than merge sort's, but it runs in \\(O(n \\log n)\\) time in the vast majority of cases.
    • Efficient cache use. During partitioning, the system can load the whole subarray into cache, making element access efficient. Algorithms such as heap sort jump between elements and do not share this advantage.
    • Small constant factors. Of these three algorithms, quick sort performs the fewest comparisons, assignments, swaps, and similar operations overall. This resembles the reason insertion sort is faster than bubble sort.
    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1154-improving-pivot-selection","level":2,"title":"11.5.4   Improving pivot selection","text":"

    Quick sort can become slower on certain inputs. Consider a fully reverse-sorted array. Because the leftmost element is chosen as the pivot, partitioning moves it to the far right, leaving subarrays of lengths \\(n - 1\\) and \\(0\\). Continuing recursively produces a subarray of length \\(0\\) after every partition. Divide and conquer becomes ineffective, and quick sort degenerates into a process resembling bubble sort.

    To reduce this risk, we can improve how the pivot is selected. For example, choose a random element as the pivot. With bad luck, however, every pivot may still be poor, leading to disappointing performance.

    Programming languages generally produce pseudorandom numbers. A test case constructed specifically for a pseudorandom sequence can still degrade quick sort's performance.

    For further improvement, choose three candidate elements, usually the first, last, and middle elements, and use their median as the pivot. This greatly increases the chance that the pivot is neither too small nor too large. We can use more candidates to improve robustness further. This approach greatly reduces the likelihood of degradation to \\(O(n^2)\\).

    An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
    def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int:\n    \"\"\"Select the median of three candidate elements\"\"\"\n    l, m, r = nums[left], nums[mid], nums[right]\n    if (l <= m <= r) or (r <= m <= l):\n        return mid  # m lies between l and r\n    if (m <= l <= r) or (r <= l <= m):\n        return left  # l lies between m and r\n    return right\n\ndef partition(self, nums: list[int], left: int, right: int) -> int:\n    \"\"\"Two-pointer partitioning (median of three)\"\"\"\n    # Select the median of the leftmost, middle, and rightmost elements as the pivot\n    med = self.median_three(nums, left, (left + right) // 2, right)\n    # Swap the median into the leftmost position of the current subarray\n    nums[left], nums[med] = nums[med], nums[left]\n    # Use nums[left] as the pivot\n    i, j = left, right\n    while i < j:\n        while i < j and nums[j] >= nums[left]:\n            j -= 1  # Scan from right to left for the first element smaller than the pivot\n        while i < j and nums[i] <= nums[left]:\n            i += 1  # Scan from left to right for the first element larger than the pivot\n        # Swap the elements\n        nums[i], nums[j] = nums[j], nums[i]\n    # Swap the pivot into the boundary position between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i  # Return the pivot index\n
    quick_sort.cpp
    /* Select the median of three candidate elements */\nint medianThree(vector<int> &nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m lies between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l lies between m and r\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint partition(vector<int> &nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the current subarray\n    swap(nums[left], nums[med]);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;                // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;                // Scan from left to right for the first element larger than the pivot\n        swap(nums[i], nums[j]); // Swap these two elements\n    }\n    swap(nums[i], nums[left]);  // Swap the pivot into the boundary position between the two subarrays\n    return i;                   // Return the pivot index\n}\n
    quick_sort.java
    /* Select the median of three candidate elements */\nint medianThree(int[] nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m lies between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l lies between m and r\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint partition(int[] nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the current subarray\n    swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left);  // Swap the pivot into the boundary position between the two subarrays\n    return i;             // Return the pivot index\n}\n
    quick_sort.cs
    /* Select the median of three candidate elements */\nint MedianThree(int[] nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m lies between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l lies between m and r\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint Partition(int[] nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = MedianThree(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the current subarray\n    Swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        Swap(nums, i, j); // Swap these two elements\n    }\n    Swap(nums, i, left);  // Swap the pivot into the boundary position between the two subarrays\n    return i;             // Return the pivot index\n}\n
    quick_sort.go
    /* Select the median of three candidate elements */\nfunc (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int {\n    l, m, r := nums[left], nums[mid], nums[right]\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid // m lies between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left // l lies between m and r\n    }\n    return right\n}\n\n/* Two-pointer partitioning (median of three)*/\nfunc (q *quickSortMedian) partition(nums []int, left, right int) int {\n    // Select the median of the left, middle, and right elements as the pivot\n    med := q.medianThree(nums, left, (left+right)/2, right)\n    // Swap the median into the leftmost position of the current subarray\n    nums[left], nums[med] = nums[med], nums[left]\n    // Use nums[left] as the pivot\n    i, j := left, right\n    for i < j {\n        for i < j && nums[j] >= nums[left] {\n            j-- //Scan from right to left for the first element smaller than the pivot\n        }\n        for i < j && nums[i] <= nums[left] {\n            i++ //Scan from left to right for the first element larger than the pivot\n        }\n        //Swap the elements\n        nums[i], nums[j] = nums[j], nums[i]\n    }\n    //Swap the pivot into the boundary position between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i //Return the pivot index\n}\n
    quick_sort.swift
    /* Select the median of three candidate elements */\nfunc medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int {\n    let l = nums[left]\n    let m = nums[mid]\n    let r = nums[right]\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid // m lies between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left // l lies between m and r\n    }\n    return right\n}\n\n/* Two-pointer partitioning (median of three) */\nfunc partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int {\n    // Select the median of three candidate elements\n    let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right)\n    // Swap the median into the leftmost position of the current subarray\n    nums.swapAt(left, med)\n    return partition(nums: &nums, left: left, right: right)\n}\n
    quick_sort.js
    /* Select the median of three candidate elements */\nmedianThree(nums, left, mid, right) {\n    let l = nums[left],\n        m = nums[mid],\n        r = nums[right];\n    // m lies between l and r\n    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;\n    // l lies between m and r\n    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\npartition(nums, left, right) {\n    // Select the median of three candidate elements\n    let med = this.medianThree(\n        nums,\n        left,\n        Math.floor((left + right) / 2),\n        right\n    );\n    // Swap the median into the leftmost position of the current subarray\n    this.swap(nums, left, med);\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) j--; // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left]) i++; // Scan from left to right for the first element larger than the pivot\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i; // Return the pivot index\n}\n
    quick_sort.ts
    /* Select the median of three candidate elements */\nmedianThree(\n    nums: number[],\n    left: number,\n    mid: number,\n    right: number\n): number {\n    let l = nums[left],\n        m = nums[mid],\n        r = nums[right];\n    // m lies between l and r\n    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;\n    // l lies between m and r\n    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\npartition(nums: number[], left: number, right: number): number {\n    // Select the median of three candidate elements\n    let med = this.medianThree(\n        nums,\n        left,\n        Math.floor((left + right) / 2),\n        right\n    );\n    // Swap the median into the leftmost position of the current subarray\n    this.swap(nums, left, med);\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j--; // Scan from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i++; // Scan from left to right for the first element larger than the pivot\n        }\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i; // Return the pivot index\n}\n
    quick_sort.dart
    /* Select the median of three candidate elements */\nint _medianThree(List<int> nums, int left, int mid, int right) {\n  int l = nums[left], m = nums[mid], r = nums[right];\n  if ((l <= m && m <= r) || (r <= m && m <= l))\n    return mid; // m lies between l and r\n  if ((m <= l && l <= r) || (r <= l && l <= m))\n    return left; // l lies between m and r\n  return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint _partition(List<int> nums, int left, int right) {\n  // Select the median of three candidate elements\n  int med = _medianThree(nums, left, (left + right) ~/ 2, right);\n  // Swap the median into the leftmost position of the current subarray\n  _swap(nums, left, med);\n  // Use nums[left] as the pivot\n  int i = left, j = right;\n  while (i < j) {\n    while (i < j && nums[j] >= nums[left]) j--; // Scan from right to left for the first element smaller than the pivot\n    while (i < j && nums[i] <= nums[left]) i++; // Scan from left to right for the first element larger than the pivot\n    _swap(nums, i, j); // Swap these two elements\n  }\n  _swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n  return i; // Return the pivot index\n}\n
    quick_sort.rs
    /* Select the median of three candidate elements */\nfn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize {\n    let (l, m, r) = (nums[left], nums[mid], nums[right]);\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid; // m lies between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left; // l lies between m and r\n    }\n    right\n}\n\n/* Two-pointer partitioning (median of three) */\nfn partition(nums: &mut [i32], left: usize, right: usize) -> usize {\n    // Select the median of three candidate elements\n    let med = Self::median_three(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the current subarray\n    nums.swap(left, med);\n    // Use nums[left] as the pivot\n    let (mut i, mut j) = (left, right);\n    while i < j {\n        while i < j && nums[j] >= nums[left] {\n            j -= 1; // Scan from right to left for the first element smaller than the pivot\n        }\n        while i < j && nums[i] <= nums[left] {\n            i += 1; // Scan from left to right for the first element larger than the pivot\n        }\n        nums.swap(i, j); // Swap these two elements\n    }\n    nums.swap(i, left); // Swap the pivot into the boundary position between the two subarrays\n    i // Return the pivot index\n}\n
    quick_sort.c
    /* Select the median of three candidate elements */\nint medianThree(int nums[], int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m lies between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l lies between m and r\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint partitionMedian(int nums[], int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the array\n    swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--; // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i;            // Return the pivot index\n}\n
    quick_sort.kt
    /* Select the median of three candidate elements */\nfun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int {\n    val l = nums[left]\n    val m = nums[mid]\n    val r = nums[right]\n    if ((m in l..r) || (m in r..l))\n        return mid  // m lies between l and r\n    if ((l in m..r) || (l in r..m))\n        return left // l lies between m and r\n    return right\n}\n\n/* Two-pointer partitioning (median of three) */\nfun partitionMedian(nums: IntArray, left: Int, right: Int): Int {\n    // Select the median of three candidate elements\n    val med = medianThree(nums, left, (left + right) / 2, right)\n    // Swap the median into the leftmost position of the current subarray\n    swap(nums, left, med)\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--                      // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++                      // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j)             // Swap these two elements\n    }\n    swap(nums, i, left)              // Swap the pivot into the boundary position between the two subarrays\n    return i                         // Return the pivot index\n}\n
    quick_sort.rb
    ### Select the median of three candidate elements ###\ndef median_three(nums, left, mid, right)\n  # Select the median of three candidate elements\n  _l, _m, _r = nums[left], nums[mid], nums[right]\n  # _m lies between _l and _r\n  return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l)\n  # _l lies between _m and _r\n  return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m)\n  return right\nend\n\n### Two-pointer partitioning (median of three)###\ndef partition(nums, left, right)\n  ### Use nums[left] as the pivot\n  med = median_three(nums, left, (left + right) / 2, right)\n  # Swap the median into the leftmost position of the current subarray\n  nums[left], nums[med] = nums[med], nums[left]\n  i, j = left, right\n  while i < j\n    while i < j && nums[j] >= nums[left]\n      j -= 1 # Scan from right to left for the first element smaller than the pivot\n    end\n    while i < j && nums[i] <= nums[left]\n      i += 1 # Scan from left to right for the first element larger than the pivot\n    end\n    # Swap the elements\n    nums[i], nums[j] = nums[j], nums[i]\n  end\n  # Swap the pivot into the boundary position between the two subarrays\n  nums[i], nums[left] = nums[left], nums[i]\n  i # Return the pivot index\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1155-reducing-recursion-depth","level":2,"title":"11.5.5   Reducing recursion depth","text":"

    Quick sort may use considerable space on certain inputs. For a fully sorted input, let the current subarray have length \\(m\\). Each partition produces a left subarray of length \\(0\\) and a right subarray of length \\(m - 1\\). Each recursive call therefore reduces the problem size by only one element, producing a recursion tree of height \\(n - 1\\) and requiring \\(O(n)\\) space for stack frames.

    To prevent stack frames from accumulating, compare the two subarray lengths after each partition and recurse only on the shorter one. Since that subarray has length at most \\(n / 2\\), the recursion depth is at most \\(\\log n\\), reducing the worst-case space complexity to \\(O(\\log n)\\). The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
    def quick_sort(self, nums: list[int], left: int, right: int):\n    \"\"\"Quick sort (recursion depth optimization)\"\"\"\n    # Stop when the subarray has at most one element\n    while left < right:\n        # Perform two-pointer partitioning\n        pivot = self.partition(nums, left, right)\n        # Apply quick sort to the shorter of the two subarrays\n        if pivot - left < right - pivot:\n            self.quick_sort(nums, left, pivot - 1)  # Recursively sort the left subarray\n            left = pivot + 1  # The remaining unsorted range is [pivot + 1, right]\n        else:\n            self.quick_sort(nums, pivot + 1, right)  # Recursively sort the right subarray\n            right = pivot - 1  # The remaining unsorted range is [left, pivot - 1]\n
    quick_sort.cpp
    /* Quick sort (recursion depth optimization) */\nvoid quickSort(vector<int> &nums, int left, int right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        int pivot = partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1;                 // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1;                 // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.java
    /* Quick sort (recursion depth optimization) */\nvoid quickSort(int[] nums, int left, int right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        int pivot = partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.cs
    /* Quick sort (recursion depth optimization) */\nvoid QuickSort(int[] nums, int left, int right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        int pivot = Partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            QuickSort(nums, left, pivot - 1);  // Recursively sort the left subarray\n            left = pivot + 1;  // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            QuickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.go
    /* Quick sort (recursion depth optimization)*/\nfunc (q *quickSortTailCall) quickSort(nums []int, left, right int) {\n    // Stop when the subarray has at most one element\n    for left < right {\n        // Perform two-pointer partitioning\n        pivot := q.partition(nums, left, right)\n        // Apply quick sort to the shorter of the two subarrays\n        if pivot-left < right-pivot {\n            q.quickSort(nums, left, pivot-1) // Recursively sort the left subarray\n            left = pivot + 1                 // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            q.quickSort(nums, pivot+1, right) // Recursively sort the right subarray\n            right = pivot - 1                 // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.swift
    /* Quick sort (recursion depth optimization) */\nfunc quickSortTailCall(nums: inout [Int], left: Int, right: Int) {\n    var left = left\n    var right = right\n    // Stop when the subarray has at most one element\n    while left < right {\n        // Perform two-pointer partitioning\n        let pivot = partition(nums: &nums, left: left, right: right)\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left) < (right - pivot) {\n            quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // Recursively sort the left subarray\n            left = pivot + 1 // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // Recursively sort the right subarray\n            right = pivot - 1 // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.js
    /* Quick sort (recursion depth optimization) */\nquickSort(nums, left, right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        let pivot = this.partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.ts
    /* Quick sort (recursion depth optimization) */\nquickSort(nums: number[], left: number, right: number): void {\n    // Stop when the subarray has zero or one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        let pivot = this.partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.dart
    /* Quick sort (recursion depth optimization) */\nvoid quickSort(List<int> nums, int left, int right) {\n  // Stop when the subarray has at most one element\n  while (left < right) {\n    // Perform two-pointer partitioning\n    int pivot = _partition(nums, left, right);\n    // Apply quick sort to the shorter of the two subarrays\n    if (pivot - left < right - pivot) {\n      quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n      left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n    } else {\n      quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n      right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n    }\n  }\n}\n
    quick_sort.rs
    /* Quick sort (recursion depth optimization) */\npub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) {\n    // Stop when the subarray has at most one element\n    while left < right {\n        // Perform two-pointer partitioning\n        let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n        // Apply quick sort to the shorter of the two subarrays\n        if pivot - left < right - pivot {\n            Self::quick_sort(left, pivot - 1, nums); // Recursively sort the left subarray\n            left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            Self::quick_sort(pivot + 1, right, nums); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.c
    /* Quick sort (recursion depth optimization) */\nvoid quickSortTailCall(int nums[], int left, int right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        int pivot = partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            // Recursively sort the left subarray\n            quickSortTailCall(nums, left, pivot - 1);\n            // The remaining unsorted range is [pivot + 1, right]\n            left = pivot + 1;\n        } else {\n            // Recursively sort the right subarray\n            quickSortTailCall(nums, pivot + 1, right);\n            // The remaining unsorted range is [left, pivot - 1]\n            right = pivot - 1;\n        }\n    }\n}\n
    quick_sort.kt
    /* Quick sort (recursion depth optimization) */\nfun quickSortTailCall(nums: IntArray, left: Int, right: Int) {\n    // Stop when the subarray has at most one element\n    var l = left\n    var r = right\n    while (l < r) {\n        // Perform two-pointer partitioning\n        val pivot = partition(nums, l, r)\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - l < r - pivot) {\n            quickSort(nums, l, pivot - 1) // Recursively sort the left subarray\n            l = pivot + 1 // The remaining unsorted range is [pivot + 1, r]\n        } else {\n            quickSort(nums, pivot + 1, r) // Recursively sort the right subarray\n            r = pivot - 1 // The remaining unsorted range is [l, pivot - 1]\n        }\n    }\n}\n
    quick_sort.rb
    ### Quick sort (recursion depth optimization)###\ndef quick_sort(nums, left, right)\n  # Continue while the subarray contains more than one element\n  while left < right\n    # Two-pointer partitioning\n    pivot = partition(nums, left, right)\n    # Apply quick sort to the shorter of the two subarrays\n    if pivot - left < right - pivot\n      quick_sort(nums, left, pivot - 1)\n      left = pivot + 1 # The remaining unsorted range is [pivot + 1, right]\n    else\n      quick_sort(nums, pivot + 1, right)\n      right = pivot - 1 # The remaining unsorted range is [left, pivot - 1]\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/","level":1,"title":"11.10   Radix sort","text":"

    The previous section introduced counting sort, which suits a large number of elements \\(n\\) with a small value range \\(m\\). Suppose we need to sort \\(n = 10^6\\) student IDs, each an \\(8\\)-digit number. The value range \\(m = 10^8\\) is very large, so counting sort would require substantial memory. Radix sort avoids this problem.

    Radix sort shares counting sort's core idea of sorting by counting occurrences. It also uses the relative significance of digit positions, sorting one digit at a time to obtain the final order.

    ","path":["Chapter 11.   Sorting","11.10   Radix sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11101-algorithm-steps","level":2,"title":"11.10.1   Algorithm steps","text":"

    For the student IDs, number the least significant digit as \\(1\\) and the most significant as \\(8\\). Radix sort proceeds as shown below.

    1. Initialize the digit position \\(k = 1\\).
    2. Apply counting sort to the \\(k\\)th digit of each student ID. The data is now sorted in ascending order by its \\(k\\)th digit.
    3. Increase the digit position \\(k\\) by \\(1\\) and return to step 2.. Continue until all digit positions have been processed.

    Figure 11-18   The radix sort process

    Now consider the implementation. For a number \\(x\\) in base \\(d\\), its \\(k\\)th digit \\(x_k\\) can be obtained with the following formula:

    \\[ x_k = \\lfloor\\frac{x}{d^{k-1}}\\rfloor \\bmod d \\]

    Here, \\(\\lfloor a \\rfloor\\) rounds the floating-point value \\(a\\) down, and \\(\\bmod \\: d\\) takes the remainder modulo \\(d\\). For the student IDs, \\(d = 10\\) and \\(k \\in [1, 8]\\).

    We also need to modify counting sort slightly so that it sorts by the \\(k\\)th digit:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby radix_sort.py
    def digit(num: int, exp: int) -> int:\n    \"\"\"Get the k-th digit of num, where exp = 10^(k-1)\"\"\"\n    # Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num // exp) % 10\n\ndef counting_sort_digit(nums: list[int], exp: int):\n    \"\"\"Counting sort (sort nums by the k-th digit)\"\"\"\n    # Decimal digits range from 0 to 9, so the bucket array needs length 10\n    counter = [0] * 10\n    n = len(nums)\n    # Count the occurrences of each digit from 0 to 9\n    for i in range(n):\n        d = digit(nums[i], exp)  # Get the k-th digit of nums[i] and store it in d\n        counter[d] += 1  # Count the occurrences of digit d\n    # Compute prefix sums to convert occurrence counts into array positions\n    for i in range(1, 10):\n        counter[i] += counter[i - 1]\n    # Traverse in reverse and place each element into res according to the bucket counts\n    res = [0] * n\n    for i in range(n - 1, -1, -1):\n        d = digit(nums[i], exp)\n        j = counter[d] - 1  # Get the destination index j for digit d in the array\n        res[j] = nums[i]  # Place the current element at index j\n        counter[d] -= 1  # Decrease the count for d by 1\n    # Overwrite the original array nums with the result\n    for i in range(n):\n        nums[i] = res[i]\n\ndef radix_sort(nums: list[int]):\n    \"\"\"Radix sort\"\"\"\n    # Get the largest array element to determine the maximum number of digits\n    m = max(nums)\n    # Process digits from least significant to most significant\n    exp = 1\n    while exp <= m:\n        # Apply counting sort to the k-th digit of the array elements\n        # k = 1 -> exp = 1\n        # k = 2 -> exp = 10\n        # That is, exp = 10^(k-1)\n        counting_sort_digit(nums, exp)\n        exp *= 10\n
    radix_sort.cpp
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid countingSortDigit(vector<int> &nums, int exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    vector<int> counter(10, 0);\n    int n = nums.size();\n    // Count the occurrences of each digit from 0 to 9\n    for (int i = 0; i < n; i++) {\n        int d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++;                // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    vector<int> res(n, 0);\n    for (int i = n - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (int i = 0; i < n; i++)\n        nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(vector<int> &nums) {\n    // Get the largest array element to determine the maximum number of digits\n    int m = *max_element(nums.begin(), nums.end());\n    // Process digits from least significant to most significant\n    for (int exp = 1; exp <= m; exp *= 10)\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n}\n
    radix_sort.java
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid countingSortDigit(int[] nums, int exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    int[] counter = new int[10];\n    int n = nums.length;\n    // Count the occurrences of each digit from 0 to 9\n    for (int i = 0; i < n; i++) {\n        int d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++;                // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (int i = 0; i < n; i++)\n        nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(int[] nums) {\n    // Get the largest array element to determine the maximum number of digits\n    int m = Integer.MIN_VALUE;\n    for (int num : nums)\n        if (num > m)\n            m = num;\n    // Process digits from least significant to most significant\n    for (int exp = 1; exp <= m; exp *= 10) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
    radix_sort.cs
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nint Digit(int num, int exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid CountingSortDigit(int[] nums, int exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    int[] counter = new int[10];\n    int n = nums.Length;\n    // Count the occurrences of each digit from 0 to 9\n    for (int i = 0; i < n; i++) {\n        int d = Digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++;                // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int d = Digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nvoid RadixSort(int[] nums) {\n    // Get the largest array element to determine the maximum number of digits\n    int m = int.MinValue;\n    foreach (int num in nums) {\n        if (num > m) m = num;\n    }\n    // Process digits from least significant to most significant\n    for (int exp = 1; exp <= m; exp *= 10) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        CountingSortDigit(nums, exp);\n    }\n}\n
    radix_sort.go
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfunc digit(num, exp int) int {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfunc countingSortDigit(nums []int, exp int) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    counter := make([]int, 10)\n    n := len(nums)\n    // Count the occurrences of each digit from 0 to 9\n    for i := 0; i < n; i++ {\n        d := digit(nums[i], exp) // Get the k-th digit of nums[i] and store it in d\n        counter[d]++             // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for i := 1; i < 10; i++ {\n        counter[i] += counter[i-1]\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    res := make([]int, n)\n    for i := n - 1; i >= 0; i-- {\n        d := digit(nums[i], exp)\n        j := counter[d] - 1 // Get the destination index j for digit d in the array\n        res[j] = nums[i]    // Place the current element at index j\n        counter[d]--        // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for i := 0; i < n; i++ {\n        nums[i] = res[i]\n    }\n}\n\n/* Radix sort */\nfunc radixSort(nums []int) {\n    // Get the largest array element to determine the maximum number of digits\n    max := math.MinInt\n    for _, num := range nums {\n        if num > max {\n            max = num\n        }\n    }\n    // Process digits from least significant to most significant\n    for exp := 1; max >= exp; exp *= 10 {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp)\n    }\n}\n
    radix_sort.swift
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfunc digit(num: Int, exp: Int) -> Int {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    (num / exp) % 10\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfunc countingSortDigit(nums: inout [Int], exp: Int) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    var counter = Array(repeating: 0, count: 10)\n    // Count the occurrences of each digit from 0 to 9\n    for i in nums.indices {\n        let d = digit(num: nums[i], exp: exp) // Get the k-th digit of nums[i] and store it in d\n        counter[d] += 1 // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for i in 1 ..< 10 {\n        counter[i] += counter[i - 1]\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    var res = Array(repeating: 0, count: nums.count)\n    for i in nums.indices.reversed() {\n        let d = digit(num: nums[i], exp: exp)\n        let j = counter[d] - 1 // Get the destination index j for digit d in the array\n        res[j] = nums[i] // Place the current element at index j\n        counter[d] -= 1 // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for i in nums.indices {\n        nums[i] = res[i]\n    }\n}\n\n/* Radix sort */\nfunc radixSort(nums: inout [Int]) {\n    // Get the largest array element to determine the maximum number of digits\n    var m = Int.min\n    for num in nums {\n        if num > m {\n            m = num\n        }\n    }\n    // Process digits from least significant to most significant\n    for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums: &nums, exp: exp)\n    }\n}\n
    radix_sort.js
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfunction digit(num, exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return Math.floor(num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfunction countingSortDigit(nums, exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    const counter = new Array(10).fill(0);\n    const n = nums.length;\n    // Count the occurrences of each digit from 0 to 9\n    for (let i = 0; i < n; i++) {\n        const d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++; // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (let i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    const res = new Array(n).fill(0);\n    for (let i = n - 1; i >= 0; i--) {\n        const d = digit(nums[i], exp);\n        const j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d]--; // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nfunction radixSort(nums) {\n    // Get the largest array element to determine the maximum number of digits\n    let m = Math.max(... nums);\n    // Process digits from least significant to most significant\n    for (let exp = 1; exp <= m; exp *= 10) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
    radix_sort.ts
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfunction digit(num: number, exp: number): number {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return Math.floor(num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfunction countingSortDigit(nums: number[], exp: number): void {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    const counter = new Array(10).fill(0);\n    const n = nums.length;\n    // Count the occurrences of each digit from 0 to 9\n    for (let i = 0; i < n; i++) {\n        const d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++; // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (let i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    const res = new Array(n).fill(0);\n    for (let i = n - 1; i >= 0; i--) {\n        const d = digit(nums[i], exp);\n        const j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d]--; // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nfunction radixSort(nums: number[]): void {\n    // Get the largest array element to determine the maximum number of digits\n    let m: number = Math.max(... nums);\n    // Process digits from least significant to most significant\n    for (let exp = 1; exp <= m; exp *= 10) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
    radix_sort.dart
    /* Get the k-th digit of _num, where exp = 10^(k-1) */\nint digit(int _num, int exp) {\n  // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n  return (_num ~/ exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid countingSortDigit(List<int> nums, int exp) {\n  // Decimal digits range from 0 to 9, so the bucket array needs length 10\n  List<int> counter = List<int>.filled(10, 0);\n  int n = nums.length;\n  // Count the occurrences of each digit from 0 to 9\n  for (int i = 0; i < n; i++) {\n    int d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n    counter[d]++; // Count the occurrences of digit d\n  }\n  // Compute prefix sums to convert occurrence counts into array positions\n  for (int i = 1; i < 10; i++) {\n    counter[i] += counter[i - 1];\n  }\n  // Traverse in reverse and place each element into res according to the bucket counts\n  List<int> res = List<int>.filled(n, 0);\n  for (int i = n - 1; i >= 0; i--) {\n    int d = digit(nums[i], exp);\n    int j = counter[d] - 1; // Get the destination index j for digit d in the array\n    res[j] = nums[i]; // Place the current element at index j\n    counter[d]--; // Decrease the count for d by 1\n  }\n  // Overwrite the original array nums with the result\n  for (int i = 0; i < n; i++) nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(List<int> nums) {\n  // Get the largest array element to determine the maximum number of digits\n  // On native platforms, Dart int values are 64-bit integers\n  int m = -1 << 63;\n  for (int _num in nums) if (_num > m) m = _num;\n  // Process digits from least significant to most significant\n  for (int exp = 1; exp <= m; exp *= 10)\n    // Apply counting sort to the k-th digit of the array elements\n    // k = 1 -> exp = 1\n    // k = 2 -> exp = 10\n    // That is, exp = 10^(k-1)\n    countingSortDigit(nums, exp);\n}\n
    radix_sort.rs
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfn digit(num: i32, exp: i32) -> usize {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return ((num / exp) % 10) as usize;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfn counting_sort_digit(nums: &mut [i32], exp: i32) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    let mut counter = [0; 10];\n    let n = nums.len();\n    // Count the occurrences of each digit from 0 to 9\n    for i in 0..n {\n        let d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d] += 1; // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for i in 1..10 {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    let mut res = vec![0; n];\n    for i in (0..n).rev() {\n        let d = digit(nums[i], exp);\n        let j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d] -= 1; // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    nums.copy_from_slice(&res);\n}\n\n/* Radix sort */\nfn radix_sort(nums: &mut [i32]) {\n    // Get the largest array element to determine the maximum number of digits\n    let m = *nums.into_iter().max().unwrap();\n    // Process digits from least significant to most significant\n    let mut exp = 1;\n    while exp <= m {\n        counting_sort_digit(nums, exp);\n        exp *= 10;\n    }\n}\n
    radix_sort.c
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid countingSortDigit(int nums[], int size, int exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    int *counter = (int *)malloc((sizeof(int) * 10));\n    memset(counter, 0, sizeof(int) * 10); // Initialize the counts to 0\n    // Count the occurrences of each digit from 0 to 9\n    for (int i = 0; i < size; i++) {\n        // Get the k-th digit of nums[i] and store it in d\n        int d = digit(nums[i], exp);\n        // Count the occurrences of digit d\n        counter[d]++;\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    int *res = (int *)malloc(sizeof(int) * size);\n    for (int i = size - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (int i = 0; i < size; i++) {\n        nums[i] = res[i];\n    }\n    // Free memory\n    free(res);\n    free(counter);\n}\n\n/* Radix sort */\nvoid radixSort(int nums[], int size) {\n    // Get the largest array element to determine the maximum number of digits\n    int max = INT32_MIN;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > max) {\n            max = nums[i];\n        }\n    }\n    // Process digits from least significant to most significant\n    for (int exp = 1; max >= exp; exp *= 10)\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, size, exp);\n}\n
    radix_sort.kt
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfun digit(num: Int, exp: Int): Int {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfun countingSortDigit(nums: IntArray, exp: Int) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    val counter = IntArray(10)\n    val n = nums.size\n    // Count the occurrences of each digit from 0 to 9\n    for (i in 0..<n) {\n        val d = digit(nums[i], exp) // Get the k-th digit of nums[i] and store it in d\n        counter[d]++                // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (i in 1..9) {\n        counter[i] += counter[i - 1]\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    val res = IntArray(n)\n    for (i in n - 1 downTo 0) {\n        val d = digit(nums[i], exp)\n        val j = counter[d] - 1 // Get the destination index j for digit d in the array\n        res[j] = nums[i]       // Place the current element at index j\n        counter[d]--           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (i in 0..<n)\n        nums[i] = res[i]\n}\n\n/* Radix sort */\nfun radixSort(nums: IntArray) {\n    // Get the largest array element to determine the maximum number of digits\n    var m = Int.MIN_VALUE\n    for (num in nums) if (num > m) m = num\n    var exp = 1\n    // Process digits from least significant to most significant\n    while (exp <= m) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp)\n        exp *= 10\n    }\n}\n
    radix_sort.rb
    ### Get the k-th digit of num, where exp = 10^(k-1) ###\ndef digit(num, exp)\n  # Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n  (num / exp) % 10\nend\n\n### Counting sort (sort nums by the k-th digit)###\ndef counting_sort_digit(nums, exp)\n  # Decimal digits range from 0 to 9, so the bucket array needs length 10\n  counter = Array.new(10, 0)\n  n = nums.length\n  # Count the occurrences of each digit from 0 to 9\n  for i in 0...n\n    d = digit(nums[i], exp) # Get the k-th digit of nums[i] and store it in d\n    counter[d] += 1 # Count the occurrences of digit d\n  end\n  # Compute prefix sums to convert occurrence counts into array positions\n  (1...10).each { |i| counter[i] += counter[i - 1] }\n  # Traverse in reverse and place each element into res according to the bucket counts\n  res = Array.new(n, 0)\n  for i in (n - 1).downto(0)\n    d = digit(nums[i], exp)\n    j = counter[d] - 1 # Get the destination index j for digit d in the array\n    res[j] = nums[i] # Place the current element at index j\n    counter[d] -= 1 # Decrease the count for d by 1\n  end\n  # Overwrite the original array nums with the result\n  (0...n).each { |i| nums[i] = res[i] }\nend\n\n### Radix sort ###\ndef radix_sort(nums)\n  # Get the largest array element to determine the maximum number of digits\n  m = nums.max\n  # Process digits from least significant to most significant\n  exp = 1\n  while exp <= m\n    # Apply counting sort to the k-th digit of the array elements\n    # k = 1 -> exp = 1\n    # k = 2 -> exp = 10\n    # That is, exp = 10^(k-1)\n    counting_sort_digit(nums, exp)\n    exp *= 10\n  end\nend\n
    Visualize execution

    View full screen >

    Why start with the least significant digit?

    In successive rounds, a later sort takes precedence over an earlier one. For example, if the first round orders two elements as \\(a < b\\) but the second orders them as \\(a > b\\), the second result replaces the first. Higher digits have greater priority, so lower digits must be sorted first.

    ","path":["Chapter 11.   Sorting","11.10   Radix sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11102-algorithm-properties","level":2,"title":"11.10.2   Algorithm properties","text":"

    Compared with counting sort, radix sort handles a larger range of values, provided the data can be represented with a fixed number of digits and that number is not too large. For example, floating-point numbers are unsuitable because their digit count \\(k\\) may be so large that \\(O(nk) \\gg O(n^2)\\).

    • Time complexity: \\(O(nk)\\); not adaptive. For \\(n\\) elements represented in base \\(d\\) with at most \\(k\\) digits, counting sort on one digit takes \\(O(n + d)\\) time. Sorting all \\(k\\) digits takes \\(O((n + d)k)\\). Usually \\(d\\) and \\(k\\) are relatively small, so the complexity approaches \\(O(n)\\).
    • Space complexity: \\(O(n + d)\\); not in-place. Like counting sort, radix sort requires arrays res and counter of lengths \\(n\\) and \\(d\\).
    • Stable. Radix sort is stable when the counting sort used within it is stable. If counting sort is not stable, radix sort cannot guarantee a correctly sorted result.
    ","path":["Chapter 11.   Sorting","11.10   Radix sort"],"tags":[]},{"location":"chapter_sorting/selection_sort/","level":1,"title":"11.2   Selection sort","text":"

    Selection sort works very simply: in each round, select the smallest element from the unsorted section and place it at the end of the sorted section.

    For an array of length \\(n\\), the algorithm proceeds as shown below.

    1. Initially, all elements are unsorted, so the unsorted index interval is \\([0, n-1]\\).
    2. Select the smallest element in \\([0, n-1]\\) and swap it with the element at index \\(0\\). The first element is now sorted.
    3. Select the smallest element in \\([1, n-1]\\) and swap it with the element at index \\(1\\). The first two elements are now sorted.
    4. Continue in this way. After \\(n - 1\\) rounds of selection and swapping, the first \\(n - 1\\) elements are sorted.
    5. The remaining element must be the largest and needs no sorting. The array is now sorted.
    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 11-2   Steps of selection sort

    In the code, \\(k\\) tracks the smallest element in the unsorted section:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby selection_sort.py
    def selection_sort(nums: list[int]):\n    \"\"\"Selection sort\"\"\"\n    n = len(nums)\n    # Outer loop: the unsorted range is [i, n-1]\n    for i in range(n - 1):\n        # Inner loop: find the smallest element in the unsorted range\n        k = i\n        for j in range(i + 1, n):\n            if nums[j] < nums[k]:\n                k = j  # Record the index of the smallest element\n        # Swap the smallest element with the first element of the unsorted range\n        nums[i], nums[k] = nums[k], nums[i]\n
    selection_sort.cpp
    /* Selection sort */\nvoid selectionSort(vector<int> &nums) {\n    int n = nums.size();\n    // Outer loop: the unsorted range is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        swap(nums[i], nums[k]);\n    }\n}\n
    selection_sort.java
    /* Selection sort */\nvoid selectionSort(int[] nums) {\n    int n = nums.length;\n    // Outer loop: the unsorted range is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        int temp = nums[i];\n        nums[i] = nums[k];\n        nums[k] = temp;\n    }\n}\n
    selection_sort.cs
    /* Selection sort */\nvoid SelectionSort(int[] nums) {\n    int n = nums.Length;\n    // Outer loop: the unsorted range is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        (nums[k], nums[i]) = (nums[i], nums[k]);\n    }\n}\n
    selection_sort.go
    /* Selection sort */\nfunc selectionSort(nums []int) {\n    n := len(nums)\n    // Outer loop: the unsorted range is [i, n-1]\n    for i := 0; i < n-1; i++ {\n        // Inner loop: find the smallest element in the unsorted range\n        k := i\n        for j := i + 1; j < n; j++ {\n            if nums[j] < nums[k] {\n                // Record the index of the smallest element\n                k = j\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        nums[i], nums[k] = nums[k], nums[i]\n\n    }\n}\n
    selection_sort.swift
    /* Selection sort */\nfunc selectionSort(nums: inout [Int]) {\n    // Outer loop: the unsorted range is [i, n-1]\n    for i in nums.indices.dropLast() {\n        // Inner loop: find the smallest element in the unsorted range\n        var k = i\n        for j in nums.indices.dropFirst(i + 1) {\n            if nums[j] < nums[k] {\n                k = j // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        nums.swapAt(i, k)\n    }\n}\n
    selection_sort.js
    /* Selection sort */\nfunction selectionSort(nums) {\n    let n = nums.length;\n    // Outer loop: the unsorted range is [i, n-1]\n    for (let i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        let k = i;\n        for (let j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k]) {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        [nums[i], nums[k]] = [nums[k], nums[i]];\n    }\n}\n
    selection_sort.ts
    /* Selection sort */\nfunction selectionSort(nums: number[]): void {\n    let n = nums.length;\n    // Outer loop: the unsorted range is [i, n-1]\n    for (let i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        let k = i;\n        for (let j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k]) {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        [nums[i], nums[k]] = [nums[k], nums[i]];\n    }\n}\n
    selection_sort.dart
    /* Selection sort */\nvoid selectionSort(List<int> nums) {\n  int n = nums.length;\n  // Outer loop: the unsorted range is [i, n-1]\n  for (int i = 0; i < n - 1; i++) {\n    // Inner loop: find the smallest element in the unsorted range\n    int k = i;\n    for (int j = i + 1; j < n; j++) {\n      if (nums[j] < nums[k]) k = j; // Record the index of the smallest element\n    }\n    // Swap the smallest element with the first element of the unsorted range\n    int temp = nums[i];\n    nums[i] = nums[k];\n    nums[k] = temp;\n  }\n}\n
    selection_sort.rs
    /* Selection sort */\nfn selection_sort(nums: &mut [i32]) {\n    if nums.is_empty() {\n        return;\n    }\n    let n = nums.len();\n    // Outer loop: the unsorted range is [i, n-1]\n    for i in 0..n - 1 {\n        // Inner loop: find the smallest element in the unsorted range\n        let mut k = i;\n        for j in i + 1..n {\n            if nums[j] < nums[k] {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        nums.swap(i, k);\n    }\n}\n
    selection_sort.c
    /* Selection sort */\nvoid selectionSort(int nums[], int n) {\n    // Outer loop: the unsorted range is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        int temp = nums[i];\n        nums[i] = nums[k];\n        nums[k] = temp;\n    }\n}\n
    selection_sort.kt
    /* Selection sort */\nfun selectionSort(nums: IntArray) {\n    val n = nums.size\n    // Outer loop: the unsorted range is [i, n-1]\n    for (i in 0..<n - 1) {\n        var k = i\n        // Inner loop: find the smallest element in the unsorted range\n        for (j in i + 1..<n) {\n            if (nums[j] < nums[k])\n                k = j // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        val temp = nums[i]\n        nums[i] = nums[k]\n        nums[k] = temp\n    }\n}\n
    selection_sort.rb
    ### Selection sort ###\ndef selection_sort(nums)\n  n = nums.length\n  # Outer loop: the unsorted range is [i, n-1]\n  for i in 0...(n - 1)\n    # Inner loop: find the smallest element in the unsorted range\n    k = i\n    for j in (i + 1)...n\n      if nums[j] < nums[k]\n        k = j # Record the index of the smallest element\n      end\n    end\n    # Swap the smallest element with the first element of the unsorted range\n    nums[i], nums[k] = nums[k], nums[i]\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.2   Selection sort"],"tags":[]},{"location":"chapter_sorting/selection_sort/#1121-algorithm-properties","level":2,"title":"11.2.1   Algorithm properties","text":"
    • Time complexity: \\(O(n^2)\\); not adaptive. The outer loop runs \\(n - 1\\) times. The inner loop runs \\(n - 1\\) times in the first round and \\(1\\) time in the last, giving successive counts of \\(n - 1\\), \\(n - 2\\), \\(\\dots\\), \\(2\\), and \\(1\\), which sum to \\(\\frac{n(n - 1)}{2}\\).
    • Space complexity: \\(O(1)\\); in-place. Pointers \\(i\\) and \\(j\\) use constant extra space.
    • Not stable. As shown below, nums[i] may be swapped to the right of an equal element, changing their relative order.

    Figure 11-3   An example showing that selection sort is not stable

    ","path":["Chapter 11.   Sorting","11.2   Selection sort"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/","level":1,"title":"11.1   Sorting algorithms","text":"

    A sorting algorithm arranges a dataset in a specified order. Sorting is widely used because ordered data is often easier to search, analyze, and process efficiently.

    As shown below, the data may consist of integers, floating-point numbers, characters, strings, and other types. The ordering rule can be chosen as needed: numerical magnitude, ASCII character order, or a custom rule.

    Figure 11-1   Examples of data types and ordering rules

    ","path":["Chapter 11.   Sorting","11.1   Sorting algorithms"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1111-evaluation-criteria","level":2,"title":"11.1.1   Evaluation criteria","text":"

    Efficiency: We want low time complexity and few operations overall, corresponding to smaller constant factors in the complexity. Efficiency is especially important for large datasets.

    In-place operation: An in-place sort works directly on the original array without an auxiliary array, saving memory. It usually moves data fewer times and runs faster as well.

    Stability: A stable sort preserves the relative order of equal elements in the array.

    Stable sorting is necessary for multilevel sorting. Suppose a table stores student names in its first column and ages in its second. An unstable sort may destroy the input's existing order:

    # The input is already sorted by name\n# (name, age)\n  ('A', 19)\n  ('B', 18)\n  ('C', 21)\n  ('D', 19)\n  ('E', 23)\n\n# Suppose an unstable algorithm sorts the list by age.\n# The relative positions of ('D', 19) and ('A', 19) change,\n# so the original ordering by name is lost\n  ('B', 18)\n  ('D', 19)\n  ('A', 19)\n  ('C', 21)\n  ('E', 23)\n

    Adaptiveness: An adaptive sort uses ordering already present in the input to reduce computation and improve efficiency. Its best-case time complexity is usually better than its average complexity.

    Comparison-based or not: A comparison-based sort uses comparison operators (\\(<\\), \\(=\\), \\(>\\)) to determine the relative order of elements and sort the array. Its worst-case time complexity has a lower bound of \\(\\Omega(n \\log n)\\). A non-comparison sort does not use comparison operators and can achieve \\(O(n)\\) time, but is less broadly applicable.

    ","path":["Chapter 11.   Sorting","11.1   Sorting algorithms"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1112-the-ideal-sorting-algorithm","level":2,"title":"11.1.2   The ideal sorting algorithm","text":"

    Fast, in-place, stable, adaptive, and broadly applicable. No sorting algorithm with all these properties has yet been found. We must therefore choose according to the characteristics of the data and the requirements of the problem.

    Next, we will study different sorting algorithms and analyze their strengths and weaknesses using these criteria.

    ","path":["Chapter 11.   Sorting","11.1   Sorting algorithms"],"tags":[]},{"location":"chapter_sorting/summary/","level":1,"title":"11.11   Summary","text":"","path":["Chapter 11.   Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_sorting/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Bubble sort sorts by swapping adjacent elements. Adding a flag to stop early improves its best-case time complexity to \\(O(n)\\).
    • Insertion sort inserts one element from the unsorted section into its correct position in the sorted section each round. Although its time complexity is \\(O(n^2)\\), its low operation count makes it popular for small datasets.
    • Quick sort sorts through partitioning. Repeatedly choosing the worst pivot can degrade its time complexity to \\(O(n^2)\\). Median or random pivot selection reduces this risk. Recursing on the shorter subarray reduces recursion depth and improves space complexity to \\(O(\\log n)\\).
    • Merge sort has division and merging phases, exemplifying divide and conquer. Sorting an array requires an auxiliary array and \\(O(n)\\) space, while sorting a linked list can be optimized to use \\(O(1)\\) space.
    • Bucket sort has three steps: distribute data into buckets, sort within each bucket, and combine the results. It also uses divide and conquer and suits very large datasets. The key is distributing data evenly.
    • Counting sort is a special case of bucket sort that sorts by counting occurrences. It suits large datasets with a limited value range and requires data that can be converted to positive integers.
    • Radix sort sorts one digit at a time and requires data that can be represented as numbers with a fixed number of digits.
    • Ideally, a sorting algorithm would be efficient, stable, in-place, and adaptive. As with other data structures and algorithms, however, no single sorting algorithm meets all these requirements. In practice, choose one according to the data's properties.
    • Figure 11-19 compares common sorting algorithms in efficiency, stability, in-place operation, and adaptiveness.

    Figure 11-19   Comparison of sorting algorithms

    ","path":["Chapter 11.   Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_sorting/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: When is stability necessary in a sorting algorithm?

    We often sort objects by one of their attributes. For example, students have names and heights, and we want a multilevel sort. First sort by name to obtain (A, 180) (B, 185) (C, 170) (D, 170), then sort by height. An unstable algorithm may produce (D, 170) (C, 170) (A, 180) (B, 185).

    Students D and C have switched positions, breaking the ordering by name. This is not what we want.

    Q: Can the right-to-left and left-to-right scans in partitioning be reversed?

    No. When the leftmost element is the pivot, the right-to-left scan must come first. This may seem counterintuitive, so let us examine why.

    The final step of partition() swaps nums[left] and nums[i]. Afterward, every element to the left of the pivot must be <= the pivot, so nums[left] >= nums[i] must hold before that swap. If we scan left to right first and fail to find a larger element, the loop stops at i == j, where it is possible that nums[j] == nums[i] > nums[left]. The final swap would then move an element larger than the pivot to the far left, causing partitioning to fail.

    For example, given [0, 0, 0, 0, 1], scanning left to right first produces [1, 0, 0, 0, 0] after partitioning, which is incorrect.

    Conversely, if we choose nums[right] as the pivot, we must scan left to right first.

    Q: In the recursion-depth optimization for quick sort, why does choosing the shorter subarray keep the depth at most \\(\\log n\\)?

    Recursion depth is the number of recursive calls that have not yet returned. Each partition divides the array into two subarrays. After the optimization, the subarray chosen for recursion is at most half the original length. Even in the worst case, where it is always exactly half, the depth is \\(\\log n\\).

    In the original quick sort, we may repeatedly recurse on the larger subarray, with lengths \\(n\\), \\(n - 1\\), \\(\\dots\\), \\(2\\), and \\(1\\) in the worst case, giving depth \\(n\\). The optimization prevents this.

    Q: If all array elements are equal, does quick sort take \\(O(n^2)\\) time? How can we handle this degeneration?

    Yes. In this case, consider partitioning into three sections: elements smaller than, equal to, and greater than the pivot. Recurse only on the smaller and greater sections. If all elements are equal, a single partition then completes the sort.

    Q: Why is bucket sort's worst-case time complexity \\(O(n^2)\\)?

    In the worst case, all elements fall into one bucket. If an \\(O(n^2)\\) algorithm is used to sort that bucket, the overall time complexity is \\(O(n^2)\\).

    ","path":["Chapter 11.   Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/","level":1,"title":"Chapter 5.   Stacks and queues","text":"

    Abstract

    A stack is like cats piled on top of one another; a queue is like cats standing in line.

    They represent last-in, first-out and first-in, first-out order, respectively.

    ","path":["Chapter 5.   Stacks and queues"],"tags":[]},{"location":"chapter_stack_and_queue/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 5.1   Stacks
    • 5.2   Queues
    • 5.3   Deques
    • 5.4   Summary
    • 5.5   Exercises
    ","path":["Chapter 5.   Stacks and queues"],"tags":[]},{"location":"chapter_stack_and_queue/deque/","level":1,"title":"5.3   Deques","text":"

    A queue allows removal only at the front and insertion only at the rear. As shown below, a double-ended queue, or deque, is more flexible: elements can be added or removed at either end.

    Figure 5-7   Deque operations

    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#531-common-deque-operations","level":2,"title":"5.3.1   Common deque operations","text":"

    Table 5-3 lists common operations. Actual method names depend on the programming language.

    Table 5-3   Efficiency of deque operations

    Method Description Time complexity push_first() Add an element at the front \\(O(1)\\) push_last() Add an element at the rear \\(O(1)\\) pop_first() Remove the front element \\(O(1)\\) pop_last() Remove the rear element \\(O(1)\\) peek_first() Access the front element \\(O(1)\\) peek_last() Access the rear element \\(O(1)\\)

    Again, we can use a deque class already provided by the programming language:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby deque.py
    from collections import deque\n\n# Initialize a deque\ndeq: deque[int] = deque()\n\n# Enqueue elements\ndeq.append(2)      # Add at the rear\ndeq.append(5)\ndeq.append(4)\ndeq.appendleft(3)  # Add at the front\ndeq.appendleft(1)\n\n# Access elements\nfront: int = deq[0]  # Front element\nrear: int = deq[-1]  # Rear element\n\n# Dequeue elements\npop_front: int = deq.popleft()  # Remove the front element\npop_rear: int = deq.pop()       # Remove the rear element\n\n# Get the deque size\nsize: int = len(deq)\n\n# Check whether the deque is empty\nis_empty: bool = len(deq) == 0\n
    deque.cpp
    /* Initialize a deque */\ndeque<int> deque;\n\n/* Enqueue elements */\ndeque.push_back(2);   // Add at the rear\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3);  // Add at the front\ndeque.push_front(1);\n\n/* Access elements */\nint front = deque.front(); // Front element\nint back = deque.back();   // Rear element\n\n/* Dequeue elements */\ndeque.pop_front();  // Remove the front element\ndeque.pop_back();   // Remove the rear element\n\n/* Get the deque size */\nint size = deque.size();\n\n/* Check whether the deque is empty */\nbool empty = deque.empty();\n
    deque.java
    /* Initialize a deque */\nDeque<Integer> deque = new LinkedList<>();\n\n/* Enqueue elements */\ndeque.offerLast(2);   // Add at the rear\ndeque.offerLast(5);\ndeque.offerLast(4);\ndeque.offerFirst(3);  // Add at the front\ndeque.offerFirst(1);\n\n/* Access elements */\nint peekFirst = deque.peekFirst();  // Front element\nint peekLast = deque.peekLast();    // Rear element\n\n/* Dequeue elements */\nint popFirst = deque.pollFirst();  // Remove the front element\nint popLast = deque.pollLast();    // Remove the rear element\n\n/* Get the deque size */\nint size = deque.size();\n\n/* Check whether the deque is empty */\nboolean isEmpty = deque.isEmpty();\n
    deque.cs
    /* Initialize a deque */\n// In C#, use a LinkedList as a deque\nLinkedList<int> deque = new();\n\n/* Enqueue elements */\ndeque.AddLast(2);   // Add at the rear\ndeque.AddLast(5);\ndeque.AddLast(4);\ndeque.AddFirst(3);  // Add at the front\ndeque.AddFirst(1);\n\n/* Access elements */\nint peekFirst = deque.First.Value;  // Front element\nint peekLast = deque.Last.Value;    // Rear element\n\n/* Dequeue elements */\ndeque.RemoveFirst();  // Remove the front element\ndeque.RemoveLast();   // Remove the rear element\n\n/* Get the deque size */\nint size = deque.Count;\n\n/* Check whether the deque is empty */\nbool isEmpty = deque.Count == 0;\n
    deque_test.go
    /* Initialize a deque */\n// In Go, use a list as a deque\ndeque := list.New()\n\n/* Enqueue elements */\ndeque.PushBack(2)      // Add at the rear\ndeque.PushBack(5)\ndeque.PushBack(4)\ndeque.PushFront(3)     // Add at the front\ndeque.PushFront(1)\n\n/* Access elements */\nfront := deque.Front() // Front element\nrear := deque.Back()   // Rear element\n\n/* Dequeue elements */\ndeque.Remove(front)    // Remove the front element\ndeque.Remove(rear)     // Remove the rear element\n\n/* Get the deque size */\nsize := deque.Len()\n\n/* Check whether the deque is empty */\nisEmpty := deque.Len() == 0\n
    deque.swift
    /* Initialize a deque */\n// Swift has no built-in deque class; use an Array as a deque\nvar deque: [Int] = []\n\n/* Enqueue elements */\ndeque.append(2) // Add at the rear\ndeque.append(5)\ndeque.append(4)\ndeque.insert(3, at: 0) // Add at the front\ndeque.insert(1, at: 0)\n\n/* Access elements */\nlet peekFirst = deque.first! // Front element\nlet peekLast = deque.last! // Rear element\n\n/* Dequeue elements */\n// When using an Array, popFirst takes O(n) time\nlet popFirst = deque.removeFirst() // Remove the front element\nlet popLast = deque.removeLast() // Remove the rear element\n\n/* Get the deque size */\nlet size = deque.count\n\n/* Check whether the deque is empty */\nlet isEmpty = deque.isEmpty\n
    deque.js
    /* Initialize a deque */\n// JavaScript has no built-in deque; use an Array instead\nconst deque = [];\n\n/* Enqueue elements */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// Note that this is an array, so unshift() takes O(n) time\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* Access elements */\nconst peekFirst = deque[0];\nconst peekLast = deque[deque.length - 1];\n\n/* Dequeue elements */\n// Note that this is an array, so shift() takes O(n) time\nconst popFront = deque.shift();\nconst popBack = deque.pop();\n\n/* Get the deque size */\nconst size = deque.length;\n\n/* Check whether the deque is empty */\nconst isEmpty = size === 0;\n
    deque.ts
    /* Initialize a deque */\n// TypeScript has no built-in deque; use an Array instead\nconst deque: number[] = [];\n\n/* Enqueue elements */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// Note that this is an array, so unshift() takes O(n) time\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* Access elements */\nconst peekFirst: number = deque[0];\nconst peekLast: number = deque[deque.length - 1];\n\n/* Dequeue elements */\n// Note that this is an array, so shift() takes O(n) time\nconst popFront: number = deque.shift() as number;\nconst popBack: number = deque.pop() as number;\n\n/* Get the deque size */\nconst size: number = deque.length;\n\n/* Check whether the deque is empty */\nconst isEmpty: boolean = size === 0;\n
    deque.dart
    /* Initialize a deque */\n// In Dart, Queue is defined as a deque\nQueue<int> deque = Queue<int>();\n\n/* Enqueue elements */\ndeque.addLast(2);  // Add at the rear\ndeque.addLast(5);\ndeque.addLast(4);\ndeque.addFirst(3); // Add at the front\ndeque.addFirst(1);\n\n/* Access elements */\nint peekFirst = deque.first; // Front element\nint peekLast = deque.last;   // Rear element\n\n/* Dequeue elements */\nint popFirst = deque.removeFirst(); // Remove the front element\nint popLast = deque.removeLast();   // Remove the rear element\n\n/* Get the deque size */\nint size = deque.length;\n\n/* Check whether the deque is empty */\nbool isEmpty = deque.isEmpty;\n
    deque.rs
    /* Initialize a deque */\nlet mut deque: VecDeque<u32> = VecDeque::new();\n\n/* Enqueue elements */\ndeque.push_back(2);  // Add at the rear\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3); // Add at the front\ndeque.push_front(1);\n\n/* Access elements */\nif let Some(front) = deque.front() { // Front element\n}\nif let Some(rear) = deque.back() {   // Rear element\n}\n\n/* Dequeue elements */\nif let Some(pop_front) = deque.pop_front() { // Remove the front element\n}\nif let Some(pop_rear) = deque.pop_back() {   // Remove the rear element\n}\n\n/* Get the deque size */\nlet size = deque.len();\n\n/* Check whether the deque is empty */\nlet is_empty = deque.is_empty();\n
    deque.c
    // C provides no built-in deque\n
    deque.kt
    /* Initialize a deque */\nval deque = LinkedList<Int>()\n\n/* Enqueue elements */\ndeque.offerLast(2)  // Add at the rear\ndeque.offerLast(5)\ndeque.offerLast(4)\ndeque.offerFirst(3) // Add at the front\ndeque.offerFirst(1)\n\n/* Access elements */\nval peekFirst = deque.peekFirst() // Front element\nval peekLast = deque.peekLast()   // Rear element\n\n/* Dequeue elements */\nval popFirst = deque.pollFirst() // Remove the front element\nval popLast = deque.pollLast()   // Remove the rear element\n\n/* Get the deque size */\nval size = deque.size\n\n/* Check whether the deque is empty */\nval isEmpty = deque.isEmpty()\n
    deque.rb
    # Initialize a deque\n# Ruby has no built-in deque; use an Array instead\ndeque = []\n\n# Enqueue elements\ndeque << 2\ndeque << 5\ndeque << 4\n# Note that this is an array, so Array#unshift takes O(n) time\ndeque.unshift(3)\ndeque.unshift(1)\n\n# Access elements\npeek_first = deque.first\npeek_last = deque.last\n\n# Dequeue elements\n# Note that this is an array, so Array#shift takes O(n) time\npop_front = deque.shift\npop_back = deque.pop\n\n# Get the deque size\nsize = deque.length\n\n# Check whether the deque is empty\nis_empty = size.zero?\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#532-implementing-a-deque","level":2,"title":"5.3.2   Implementing a deque *","text":"

    A deque is implemented much like a queue, using either a linked list or an array as the underlying structure.

    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#1-implementation-with-a-doubly-linked-list","level":3,"title":"1.   Implementation with a doubly linked list","text":"

    In the previous section, we used a singly linked list for a queue because it easily supports removing the head for dequeueing and adding after the tail for enqueueing.

    A deque supports both insertion and removal at both ends. It therefore needs the symmetric operations in the other direction as well. For this reason, we use a doubly linked list.

    As shown below, treat the list's head and tail as the deque's front and rear, and support adding and removing nodes at both ends.

    <1><2><3><4><5>

    Figure 5-8   Insertion and removal in a linked-list deque

    The implementation is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_deque.py
    class ListNode:\n    \"\"\"Doubly linked list node\"\"\"\n\n    def __init__(self, val: int):\n        \"\"\"Constructor\"\"\"\n        self.val: int = val\n        self.next: ListNode | None = None  # Reference to the next node\n        self.prev: ListNode | None = None  # Reference to the previous node\n\nclass LinkedListDeque:\n    \"\"\"Deque implemented with a doubly linked list\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._front: ListNode | None = None  # Head node front\n        self._rear: ListNode | None = None  # Tail node rear\n        self._size: int = 0  # Deque size\n\n    def size(self) -> int:\n        \"\"\"Get the deque size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the deque is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int, is_front: bool):\n        \"\"\"Enqueue operation\"\"\"\n        node = ListNode(num)\n        # If the linked list is empty, make both front and rear point to node\n        if self.is_empty():\n            self._front = self._rear = node\n        # Enqueue at the front\n        elif is_front:\n            # Add node to the head of the linked list\n            self._front.prev = node\n            node.next = self._front\n            self._front = node  # Update the head node\n        # Enqueue at the rear\n        else:\n            # Add node to the tail of the linked list\n            self._rear.next = node\n            node.prev = self._rear\n            self._rear = node  # Update the tail node\n        self._size += 1  # Update the deque size\n\n    def push_first(self, num: int):\n        \"\"\"Enqueue at the front\"\"\"\n        self.push(num, True)\n\n    def push_last(self, num: int):\n        \"\"\"Enqueue at the rear\"\"\"\n        self.push(num, False)\n\n    def pop(self, is_front: bool) -> int:\n        \"\"\"Dequeue operation\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        # When the deque size is 1, the head and tail point to the same node\n        if self._size == 1:\n            val: int = self._front.val\n            self._front = None\n            self._rear = None\n        # Dequeue from the front\n        elif is_front:\n            val: int = self._front.val  # Save the head node value\n            # Remove the head node\n            fnext: ListNode | None = self._front.next\n            fnext.prev = None\n            self._front.next = None\n            self._front = fnext  # Update the head node\n        # Dequeue from the rear\n        else:\n            val: int = self._rear.val  # Save the tail node value\n            # Remove the tail node\n            rprev: ListNode | None = self._rear.prev\n            rprev.next = None\n            self._rear.prev = None\n            self._rear = rprev  # Update the tail node\n        self._size -= 1  # Update the deque size\n        return val\n\n    def pop_first(self) -> int:\n        \"\"\"Dequeue from the front\"\"\"\n        return self.pop(True)\n\n    def pop_last(self) -> int:\n        \"\"\"Dequeue from the rear\"\"\"\n        return self.pop(False)\n\n    def peek_first(self) -> int:\n        \"\"\"Access the front element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        return self._front.val\n\n    def peek_last(self) -> int:\n        \"\"\"Access the rear element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        return self._rear.val\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return an array for printing\"\"\"\n        node = self._front\n        res = [0] * self.size()\n        for i in range(self.size()):\n            res[i] = node.val\n            node = node.next\n        return res\n
    linkedlist_deque.cpp
    /* Doubly linked list node */\nstruct DoublyListNode {\n    int val;              // Node value\n    DoublyListNode *next; // Pointer to the next node\n    DoublyListNode *prev; // Pointer to the previous node\n    DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {\n    }\n};\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n  private:\n    DoublyListNode *front, *rear; // Head node front and tail node rear\n    int queSize = 0;              // Deque size\n\n  public:\n    /* Constructor */\n    LinkedListDeque() : front(nullptr), rear(nullptr) {\n    }\n\n    /* Destructor */\n    ~LinkedListDeque() {\n        // Traverse the linked list and delete each node to free its memory\n        DoublyListNode *pre, *cur = front;\n        while (cur != nullptr) {\n            pre = cur;\n            cur = cur->next;\n            delete pre;\n        }\n    }\n\n    /* Get the deque size */\n    int size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue operation */\n    void push(int num, bool isFront) {\n        DoublyListNode *node = new DoublyListNode(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty())\n            front = rear = node;\n        // Enqueue at the front\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front->prev = node;\n            node->next = front;\n            front = node; // Update the head node\n        // Enqueue at the rear\n        } else {\n            // Add node to the tail of the linked list\n            rear->next = node;\n            node->prev = rear;\n            rear = node; // Update the tail node\n        }\n        queSize++; // Update the deque size\n    }\n\n    /* Enqueue at the front */\n    void pushFirst(int num) {\n        push(num, true);\n    }\n\n    /* Enqueue at the rear */\n    void pushLast(int num) {\n        push(num, false);\n    }\n\n    /* Dequeue operation */\n    int pop(bool isFront) {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        int val;\n        // When the deque size is 1, the head and tail point to the same node\n        if (queSize == 1) {\n            val = front->val;\n            delete front; // Both point to the same node, so free it only once\n            front = nullptr;\n            rear = nullptr;\n        }\n        // Dequeue from the front\n        else if (isFront) {\n            val = front->val; // Save the head node value\n            // Remove the head node\n            DoublyListNode *fNext = front->next;\n            fNext->prev = nullptr;\n            front->next = nullptr;\n            delete front;\n            front = fNext; // Update the head node\n        // Dequeue from the rear\n        } else {\n            val = rear->val; // Save the tail node value\n            // Remove the tail node\n            DoublyListNode *rPrev = rear->prev;\n            rPrev->next = nullptr;\n            rear->prev = nullptr;\n            delete rear;\n            rear = rPrev; // Update the tail node\n        }\n        queSize--; // Update the deque size\n        return val;\n    }\n\n    /* Dequeue from the front */\n    int popFirst() {\n        return pop(true);\n    }\n\n    /* Dequeue from the rear */\n    int popLast() {\n        return pop(false);\n    }\n\n    /* Access the front element */\n    int peekFirst() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return front->val;\n    }\n\n    /* Access the rear element */\n    int peekLast() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return rear->val;\n    }\n\n    /* Return a vector for printing */\n    vector<int> toVector() {\n        DoublyListNode *node = front;\n        vector<int> res(size());\n        for (int i = 0; i < res.size(); i++) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
    linkedlist_deque.java
    /* Doubly linked list node */\nclass ListNode {\n    int val; // Node value\n    ListNode next; // Reference to the next node\n    ListNode prev; // Reference to the previous node\n\n    ListNode(int val) {\n        this.val = val;\n        prev = next = null;\n    }\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    private ListNode front, rear; // Head node front and tail node rear\n    private int queSize = 0; // Deque size\n\n    public LinkedListDeque() {\n        front = rear = null;\n    }\n\n    /* Get the deque size */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue operation */\n    private void push(int num, boolean isFront) {\n        ListNode node = new ListNode(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty())\n            front = rear = node;\n        // Enqueue at the front\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front.prev = node;\n            node.next = front;\n            front = node; // Update the head node\n        // Enqueue at the rear\n        } else {\n            // Add node to the tail of the linked list\n            rear.next = node;\n            node.prev = rear;\n            rear = node; // Update the tail node\n        }\n        queSize++; // Update the queue size\n    }\n\n    /* Enqueue at the front */\n    public void pushFirst(int num) {\n        push(num, true);\n    }\n\n    /* Enqueue at the rear */\n    public void pushLast(int num) {\n        push(num, false);\n    }\n\n    /* Dequeue operation */\n    private int pop(boolean isFront) {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        int val;\n        // When the queue size is 1, the head and tail point to the same node\n        if (queSize == 1) {\n            val = front.val;\n            front = null;\n            rear = null;\n        }\n        // Dequeue from the front\n        else if (isFront) {\n            val = front.val; // Save the head node value\n            // Remove the head node\n            ListNode fNext = front.next;\n            fNext.prev = null;\n            front.next = null;\n            front = fNext; // Update the head node\n        // Dequeue from the rear\n        } else {\n            val = rear.val; // Save the tail node value\n            // Remove the tail node\n            ListNode rPrev = rear.prev;\n            rPrev.next = null;\n            rear.prev = null;\n            rear = rPrev; // Update the tail node\n        }\n        queSize--; // Update the queue size\n        return val;\n    }\n\n    /* Dequeue from the front */\n    public int popFirst() {\n        return pop(true);\n    }\n\n    /* Dequeue from the rear */\n    public int popLast() {\n        return pop(false);\n    }\n\n    /* Access the front element */\n    public int peekFirst() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return front.val;\n    }\n\n    /* Access the rear element */\n    public int peekLast() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return rear.val;\n    }\n\n    /* Return an array for printing */\n    public int[] toArray() {\n        ListNode node = front;\n        int[] res = new int[size()];\n        for (int i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_deque.cs
    /* Doubly linked list node */\nclass ListNode(int val) {\n    public int val = val;       // Node value\n    public ListNode? next = null; // Reference to the next node\n    public ListNode? prev = null; // Reference to the previous node\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    ListNode? front, rear; // Head node front and tail node rear\n    int queSize = 0;      // Deque size\n\n    public LinkedListDeque() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the deque size */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Enqueue operation */\n    void Push(int num, bool isFront) {\n        ListNode node = new(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (IsEmpty()) {\n            front = node;\n            rear = node;\n        }\n        // Enqueue at the front\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front!.prev = node;\n            node.next = front;\n            front = node; // Update the head node                           \n        }\n        // Enqueue at the rear\n        else {\n            // Add node to the tail of the linked list\n            rear!.next = node;\n            node.prev = rear;\n            rear = node;  // Update the tail node\n        }\n\n        queSize++; // Update the queue size\n    }\n\n    /* Enqueue at the front */\n    public void PushFirst(int num) {\n        Push(num, true);\n    }\n\n    /* Enqueue at the rear */\n    public void PushLast(int num) {\n        Push(num, false);\n    }\n\n    /* Dequeue operation */\n    int? Pop(bool isFront) {\n        if (IsEmpty())\n            throw new Exception();\n        int? val;\n        // When the queue size is 1, the head and tail point to the same node\n        if (queSize == 1) {\n            val = front!.val;\n            front = null;\n            rear = null;\n        }\n        // Dequeue from the front\n        else if (isFront) {\n            val = front!.val; // Save the head node value\n            // Remove the head node\n            ListNode? fNext = front?.next;\n            fNext!.prev = null;\n            front.next = null;\n            front = fNext;   // Update the head node\n        }\n        // Dequeue from the rear\n        else {\n            val = rear!.val;  // Save the tail node value\n            // Remove the tail node\n            ListNode? rPrev = rear?.prev;\n            rPrev!.next = null;\n            rear.prev = null;\n            rear = rPrev;    // Update the tail node\n        }\n\n        queSize--; // Update the queue size\n        return val;\n    }\n\n    /* Dequeue from the front */\n    public int? PopFirst() {\n        return Pop(true);\n    }\n\n    /* Dequeue from the rear */\n    public int? PopLast() {\n        return Pop(false);\n    }\n\n    /* Access the front element */\n    public int? PeekFirst() {\n        if (IsEmpty())\n            throw new Exception();\n        return front?.val;\n    }\n\n    /* Access the rear element */\n    public int? PeekLast() {\n        if (IsEmpty())\n            throw new Exception();\n        return rear?.val;\n    }\n\n    /* Return an array for printing */\n    public int?[] ToArray() {\n        ListNode? node = front;\n        int?[] res = new int?[Size()];\n        for (int i = 0; i < res.Length; i++) {\n            res[i] = node?.val;\n            node = node?.next;\n        }\n\n        return res;\n    }\n}\n
    linkedlist_deque.go
    /* Deque implemented with a doubly linked list */\ntype linkedListDeque struct {\n    // Use the standard library list package\n    data *list.List\n}\n\n/* Initialize the deque */\nfunc newLinkedListDeque() *linkedListDeque {\n    return &linkedListDeque{\n        data: list.New(),\n    }\n}\n\n/* Enqueue an element at the front */\nfunc (s *linkedListDeque) pushFirst(value any) {\n    s.data.PushFront(value)\n}\n\n/* Enqueue an element at the rear */\nfunc (s *linkedListDeque) pushLast(value any) {\n    s.data.PushBack(value)\n}\n\n/* Dequeue the front element */\nfunc (s *linkedListDeque) popFirst() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Dequeue the rear element */\nfunc (s *linkedListDeque) popLast() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Access the front element */\nfunc (s *linkedListDeque) peekFirst() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    return e.Value\n}\n\n/* Access the rear element */\nfunc (s *linkedListDeque) peekLast() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    return e.Value\n}\n\n/* Get the queue size */\nfunc (s *linkedListDeque) size() int {\n    return s.data.Len()\n}\n\n/* Check whether the queue is empty */\nfunc (s *linkedListDeque) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get the list for printing */\nfunc (s *linkedListDeque) toList() *list.List {\n    return s.data\n}\n
    linkedlist_deque.swift
    /* Doubly linked list node */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the next node\n    weak var prev: ListNode? // Reference to the previous node\n\n    init(val: Int) {\n        self.val = val\n    }\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    private var front: ListNode? // Head node front\n    private var rear: ListNode? // Tail node rear\n    private var _size: Int // Deque size\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the deque size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the deque is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue operation */\n    private func push(num: Int, isFront: Bool) {\n        let node = ListNode(val: num)\n        // If the linked list is empty, make both front and rear point to node\n        if isEmpty() {\n            front = node\n            rear = node\n        }\n        // Enqueue at the front\n        else if isFront {\n            // Add node to the head of the linked list\n            front?.prev = node\n            node.next = front\n            front = node // Update the head node\n        }\n        // Enqueue at the rear\n        else {\n            // Add node to the tail of the linked list\n            rear?.next = node\n            node.prev = rear\n            rear = node // Update the tail node\n        }\n        _size += 1 // Update the deque size\n    }\n\n    /* Enqueue at the front */\n    func pushFirst(num: Int) {\n        push(num: num, isFront: true)\n    }\n\n    /* Enqueue at the rear */\n    func pushLast(num: Int) {\n        push(num: num, isFront: false)\n    }\n\n    /* Dequeue operation */\n    private func pop(isFront: Bool) -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        let val: Int\n        // When the deque size is 1, the head and tail point to the same node\n        if _size == 1 {\n            val = front!.val\n            front = nil\n            rear = nil\n        }\n        // Dequeue from the front\n        else if isFront {\n            val = front!.val // Save the head node value\n            // Remove the head node\n            let fNext = front?.next\n            fNext?.prev = nil\n            front?.next = nil\n            front = fNext // Update the head node\n        }\n        // Dequeue from the rear\n        else {\n            val = rear!.val // Save the tail node value\n            // Remove the tail node\n            let rPrev = rear?.prev\n            rPrev?.next = nil\n            rear?.prev = nil\n            rear = rPrev // Update the tail node\n        }\n        _size -= 1 // Update the deque size\n        return val\n    }\n\n    /* Dequeue from the front */\n    func popFirst() -> Int {\n        pop(isFront: true)\n    }\n\n    /* Dequeue from the rear */\n    func popLast() -> Int {\n        pop(isFront: false)\n    }\n\n    /* Access the front element */\n    func peekFirst() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return front!.val\n    }\n\n    /* Access the rear element */\n    func peekLast() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return rear!.val\n    }\n\n    /* Return an array for printing */\n    func toArray() -> [Int] {\n        var node = front\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
    linkedlist_deque.js
    /* Doubly linked list node */\nclass ListNode {\n    prev; // Reference (pointer) to the previous node\n    next; // Reference (pointer) to the next node\n    val; // Node value\n\n    constructor(val) {\n        this.val = val;\n        this.next = null;\n        this.prev = null;\n    }\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    #front; // Head node front\n    #rear; // Tail node rear\n    #queSize; // Deque size\n\n    constructor() {\n        this.#front = null;\n        this.#rear = null;\n        this.#queSize = 0;\n    }\n\n    /* Enqueue at the rear */\n    pushLast(val) {\n        const node = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.#queSize === 0) {\n            this.#front = node;\n            this.#rear = node;\n        } else {\n            // Add node to the tail of the linked list\n            this.#rear.next = node;\n            node.prev = this.#rear;\n            this.#rear = node; // Update the tail node\n        }\n        this.#queSize++;\n    }\n\n    /* Enqueue at the front */\n    pushFirst(val) {\n        const node = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.#queSize === 0) {\n            this.#front = node;\n            this.#rear = node;\n        } else {\n            // Add node to the head of the linked list\n            this.#front.prev = node;\n            node.next = this.#front;\n            this.#front = node; // Update the head node\n        }\n        this.#queSize++;\n    }\n\n    /* Dequeue from the rear */\n    popLast() {\n        if (this.#queSize === 0) {\n            return null;\n        }\n        const value = this.#rear.val; // Store the tail node value\n        // When the queue size is 1, the head and tail point to the same node\n        if (this.#queSize === 1) {\n            this.#front = null;\n            this.#rear = null;\n        }\n        // Remove the tail node\n        else {\n            let temp = this.#rear.prev;\n            temp.next = null;\n            this.#rear.prev = null;\n            this.#rear = temp; // Update the tail node\n        }\n        this.#queSize--;\n        return value;\n    }\n\n    /* Dequeue from the front */\n    popFirst() {\n        if (this.#queSize === 0) {\n            return null;\n        }\n        const value = this.#front.val; // Store the head node value\n        // When the queue size is 1, the head and tail point to the same node\n        if (this.#queSize === 1) {\n            this.#front = null;\n            this.#rear = null;\n        }\n        // Remove the head node\n        else {\n            let temp = this.#front.next;\n            temp.prev = null;\n            this.#front.next = null;\n            this.#front = temp; // Update the head node\n        }\n        this.#queSize--;\n        return value;\n    }\n\n    /* Access the rear element */\n    peekLast() {\n        return this.#queSize === 0 ? null : this.#rear.val;\n    }\n\n    /* Access the front element */\n    peekFirst() {\n        return this.#queSize === 0 ? null : this.#front.val;\n    }\n\n    /* Get the deque size */\n    size() {\n        return this.#queSize;\n    }\n\n    /* Check whether the deque is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Print the deque */\n    print() {\n        const arr = [];\n        let temp = this.#front;\n        while (temp !== null) {\n            arr.push(temp.val);\n            temp = temp.next;\n        }\n        console.log('[' + arr.join(', ') + ']');\n    }\n}\n
    linkedlist_deque.ts
    /* Doubly linked list node */\nclass ListNode {\n    prev: ListNode; // Reference (pointer) to the previous node\n    next: ListNode; // Reference (pointer) to the next node\n    val: number; // Node value\n\n    constructor(val: number) {\n        this.val = val;\n        this.next = null;\n        this.prev = null;\n    }\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    private front: ListNode; // Head node front\n    private rear: ListNode; // Tail node rear\n    private queSize: number; // Deque size\n\n    constructor() {\n        this.front = null;\n        this.rear = null;\n        this.queSize = 0;\n    }\n\n    /* Enqueue at the rear */\n    pushLast(val: number): void {\n        const node: ListNode = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.queSize === 0) {\n            this.front = node;\n            this.rear = node;\n        } else {\n            // Add node to the tail of the linked list\n            this.rear.next = node;\n            node.prev = this.rear;\n            this.rear = node; // Update the tail node\n        }\n        this.queSize++;\n    }\n\n    /* Enqueue at the front */\n    pushFirst(val: number): void {\n        const node: ListNode = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.queSize === 0) {\n            this.front = node;\n            this.rear = node;\n        } else {\n            // Add node to the head of the linked list\n            this.front.prev = node;\n            node.next = this.front;\n            this.front = node; // Update the head node\n        }\n        this.queSize++;\n    }\n\n    /* Dequeue from the rear */\n    popLast(): number {\n        if (this.queSize === 0) {\n            return null;\n        }\n        const value: number = this.rear!.val; // Store the tail node value\n        // When the queue size is 1, the head and tail point to the same node\n        if (this.queSize === 1) {\n            this.front = null;\n            this.rear = null;\n        }\n        // Remove the tail node\n        else {\n            let temp: ListNode = this.rear!.prev;\n            temp!.next = null;\n            this.rear!.prev = null;\n            this.rear = temp; // Update the tail node\n        }\n        this.queSize--;\n        return value;\n    }\n\n    /* Dequeue from the front */\n    popFirst(): number {\n        if (this.queSize === 0) {\n            return null;\n        }\n        const value: number = this.front.val; // Store the head node value\n        // When the queue size is 1, the head and tail point to the same node\n        if (this.queSize === 1) {\n            this.front = null;\n            this.rear = null;\n        }\n        // Remove the head node\n        else {\n            let temp: ListNode = this.front!.next;\n            temp!.prev = null;\n            this.front!.next = null;\n            this.front = temp; // Update the head node\n        }\n        this.queSize--;\n        return value;\n    }\n\n    /* Access the rear element */\n    peekLast(): number {\n        return this.queSize === 0 ? null : this.rear.val;\n    }\n\n    /* Access the front element */\n    peekFirst(): number {\n        return this.queSize === 0 ? null : this.front.val;\n    }\n\n    /* Get the deque size */\n    size(): number {\n        return this.queSize;\n    }\n\n    /* Check whether the deque is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Print the deque */\n    print(): void {\n        const arr: number[] = [];\n        let temp: ListNode = this.front;\n        while (temp !== null) {\n            arr.push(temp.val);\n            temp = temp.next;\n        }\n        console.log('[' + arr.join(', ') + ']');\n    }\n}\n
    linkedlist_deque.dart
    /* Doubly linked list node */\nclass ListNode {\n  int val; // Node value\n  ListNode? next; // Reference to the next node\n  ListNode? prev; // Reference to the previous node\n\n  ListNode(this.val, {this.next, this.prev});\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n  late ListNode? _front; // Head node _front\n  late ListNode? _rear; // Tail node _rear\n  int _queSize = 0; // Deque size\n\n  LinkedListDeque() {\n    this._front = null;\n    this._rear = null;\n  }\n\n  /* Get the deque size */\n  int size() {\n    return this._queSize;\n  }\n\n  /* Check whether the deque is empty */\n  bool isEmpty() {\n    return size() == 0;\n  }\n\n  /* Enqueue operation */\n  void push(int _num, bool isFront) {\n    final ListNode node = ListNode(_num);\n    if (isEmpty()) {\n      // If the linked list is empty, make both _front and _rear point to node\n      _front = _rear = node;\n    } else if (isFront) {\n      // Enqueue at the front\n      // Add node to the head of the linked list\n      _front!.prev = node;\n      node.next = _front;\n      _front = node; // Update the head node\n    } else {\n      // Enqueue at the rear\n      // Add node to the tail of the linked list\n      _rear!.next = node;\n      node.prev = _rear;\n      _rear = node; // Update the tail node\n    }\n    _queSize++; // Update the queue size\n  }\n\n  /* Enqueue at the front */\n  void pushFirst(int _num) {\n    push(_num, true);\n  }\n\n  /* Enqueue at the rear */\n  void pushLast(int _num) {\n    push(_num, false);\n  }\n\n  /* Dequeue operation */\n  int? pop(bool isFront) {\n    // If the queue is empty, return null immediately\n    if (isEmpty()) {\n      return null;\n    }\n    final int val;\n    // When the queue size is 1, the head and tail point to the same node\n    if (_queSize == 1) {\n      val = _front!.val;\n      _front = null;\n      _rear = null;\n    }\n    // Dequeue from the front\n    else if (isFront) {\n      val = _front!.val; // Save the head node value\n      // Remove the head node\n      ListNode? fNext = _front!.next;\n      fNext!.prev = null;\n      _front!.next = null;\n      _front = fNext; // Update the head node\n    // Dequeue from the rear\n    } else {\n      val = _rear!.val; // Save the tail node value\n      // Remove the tail node\n      ListNode? rPrev = _rear!.prev;\n      rPrev!.next = null;\n      _rear!.prev = null;\n      _rear = rPrev; // Update the tail node\n    }\n    _queSize--; // Update the queue size\n    return val;\n  }\n\n  /* Dequeue from the front */\n  int? popFirst() {\n    return pop(true);\n  }\n\n  /* Dequeue from the rear */\n  int? popLast() {\n    return pop(false);\n  }\n\n  /* Access the front element */\n  int? peekFirst() {\n    return _front?.val;\n  }\n\n  /* Access the rear element */\n  int? peekLast() {\n    return _rear?.val;\n  }\n\n  /* Return an array for printing */\n  List<int> toArray() {\n    ListNode? node = _front;\n    final List<int> res = [];\n    for (int i = 0; i < _queSize; i++) {\n      res.add(node!.val);\n      node = node.next;\n    }\n    return res;\n  }\n}\n
    linkedlist_deque.rs
    /* Doubly linked list node */\npub struct ListNode<T> {\n    pub val: T,                                 // Node value\n    pub next: Option<Rc<RefCell<ListNode<T>>>>, // Pointer to the next node\n    pub prev: Option<Rc<RefCell<ListNode<T>>>>, // Pointer to the previous node\n}\n\nimpl<T> ListNode<T> {\n    pub fn new(val: T) -> Rc<RefCell<ListNode<T>>> {\n        Rc::new(RefCell::new(ListNode {\n            val,\n            next: None,\n            prev: None,\n        }))\n    }\n}\n\n/* Deque implemented with a doubly linked list */\n#[allow(dead_code)]\npub struct LinkedListDeque<T> {\n    front: Option<Rc<RefCell<ListNode<T>>>>, // Head node front\n    rear: Option<Rc<RefCell<ListNode<T>>>>,  // Tail node rear\n    que_size: usize,                         // Deque size\n}\n\nimpl<T: Copy> LinkedListDeque<T> {\n    pub fn new() -> Self {\n        Self {\n            front: None,\n            rear: None,\n            que_size: 0,\n        }\n    }\n\n    /* Get the deque size */\n    pub fn size(&self) -> usize {\n        return self.que_size;\n    }\n\n    /* Check whether the deque is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.que_size == 0;\n    }\n\n    /* Enqueue operation */\n    fn push(&mut self, num: T, is_front: bool) {\n        let node = ListNode::new(num);\n        // Enqueue at the front\n        if is_front {\n            match self.front.take() {\n                // If the linked list is empty, make both front and rear point to node\n                None => {\n                    self.rear = Some(node.clone());\n                    self.front = Some(node);\n                }\n                // Add node to the head of the linked list\n                Some(old_front) => {\n                    old_front.borrow_mut().prev = Some(node.clone());\n                    node.borrow_mut().next = Some(old_front);\n                    self.front = Some(node); // Update the head node\n                }\n            }\n        }\n        // Enqueue at the rear\n        else {\n            match self.rear.take() {\n                // If the linked list is empty, make both front and rear point to node\n                None => {\n                    self.front = Some(node.clone());\n                    self.rear = Some(node);\n                }\n                // Add node to the tail of the linked list\n                Some(old_rear) => {\n                    old_rear.borrow_mut().next = Some(node.clone());\n                    node.borrow_mut().prev = Some(old_rear);\n                    self.rear = Some(node); // Update the tail node\n                }\n            }\n        }\n        self.que_size += 1; // Update the deque size\n    }\n\n    /* Enqueue at the front */\n    pub fn push_first(&mut self, num: T) {\n        self.push(num, true);\n    }\n\n    /* Enqueue at the rear */\n    pub fn push_last(&mut self, num: T) {\n        self.push(num, false);\n    }\n\n    /* Dequeue operation */\n    fn pop(&mut self, is_front: bool) -> Option<T> {\n        // If the deque is empty, return None immediately\n        if self.is_empty() {\n            return None;\n        };\n        // Dequeue from the front\n        if is_front {\n            self.front.take().map(|old_front| {\n                match old_front.borrow_mut().next.take() {\n                    Some(new_front) => {\n                        new_front.borrow_mut().prev.take();\n                        self.front = Some(new_front); // Update the head node\n                    }\n                    None => {\n                        self.rear.take();\n                    }\n                }\n                self.que_size -= 1; // Update the deque size\n                old_front.borrow().val\n            })\n        }\n        // Dequeue from the rear\n        else {\n            self.rear.take().map(|old_rear| {\n                match old_rear.borrow_mut().prev.take() {\n                    Some(new_rear) => {\n                        new_rear.borrow_mut().next.take();\n                        self.rear = Some(new_rear); // Update the tail node\n                    }\n                    None => {\n                        self.front.take();\n                    }\n                }\n                self.que_size -= 1; // Update the deque size\n                old_rear.borrow().val\n            })\n        }\n    }\n\n    /* Dequeue from the front */\n    pub fn pop_first(&mut self) -> Option<T> {\n        return self.pop(true);\n    }\n\n    /* Dequeue from the rear */\n    pub fn pop_last(&mut self) -> Option<T> {\n        return self.pop(false);\n    }\n\n    /* Access the front element */\n    pub fn peek_first(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.front.as_ref()\n    }\n\n    /* Access the rear element */\n    pub fn peek_last(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.rear.as_ref()\n    }\n\n    /* Return a vector for printing */\n    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n        let mut res: Vec<T> = Vec::new();\n        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {\n            if let Some(cur) = cur {\n                res.push(cur.borrow().val);\n                recur(cur.borrow().next.as_ref(), res);\n            }\n        }\n\n        recur(head, &mut res);\n        res\n    }\n}\n
    linkedlist_deque.c
    /* Doubly linked list node */\ntypedef struct DoublyListNode {\n    int val;                     // Node value\n    struct DoublyListNode *next; // Next node\n    struct DoublyListNode *prev; // Previous node\n} DoublyListNode;\n\n/* Constructor */\nDoublyListNode *newDoublyListNode(int num) {\n    DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode));\n    new->val = num;\n    new->next = NULL;\n    new->prev = NULL;\n    return new;\n}\n\n/* Destructor */\nvoid delDoublyListNode(DoublyListNode *node) {\n    free(node);\n}\n\n/* Deque implemented with a doubly linked list */\ntypedef struct {\n    DoublyListNode *front, *rear; // Head node front and tail node rear\n    int queSize;                  // Deque size\n} LinkedListDeque;\n\n/* Constructor */\nLinkedListDeque *newLinkedListDeque() {\n    LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque));\n    deque->front = NULL;\n    deque->rear = NULL;\n    deque->queSize = 0;\n    return deque;\n}\n\n/* Destructor */\nvoid delLinkedListdeque(LinkedListDeque *deque) {\n    // Free all nodes\n    for (int i = 0; i < deque->queSize && deque->front != NULL; i++) {\n        DoublyListNode *tmp = deque->front;\n        deque->front = deque->front->next;\n        free(tmp);\n    }\n    // Free the deque structure\n    free(deque);\n}\n\n/* Get the queue size */\nint size(LinkedListDeque *deque) {\n    return deque->queSize;\n}\n\n/* Check whether the queue is empty */\nbool empty(LinkedListDeque *deque) {\n    return (size(deque) == 0);\n}\n\n/* Enqueue */\nvoid push(LinkedListDeque *deque, int num, bool isFront) {\n    DoublyListNode *node = newDoublyListNode(num);\n    // If the list is empty, make both front and rear point to node\n    if (empty(deque)) {\n        deque->front = deque->rear = node;\n    }\n    // Enqueue at the front\n    else if (isFront) {\n        // Add node to the head of the linked list\n        deque->front->prev = node;\n        node->next = deque->front;\n        deque->front = node; // Update the head node\n    }\n    // Enqueue at the rear\n    else {\n        // Add node to the tail of the linked list\n        deque->rear->next = node;\n        node->prev = deque->rear;\n        deque->rear = node;\n    }\n    deque->queSize++; // Update the queue size\n}\n\n/* Enqueue at the front */\nvoid pushFirst(LinkedListDeque *deque, int num) {\n    push(deque, num, true);\n}\n\n/* Enqueue at the rear */\nvoid pushLast(LinkedListDeque *deque, int num) {\n    push(deque, num, false);\n}\n\n/* Access the front element */\nint peekFirst(LinkedListDeque *deque) {\n    assert(size(deque) && deque->front);\n    return deque->front->val;\n}\n\n/* Access the rear element */\nint peekLast(LinkedListDeque *deque) {\n    assert(size(deque) && deque->rear);\n    return deque->rear->val;\n}\n\n/* Dequeue */\nint pop(LinkedListDeque *deque, bool isFront) {\n    if (empty(deque))\n        return -1;\n    int val;\n    // When the queue size is 1, the head and tail point to the same node\n    if (deque->queSize == 1) {\n        val = deque->front->val;\n        delDoublyListNode(deque->front); // Both point to the same node, so free it only once\n        deque->front = NULL;\n        deque->rear = NULL;\n    }\n    // Dequeue from the front\n    else if (isFront) {\n        val = peekFirst(deque); // Save the head node value\n        DoublyListNode *fNext = deque->front->next;\n        fNext->prev = NULL;\n        deque->front->next = NULL;\n        delDoublyListNode(deque->front);\n        deque->front = fNext; // Update the head node\n    }\n    // Dequeue from the rear\n    else {\n        val = peekLast(deque); // Save the tail node value\n        DoublyListNode *rPrev = deque->rear->prev;\n        rPrev->next = NULL;\n        deque->rear->prev = NULL;\n        delDoublyListNode(deque->rear);\n        deque->rear = rPrev; // Update the tail node\n    }\n    deque->queSize--; // Update the queue size\n    return val;\n}\n\n/* Dequeue from the front */\nint popFirst(LinkedListDeque *deque) {\n    return pop(deque, true);\n}\n\n/* Dequeue from the rear */\nint popLast(LinkedListDeque *deque) {\n    return pop(deque, false);\n}\n\n/* Print the queue */\nvoid printLinkedListDeque(LinkedListDeque *deque) {\n    int *arr = malloc(sizeof(int) * deque->queSize);\n    // Copy the linked list data into an array\n    int i;\n    DoublyListNode *node;\n    for (i = 0, node = deque->front; i < deque->queSize; i++) {\n        arr[i] = node->val;\n        node = node->next;\n    }\n    printArray(arr, deque->queSize);\n    free(arr);\n}\n
    linkedlist_deque.kt
    /* Doubly linked list node */\nclass ListNode(var _val: Int) {\n    // The node value is stored in _val\n    var next: ListNode? = null // Reference to the next node\n    var prev: ListNode? = null // Reference to the previous node\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    private var front: ListNode? = null // Head node front\n    private var rear: ListNode? = null // Tail node rear\n    private var queSize: Int = 0 // Deque size\n\n    /* Get the deque size */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check whether the deque is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Enqueue operation */\n    fun push(num: Int, isFront: Boolean) {\n        val node = ListNode(num)\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty()) {\n            rear = node\n            front = rear\n            // Enqueue at the front\n        } else if (isFront) {\n            // Add node to the head of the linked list\n            front?.prev = node\n            node.next = front\n            front = node // Update the head node\n            // Enqueue at the rear\n        } else {\n            // Add node to the tail of the linked list\n            rear?.next = node\n            node.prev = rear\n            rear = node // Update the tail node\n        }\n        queSize++ // Update the queue size\n    }\n\n    /* Enqueue at the front */\n    fun pushFirst(num: Int) {\n        push(num, true)\n    }\n\n    /* Enqueue at the rear */\n    fun pushLast(num: Int) {\n        push(num, false)\n    }\n\n    /* Dequeue operation */\n    fun pop(isFront: Boolean): Int {\n        if (isEmpty()) \n            throw IndexOutOfBoundsException()\n        val _val: Int\n        // When the queue size is 1, the head and tail point to the same node\n        if (queSize == 1) {\n            _val = front!!._val\n            front = null\n            rear = null\n        }\n        // Dequeue from the front\n        else if (isFront) {\n            _val = front!!._val // Save the head node value\n            // Remove the head node\n            val fNext = front!!.next\n            fNext!!.prev = null\n            front!!.next = null\n            front = fNext // Update the head node\n            // Dequeue from the rear\n        } else {\n            _val = rear!!._val // Save the tail node value\n            // Remove the tail node\n            val rPrev = rear!!.prev\n            rPrev!!.next = null\n            rear!!.prev = null\n            rear = rPrev // Update the tail node\n        }\n        queSize-- // Update the queue size\n        return _val\n    }\n\n    /* Dequeue from the front */\n    fun popFirst(): Int {\n        return pop(true)\n    }\n\n    /* Dequeue from the rear */\n    fun popLast(): Int {\n        return pop(false)\n    }\n\n    /* Access the front element */\n    fun peekFirst(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return front!!._val\n    }\n\n    /* Access the rear element */\n    fun peekLast(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return rear!!._val\n    }\n\n    /* Return an array for printing */\n    fun toArray(): IntArray {\n        var node = front\n        val res = IntArray(size())\n        for (i in res.indices) {\n            res[i] = node!!._val\n            node = node.next\n        }\n        return res\n    }\n}\n
    linkedlist_deque.rb
    =begin\nFile: linkedlist_deque.rb\nCreated Time: 2024-04-06\nAuthor: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)\n=end\n\n### Doubly linked list node\nclass ListNode\n  attr_accessor :val\n  attr_accessor :next # Reference to the next node\n  attr_accessor :prev # Reference to the previous node\n\n  ### Constructor ###\n  def initialize(val)\n    @val = val\n  end\nend\n\n### Deque implemented with a doubly linked list ###\nclass LinkedListDeque\n  ### Get the deque size ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @front = nil  # Head node front\n    @rear = nil   # Tail node rear\n    @size = 0     # Deque size\n  end\n\n  ### Check whether the deque is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue operation ###\n  def push(num, is_front)\n    node = ListNode.new(num)\n    # If the list is empty, point both front and rear to node\n    if is_empty?\n      @front = @rear = node\n    # Enqueue at the front\n    elsif is_front\n      # Add node to the head of the linked list\n      @front.prev = node\n      node.next = @front\n      @front = node # Update the head node\n    # Enqueue at the rear\n    else\n      # Add node to the tail of the linked list\n      @rear.next = node\n      node.prev = @rear\n      @rear = node # Update the tail node\n    end\n    @size += 1 # Update the queue size\n  end\n\n  ### Enqueue at the front ###\n  def push_first(num)\n    push(num, true)\n  end\n\n  ### Enqueue at the rear ###\n  def push_last(num)\n    push(num, false)\n  end\n\n  ### Dequeue operation ###\n  def pop(is_front)\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    # When the queue size is 1, the head and tail point to the same node\n    if @size == 1\n      val = @front.val\n      @front = nil\n      @rear = nil\n    # Dequeue from the front\n    elsif is_front\n      val = @front.val # Save the head node value\n      # Remove the head node\n      fnext = @front.next\n      fnext.prev = nil\n      @front.next = nil\n      @front = fnext # Update the head node\n    # Dequeue from the rear\n    else\n      val = @rear.val # Save the tail node value\n      # Remove the tail node\n      rprev = @rear.prev\n      rprev.next = nil\n      @rear.prev = nil\n      @rear = rprev # Update the tail node\n    end\n    @size -= 1 # Update the queue size\n\n    val\n  end\n\n  ### Dequeue from the front ###\n  def pop_first\n    pop(true)\n  end\n\n  ### Dequeue from the rear ###\n  def pop_last\n    pop(false)\n  end\n\n  ### Access the front element ###\n  def peek_first\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @front.val\n  end\n\n  ### Access the rear element ###\n  def peek_last\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @rear.val\n  end\n\n  ### Return an array for printing ###\n  def to_array\n    node = @front\n    res = Array.new(size, 0)\n    for i in 0...size\n      res[i] = node.val\n      node = node.next\n    end\n    res\n  end\nend\n
    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#2-implementation-with-an-array","level":3,"title":"2.   Implementation with an array","text":"

    As with an array-based queue, we can use a circular array to implement a deque, as shown below.

    <1><2><3><4><5>

    Figure 5-9   Insertion and removal in an array-based deque

    Starting with the queue implementation, we need only add methods for insertion at the front and removal at the rear:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_deque.py
    class ArrayDeque:\n    \"\"\"Deque implemented with a circular array\"\"\"\n\n    def __init__(self, capacity: int):\n        \"\"\"Constructor\"\"\"\n        self._nums: list[int] = [0] * capacity\n        self._front: int = 0\n        self._size: int = 0\n\n    def capacity(self) -> int:\n        \"\"\"Get the deque capacity\"\"\"\n        return len(self._nums)\n\n    def size(self) -> int:\n        \"\"\"Get the deque size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the deque is empty\"\"\"\n        return self._size == 0\n\n    def index(self, i: int) -> int:\n        \"\"\"Calculate an index in the circular array\"\"\"\n        # Use modulo to connect the end of the array to its beginning\n        # When i passes the end of the array, wrap around to the beginning\n        # When i passes the beginning of the array, wrap around to the end\n        return (i + self.capacity()) % self.capacity()\n\n    def push_first(self, num: int):\n        \"\"\"Enqueue at the front\"\"\"\n        if self._size == self.capacity():\n            print(\"Deque is full\")\n            return\n        # Move the front pointer one position to the left\n        # Use modulo to wrap front to the end when it passes the beginning of the array\n        self._front = self.index(self._front - 1)\n        # Add num to the front\n        self._nums[self._front] = num\n        self._size += 1\n\n    def push_last(self, num: int):\n        \"\"\"Enqueue at the rear\"\"\"\n        if self._size == self.capacity():\n            print(\"Deque is full\")\n            return\n        # Calculate the rear pointer, which points one position past the last element\n        rear = self.index(self._front + self._size)\n        # Add num to the rear\n        self._nums[rear] = num\n        self._size += 1\n\n    def pop_first(self) -> int:\n        \"\"\"Dequeue from the front\"\"\"\n        num = self.peek_first()\n        # Advance the front pointer by one position\n        self._front = self.index(self._front + 1)\n        self._size -= 1\n        return num\n\n    def pop_last(self) -> int:\n        \"\"\"Dequeue from the rear\"\"\"\n        num = self.peek_last()\n        self._size -= 1\n        return num\n\n    def peek_first(self) -> int:\n        \"\"\"Access the front element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        return self._nums[self._front]\n\n    def peek_last(self) -> int:\n        \"\"\"Access the rear element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        # Calculate the index of the last element\n        last = self.index(self._front + self._size - 1)\n        return self._nums[last]\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return an array for printing\"\"\"\n        # Convert only the list elements within the occupied range\n        res = []\n        for i in range(self._size):\n            res.append(self._nums[self.index(self._front + i)])\n        return res\n
    array_deque.cpp
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n  private:\n    vector<int> nums; // Array for storing deque elements\n    int front;        // Front index, identifying the first element\n    int queSize;      // Deque size\n\n  public:\n    /* Constructor */\n    ArrayDeque(int capacity) {\n        nums.resize(capacity);\n        front = queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    int capacity() {\n        return nums.size();\n    }\n\n    /* Get the deque size */\n    int size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    bool isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate an index in the circular array */\n    int index(int i) {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + capacity()) % capacity();\n    }\n\n    /* Enqueue at the front */\n    void pushFirst(int num) {\n        if (queSize == capacity()) {\n            cout << \"Deque is full\" << endl;\n            return;\n        }\n        // Move the front index one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = index(front - 1);\n        // Add num to the front\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Enqueue at the rear */\n    void pushLast(int num) {\n        if (queSize == capacity()) {\n            cout << \"Deque is full\" << endl;\n            return;\n        }\n        // Calculate the rear index, one position past the last element\n        int rear = index(front + queSize);\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue from the front */\n    int popFirst() {\n        int num = peekFirst();\n        // Advance the front index by one position\n        front = index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    int popLast() {\n        int num = peekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    int peekFirst() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return nums[front];\n    }\n\n    /* Access the rear element */\n    int peekLast() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        // Calculate the index of the last element\n        int last = index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return a vector for printing */\n    vector<int> toVector() {\n        // Copy only the elements in the occupied range\n        vector<int> res(queSize);\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[index(j)];\n        }\n        return res;\n    }\n};\n
    array_deque.java
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    private int[] nums; // Array that stores the deque elements\n    private int front; // Front pointer, pointing to the first element\n    private int queSize; // Deque size\n\n    /* Constructor */\n    public ArrayDeque(int capacity) {\n        this.nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    public int capacity() {\n        return nums.length;\n    }\n\n    /* Get the deque size */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    public boolean isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate an index in the circular array */\n    private int index(int i) {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + capacity()) % capacity();\n    }\n\n    /* Enqueue at the front */\n    public void pushFirst(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Deque is full\");\n            return;\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = index(front - 1);\n        // Add num to the front\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Enqueue at the rear */\n    public void pushLast(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Deque is full\");\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        int rear = index(front + queSize);\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue from the front */\n    public int popFirst() {\n        int num = peekFirst();\n        // Advance the front pointer by one position\n        front = index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    public int popLast() {\n        int num = peekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int peekFirst() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return nums[front];\n    }\n\n    /* Access the rear element */\n    public int peekLast() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        // Calculate the index of the last element\n        int last = index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return an array for printing */\n    public int[] toArray() {\n        // Convert only the list elements within the occupied range\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[index(j)];\n        }\n        return res;\n    }\n}\n
    array_deque.cs
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    int[] nums;  // Array that stores the deque elements\n    int front;   // Front pointer, pointing to the first element\n    int queSize; // Deque size\n\n    /* Constructor */\n    public ArrayDeque(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    int Capacity() {\n        return nums.Length;\n    }\n\n    /* Get the deque size */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    public bool IsEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate an index in the circular array */\n    int Index(int i) {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + Capacity()) % Capacity();\n    }\n\n    /* Enqueue at the front */\n    public void PushFirst(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Deque is full\");\n            return;\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = Index(front - 1);\n        // Add num to the front\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Enqueue at the rear */\n    public void PushLast(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Deque is full\");\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        int rear = Index(front + queSize);\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue from the front */\n    public int PopFirst() {\n        int num = PeekFirst();\n        // Advance the front pointer by one position\n        front = Index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    public int PopLast() {\n        int num = PeekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int PeekFirst() {\n        if (IsEmpty()) {\n            throw new InvalidOperationException();\n        }\n        return nums[front];\n    }\n\n    /* Access the rear element */\n    public int PeekLast() {\n        if (IsEmpty()) {\n            throw new InvalidOperationException();\n        }\n        // Calculate the index of the last element\n        int last = Index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return an array for printing */\n    public int[] ToArray() {\n        // Copy only the elements within the occupied range\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[Index(j)];\n        }\n        return res;\n    }\n}\n
    array_deque.go
    /* Deque implemented with a circular array */\ntype arrayDeque struct {\n    nums        []int // Array that stores the deque elements\n    front       int   // Front pointer, pointing to the first element\n    queSize     int   // Deque size\n    queCapacity int   // Queue capacity (the maximum number of elements)\n}\n\n/* Initialize the queue */\nfunc newArrayDeque(queCapacity int) *arrayDeque {\n    return &arrayDeque{\n        nums:        make([]int, queCapacity),\n        queCapacity: queCapacity,\n        front:       0,\n        queSize:     0,\n    }\n}\n\n/* Get the deque size */\nfunc (q *arrayDeque) size() int {\n    return q.queSize\n}\n\n/* Check whether the deque is empty */\nfunc (q *arrayDeque) isEmpty() bool {\n    return q.queSize == 0\n}\n\n/* Calculate an index in the circular array */\nfunc (q *arrayDeque) index(i int) int {\n    // Use modulo to connect the end of the array to its beginning\n    // When i passes the end of the array, wrap around to the beginning\n    // When i passes the beginning of the array, wrap around to the end\n    return (i + q.queCapacity) % q.queCapacity\n}\n\n/* Enqueue at the front */\nfunc (q *arrayDeque) pushFirst(num int) {\n    if q.queSize == q.queCapacity {\n        fmt.Println(\"Deque is full\")\n        return\n    }\n    // Move the front pointer one position to the left\n    // Use modulo to wrap front to the end when it passes the beginning of the array\n    q.front = q.index(q.front - 1)\n    // Add num to the front\n    q.nums[q.front] = num\n    q.queSize++\n}\n\n/* Enqueue at the rear */\nfunc (q *arrayDeque) pushLast(num int) {\n    if q.queSize == q.queCapacity {\n        fmt.Println(\"Deque is full\")\n        return\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    rear := q.index(q.front + q.queSize)\n    // Add num to the rear\n    q.nums[rear] = num\n    q.queSize++\n}\n\n/* Dequeue from the front */\nfunc (q *arrayDeque) popFirst() any {\n    num := q.peekFirst()\n    if num == nil {\n        return nil\n    }\n    // Advance the front pointer by one position\n    q.front = q.index(q.front + 1)\n    q.queSize--\n    return num\n}\n\n/* Dequeue from the rear */\nfunc (q *arrayDeque) popLast() any {\n    num := q.peekLast()\n    if num == nil {\n        return nil\n    }\n    q.queSize--\n    return num\n}\n\n/* Access the front element */\nfunc (q *arrayDeque) peekFirst() any {\n    if q.isEmpty() {\n        return nil\n    }\n    return q.nums[q.front]\n}\n\n/* Access the rear element */\nfunc (q *arrayDeque) peekLast() any {\n    if q.isEmpty() {\n        return nil\n    }\n    // Calculate the index of the last element\n    last := q.index(q.front + q.queSize - 1)\n    return q.nums[last]\n}\n\n/* Get a slice for printing */\nfunc (q *arrayDeque) toSlice() []int {\n    // Convert only the list elements within the occupied range\n    res := make([]int, q.queSize)\n    for i, j := 0, q.front; i < q.queSize; i++ {\n        res[i] = q.nums[q.index(j)]\n        j++\n    }\n    return res\n}\n
    array_deque.swift
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    private var nums: [Int] // Array that stores the deque elements\n    private var front: Int // Front index, identifying the first element\n    private var _size: Int // Deque size\n\n    /* Constructor */\n    init(capacity: Int) {\n        nums = Array(repeating: 0, count: capacity)\n        front = 0\n        _size = 0\n    }\n\n    /* Get the deque capacity */\n    func capacity() -> Int {\n        nums.count\n    }\n\n    /* Get the deque size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the deque is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Calculate an index in the circular array */\n    private func index(i: Int) -> Int {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        (i + capacity()) % capacity()\n    }\n\n    /* Enqueue at the front */\n    func pushFirst(num: Int) {\n        if size() == capacity() {\n            print(\"Deque is full\")\n            return\n        }\n        // Move the front index one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = index(i: front - 1)\n        // Add num to the front\n        nums[front] = num\n        _size += 1\n    }\n\n    /* Enqueue at the rear */\n    func pushLast(num: Int) {\n        if size() == capacity() {\n            print(\"Deque is full\")\n            return\n        }\n        // Calculate the rear index, one position past the last element\n        let rear = index(i: front + size())\n        // Add num to the rear\n        nums[rear] = num\n        _size += 1\n    }\n\n    /* Dequeue from the front */\n    func popFirst() -> Int {\n        let num = peekFirst()\n        // Advance the front index by one position\n        front = index(i: front + 1)\n        _size -= 1\n        return num\n    }\n\n    /* Dequeue from the rear */\n    func popLast() -> Int {\n        let num = peekLast()\n        _size -= 1\n        return num\n    }\n\n    /* Access the front element */\n    func peekFirst() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return nums[front]\n    }\n\n    /* Access the rear element */\n    func peekLast() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        // Calculate the index of the last element\n        let last = index(i: front + size() - 1)\n        return nums[last]\n    }\n\n    /* Return an array for printing */\n    func toArray() -> [Int] {\n        // Include only the elements in the occupied range\n        (front ..< front + size()).map { nums[index(i: $0)] }\n    }\n}\n
    array_deque.js
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    #nums; // Array for storing deque elements\n    #front; // Front pointer, pointing to the first element\n    #queSize; // Deque size\n\n    /* Constructor */\n    constructor(capacity) {\n        this.#nums = new Array(capacity);\n        this.#front = 0;\n        this.#queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    capacity() {\n        return this.#nums.length;\n    }\n\n    /* Get the deque size */\n    size() {\n        return this.#queSize;\n    }\n\n    /* Check whether the deque is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Calculate an index in the circular array */\n    index(i) {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + this.capacity()) % this.capacity();\n    }\n\n    /* Enqueue at the front */\n    pushFirst(num) {\n        if (this.#queSize === this.capacity()) {\n            console.log('Deque is full');\n            return;\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        this.#front = this.index(this.#front - 1);\n        // Add num to the front\n        this.#nums[this.#front] = num;\n        this.#queSize++;\n    }\n\n    /* Enqueue at the rear */\n    pushLast(num) {\n        if (this.#queSize === this.capacity()) {\n            console.log('Deque is full');\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        const rear = this.index(this.#front + this.#queSize);\n        // Add num to the rear\n        this.#nums[rear] = num;\n        this.#queSize++;\n    }\n\n    /* Dequeue from the front */\n    popFirst() {\n        const num = this.peekFirst();\n        // Advance the front pointer by one position\n        this.#front = this.index(this.#front + 1);\n        this.#queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    popLast() {\n        const num = this.peekLast();\n        this.#queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peekFirst() {\n        if (this.isEmpty()) throw new Error('Deque is empty');\n        return this.#nums[this.#front];\n    }\n\n    /* Access the rear element */\n    peekLast() {\n        if (this.isEmpty()) throw new Error('Deque is empty');\n        // Calculate the index of the last element\n        const last = this.index(this.#front + this.#queSize - 1);\n        return this.#nums[last];\n    }\n\n    /* Return an array for printing */\n    toArray() {\n        // Convert only the list elements within the occupied range\n        const res = [];\n        for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) {\n            res[i] = this.#nums[this.index(j)];\n        }\n        return res;\n    }\n}\n
    array_deque.ts
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    private nums: number[]; // Array for storing deque elements\n    private front: number; // Front pointer, pointing to the first element\n    private queSize: number; // Deque size\n\n    /* Constructor */\n    constructor(capacity: number) {\n        this.nums = new Array(capacity);\n        this.front = 0;\n        this.queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    capacity(): number {\n        return this.nums.length;\n    }\n\n    /* Get the deque size */\n    size(): number {\n        return this.queSize;\n    }\n\n    /* Check whether the deque is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Calculate an index in the circular array */\n    index(i: number): number {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + this.capacity()) % this.capacity();\n    }\n\n    /* Enqueue at the front */\n    pushFirst(num: number): void {\n        if (this.queSize === this.capacity()) {\n            console.log('Deque is full');\n            return;\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        this.front = this.index(this.front - 1);\n        // Add num to the front\n        this.nums[this.front] = num;\n        this.queSize++;\n    }\n\n    /* Enqueue at the rear */\n    pushLast(num: number): void {\n        if (this.queSize === this.capacity()) {\n            console.log('Deque is full');\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        const rear: number = this.index(this.front + this.queSize);\n        // Add num to the rear\n        this.nums[rear] = num;\n        this.queSize++;\n    }\n\n    /* Dequeue from the front */\n    popFirst(): number {\n        const num: number = this.peekFirst();\n        // Advance the front pointer by one position\n        this.front = this.index(this.front + 1);\n        this.queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    popLast(): number {\n        const num: number = this.peekLast();\n        this.queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peekFirst(): number {\n        if (this.isEmpty()) throw new Error('Deque is empty');\n        return this.nums[this.front];\n    }\n\n    /* Access the rear element */\n    peekLast(): number {\n        if (this.isEmpty()) throw new Error('Deque is empty');\n        // Calculate the index of the last element\n        const last = this.index(this.front + this.queSize - 1);\n        return this.nums[last];\n    }\n\n    /* Return an array for printing */\n    toArray(): number[] {\n        // Convert only the list elements within the occupied range\n        const res: number[] = [];\n        for (let i = 0, j = this.front; i < this.queSize; i++, j++) {\n            res[i] = this.nums[this.index(j)];\n        }\n        return res;\n    }\n}\n
    array_deque.dart
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n  late List<int> _nums; // Array for storing deque elements\n  late int _front; // Front pointer, pointing to the first element\n  late int _queSize; // Deque size\n\n  /* Constructor */\n  ArrayDeque(int capacity) {\n    this._nums = List.filled(capacity, 0);\n    this._front = this._queSize = 0;\n  }\n\n  /* Get the deque capacity */\n  int capacity() {\n    return _nums.length;\n  }\n\n  /* Get the deque size */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check whether the deque is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Calculate an index in the circular array */\n  int index(int i) {\n    // Use modulo to connect the end of the array to its beginning\n    // When i passes the end of the array, wrap around to the beginning\n    // When i passes the beginning of the array, wrap around to the end\n    return (i + capacity()) % capacity();\n  }\n\n  /* Enqueue at the front */\n  void pushFirst(int _num) {\n    if (_queSize == capacity()) {\n      throw Exception(\"Deque is full\");\n    }\n    // Move the front pointer one position to the left\n    // Use modulo to wrap _front to the end when it passes the beginning of the array\n    _front = index(_front - 1);\n    // Add _num to the front\n    _nums[_front] = _num;\n    _queSize++;\n  }\n\n  /* Enqueue at the rear */\n  void pushLast(int _num) {\n    if (_queSize == capacity()) {\n      throw Exception(\"Deque is full\");\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    int rear = index(_front + _queSize);\n    // Add _num to the rear\n    _nums[rear] = _num;\n    _queSize++;\n  }\n\n  /* Dequeue from the front */\n  int popFirst() {\n    int _num = peekFirst();\n    // Move the front pointer one position to the right\n    _front = index(_front + 1);\n    _queSize--;\n    return _num;\n  }\n\n  /* Dequeue from the rear */\n  int popLast() {\n    int _num = peekLast();\n    _queSize--;\n    return _num;\n  }\n\n  /* Access the front element */\n  int peekFirst() {\n    if (isEmpty()) {\n      throw Exception(\"Deque is empty\");\n    }\n    return _nums[_front];\n  }\n\n  /* Access the rear element */\n  int peekLast() {\n    if (isEmpty()) {\n      throw Exception(\"Deque is empty\");\n    }\n    // Calculate the index of the last element\n    int last = index(_front + _queSize - 1);\n    return _nums[last];\n  }\n\n  /* Return an array for printing */\n  List<int> toArray() {\n    // Convert only the list elements within the occupied range\n    List<int> res = List.filled(_queSize, 0);\n    for (int i = 0, j = _front; i < _queSize; i++, j++) {\n      res[i] = _nums[index(j)];\n    }\n    return res;\n  }\n}\n
    array_deque.rs
    /* Deque implemented with a circular array */\nstruct ArrayDeque<T> {\n    nums: Vec<T>,    // Array for storing deque elements\n    front: usize,    // Front index, identifying the first element\n    que_size: usize, // Deque size\n}\n\nimpl<T: Copy + Default> ArrayDeque<T> {\n    /* Constructor */\n    pub fn new(capacity: usize) -> Self {\n        Self {\n            nums: vec![T::default(); capacity],\n            front: 0,\n            que_size: 0,\n        }\n    }\n\n    /* Get the deque capacity */\n    pub fn capacity(&self) -> usize {\n        self.nums.len()\n    }\n\n    /* Get the deque size */\n    pub fn size(&self) -> usize {\n        self.que_size\n    }\n\n    /* Check whether the deque is empty */\n    pub fn is_empty(&self) -> bool {\n        self.que_size == 0\n    }\n\n    /* Calculate an index in the circular array */\n    fn index(&self, i: i32) -> usize {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        ((i + self.capacity() as i32) % self.capacity() as i32) as usize\n    }\n\n    /* Enqueue at the front */\n    pub fn push_first(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Deque is full\");\n            return;\n        }\n        // Move the front index one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        self.front = self.index(self.front as i32 - 1);\n        // Add num to the front\n        self.nums[self.front] = num;\n        self.que_size += 1;\n    }\n\n    /* Enqueue at the rear */\n    pub fn push_last(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Deque is full\");\n            return;\n        }\n        // Calculate the rear index, one position past the last element\n        let rear = self.index(self.front as i32 + self.que_size as i32);\n        // Add num to the rear\n        self.nums[rear] = num;\n        self.que_size += 1;\n    }\n\n    /* Dequeue from the front */\n    fn pop_first(&mut self) -> T {\n        let num = self.peek_first();\n        // Advance the front index by one position\n        self.front = self.index(self.front as i32 + 1);\n        self.que_size -= 1;\n        num\n    }\n\n    /* Dequeue from the rear */\n    fn pop_last(&mut self) -> T {\n        let num = self.peek_last();\n        self.que_size -= 1;\n        num\n    }\n\n    /* Access the front element */\n    fn peek_first(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Deque is empty\")\n        };\n        self.nums[self.front]\n    }\n\n    /* Access the rear element */\n    fn peek_last(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Deque is empty\")\n        };\n        // Calculate the index of the last element\n        let last = self.index(self.front as i32 + self.que_size as i32 - 1);\n        self.nums[last]\n    }\n\n    /* Return a vector for printing */\n    fn to_array(&self) -> Vec<T> {\n        // Convert only the list elements within the occupied range\n        let mut res = vec![T::default(); self.que_size];\n        let mut j = self.front;\n        for i in 0..self.que_size {\n            res[i] = self.nums[self.index(j as i32)];\n            j += 1;\n        }\n        res\n    }\n}\n
    array_deque.c
    /* Deque implemented with a circular array */\ntypedef struct {\n    int *nums;       // Array that stores the queue elements\n    int front;       // Front pointer, pointing to the first element\n    int queSize;     // Current number of elements in the deque\n    int queCapacity; // Queue capacity\n} ArrayDeque;\n\n/* Constructor */\nArrayDeque *newArrayDeque(int capacity) {\n    ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque));\n    // Initialize the array\n    deque->queCapacity = capacity;\n    deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity);\n    deque->front = deque->queSize = 0;\n    return deque;\n}\n\n/* Destructor */\nvoid delArrayDeque(ArrayDeque *deque) {\n    free(deque->nums);\n    free(deque);\n}\n\n/* Get the deque capacity */\nint capacity(ArrayDeque *deque) {\n    return deque->queCapacity;\n}\n\n/* Get the deque size */\nint size(ArrayDeque *deque) {\n    return deque->queSize;\n}\n\n/* Check whether the deque is empty */\nbool empty(ArrayDeque *deque) {\n    return deque->queSize == 0;\n}\n\n/* Calculate an index in the circular array */\nint dequeIndex(ArrayDeque *deque, int i) {\n    // Use modulo to connect the end of the array to its beginning\n    // When i passes the end of the array, wrap to the beginning\n    // When i passes the beginning of the array, wrap around to the end\n    return ((i + capacity(deque)) % capacity(deque));\n}\n\n/* Enqueue at the front */\nvoid pushFirst(ArrayDeque *deque, int num) {\n    if (deque->queSize == capacity(deque)) {\n        printf(\"Deque is full\\r\\n\");\n        return;\n    }\n    // Move the front pointer one position to the left\n    // Use modulo to wrap front to the end when it passes the beginning\n    deque->front = dequeIndex(deque, deque->front - 1);\n    // Add num to the front\n    deque->nums[deque->front] = num;\n    deque->queSize++;\n}\n\n/* Enqueue at the rear */\nvoid pushLast(ArrayDeque *deque, int num) {\n    if (deque->queSize == capacity(deque)) {\n        printf(\"Deque is full\\r\\n\");\n        return;\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    int rear = dequeIndex(deque, deque->front + deque->queSize);\n    // Add num to the rear\n    deque->nums[rear] = num;\n    deque->queSize++;\n}\n\n/* Access the front element */\nint peekFirst(ArrayDeque *deque) {\n    // Access error: the deque is empty\n    assert(empty(deque) == 0);\n    return deque->nums[deque->front];\n}\n\n/* Access the rear element */\nint peekLast(ArrayDeque *deque) {\n    // Access error: the deque is empty\n    assert(empty(deque) == 0);\n    int last = dequeIndex(deque, deque->front + deque->queSize - 1);\n    return deque->nums[last];\n}\n\n/* Dequeue from the front */\nint popFirst(ArrayDeque *deque) {\n    int num = peekFirst(deque);\n    // Advance the front pointer by one position\n    deque->front = dequeIndex(deque, deque->front + 1);\n    deque->queSize--;\n    return num;\n}\n\n/* Dequeue from the rear */\nint popLast(ArrayDeque *deque) {\n    int num = peekLast(deque);\n    deque->queSize--;\n    return num;\n}\n\n/* Return an array for printing */\nint *toArray(ArrayDeque *deque, int *queSize) {\n    *queSize = deque->queSize;\n    int *res = (int *)calloc(deque->queSize, sizeof(int));\n    int j = deque->front;\n    for (int i = 0; i < deque->queSize; i++) {\n        res[i] = deque->nums[j % deque->queCapacity];\n        j++;\n    }\n    return res;\n}\n
    array_deque.kt
    /* Constructor */\nclass ArrayDeque(capacity: Int) {\n    private var nums: IntArray = IntArray(capacity) // Array that stores the deque elements\n    private var front: Int = 0 // Front pointer, pointing to the first element\n    private var queSize: Int = 0 // Deque size\n\n    /* Get the deque capacity */\n    fun capacity(): Int {\n        return nums.size\n    }\n\n    /* Get the deque size */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check whether the deque is empty */\n    fun isEmpty(): Boolean {\n        return queSize == 0\n    }\n\n    /* Calculate an index in the circular array */\n    private fun index(i: Int): Int {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + capacity()) % capacity()\n    }\n\n    /* Enqueue at the front */\n    fun pushFirst(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Deque is full\")\n            return\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = index(front - 1)\n        // Add num to the front\n        nums[front] = num\n        queSize++\n    }\n\n    /* Enqueue at the rear */\n    fun pushLast(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Deque is full\")\n            return\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        val rear = index(front + queSize)\n        // Add num to the rear\n        nums[rear] = num\n        queSize++\n    }\n\n    /* Dequeue from the front */\n    fun popFirst(): Int {\n        val num = peekFirst()\n        // Advance the front pointer by one position\n        front = index(front + 1)\n        queSize--\n        return num\n    }\n\n    /* Dequeue from the rear */\n    fun popLast(): Int {\n        val num = peekLast()\n        queSize--\n        return num\n    }\n\n    /* Access the front element */\n    fun peekFirst(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return nums[front]\n    }\n\n    /* Access the rear element */\n    fun peekLast(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        // Calculate the index of the last element\n        val last = index(front + queSize - 1)\n        return nums[last]\n    }\n\n    /* Return an array for printing */\n    fun toArray(): IntArray {\n        // Copy only the elements within the occupied range\n        val res = IntArray(queSize)\n        var i = 0\n        var j = front\n        while (i < queSize) {\n            res[i] = nums[index(j)]\n            i++\n            j++\n        }\n        return res\n    }\n}\n
    array_deque.rb
    ### Deque implemented with a circular array ###\nclass ArrayDeque\n  ### Get the deque size ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize(capacity)\n    @nums = Array.new(capacity, 0)\n    @front = 0\n    @size = 0\n  end\n\n  ### Get the deque capacity ###\n  def capacity\n    @nums.length\n  end\n\n  ### Check whether the deque is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue at the front ###\n  def push_first(num)\n    if size == capacity\n      puts 'Deque is full'\n      return\n    end\n\n    # Move the front pointer one position to the left\n    # Use modulo to wrap front to the end when it passes the beginning of the array\n    @front = index(@front - 1)\n    # Add num to the front\n    @nums[@front] = num\n    @size += 1\n  end\n\n  ### Enqueue at the rear ###\n  def push_last(num)\n    if size == capacity\n      puts 'Deque is full'\n      return\n    end\n\n    # Calculate the rear pointer, which points one position past the last element\n    rear = index(@front + size)\n    # Add num to the rear\n    @nums[rear] = num\n    @size += 1\n  end\n\n  ### Dequeue from the front ###\n  def pop_first\n    num = peek_first\n    # Advance the front pointer by one position\n    @front = index(@front + 1)\n    @size -= 1\n    num\n  end\n\n  ### Dequeue from the rear ###\n  def pop_last\n    num = peek_last\n    @size -= 1\n    num\n  end\n\n  ### Access the front element ###\n  def peek_first\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @nums[@front]\n  end\n\n  ### Access the rear element ###\n  def peek_last\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    # Calculate the index of the last element\n    last = index(@front + size - 1)\n    @nums[last]\n  end\n\n  ### Return an array for printing ###\n  def to_array\n    # Convert only the list elements within the occupied range\n    res = []\n    for i in 0...size\n      res << @nums[index(@front + i)]\n    end\n    res\n  end\n\n  private\n\n  ### Calculate an index in the circular array ###\n  def index(i)\n    # Use modulo to connect the end of the array to its beginning\n    # When i passes the end of the array, wrap around to the beginning\n    # When i passes the beginning of the array, wrap around to the end\n    (i + capacity) % capacity\n  end\nend\n
    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#533-applications","level":2,"title":"5.3.3   Applications","text":"

    A deque combines stack and queue behavior, so it supports all their applications while offering greater flexibility.

    Software undo is usually implemented with a stack: the system uses push to record each change and pop to undo it. Because resources are limited, software often restricts the history to a fixed number of steps, such as \\(50\\). When the stack grows beyond \\(50\\), the oldest entry must be removed from the bottom, the front of the deque. A stack cannot provide this operation, so a deque is needed instead. The core undo behavior still follows last-in, first-out order; the deque simply provides flexibility for the additional logic.

    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/","level":1,"title":"5.5   Exercises","text":"","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#551-check-your-understanding","level":2,"title":"5.5.1   Check your understanding","text":"","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#1-which-element-comes-out-first-in-a-stack-or-queue","level":3,"title":"1.   Which element comes out first in a stack or queue?","text":"

    Start with an empty stack S and an empty queue Q. Perform the same operations on each:

    Step 1: Add A. Step 2: Add B. Step 3: Remove and record one element. Step 4: Add C. Step 5: Keep removing and recording elements until the container is empty.

    Write the order in which elements are removed from S and from Q. Explain the difference using last in, first out or first in, first out.

    Reference answer

    The removal order for stack S is B, C, A. After adding A, B, we first pop B, the most recently added element, and then add C. We then pop C, A in order. This is last in, first out.

    The removal order for queue Q is A, B, C. After adding A, B, we first remove A, the earliest element added. We then add C and remove B, C in order. This is first in, first out.

    ","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#2-what-happens-when-the-rear-passes-the-end-of-the-array","level":3,"title":"2.   What happens when the rear passes the end of the array?","text":"

    A queue uses a circular array of length 5, with indices 0-4. Currently, front = 3 and size = 2, with queue elements A, B at indices 3 and 4, respectively.

    1. When enqueueing C, at which index should C be placed? What is size afterward?
    2. If we then dequeue once, which element is removed? What are the new values of front and size?
    3. What is the logical order from front to rear now? Does dequeuing require moving the other array elements? Why?
    Reference answer
    1. The new element's position is (front + size) % 5 = (3 + 2) % 5 = 0, so C goes at index 0. After enqueueing, size = 3.

    2. Dequeueing removes the current front element, A. The new front index is (3 + 1) % 5 = 4, so front = 4 and size = 2.

    3. The valid elements are logically ordered as B, C, with B at index 4 and C at index 0. Dequeueing only advances front and updates size. The circular array uses the remainder operation to wrap indices back to the beginning, so the other elements do not need to shift forward together.

    ","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#3-operations-at-both-ends-of-a-deque","level":3,"title":"3.   Operations at both ends of a deque","text":"

    Here, push_first adds at the front, push_last adds at the rear, pop_first removes from the front, and pop_last removes from the rear.

    Starting with an empty deque deq, perform:

    1. push_last(A)
    2. push_last(B)
    3. push_first(C)
    4. pop_last()
    5. push_last(D)
    6. pop_first()
    1. Which elements are removed by the two pop operations?
    2. Which elements remain, from front to rear, after all the operations?
    3. Examine these six steps. Can a queue that allows additions only at the rear and removals only at the front perform them all? If not, identify the unsupported operations. Then explain whether a deque can perform them all, and why.
    Reference answer

    After the first three steps, the deque contains [C, A, B] from front to rear.

    1. pop_last() removes B. After adding D, the deque is [C, A, D]. Then pop_first() removes C.

    2. The remaining elements are [A, D].

    3. A queue that allows additions only at the rear and removals only at the front cannot perform every operation: step 3, push_first(C), adds at the front, and step 4, pop_last(), removes from the rear. Neither operation is supported by that queue. A deque supports additions and removals at both ends, so it can perform all six steps.

    ","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#552-programming-exercises","level":2,"title":"5.5.2   Programming exercises","text":"","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#1-checking-a-bracket-sequence","level":3,"title":"1.   Checking a bracket sequence","text":"

    Given a string s containing only the three types of brackets (), [], and {}, use a stack to determine whether it is valid.

    A valid sequence must satisfy both conditions: each closing bracket matches the type of the most recent unmatched opening bracket, and no unmatched opening brackets remain at the end. Return a Boolean result.

    Hints
    1. You can map each closing bracket to its matching opening bracket.
    2. When you encounter a closing bracket, first check whether the stack is empty, then check whether its top matches.
    3. The stack must also be empty when the scan finishes.

    LeetCode

    ","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/queue/","level":1,"title":"5.2   Queues","text":"

    A queue is a linear data structure that follows a first-in, first-out rule. It models a line of people: newcomers join at the back, while those at the front leave one by one.

    As shown below, the two ends are called the “front” and the “rear.” Adding an element at the rear is called “enqueueing,” and removing an element from the front is called “dequeueing.”

    Figure 5-4   The first-in, first-out rule of a queue

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#521-common-queue-operations","level":2,"title":"5.2.1   Common queue operations","text":"

    Table 5-2 lists common operations. Method names vary between programming languages; here we use the same names as for stacks.

    Table 5-2   Efficiency of queue operations

    Method Description Time complexity push() Enqueue an element at the rear \\(O(1)\\) pop() Dequeue the front element \\(O(1)\\) peek() Access the front element \\(O(1)\\)

    We can use a queue class already provided by the programming language:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby queue.py
    from collections import deque\n\n# Initialize a queue\n# In Python, we usually use the deque class as a queue\n# Although queue.Queue() is a dedicated queue class, it is less convenient and is not recommended here\nque: deque[int] = deque()\n\n# Enqueue elements\nque.append(1)\nque.append(3)\nque.append(2)\nque.append(5)\nque.append(4)\n\n# Access the front element\nfront: int = que[0]\n\n# Dequeue an element\npop: int = que.popleft()\n\n# Get the queue size\nsize: int = len(que)\n\n# Check whether the queue is empty\nis_empty: bool = len(que) == 0\n
    queue.cpp
    /* Initialize a queue */\nqueue<int> queue;\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access the front element */\nint front = queue.front();\n\n/* Dequeue an element */\nqueue.pop();\n\n/* Get the queue size */\nint size = queue.size();\n\n/* Check whether the queue is empty */\nbool empty = queue.empty();\n
    queue.java
    /* Initialize a queue */\nQueue<Integer> queue = new LinkedList<>();\n\n/* Enqueue elements */\nqueue.offer(1);\nqueue.offer(3);\nqueue.offer(2);\nqueue.offer(5);\nqueue.offer(4);\n\n/* Access the front element */\nint peek = queue.peek();\n\n/* Dequeue an element */\nint pop = queue.poll();\n\n/* Get the queue size */\nint size = queue.size();\n\n/* Check whether the queue is empty */\nboolean isEmpty = queue.isEmpty();\n
    queue.cs
    /* Initialize a queue */\nQueue<int> queue = new();\n\n/* Enqueue elements */\nqueue.Enqueue(1);\nqueue.Enqueue(3);\nqueue.Enqueue(2);\nqueue.Enqueue(5);\nqueue.Enqueue(4);\n\n/* Access the front element */\nint peek = queue.Peek();\n\n/* Dequeue an element */\nint pop = queue.Dequeue();\n\n/* Get the queue size */\nint size = queue.Count;\n\n/* Check whether the queue is empty */\nbool isEmpty = queue.Count == 0;\n
    queue_test.go
    /* Initialize a queue */\n// In Go, use a list as a queue\nqueue := list.New()\n\n/* Enqueue elements */\nqueue.PushBack(1)\nqueue.PushBack(3)\nqueue.PushBack(2)\nqueue.PushBack(5)\nqueue.PushBack(4)\n\n/* Access the front element */\npeek := queue.Front()\n\n/* Dequeue an element */\npop := queue.Front()\nqueue.Remove(pop)\n\n/* Get the queue size */\nsize := queue.Len()\n\n/* Check whether the queue is empty */\nisEmpty := queue.Len() == 0\n
    queue.swift
    /* Initialize a queue */\n// Swift has no built-in queue class; use an Array as a queue\nvar queue: [Int] = []\n\n/* Enqueue elements */\nqueue.append(1)\nqueue.append(3)\nqueue.append(2)\nqueue.append(5)\nqueue.append(4)\n\n/* Access the front element */\nlet peek = queue.first!\n\n/* Dequeue an element */\n// Because this is an array, removeFirst takes O(n) time\nlet pop = queue.removeFirst()\n\n/* Get the queue size */\nlet size = queue.count\n\n/* Check whether the queue is empty */\nlet isEmpty = queue.isEmpty\n
    queue.js
    /* Initialize a queue */\n// JavaScript has no built-in queue; use an Array as a queue\nconst queue = [];\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access the front element */\nconst peek = queue[0];\n\n/* Dequeue an element */\n// The underlying structure is an array, so shift() takes O(n) time\nconst pop = queue.shift();\n\n/* Get the queue size */\nconst size = queue.length;\n\n/* Check whether the queue is empty */\nconst empty = queue.length === 0;\n
    queue.ts
    /* Initialize a queue */\n// TypeScript has no built-in queue; use an Array as a queue\nconst queue: number[] = [];\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access the front element */\nconst peek = queue[0];\n\n/* Dequeue an element */\n// The underlying structure is an array, so shift() takes O(n) time\nconst pop = queue.shift();\n\n/* Get the queue size */\nconst size = queue.length;\n\n/* Check whether the queue is empty */\nconst empty = queue.length === 0;\n
    queue.dart
    /* Initialize a queue */\n// In Dart, Queue is a deque and can also be used as a queue\nQueue<int> queue = Queue();\n\n/* Enqueue elements */\nqueue.add(1);\nqueue.add(3);\nqueue.add(2);\nqueue.add(5);\nqueue.add(4);\n\n/* Access the front element */\nint peek = queue.first;\n\n/* Dequeue an element */\nint pop = queue.removeFirst();\n\n/* Get the queue size */\nint size = queue.length;\n\n/* Check whether the queue is empty */\nbool isEmpty = queue.isEmpty;\n
    queue.rs
    /* Initialize a deque */\n// In Rust, use a deque as an ordinary queue\nlet mut deque: VecDeque<u32> = VecDeque::new();\n\n/* Enqueue elements */\ndeque.push_back(1);\ndeque.push_back(3);\ndeque.push_back(2);\ndeque.push_back(5);\ndeque.push_back(4);\n\n/* Access the front element */\nif let Some(front) = deque.front() {\n}\n\n/* Dequeue an element */\nif let Some(pop) = deque.pop_front() {\n}\n\n/* Get the queue size */\nlet size = deque.len();\n\n/* Check whether the queue is empty */\nlet is_empty = deque.is_empty();\n
    queue.c
    // C provides no built-in queue\n
    queue.kt
    /* Initialize a queue */\nval queue = LinkedList<Int>()\n\n/* Enqueue elements */\nqueue.offer(1)\nqueue.offer(3)\nqueue.offer(2)\nqueue.offer(5)\nqueue.offer(4)\n\n/* Access the front element */\nval peek = queue.peek()\n\n/* Dequeue an element */\nval pop = queue.poll()\n\n/* Get the queue size */\nval size = queue.size\n\n/* Check whether the queue is empty */\nval isEmpty = queue.isEmpty()\n
    queue.rb
    # Initialize a queue\n# Ruby's built-in Thread::Queue has no peek or traversal methods; use an Array as a queue\nqueue = []\n\n# Enqueue elements\nqueue.push(1)\nqueue.push(3)\nqueue.push(2)\nqueue.push(5)\nqueue.push(4)\n\n# Access the front element of the queue\npeek = queue.first\n\n# Dequeue an element\n# Note that this is an array, so Array#shift takes O(n) time\npop = queue.shift\n\n# Get the queue size\nsize = queue.length\n\n# Check whether the queue is empty\nis_empty = queue.empty?\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#522-implementing-a-queue","level":2,"title":"5.2.2   Implementing a queue","text":"

    A queue needs a data structure that allows insertion at one end and deletion at the other. Both linked lists and arrays can meet this requirement.

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#1-implementation-with-a-linked-list","level":3,"title":"1.   Implementation with a linked list","text":"

    As shown below, treat the linked list's head and tail as the queue's front and rear. Nodes may be added only at the rear and removed only from the front.

    <1><2><3>

    Figure 5-5   Enqueue and dequeue in a linked-list queue

    Here is an implementation using a linked list:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_queue.py
    class LinkedListQueue:\n    \"\"\"Queue implemented with a linked list\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._front: ListNode | None = None  # Head node front\n        self._rear: ListNode | None = None  # Tail node rear\n        self._size: int = 0\n\n    def size(self) -> int:\n        \"\"\"Get the queue size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int):\n        \"\"\"Enqueue\"\"\"\n        # Add num after the tail node\n        node = ListNode(num)\n        # If the queue is empty, make both the head and tail point to this node\n        if self._front is None:\n            self._front = node\n            self._rear = node\n        # If the queue is not empty, add this node after the tail node\n        else:\n            self._rear.next = node\n            self._rear = node\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Dequeue\"\"\"\n        num = self.peek()\n        # Remove the head node\n        self._front = self._front.next\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access the front element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Queue is empty\")\n        return self._front.val\n\n    def to_list(self) -> list[int]:\n        \"\"\"Convert to a list for printing\"\"\"\n        queue = []\n        temp = self._front\n        while temp:\n            queue.append(temp.val)\n            temp = temp.next\n        return queue\n
    linkedlist_queue.cpp
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n  private:\n    ListNode *front, *rear; // Head node front and tail node rear\n    int queSize;\n\n  public:\n    LinkedListQueue() {\n        front = nullptr;\n        rear = nullptr;\n        queSize = 0;\n    }\n\n    ~LinkedListQueue() {\n        // Traverse the linked list and delete each node to free its memory\n        freeMemoryLinkedList(front);\n    }\n\n    /* Get the queue size */\n    int size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    bool isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    void push(int num) {\n        // Add num after the tail node\n        ListNode *node = new ListNode(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (front == nullptr) {\n            front = node;\n            rear = node;\n        }\n        // If the queue is not empty, add this node after the tail node\n        else {\n            rear->next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    int pop() {\n        int num = peek();\n        // Remove the head node\n        ListNode *tmp = front;\n        front = front->next;\n        // Free memory\n        delete tmp;\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    int peek() {\n        if (size() == 0)\n            throw out_of_range(\"Queue is empty\");\n        return front->val;\n    }\n\n    /* Convert the linked list to a vector and return it */\n    vector<int> toVector() {\n        ListNode *node = front;\n        vector<int> res(size());\n        for (int i = 0; i < res.size(); i++) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
    linkedlist_queue.java
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    private ListNode front, rear; // Head node front and tail node rear\n    private int queSize = 0;\n\n    public LinkedListQueue() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the queue size */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue */\n    public void push(int num) {\n        // Add num after the tail node\n        ListNode node = new ListNode(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (front == null) {\n            front = node;\n            rear = node;\n        // If the queue is not empty, add this node after the tail node\n        } else {\n            rear.next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int pop() {\n        int num = peek();\n        // Remove the head node\n        front = front.next;\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return front.val;\n    }\n\n    /* Convert the linked list to an array and return it */\n    public int[] toArray() {\n        ListNode node = front;\n        int[] res = new int[size()];\n        for (int i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_queue.cs
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    ListNode? front, rear;  // Head node front and tail node rear \n    int queSize = 0;\n\n    public LinkedListQueue() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the queue size */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Enqueue */\n    public void Push(int num) {\n        // Add num after the tail node\n        ListNode node = new(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (front == null) {\n            front = node;\n            rear = node;\n            // If the queue is not empty, add this node after the tail node\n        } else if (rear != null) {\n            rear.next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int Pop() {\n        int num = Peek();\n        // Remove the head node\n        front = front?.next;\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return front!.val;\n    }\n\n    /* Convert the linked list to an array and return it */\n    public int[] ToArray() {\n        if (front == null)\n            return [];\n\n        ListNode? node = front;\n        int[] res = new int[Size()];\n        for (int i = 0; i < res.Length; i++) {\n            res[i] = node!.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_queue.go
    /* Queue implemented with a linked list */\ntype linkedListQueue struct {\n    // Use the standard library list package to implement the queue\n    data *list.List\n}\n\n/* Initialize the queue */\nfunc newLinkedListQueue() *linkedListQueue {\n    return &linkedListQueue{\n        data: list.New(),\n    }\n}\n\n/* Enqueue */\nfunc (s *linkedListQueue) push(value any) {\n    s.data.PushBack(value)\n}\n\n/* Dequeue */\nfunc (s *linkedListQueue) pop() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Access the front element */\nfunc (s *linkedListQueue) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    return e.Value\n}\n\n/* Get the queue size */\nfunc (s *linkedListQueue) size() int {\n    return s.data.Len()\n}\n\n/* Check whether the queue is empty */\nfunc (s *linkedListQueue) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get the list for printing */\nfunc (s *linkedListQueue) toList() *list.List {\n    return s.data\n}\n
    linkedlist_queue.swift
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    private var front: ListNode? // Head node\n    private var rear: ListNode? // Tail node\n    private var _size: Int\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the queue size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue */\n    func push(num: Int) {\n        // Add num after the tail node\n        let node = ListNode(x: num)\n        // If the queue is empty, make both the head and tail point to this node\n        if front == nil {\n            front = node\n            rear = node\n        }\n        // If the queue is not empty, add this node after the tail node\n        else {\n            rear?.next = node\n            rear = node\n        }\n        _size += 1\n    }\n\n    /* Dequeue */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        // Remove the head node\n        front = front?.next\n        _size -= 1\n        return num\n    }\n\n    /* Access the front element */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Queue is empty\")\n        }\n        return front!.val\n    }\n\n    /* Convert the linked list to an array and return it */\n    func toArray() -> [Int] {\n        var node = front\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
    linkedlist_queue.js
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    #front; // Head node #front\n    #rear; // Tail node #rear\n    #queSize = 0;\n\n    constructor() {\n        this.#front = null;\n        this.#rear = null;\n    }\n\n    /* Get the queue size */\n    get size() {\n        return this.#queSize;\n    }\n\n    /* Check whether the queue is empty */\n    isEmpty() {\n        return this.size === 0;\n    }\n\n    /* Enqueue */\n    push(num) {\n        // Add num after the tail node\n        const node = new ListNode(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (!this.#front) {\n            this.#front = node;\n            this.#rear = node;\n            // If the queue is not empty, add this node after the tail node\n        } else {\n            this.#rear.next = node;\n            this.#rear = node;\n        }\n        this.#queSize++;\n    }\n\n    /* Dequeue */\n    pop() {\n        const num = this.peek();\n        // Remove the head node\n        this.#front = this.#front.next;\n        this.#queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peek() {\n        if (this.size === 0) throw new Error('Queue is empty');\n        return this.#front.val;\n    }\n\n    /* Convert the linked list to an Array and return it */\n    toArray() {\n        let node = this.#front;\n        const res = new Array(this.size);\n        for (let i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_queue.ts
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    private front: ListNode | null; // Head node front\n    private rear: ListNode | null; // Tail node rear\n    private queSize: number = 0;\n\n    constructor() {\n        this.front = null;\n        this.rear = null;\n    }\n\n    /* Get the queue size */\n    get size(): number {\n        return this.queSize;\n    }\n\n    /* Check whether the queue is empty */\n    isEmpty(): boolean {\n        return this.size === 0;\n    }\n\n    /* Enqueue */\n    push(num: number): void {\n        // Add num after the tail node\n        const node = new ListNode(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (!this.front) {\n            this.front = node;\n            this.rear = node;\n            // If the queue is not empty, add this node after the tail node\n        } else {\n            this.rear!.next = node;\n            this.rear = node;\n        }\n        this.queSize++;\n    }\n\n    /* Dequeue */\n    pop(): number {\n        const num = this.peek();\n        if (!this.front) throw new Error('Queue is empty');\n        // Remove the head node\n        this.front = this.front.next;\n        this.queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peek(): number {\n        if (this.size === 0) throw new Error('Queue is empty');\n        return this.front!.val;\n    }\n\n    /* Convert the linked list to an Array and return it */\n    toArray(): number[] {\n        let node = this.front;\n        const res = new Array<number>(this.size);\n        for (let i = 0; i < res.length; i++) {\n            res[i] = node!.val;\n            node = node!.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_queue.dart
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n  ListNode? _front; // Head node _front\n  ListNode? _rear; // Tail node _rear\n  int _queSize = 0; // Queue size\n\n  LinkedListQueue() {\n    _front = null;\n    _rear = null;\n  }\n\n  /* Get the queue size */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check whether the queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Enqueue */\n  void push(int _num) {\n    // Add _num after the tail node\n    final node = ListNode(_num);\n    // If the queue is empty, make both the head and tail point to this node\n    if (_front == null) {\n      _front = node;\n      _rear = node;\n    } else {\n      // If the queue is not empty, add this node after the tail node\n      _rear!.next = node;\n      _rear = node;\n    }\n    _queSize++;\n  }\n\n  /* Dequeue */\n  int pop() {\n    final int _num = peek();\n    // Remove the head node\n    _front = _front!.next;\n    _queSize--;\n    return _num;\n  }\n\n  /* Access the front element */\n  int peek() {\n    if (_queSize == 0) {\n      throw Exception('Queue is empty');\n    }\n    return _front!.val;\n  }\n\n  /* Convert the linked list to an array and return it */\n  List<int> toArray() {\n    ListNode? node = _front;\n    final List<int> queue = [];\n    while (node != null) {\n      queue.add(node.val);\n      node = node.next;\n    }\n    return queue;\n  }\n}\n
    linkedlist_queue.rs
    /* Queue implemented with a linked list */\n#[allow(dead_code)]\npub struct LinkedListQueue<T> {\n    front: Option<Rc<RefCell<ListNode<T>>>>, // Head node front\n    rear: Option<Rc<RefCell<ListNode<T>>>>,  // Tail node rear\n    que_size: usize,                         // Queue size\n}\n\nimpl<T: Copy> LinkedListQueue<T> {\n    pub fn new() -> Self {\n        Self {\n            front: None,\n            rear: None,\n            que_size: 0,\n        }\n    }\n\n    /* Get the queue size */\n    pub fn size(&self) -> usize {\n        return self.que_size;\n    }\n\n    /* Check whether the queue is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.que_size == 0;\n    }\n\n    /* Enqueue */\n    pub fn push(&mut self, num: T) {\n        // Add num after the tail node\n        let new_rear = ListNode::new(num);\n        match self.rear.take() {\n            // If the queue is not empty, add this node after the tail node\n            Some(old_rear) => {\n                old_rear.borrow_mut().next = Some(new_rear.clone());\n                self.rear = Some(new_rear);\n            }\n            // If the queue is empty, make both the head and tail point to this node\n            None => {\n                self.front = Some(new_rear.clone());\n                self.rear = Some(new_rear);\n            }\n        }\n        self.que_size += 1;\n    }\n\n    /* Dequeue */\n    pub fn pop(&mut self) -> Option<T> {\n        self.front.take().map(|old_front| {\n            match old_front.borrow_mut().next.take() {\n                Some(new_front) => {\n                    self.front = Some(new_front);\n                }\n                None => {\n                    self.rear.take();\n                }\n            }\n            self.que_size -= 1;\n            old_front.borrow().val\n        })\n    }\n\n    /* Access the front element */\n    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.front.as_ref()\n    }\n\n    /* Convert the linked list to a vector and return it */\n    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n        let mut res: Vec<T> = Vec::new();\n\n        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {\n            if let Some(cur) = cur {\n                res.push(cur.borrow().val);\n                recur(cur.borrow().next.as_ref(), res);\n            }\n        }\n\n        recur(head, &mut res);\n\n        res\n    }\n}\n
    linkedlist_queue.c
    /* Queue implemented with a linked list */\ntypedef struct {\n    ListNode *front, *rear;\n    int queSize;\n} LinkedListQueue;\n\n/* Constructor */\nLinkedListQueue *newLinkedListQueue() {\n    LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue));\n    queue->front = NULL;\n    queue->rear = NULL;\n    queue->queSize = 0;\n    return queue;\n}\n\n/* Destructor */\nvoid delLinkedListQueue(LinkedListQueue *queue) {\n    // Free all nodes\n    while (queue->front != NULL) {\n        ListNode *tmp = queue->front;\n        queue->front = queue->front->next;\n        free(tmp);\n    }\n    // Free the queue structure\n    free(queue);\n}\n\n/* Get the queue size */\nint size(LinkedListQueue *queue) {\n    return queue->queSize;\n}\n\n/* Check whether the queue is empty */\nbool empty(LinkedListQueue *queue) {\n    return (size(queue) == 0);\n}\n\n/* Enqueue */\nvoid push(LinkedListQueue *queue, int num) {\n    // Add node at the tail\n    ListNode *node = newListNode(num);\n    // If the queue is empty, make both the head and tail point to this node\n    if (queue->front == NULL) {\n        queue->front = node;\n        queue->rear = node;\n    }\n    // If the queue is not empty, add this node after the tail node\n    else {\n        queue->rear->next = node;\n        queue->rear = node;\n    }\n    queue->queSize++;\n}\n\n/* Access the front element */\nint peek(LinkedListQueue *queue) {\n    assert(size(queue) && queue->front);\n    return queue->front->val;\n}\n\n/* Dequeue */\nint pop(LinkedListQueue *queue) {\n    int num = peek(queue);\n    ListNode *tmp = queue->front;\n    queue->front = queue->front->next;\n    free(tmp);\n    queue->queSize--;\n    return num;\n}\n\n/* Print the queue */\nvoid printLinkedListQueue(LinkedListQueue *queue) {\n    int *arr = malloc(sizeof(int) * queue->queSize);\n    // Copy the linked list data into an array\n    int i;\n    ListNode *node;\n    for (i = 0, node = queue->front; i < queue->queSize; i++) {\n        arr[i] = node->val;\n        node = node->next;\n    }\n    printArray(arr, queue->queSize);\n    free(arr);\n}\n
    linkedlist_queue.kt
    /* Queue implemented with a linked list */\nclass LinkedListQueue(\n    // Head node front and tail node rear\n    private var front: ListNode? = null,\n    private var rear: ListNode? = null,\n    private var queSize: Int = 0\n) {\n\n    /* Get the queue size */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check whether the queue is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Enqueue */\n    fun push(num: Int) {\n        // Add num after the tail node\n        val node = ListNode(num)\n        // If the queue is empty, make both the head and tail point to this node\n        if (front == null) {\n            front = node\n            rear = node\n            // If the queue is not empty, add this node after the tail node\n        } else {\n            rear?.next = node\n            rear = node\n        }\n        queSize++\n    }\n\n    /* Dequeue */\n    fun pop(): Int {\n        val num = peek()\n        // Remove the head node\n        front = front?.next\n        queSize--\n        return num\n    }\n\n    /* Access the front element */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return front!!._val\n    }\n\n    /* Convert the linked list to an array and return it */\n    fun toArray(): IntArray {\n        var node = front\n        val res = IntArray(size())\n        for (i in res.indices) {\n            res[i] = node!!._val\n            node = node.next\n        }\n        return res\n    }\n}\n
    linkedlist_queue.rb
    ### Queue implemented with a linked list ###\nclass LinkedListQueue\n  ### Get the queue size ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @front = nil  # Head node front\n    @rear = nil   # Tail node rear\n    @size = 0\n  end\n\n  ### Check whether the queue is empty ###\n  def is_empty?\n    @front.nil?\n  end\n\n  ### Enqueue ###\n  def push(num)\n    # Add num after the tail node\n    node = ListNode.new(num)\n\n    # If the queue is empty, point both the head and tail to this node\n    if @front.nil?\n      @front = node\n      @rear = node\n    # If the queue is not empty, append this node after the tail\n    else\n      @rear.next = node\n      @rear = node\n    end\n\n    @size += 1\n  end\n\n  ### Dequeue ###\n  def pop\n    num = peek\n    # Remove the head node\n    @front = @front.next\n    @size -= 1\n    num\n  end\n\n  ### Access the front element ###\n  def peek\n    raise IndexError, 'Queue is empty' if is_empty?\n\n    @front.val\n  end\n\n  ### Convert the linked list to an Array and return it ###\n  def to_array\n    queue = []\n    temp = @front\n    while temp\n      queue << temp.val\n      temp = temp.next\n    end\n    queue\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#2-implementation-with-an-array","level":3,"title":"2.   Implementation with an array","text":"

    Removing the first element of an array takes \\(O(n)\\) time, which would make dequeueing inefficient. A useful technique avoids this problem.

    Use a variable front for the index of the front element and a variable size for the queue's length. Define rear = front + size, so rear points to the position immediately after the last element.

    With this design, the occupied interval is [front, rear - 1]. Operations work as shown below.

    • Enqueue: assign the new element to index rear and increase size by 1.
    • Dequeue: increase front by 1 and decrease size by 1.

    Both operations require only a constant amount of work and take \\(O(1)\\) time.

    <1><2><3>

    Figure 5-6   Enqueue and dequeue in an array-based queue

    There is a problem: repeated enqueueing and dequeueing move both front and rear to the right, and they cannot continue once they reach the end of the array. To solve this, treat the array as circular, with its end connected to its beginning.

    When front or rear passes the array's end, wrap it back to the beginning. The remainder operation implements this cyclic behavior:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_queue.py
    class ArrayQueue:\n    \"\"\"Queue implemented with a circular array\"\"\"\n\n    def __init__(self, size: int):\n        \"\"\"Constructor\"\"\"\n        self._nums: list[int] = [0] * size  # Array that stores the queue elements\n        self._front: int = 0  # Front pointer, pointing to the first element\n        self._size: int = 0  # Queue size\n\n    def capacity(self) -> int:\n        \"\"\"Get the queue capacity\"\"\"\n        return len(self._nums)\n\n    def size(self) -> int:\n        \"\"\"Get the queue size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int):\n        \"\"\"Enqueue\"\"\"\n        if self._size == self.capacity():\n            raise IndexError(\"Queue is full\")\n        # Calculate the rear pointer, which points one position past the last element\n        # Use modulo to wrap rear to the beginning when it passes the end of the array\n        rear: int = (self._front + self._size) % self.capacity()\n        # Add num to the rear\n        self._nums[rear] = num\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Dequeue\"\"\"\n        num: int = self.peek()\n        # Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        self._front = (self._front + 1) % self.capacity()\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access the front element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Queue is empty\")\n        return self._nums[self._front]\n\n    def to_list(self) -> list[int]:\n        \"\"\"Return a list for printing\"\"\"\n        res = [0] * self.size()\n        j: int = self._front\n        for i in range(self.size()):\n            res[i] = self._nums[(j % self.capacity())]\n            j += 1\n        return res\n
    array_queue.cpp
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n  private:\n    int *nums;       // Array that stores the queue elements\n    int front;       // Front index, identifying the first element\n    int queSize;     // Queue size\n    int queCapacity; // Queue capacity\n\n  public:\n    ArrayQueue(int capacity) {\n        // Initialize the array\n        nums = new int[capacity];\n        queCapacity = capacity;\n        front = queSize = 0;\n    }\n\n    ~ArrayQueue() {\n        delete[] nums;\n    }\n\n    /* Get the queue capacity */\n    int capacity() {\n        return queCapacity;\n    }\n\n    /* Get the queue size */\n    int size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue */\n    void push(int num) {\n        if (queSize == queCapacity) {\n            cout << \"Queue is full\" << endl;\n            return;\n        }\n        // Calculate the rear index, one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        int rear = (front + queSize) % queCapacity;\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    int pop() {\n        int num = peek();\n        // Advance the front index by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % queCapacity;\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    int peek() {\n        if (isEmpty())\n            throw out_of_range(\"Queue is empty\");\n        return nums[front];\n    }\n\n    /* Convert the array to a vector and return it */\n    vector<int> toVector() {\n        // Copy only the elements in the occupied range\n        vector<int> arr(queSize);\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            arr[i] = nums[j % queCapacity];\n        }\n        return arr;\n    }\n};\n
    array_queue.java
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    private int[] nums; // Array that stores the queue elements\n    private int front; // Front pointer, pointing to the first element\n    private int queSize; // Queue size\n\n    public ArrayQueue(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the queue capacity */\n    public int capacity() {\n        return nums.length;\n    }\n\n    /* Get the queue size */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    public boolean isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    public void push(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Queue is full\");\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        int rear = (front + queSize) % capacity();\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int pop() {\n        int num = peek();\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % capacity();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return nums[front];\n    }\n\n    /* Return an array */\n    public int[] toArray() {\n        // Convert only the list elements within the occupied range\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[j % capacity()];\n        }\n        return res;\n    }\n}\n
    array_queue.cs
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    int[] nums;  // Array that stores the queue elements\n    int front;   // Front pointer, pointing to the first element\n    int queSize; // Queue size\n\n    public ArrayQueue(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the queue capacity */\n    int Capacity() {\n        return nums.Length;\n    }\n\n    /* Get the queue size */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    public bool IsEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    public void Push(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Queue is full\");\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        int rear = (front + queSize) % Capacity();\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int Pop() {\n        int num = Peek();\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % Capacity();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return nums[front];\n    }\n\n    /* Return an array */\n    public int[] ToArray() {\n        // Copy only the elements within the occupied range\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[j % this.Capacity()];\n        }\n        return res;\n    }\n}\n
    array_queue.go
    /* Queue implemented with a circular array */\ntype arrayQueue struct {\n    nums        []int // Array that stores the queue elements\n    front       int   // Front pointer, pointing to the first element\n    queSize     int   // Queue size\n    queCapacity int   // Queue capacity (the maximum number of elements)\n}\n\n/* Initialize the queue */\nfunc newArrayQueue(queCapacity int) *arrayQueue {\n    return &arrayQueue{\n        nums:        make([]int, queCapacity),\n        queCapacity: queCapacity,\n        front:       0,\n        queSize:     0,\n    }\n}\n\n/* Get the queue size */\nfunc (q *arrayQueue) size() int {\n    return q.queSize\n}\n\n/* Check whether the queue is empty */\nfunc (q *arrayQueue) isEmpty() bool {\n    return q.queSize == 0\n}\n\n/* Enqueue */\nfunc (q *arrayQueue) push(num int) {\n    // The queue is full when queSize == queCapacity\n    if q.queSize == q.queCapacity {\n        return\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    // Use modulo to wrap rear to the beginning when it passes the end of the array\n    rear := (q.front + q.queSize) % q.queCapacity\n    // Add num to the rear\n    q.nums[rear] = num\n    q.queSize++\n}\n\n/* Dequeue */\nfunc (q *arrayQueue) pop() any {\n    num := q.peek()\n    if num == nil {\n        return nil\n    }\n\n    // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n    q.front = (q.front + 1) % q.queCapacity\n    q.queSize--\n    return num\n}\n\n/* Access the front element */\nfunc (q *arrayQueue) peek() any {\n    if q.isEmpty() {\n        return nil\n    }\n    return q.nums[q.front]\n}\n\n/* Get a slice for printing */\nfunc (q *arrayQueue) toSlice() []int {\n    rear := (q.front + q.queSize)\n    if rear >= q.queCapacity {\n        rear %= q.queCapacity\n        return append(q.nums[q.front:], q.nums[:rear]...)\n    }\n    return q.nums[q.front:rear]\n}\n
    array_queue.swift
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    private var nums: [Int] // Array that stores the queue elements\n    private var front: Int // Front index, identifying the first element\n    private var _size: Int // Queue size\n\n    init(capacity: Int) {\n        // Initialize the array\n        nums = Array(repeating: 0, count: capacity)\n        front = 0\n        _size = 0\n    }\n\n    /* Get the queue capacity */\n    func capacity() -> Int {\n        nums.count\n    }\n\n    /* Get the queue size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue */\n    func push(num: Int) {\n        if size() == capacity() {\n            print(\"Queue is full\")\n            return\n        }\n        // Calculate the rear index, one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        let rear = (front + size()) % capacity()\n        // Add num to the rear\n        nums[rear] = num\n        _size += 1\n    }\n\n    /* Dequeue */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        // Advance the front index by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % capacity()\n        _size -= 1\n        return num\n    }\n\n    /* Access the front element */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Queue is empty\")\n        }\n        return nums[front]\n    }\n\n    /* Return an array */\n    func toArray() -> [Int] {\n        // Include only the elements in the occupied range\n        (front ..< front + size()).map { nums[$0 % capacity()] }\n    }\n}\n
    array_queue.js
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    #nums; // Array that stores the queue elements\n    #front = 0; // Front pointer, pointing to the first element\n    #queSize = 0; // Queue size\n\n    constructor(capacity) {\n        this.#nums = new Array(capacity);\n    }\n\n    /* Get the queue capacity */\n    get capacity() {\n        return this.#nums.length;\n    }\n\n    /* Get the queue size */\n    get size() {\n        return this.#queSize;\n    }\n\n    /* Check whether the queue is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Enqueue */\n    push(num) {\n        if (this.size === this.capacity) {\n            console.log('Queue is full');\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        const rear = (this.#front + this.size) % this.capacity;\n        // Add num to the rear\n        this.#nums[rear] = num;\n        this.#queSize++;\n    }\n\n    /* Dequeue */\n    pop() {\n        const num = this.peek();\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        this.#front = (this.#front + 1) % this.capacity;\n        this.#queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peek() {\n        if (this.isEmpty()) throw new Error('Queue is empty');\n        return this.#nums[this.#front];\n    }\n\n    /* Return an Array */\n    toArray() {\n        // Convert only the list elements within the occupied range\n        const arr = new Array(this.size);\n        for (let i = 0, j = this.#front; i < this.size; i++, j++) {\n            arr[i] = this.#nums[j % this.capacity];\n        }\n        return arr;\n    }\n}\n
    array_queue.ts
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    private nums: number[]; // Array that stores the queue elements\n    private front: number; // Front pointer, pointing to the first element\n    private queSize: number; // Queue size\n\n    constructor(capacity: number) {\n        this.nums = new Array(capacity);\n        this.front = this.queSize = 0;\n    }\n\n    /* Get the queue capacity */\n    get capacity(): number {\n        return this.nums.length;\n    }\n\n    /* Get the queue size */\n    get size(): number {\n        return this.queSize;\n    }\n\n    /* Check whether the queue is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Enqueue */\n    push(num: number): void {\n        if (this.size === this.capacity) {\n            console.log('Queue is full');\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        const rear = (this.front + this.queSize) % this.capacity;\n        // Add num to the rear\n        this.nums[rear] = num;\n        this.queSize++;\n    }\n\n    /* Dequeue */\n    pop(): number {\n        const num = this.peek();\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        this.front = (this.front + 1) % this.capacity;\n        this.queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peek(): number {\n        if (this.isEmpty()) throw new Error('Queue is empty');\n        return this.nums[this.front];\n    }\n\n    /* Return an Array */\n    toArray(): number[] {\n        // Convert only the list elements within the occupied range\n        const arr = new Array(this.size);\n        for (let i = 0, j = this.front; i < this.size; i++, j++) {\n            arr[i] = this.nums[j % this.capacity];\n        }\n        return arr;\n    }\n}\n
    array_queue.dart
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n  late List<int> _nums; // Array that stores the queue elements\n  late int _front; // Front pointer, pointing to the first element\n  late int _queSize; // Queue size\n\n  ArrayQueue(int capacity) {\n    _nums = List.filled(capacity, 0);\n    _front = _queSize = 0;\n  }\n\n  /* Get the queue capacity */\n  int capaCity() {\n    return _nums.length;\n  }\n\n  /* Get the queue size */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check whether the queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Enqueue */\n  void push(int _num) {\n    if (_queSize == capaCity()) {\n      throw Exception(\"Queue is full\");\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    // Use modulo to wrap rear to the beginning when it passes the end of the array\n    int rear = (_front + _queSize) % capaCity();\n    // Add _num to the rear\n    _nums[rear] = _num;\n    _queSize++;\n  }\n\n  /* Dequeue */\n  int pop() {\n    int _num = peek();\n    // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n    _front = (_front + 1) % capaCity();\n    _queSize--;\n    return _num;\n  }\n\n  /* Access the front element */\n  int peek() {\n    if (isEmpty()) {\n      throw Exception(\"Queue is empty\");\n    }\n    return _nums[_front];\n  }\n\n  /* Return an array */\n  List<int> toArray() {\n    // Convert only the list elements within the occupied range\n    final List<int> res = List.filled(_queSize, 0);\n    for (int i = 0, j = _front; i < _queSize; i++, j++) {\n      res[i] = _nums[j % capaCity()];\n    }\n    return res;\n  }\n}\n
    array_queue.rs
    /* Queue implemented with a circular array */\nstruct ArrayQueue<T> {\n    nums: Vec<T>,      // Array that stores the queue elements\n    front: i32,        // Front index, identifying the first element\n    que_size: i32,     // Queue size\n    que_capacity: i32, // Queue capacity\n}\n\nimpl<T: Copy + Default> ArrayQueue<T> {\n    /* Constructor */\n    fn new(capacity: i32) -> ArrayQueue<T> {\n        ArrayQueue {\n            nums: vec![T::default(); capacity as usize],\n            front: 0,\n            que_size: 0,\n            que_capacity: capacity,\n        }\n    }\n\n    /* Get the queue capacity */\n    fn capacity(&self) -> i32 {\n        self.que_capacity\n    }\n\n    /* Get the queue size */\n    fn size(&self) -> i32 {\n        self.que_size\n    }\n\n    /* Check whether the queue is empty */\n    fn is_empty(&self) -> bool {\n        self.que_size == 0\n    }\n\n    /* Enqueue */\n    fn push(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Queue is full\");\n            return;\n        }\n        // Calculate the rear index, one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        let rear = (self.front + self.que_size) % self.que_capacity;\n        // Add num to the rear\n        self.nums[rear as usize] = num;\n        self.que_size += 1;\n    }\n\n    /* Dequeue */\n    fn pop(&mut self) -> T {\n        let num = self.peek();\n        // Advance the front index by one position, wrapping to the beginning if it passes the end\n        self.front = (self.front + 1) % self.que_capacity;\n        self.que_size -= 1;\n        num\n    }\n\n    /* Access the front element */\n    fn peek(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Index out of bounds\");\n        }\n        self.nums[self.front as usize]\n    }\n\n    /* Return a vector */\n    fn to_vector(&self) -> Vec<T> {\n        let cap = self.que_capacity;\n        let mut j = self.front;\n        let mut arr = vec![T::default(); cap as usize];\n        for i in 0..self.que_size {\n            arr[i as usize] = self.nums[(j % cap) as usize];\n            j += 1;\n        }\n        arr\n    }\n}\n
    array_queue.c
    /* Queue implemented with a circular array */\ntypedef struct {\n    int *nums;       // Array that stores the queue elements\n    int front;       // Front pointer, pointing to the first element\n    int queSize;     // Current number of elements in the queue\n    int queCapacity; // Queue capacity\n} ArrayQueue;\n\n/* Constructor */\nArrayQueue *newArrayQueue(int capacity) {\n    ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));\n    // Initialize the array\n    queue->queCapacity = capacity;\n    queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity);\n    queue->front = queue->queSize = 0;\n    return queue;\n}\n\n/* Destructor */\nvoid delArrayQueue(ArrayQueue *queue) {\n    free(queue->nums);\n    free(queue);\n}\n\n/* Get the queue capacity */\nint capacity(ArrayQueue *queue) {\n    return queue->queCapacity;\n}\n\n/* Get the queue size */\nint size(ArrayQueue *queue) {\n    return queue->queSize;\n}\n\n/* Check whether the queue is empty */\nbool empty(ArrayQueue *queue) {\n    return queue->queSize == 0;\n}\n\n/* Access the front element */\nint peek(ArrayQueue *queue) {\n    assert(size(queue) != 0);\n    return queue->nums[queue->front];\n}\n\n/* Enqueue */\nvoid push(ArrayQueue *queue, int num) {\n    if (size(queue) == capacity(queue)) {\n        printf(\"Queue is full\\r\\n\");\n        return;\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    // Use modulo to wrap rear to the beginning when it passes the end of the array\n    int rear = (queue->front + queue->queSize) % queue->queCapacity;\n    // Add num to the rear\n    queue->nums[rear] = num;\n    queue->queSize++;\n}\n\n/* Dequeue */\nint pop(ArrayQueue *queue) {\n    int num = peek(queue);\n    // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n    queue->front = (queue->front + 1) % queue->queCapacity;\n    queue->queSize--;\n    return num;\n}\n\n/* Return an array for printing */\nint *toArray(ArrayQueue *queue, int *queSize) {\n    *queSize = queue->queSize;\n    int *res = (int *)calloc(queue->queSize, sizeof(int));\n    int j = queue->front;\n    for (int i = 0; i < queue->queSize; i++) {\n        res[i] = queue->nums[j % queue->queCapacity];\n        j++;\n    }\n    return res;\n}\n
    array_queue.kt
    /* Queue implemented with a circular array */\nclass ArrayQueue(capacity: Int) {\n    private val nums: IntArray = IntArray(capacity) // Array that stores the queue elements\n    private var front: Int = 0 // Front pointer, pointing to the first element\n    private var queSize: Int = 0 // Queue size\n\n    /* Get the queue capacity */\n    fun capacity(): Int {\n        return nums.size\n    }\n\n    /* Get the queue size */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check whether the queue is empty */\n    fun isEmpty(): Boolean {\n        return queSize == 0\n    }\n\n    /* Enqueue */\n    fun push(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Queue is full\")\n            return\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        val rear = (front + queSize) % capacity()\n        // Add num to the rear\n        nums[rear] = num\n        queSize++\n    }\n\n    /* Dequeue */\n    fun pop(): Int {\n        val num = peek()\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % capacity()\n        queSize--\n        return num\n    }\n\n    /* Access the front element */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return nums[front]\n    }\n\n    /* Return an array */\n    fun toArray(): IntArray {\n        // Copy only the elements within the occupied range\n        val res = IntArray(queSize)\n        var i = 0\n        var j = front\n        while (i < queSize) {\n            res[i] = nums[j % capacity()]\n            i++\n            j++\n        }\n        return res\n    }\n}\n
    array_queue.rb
    ### Queue implemented with a circular array ###\nclass ArrayQueue\n  ### Get the queue size ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize(size)\n    @nums = Array.new(size, 0) # Array that stores the queue elements\n    @front = 0 # Front pointer, pointing to the first element\n    @size = 0 # Queue size\n  end\n\n  ### Get the queue capacity ###\n  def capacity\n    @nums.length\n  end\n\n  ### Check whether the queue is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue ###\n  def push(num)\n    raise IndexError, 'Queue is full' if size == capacity\n\n    # Calculate the rear pointer, which points one position past the last element\n    # Use modulo to wrap rear to the beginning when it passes the end of the array\n    rear = (@front + size) % capacity\n    # Add num to the rear\n    @nums[rear] = num\n    @size += 1\n  end\n\n  ### Dequeue ###\n  def pop\n    num = peek\n    # Advance the front pointer by one position, wrapping to the beginning if it passes the end\n    @front = (@front + 1) % capacity\n    @size -= 1\n    num\n  end\n\n  ### Access the front element ###\n  def peek\n    raise IndexError, 'Queue is empty' if is_empty?\n\n    @nums[@front]\n  end\n\n  ### Return a list for printing ###\n  def to_array\n    res = Array.new(size, 0)\n    j = @front\n\n    for i in 0...size\n      res[i] = @nums[j % capacity]\n      j += 1\n    end\n\n    res\n  end\nend\n
    Visualize execution

    View full screen >

    This queue still has a limitation: its capacity is fixed. We can address it by replacing the array with a dynamic array and adding resizing. Interested readers can try implementing this themselves.

    The comparison between these two implementations is the same as for stacks, so we will not repeat it here.

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#523-typical-applications","level":2,"title":"5.2.3   Typical applications","text":"
    • Taobao orders. When a shopper places an order, it enters a queue, and the system processes orders in sequence. During the Singles' Day shopping festival, enormous numbers of orders arrive in a short time, making high concurrency a major engineering challenge.
    • Tasks awaiting processing. Queues preserve processing order wherever first-come, first-served behavior is needed, such as printer jobs or restaurant orders.
    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/stack/","level":1,"title":"5.1   Stacks","text":"

    A stack is a linear data structure that follows a last-in, first-out rule.

    Think of a stack of plates on a table. If we can move only one plate at a time, reaching the bottom plate requires removing all the plates above it first. Replace the plates with elements such as integers, characters, or objects, and we have a stack.

    As shown below, the upper end is the “top” of the stack and the lower end is the “bottom.” Adding an element to the top is called “pushing,” and removing the top element is called “popping.”

    Figure 5-1   The last-in, first-out rule of a stack

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#511-common-stack-operations","level":2,"title":"5.1.1   Common stack operations","text":"

    Table 5-1 lists common operations. Actual method names depend on the programming language; here we use the familiar names push(), pop(), and peek().

    Table 5-1   Efficiency of stack operations

    Method Description Time complexity push() Push an element onto the top \\(O(1)\\) pop() Remove the top element \\(O(1)\\) peek() Access the top element \\(O(1)\\)

    We can usually use a language's built-in stack class. If no dedicated class is provided, use an array or linked list as a stack and avoid operations unrelated to stack behavior.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby stack.py
    # Initialize a stack\n# Python has no built-in stack class; use a list as a stack\nstack: list[int] = []\n\n# Push elements\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n# Access the top element\npeek: int = stack[-1]\n\n# Pop an element\npop: int = stack.pop()\n\n# Get the stack size\nsize: int = len(stack)\n\n# Check whether the stack is empty\nis_empty: bool = len(stack) == 0\n
    stack.cpp
    /* Initialize a stack */\nstack<int> stack;\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nint top = stack.top();\n\n/* Pop an element */\nstack.pop(); // No return value\n\n/* Get the stack size */\nint size = stack.size();\n\n/* Check whether the stack is empty */\nbool empty = stack.empty();\n
    stack.java
    /* Initialize a stack */\nStack<Integer> stack = new Stack<>();\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nint peek = stack.peek();\n\n/* Pop an element */\nint pop = stack.pop();\n\n/* Get the stack size */\nint size = stack.size();\n\n/* Check whether the stack is empty */\nboolean isEmpty = stack.isEmpty();\n
    stack.cs
    /* Initialize a stack */\nStack<int> stack = new();\n\n/* Push elements */\nstack.Push(1);\nstack.Push(3);\nstack.Push(2);\nstack.Push(5);\nstack.Push(4);\n\n/* Access the top element */\nint peek = stack.Peek();\n\n/* Pop an element */\nint pop = stack.Pop();\n\n/* Get the stack size */\nint size = stack.Count;\n\n/* Check whether the stack is empty */\nbool isEmpty = stack.Count == 0;\n
    stack_test.go
    /* Initialize a stack */\n// In Go, a slice is recommended for use as a stack\nvar stack []int\n\n/* Push elements */\nstack = append(stack, 1)\nstack = append(stack, 3)\nstack = append(stack, 2)\nstack = append(stack, 5)\nstack = append(stack, 4)\n\n/* Access the top element */\npeek := stack[len(stack)-1]\n\n/* Pop an element */\npop := stack[len(stack)-1]\nstack = stack[:len(stack)-1]\n\n/* Get the stack size */\nsize := len(stack)\n\n/* Check whether the stack is empty */\nisEmpty := len(stack) == 0\n
    stack.swift
    /* Initialize a stack */\n// Swift has no built-in stack class; use an Array as a stack\nvar stack: [Int] = []\n\n/* Push elements */\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n/* Access the top element */\nlet peek = stack.last!\n\n/* Pop an element */\nlet pop = stack.removeLast()\n\n/* Get the stack size */\nlet size = stack.count\n\n/* Check whether the stack is empty */\nlet isEmpty = stack.isEmpty\n
    stack.js
    /* Initialize a stack */\n// JavaScript has no built-in stack class; use an Array as a stack\nconst stack = [];\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nconst peek = stack[stack.length-1];\n\n/* Pop an element */\nconst pop = stack.pop();\n\n/* Get the stack size */\nconst size = stack.length;\n\n/* Check whether the stack is empty */\nconst is_empty = stack.length === 0;\n
    stack.ts
    /* Initialize a stack */\n// TypeScript has no built-in stack class; use an Array as a stack\nconst stack: number[] = [];\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nconst peek = stack[stack.length - 1];\n\n/* Pop an element */\nconst pop = stack.pop();\n\n/* Get the stack size */\nconst size = stack.length;\n\n/* Check whether the stack is empty */\nconst is_empty = stack.length === 0;\n
    stack.dart
    /* Initialize a stack */\n// Dart has no built-in stack class; use a List as a stack\nList<int> stack = [];\n\n/* Push elements */\nstack.add(1);\nstack.add(3);\nstack.add(2);\nstack.add(5);\nstack.add(4);\n\n/* Access the top element */\nint peek = stack.last;\n\n/* Pop an element */\nint pop = stack.removeLast();\n\n/* Get the stack size */\nint size = stack.length;\n\n/* Check whether the stack is empty */\nbool isEmpty = stack.isEmpty;\n
    stack.rs
    /* Initialize a stack */\n// Use a Vec as a stack\nlet mut stack: Vec<i32> = Vec::new();\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nlet top = stack.last().unwrap();\n\n/* Pop an element */\nlet pop = stack.pop().unwrap();\n\n/* Get the stack size */\nlet size = stack.len();\n\n/* Check whether the stack is empty */\nlet is_empty = stack.is_empty();\n
    stack.c
    // C provides no built-in stack\n
    stack.kt
    /* Initialize a stack */\nval stack = Stack<Int>()\n\n/* Push elements */\nstack.push(1)\nstack.push(3)\nstack.push(2)\nstack.push(5)\nstack.push(4)\n\n/* Access the top element */\nval peek = stack.peek()\n\n/* Pop an element */\nval pop = stack.pop()\n\n/* Get the stack size */\nval size = stack.size\n\n/* Check whether the stack is empty */\nval isEmpty = stack.isEmpty()\n
    stack.rb
    # Initialize a stack\n# Ruby has no built-in stack class; use an Array as a stack\nstack = []\n\n# Push elements\nstack << 1\nstack << 3\nstack << 2\nstack << 5\nstack << 4\n\n# Access the top element\npeek = stack.last\n\n# Pop an element\npop = stack.pop\n\n# Get the stack size\nsize = stack.length\n\n# Check whether the stack is empty\nis_empty = stack.empty?\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#512-implementing-a-stack","level":2,"title":"5.1.2   Implementing a stack","text":"

    To understand how a stack works more deeply, let us implement a stack class ourselves.

    A stack follows last-in, first-out order, so elements can be added or removed only at the top. Arrays and linked lists allow insertion and deletion at arbitrary positions, so a stack can be viewed as a restricted array or linked list. By hiding irrelevant operations, we make its public behavior conform to the stack's rules.

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#1-implementation-with-a-linked-list","level":3,"title":"1.   Implementation with a linked list","text":"

    When implementing a stack with a linked list, treat the head as the top and the tail as the bottom.

    As shown below, pushing simply inserts a node at the head, an operation called “head insertion.” Popping removes the head node.

    <1><2><3>

    Figure 5-2   Push and pop in a linked-list stack

    Here is an implementation using a linked list:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_stack.py
    class LinkedListStack:\n    \"\"\"Stack implemented with a linked list\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._peek: ListNode | None = None\n        self._size: int = 0\n\n    def size(self) -> int:\n        \"\"\"Get the stack size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the stack is empty\"\"\"\n        return self._size == 0\n\n    def push(self, val: int):\n        \"\"\"Push\"\"\"\n        node = ListNode(val)\n        node.next = self._peek\n        self._peek = node\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Pop\"\"\"\n        num = self.peek()\n        self._peek = self._peek.next\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access the top element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._peek.val\n\n    def to_list(self) -> list[int]:\n        \"\"\"Convert to a list for printing\"\"\"\n        arr = []\n        node = self._peek\n        while node:\n            arr.append(node.val)\n            node = node.next\n        arr.reverse()\n        return arr\n
    linkedlist_stack.cpp
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n  private:\n    ListNode *stackTop; // Use the head node as the stack top\n    int stkSize;        // Stack size\n\n  public:\n    LinkedListStack() {\n        stackTop = nullptr;\n        stkSize = 0;\n    }\n\n    ~LinkedListStack() {\n        // Traverse the linked list and delete each node to free its memory\n        freeMemoryLinkedList(stackTop);\n    }\n\n    /* Get the stack size */\n    int size() {\n        return stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    void push(int num) {\n        ListNode *node = new ListNode(num);\n        node->next = stackTop;\n        stackTop = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    int pop() {\n        int num = top();\n        ListNode *tmp = stackTop;\n        stackTop = stackTop->next;\n        // Free memory\n        delete tmp;\n        stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    int top() {\n        if (isEmpty())\n            throw out_of_range(\"Stack is empty\");\n        return stackTop->val;\n    }\n\n    /* Convert the linked list to a vector and return it */\n    vector<int> toVector() {\n        ListNode *node = stackTop;\n        vector<int> res(size());\n        for (int i = res.size() - 1; i >= 0; i--) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
    linkedlist_stack.java
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    private ListNode stackPeek; // Use the head node as the stack top\n    private int stkSize = 0; // Stack size\n\n    public LinkedListStack() {\n        stackPeek = null;\n    }\n\n    /* Get the stack size */\n    public int size() {\n        return stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    public void push(int num) {\n        ListNode node = new ListNode(num);\n        node.next = stackPeek;\n        stackPeek = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    public int pop() {\n        int num = peek();\n        stackPeek = stackPeek.next;\n        stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stackPeek.val;\n    }\n\n    /* Convert the list to an array and return it */\n    public int[] toArray() {\n        ListNode node = stackPeek;\n        int[] res = new int[size()];\n        for (int i = res.length - 1; i >= 0; i--) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_stack.cs
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    ListNode? stackPeek;  // Use the head node as the stack top\n    int stkSize = 0;   // Stack size\n\n    public LinkedListStack() {\n        stackPeek = null;\n    }\n\n    /* Get the stack size */\n    public int Size() {\n        return stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Push */\n    public void Push(int num) {\n        ListNode node = new(num) {\n            next = stackPeek\n        };\n        stackPeek = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    public int Pop() {\n        int num = Peek();\n        stackPeek = stackPeek!.next;\n        stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return stackPeek!.val;\n    }\n\n    /* Convert the linked list to an array and return it */\n    public int[] ToArray() {\n        if (stackPeek == null)\n            return [];\n\n        ListNode? node = stackPeek;\n        int[] res = new int[Size()];\n        for (int i = res.Length - 1; i >= 0; i--) {\n            res[i] = node!.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_stack.go
    /* Stack implemented with a linked list */\ntype linkedListStack struct {\n    // Use the standard library list package to implement the stack\n    data *list.List\n}\n\n/* Initialize the stack */\nfunc newLinkedListStack() *linkedListStack {\n    return &linkedListStack{\n        data: list.New(),\n    }\n}\n\n/* Push */\nfunc (s *linkedListStack) push(value int) {\n    s.data.PushBack(value)\n}\n\n/* Pop */\nfunc (s *linkedListStack) pop() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Access the top element */\nfunc (s *linkedListStack) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    return e.Value\n}\n\n/* Get the stack size */\nfunc (s *linkedListStack) size() int {\n    return s.data.Len()\n}\n\n/* Check whether the stack is empty */\nfunc (s *linkedListStack) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get the list for printing */\nfunc (s *linkedListStack) toList() *list.List {\n    return s.data\n}\n
    linkedlist_stack.swift
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    private var _peek: ListNode? // Use the head node as the stack top\n    private var _size: Int // Stack size\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the stack size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the stack is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Push */\n    func push(num: Int) {\n        let node = ListNode(x: num)\n        node.next = _peek\n        _peek = node\n        _size += 1\n    }\n\n    /* Pop */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        _peek = _peek?.next\n        _size -= 1\n        return num\n    }\n\n    /* Access the top element */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return _peek!.val\n    }\n\n    /* Convert the linked list to an array and return it */\n    func toArray() -> [Int] {\n        var node = _peek\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices.reversed() {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
    linkedlist_stack.js
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    #stackPeek; // Use the head node as the stack top\n    #stkSize = 0; // Stack size\n\n    constructor() {\n        this.#stackPeek = null;\n    }\n\n    /* Get the stack size */\n    get size() {\n        return this.#stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    isEmpty() {\n        return this.size === 0;\n    }\n\n    /* Push */\n    push(num) {\n        const node = new ListNode(num);\n        node.next = this.#stackPeek;\n        this.#stackPeek = node;\n        this.#stkSize++;\n    }\n\n    /* Pop */\n    pop() {\n        const num = this.peek();\n        this.#stackPeek = this.#stackPeek.next;\n        this.#stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    peek() {\n        if (!this.#stackPeek) throw new Error('Stack is empty');\n        return this.#stackPeek.val;\n    }\n\n    /* Convert the linked list to an Array and return it */\n    toArray() {\n        let node = this.#stackPeek;\n        const res = new Array(this.size);\n        for (let i = res.length - 1; i >= 0; i--) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_stack.ts
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    private stackPeek: ListNode | null; // Use the head node as the stack top\n    private stkSize: number = 0; // Stack size\n\n    constructor() {\n        this.stackPeek = null;\n    }\n\n    /* Get the stack size */\n    get size(): number {\n        return this.stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    isEmpty(): boolean {\n        return this.size === 0;\n    }\n\n    /* Push */\n    push(num: number): void {\n        const node = new ListNode(num);\n        node.next = this.stackPeek;\n        this.stackPeek = node;\n        this.stkSize++;\n    }\n\n    /* Pop */\n    pop(): number {\n        const num = this.peek();\n        if (!this.stackPeek) throw new Error('Stack is empty');\n        this.stackPeek = this.stackPeek.next;\n        this.stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    peek(): number {\n        if (!this.stackPeek) throw new Error('Stack is empty');\n        return this.stackPeek.val;\n    }\n\n    /* Convert the linked list to an Array and return it */\n    toArray(): number[] {\n        let node = this.stackPeek;\n        const res = new Array<number>(this.size);\n        for (let i = res.length - 1; i >= 0; i--) {\n            res[i] = node!.val;\n            node = node!.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_stack.dart
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n  ListNode? _stackPeek; // Use the head node as the stack top\n  int _stkSize = 0; // Stack size\n\n  LinkedListStack() {\n    _stackPeek = null;\n  }\n\n  /* Get the stack size */\n  int size() {\n    return _stkSize;\n  }\n\n  /* Check whether the stack is empty */\n  bool isEmpty() {\n    return _stkSize == 0;\n  }\n\n  /* Push */\n  void push(int _num) {\n    final ListNode node = ListNode(_num);\n    node.next = _stackPeek;\n    _stackPeek = node;\n    _stkSize++;\n  }\n\n  /* Pop */\n  int pop() {\n    final int _num = peek();\n    _stackPeek = _stackPeek!.next;\n    _stkSize--;\n    return _num;\n  }\n\n  /* Access the top element */\n  int peek() {\n    if (_stackPeek == null) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stackPeek!.val;\n  }\n\n  /* Convert the linked list to a List and return it */\n  List<int> toList() {\n    ListNode? node = _stackPeek;\n    List<int> list = [];\n    while (node != null) {\n      list.add(node.val);\n      node = node.next;\n    }\n    list = list.reversed.toList();\n    return list;\n  }\n}\n
    linkedlist_stack.rs
    /* Stack implemented with a linked list */\n#[allow(dead_code)]\npub struct LinkedListStack<T> {\n    stack_peek: Option<Rc<RefCell<ListNode<T>>>>, // Use the head node as the stack top\n    stk_size: usize,                              // Stack size\n}\n\nimpl<T: Copy> LinkedListStack<T> {\n    pub fn new() -> Self {\n        Self {\n            stack_peek: None,\n            stk_size: 0,\n        }\n    }\n\n    /* Get the stack size */\n    pub fn size(&self) -> usize {\n        return self.stk_size;\n    }\n\n    /* Check whether the stack is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.size() == 0;\n    }\n\n    /* Push */\n    pub fn push(&mut self, num: T) {\n        let node = ListNode::new(num);\n        node.borrow_mut().next = self.stack_peek.take();\n        self.stack_peek = Some(node);\n        self.stk_size += 1;\n    }\n\n    /* Pop */\n    pub fn pop(&mut self) -> Option<T> {\n        self.stack_peek.take().map(|old_head| {\n            self.stack_peek = old_head.borrow_mut().next.take();\n            self.stk_size -= 1;\n\n            old_head.borrow().val\n        })\n    }\n\n    /* Access the top element */\n    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.stack_peek.as_ref()\n    }\n\n    /* Convert the linked list to a vector and return it */\n    pub fn to_array(&self) -> Vec<T> {\n        fn _to_array<T: Sized + Copy>(head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n            if let Some(node) = head {\n                let mut nums = _to_array(node.borrow().next.as_ref());\n                nums.push(node.borrow().val);\n                return nums;\n            }\n            return Vec::new();\n        }\n\n        _to_array(self.peek())\n    }\n}\n
    linkedlist_stack.c
    /* Stack implemented with a linked list */\ntypedef struct {\n    ListNode *top; // Use the head node as the stack top\n    int size;      // Stack size\n} LinkedListStack;\n\n/* Constructor */\nLinkedListStack *newLinkedListStack() {\n    LinkedListStack *s = malloc(sizeof(LinkedListStack));\n    s->top = NULL;\n    s->size = 0;\n    return s;\n}\n\n/* Destructor */\nvoid delLinkedListStack(LinkedListStack *s) {\n    while (s->top) {\n        ListNode *n = s->top->next;\n        free(s->top);\n        s->top = n;\n    }\n    free(s);\n}\n\n/* Get the stack size */\nint size(LinkedListStack *s) {\n    return s->size;\n}\n\n/* Check whether the stack is empty */\nbool isEmpty(LinkedListStack *s) {\n    return size(s) == 0;\n}\n\n/* Push */\nvoid push(LinkedListStack *s, int num) {\n    ListNode *node = (ListNode *)malloc(sizeof(ListNode));\n    node->next = s->top; // Set the new node's pointer field\n    node->val = num;     // Set the new node's data field\n    s->top = node;       // Update the stack top\n    s->size++;           // Update the stack size\n}\n\n/* Access the top element */\nint peek(LinkedListStack *s) {\n    if (s->size == 0) {\n        printf(\"Stack is empty\\n\");\n        return INT_MAX;\n    }\n    return s->top->val;\n}\n\n/* Pop */\nint pop(LinkedListStack *s) {\n    int val = peek(s);\n    ListNode *tmp = s->top;\n    s->top = s->top->next;\n    // Free memory\n    free(tmp);\n    s->size--;\n    return val;\n}\n
    linkedlist_stack.kt
    /* Stack implemented with a linked list */\nclass LinkedListStack(\n    private var stackPeek: ListNode? = null, // Use the head node as the stack top\n    private var stkSize: Int = 0 // Stack size\n) {\n\n    /* Get the stack size */\n    fun size(): Int {\n        return stkSize\n    }\n\n    /* Check whether the stack is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Push */\n    fun push(num: Int) {\n        val node = ListNode(num)\n        node.next = stackPeek\n        stackPeek = node\n        stkSize++\n    }\n\n    /* Pop */\n    fun pop(): Int? {\n        val num = peek()\n        stackPeek = stackPeek?.next\n        stkSize--\n        return num\n    }\n\n    /* Access the top element */\n    fun peek(): Int? {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stackPeek?._val\n    }\n\n    /* Convert the list to an array and return it */\n    fun toArray(): IntArray {\n        var node = stackPeek\n        val res = IntArray(size())\n        for (i in res.size - 1 downTo 0) {\n            res[i] = node?._val!!\n            node = node.next\n        }\n        return res\n    }\n}\n
    linkedlist_stack.rb
    ### Stack implemented with a linked list ###\nclass LinkedListStack\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @size = 0\n  end\n\n  ### Check whether the stack is empty ###\n  def is_empty?\n    @peek.nil?\n  end\n\n  ### Push ###\n  def push(val)\n    node = ListNode.new(val)\n    node.next = @peek\n    @peek = node\n    @size += 1\n  end\n\n  ### Pop ###\n  def pop\n    num = peek\n    @peek = @peek.next\n    @size -= 1\n    num\n  end\n\n  ### Access the top element ###\n  def peek\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @peek.val\n  end\n\n  ### Convert the linked list to an Array and return it ###\n  def to_array\n    arr = []\n    node = @peek\n    while node\n      arr << node.val\n      node = node.next\n    end\n    arr.reverse\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#2-implementation-with-an-array","level":3,"title":"2.   Implementation with an array","text":"

    For an array-based stack, use the end of the array as the top. As shown below, pushing and popping correspond to adding and removing the last element, both taking \\(O(1)\\) time.

    <1><2><3>

    Figure 5-3   Push and pop in an array-based stack

    Since elements may keep arriving, use a dynamic array so that resizing is handled for us. An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_stack.py
    class ArrayStack:\n    \"\"\"Stack implemented with an array\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._stack: list[int] = []\n\n    def size(self) -> int:\n        \"\"\"Get the stack size\"\"\"\n        return len(self._stack)\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the stack is empty\"\"\"\n        return self.size() == 0\n\n    def push(self, item: int):\n        \"\"\"Push\"\"\"\n        self._stack.append(item)\n\n    def pop(self) -> int:\n        \"\"\"Pop\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._stack.pop()\n\n    def peek(self) -> int:\n        \"\"\"Access the top element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._stack[-1]\n\n    def to_list(self) -> list[int]:\n        \"\"\"Return a list for printing\"\"\"\n        return self._stack\n
    array_stack.cpp
    /* Stack implemented with an array */\nclass ArrayStack {\n  private:\n    vector<int> stack;\n\n  public:\n    /* Get the stack size */\n    int size() {\n        return stack.size();\n    }\n\n    /* Check whether the stack is empty */\n    bool isEmpty() {\n        return stack.size() == 0;\n    }\n\n    /* Push */\n    void push(int num) {\n        stack.push_back(num);\n    }\n\n    /* Pop */\n    int pop() {\n        int num = top();\n        stack.pop_back();\n        return num;\n    }\n\n    /* Access the top element */\n    int top() {\n        if (isEmpty())\n            throw out_of_range(\"Stack is empty\");\n        return stack.back();\n    }\n\n    /* Return a vector */\n    vector<int> toVector() {\n        return stack;\n    }\n};\n
    array_stack.java
    /* Stack implemented with an array */\nclass ArrayStack {\n    private ArrayList<Integer> stack;\n\n    public ArrayStack() {\n        // Initialize the list (dynamic array)\n        stack = new ArrayList<>();\n    }\n\n    /* Get the stack size */\n    public int size() {\n        return stack.size();\n    }\n\n    /* Check whether the stack is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    public void push(int num) {\n        stack.add(num);\n    }\n\n    /* Pop */\n    public int pop() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stack.remove(size() - 1);\n    }\n\n    /* Access the top element */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stack.get(size() - 1);\n    }\n\n    /* Convert the list to an array and return it */\n    public Object[] toArray() {\n        return stack.toArray();\n    }\n}\n
    array_stack.cs
    /* Stack implemented with an array */\nclass ArrayStack {\n    List<int> stack;\n    public ArrayStack() {\n        // Initialize the list (dynamic array)\n        stack = [];\n    }\n\n    /* Get the stack size */\n    public int Size() {\n        return stack.Count;\n    }\n\n    /* Check whether the stack is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Push */\n    public void Push(int num) {\n        stack.Add(num);\n    }\n\n    /* Pop */\n    public int Pop() {\n        if (IsEmpty())\n            throw new Exception();\n        var val = Peek();\n        stack.RemoveAt(Size() - 1);\n        return val;\n    }\n\n    /* Access the top element */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return stack[Size() - 1];\n    }\n\n    /* Convert the list to an array and return it */\n    public int[] ToArray() {\n        return [.. stack];\n    }\n}\n
    array_stack.go
    /* Stack implemented with an array */\ntype arrayStack struct {\n    data []int // Data\n}\n\n/* Initialize the stack */\nfunc newArrayStack() *arrayStack {\n    return &arrayStack{\n        // Set the stack length to 0 and its capacity to 16\n        data: make([]int, 0, 16),\n    }\n}\n\n/* Stack size */\nfunc (s *arrayStack) size() int {\n    return len(s.data)\n}\n\n/* Check whether the stack is empty */\nfunc (s *arrayStack) isEmpty() bool {\n    return s.size() == 0\n}\n\n/* Push */\nfunc (s *arrayStack) push(v int) {\n    // Appending grows the slice capacity automatically when needed\n    s.data = append(s.data, v)\n}\n\n/* Pop */\nfunc (s *arrayStack) pop() any {\n    val := s.peek()\n    s.data = s.data[:len(s.data)-1]\n    return val\n}\n\n/* Get the top element */\nfunc (s *arrayStack) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    val := s.data[len(s.data)-1]\n    return val\n}\n\n/* Get a slice for printing */\nfunc (s *arrayStack) toSlice() []int {\n    return s.data\n}\n
    array_stack.swift
    /* Stack implemented with an array */\nclass ArrayStack {\n    private var stack: [Int]\n\n    init() {\n        // Initialize the list (dynamic array)\n        stack = []\n    }\n\n    /* Get the stack size */\n    func size() -> Int {\n        stack.count\n    }\n\n    /* Check whether the stack is empty */\n    func isEmpty() -> Bool {\n        stack.isEmpty\n    }\n\n    /* Push */\n    func push(num: Int) {\n        stack.append(num)\n    }\n\n    /* Pop */\n    @discardableResult\n    func pop() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return stack.removeLast()\n    }\n\n    /* Access the top element */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return stack.last!\n    }\n\n    /* Return the stack elements as an array */\n    func toArray() -> [Int] {\n        stack\n    }\n}\n
    array_stack.js
    /* Stack implemented with an array */\nclass ArrayStack {\n    #stack;\n    constructor() {\n        this.#stack = [];\n    }\n\n    /* Get the stack size */\n    get size() {\n        return this.#stack.length;\n    }\n\n    /* Check whether the stack is empty */\n    isEmpty() {\n        return this.#stack.length === 0;\n    }\n\n    /* Push */\n    push(num) {\n        this.#stack.push(num);\n    }\n\n    /* Pop */\n    pop() {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.#stack.pop();\n    }\n\n    /* Access the top element */\n    top() {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.#stack[this.#stack.length - 1];\n    }\n\n    /* Return an Array */\n    toArray() {\n        return this.#stack;\n    }\n}\n
    array_stack.ts
    /* Stack implemented with an array */\nclass ArrayStack {\n    private stack: number[];\n    constructor() {\n        this.stack = [];\n    }\n\n    /* Get the stack size */\n    get size(): number {\n        return this.stack.length;\n    }\n\n    /* Check whether the stack is empty */\n    isEmpty(): boolean {\n        return this.stack.length === 0;\n    }\n\n    /* Push */\n    push(num: number): void {\n        this.stack.push(num);\n    }\n\n    /* Pop */\n    pop(): number | undefined {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.stack.pop();\n    }\n\n    /* Access the top element */\n    top(): number | undefined {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.stack[this.stack.length - 1];\n    }\n\n    /* Return an Array */\n    toArray() {\n        return this.stack;\n    }\n}\n
    array_stack.dart
    /* Stack implemented with an array */\nclass ArrayStack {\n  late List<int> _stack;\n  ArrayStack() {\n    _stack = [];\n  }\n\n  /* Get the stack size */\n  int size() {\n    return _stack.length;\n  }\n\n  /* Check whether the stack is empty */\n  bool isEmpty() {\n    return _stack.isEmpty;\n  }\n\n  /* Push */\n  void push(int _num) {\n    _stack.add(_num);\n  }\n\n  /* Pop */\n  int pop() {\n    if (isEmpty()) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stack.removeLast();\n  }\n\n  /* Access the top element */\n  int peek() {\n    if (isEmpty()) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stack.last;\n  }\n\n  /* Return the underlying stack array */\n  List<int> toArray() => _stack;\n}\n
    array_stack.rs
    /* Stack implemented with an array */\nstruct ArrayStack<T> {\n    stack: Vec<T>,\n}\n\nimpl<T> ArrayStack<T> {\n    /* Initialize the stack */\n    fn new() -> ArrayStack<T> {\n        ArrayStack::<T> {\n            stack: Vec::<T>::new(),\n        }\n    }\n\n    /* Get the stack size */\n    fn size(&self) -> usize {\n        self.stack.len()\n    }\n\n    /* Check whether the stack is empty */\n    fn is_empty(&self) -> bool {\n        self.size() == 0\n    }\n\n    /* Push */\n    fn push(&mut self, num: T) {\n        self.stack.push(num);\n    }\n\n    /* Pop */\n    fn pop(&mut self) -> Option<T> {\n        self.stack.pop()\n    }\n\n    /* Access the top element */\n    fn peek(&self) -> Option<&T> {\n        if self.is_empty() {\n            panic!(\"Stack is empty\")\n        };\n        self.stack.last()\n    }\n\n    /* Return a reference to the vector */\n    fn to_array(&self) -> &Vec<T> {\n        &self.stack\n    }\n}\n
    array_stack.c
    /* Stack implemented with an array */\ntypedef struct {\n    int *data;\n    int size;\n} ArrayStack;\n\n/* Constructor */\nArrayStack *newArrayStack() {\n    ArrayStack *stack = malloc(sizeof(ArrayStack));\n    // Initialize with a large capacity to avoid resizing\n    stack->data = malloc(sizeof(int) * MAX_SIZE);\n    stack->size = 0;\n    return stack;\n}\n\n/* Destructor */\nvoid delArrayStack(ArrayStack *stack) {\n    free(stack->data);\n    free(stack);\n}\n\n/* Get the stack size */\nint size(ArrayStack *stack) {\n    return stack->size;\n}\n\n/* Check whether the stack is empty */\nbool isEmpty(ArrayStack *stack) {\n    return stack->size == 0;\n}\n\n/* Push */\nvoid push(ArrayStack *stack, int num) {\n    if (stack->size == MAX_SIZE) {\n        printf(\"Stack is full\\n\");\n        return;\n    }\n    stack->data[stack->size] = num;\n    stack->size++;\n}\n\n/* Access the top element */\nint peek(ArrayStack *stack) {\n    if (stack->size == 0) {\n        printf(\"Stack is empty\\n\");\n        return INT_MAX;\n    }\n    return stack->data[stack->size - 1];\n}\n\n/* Pop */\nint pop(ArrayStack *stack) {\n    int val = peek(stack);\n    stack->size--;\n    return val;\n}\n
    array_stack.kt
    /* Stack implemented with an array */\nclass ArrayStack {\n    // Initialize the list (dynamic array)\n    private val stack = mutableListOf<Int>()\n\n    /* Get the stack size */\n    fun size(): Int {\n        return stack.size\n    }\n\n    /* Check whether the stack is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Push */\n    fun push(num: Int) {\n        stack.add(num)\n    }\n\n    /* Pop */\n    fun pop(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stack.removeAt(size() - 1)\n    }\n\n    /* Access the top element */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stack[size() - 1]\n    }\n\n    /* Convert the list to an array and return it */\n    fun toArray(): Array<Any> {\n        return stack.toTypedArray()\n    }\n}\n
    array_stack.rb
    ### Stack implemented with an array ###\nclass ArrayStack\n  ### Constructor ###\n  def initialize\n    @stack = []\n  end\n\n  ### Get the stack size ###\n  def size\n    @stack.length\n  end\n\n  ### Check whether the stack is empty ###\n  def is_empty?\n    @stack.empty?\n  end\n\n  ### Push ###\n  def push(item)\n    @stack << item\n  end\n\n  ### Pop ###\n  def pop\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @stack.pop\n  end\n\n  ### Access the top element ###\n  def peek\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @stack.last\n  end\n\n  ### Return a list for printing ###\n  def to_array\n    @stack\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#513-comparing-the-implementations","level":2,"title":"5.1.3   Comparing the implementations","text":"

    Supported operations

    Both implementations support all stack operations. An array also allows random access, but that is outside the stack abstraction and is generally not used.

    Time efficiency

    In an array-based stack, pushes and pops operate in preallocated contiguous memory, giving good cache locality and high efficiency. However, a push that exceeds the array's capacity triggers expansion, making that particular push an \\(O(n)\\) operation.

    A linked list grows flexibly and avoids the slowdown associated with array expansion. However, pushing requires creating a node object and updating pointers, so it is relatively slower. If the element is already a node object, the creation step can be skipped to improve efficiency.

    For elements of primitive types such as int or double, we can therefore draw the following conclusions.

    • An array-based stack slows down when expansion is triggered, but expansion is infrequent, so average efficiency is higher.
    • A linked-list stack provides more consistent performance.

    Space efficiency

    When a dynamic array is initialized, its initial capacity may exceed actual needs. Expansion typically multiplies the capacity by a fixed factor, such as 2, which may again leave excess space. An array-based stack can therefore waste some space.

    However, linked-list nodes also store pointers, so each node occupies relatively more space.

    We cannot say universally which implementation uses less memory; the answer depends on the situation.

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#514-typical-applications","level":2,"title":"5.1.4   Typical applications","text":"
    • Browser back and forward navigation, and software undo and redo. When a new web page is opened, the browser pushes the previous page onto a stack. Going back pops that page. Supporting both back and forward requires two stacks working together.
    • Program memory management. Each function call adds a stack frame containing the function's context. During recursion, frames are pushed as calls descend and popped as they return.
    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/summary/","level":1,"title":"5.4   Summary","text":"","path":["Chapter 5.   Stacks and queues","5.4   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • A stack follows last-in, first-out order and can be implemented with an array or linked list.
    • An array-based stack offers higher average time efficiency, but a push that triggers expansion takes \\(O(n)\\) time. A linked-list stack provides more consistent performance.
    • An array-based stack may waste some space. However, linked-list nodes require more memory than individual array elements.
    • A queue follows first-in, first-out order and can also be implemented with an array or linked list. Their time and space tradeoffs resemble those of stack implementations.
    • A deque is a more flexible queue that allows insertion and removal at both ends.
    ","path":["Chapter 5.   Stacks and queues","5.4   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Are browser back and forward navigation implemented with a doubly linked list?

    Browser navigation fundamentally reflects stack behavior. Visiting a new page adds it to the top of a stack; clicking Back removes it. A deque conveniently supports additional operations, as discussed in the deque section.

    Q: After popping a node, do we need to free its memory?

    If the node will still be used, do not free it. If it is no longer needed, languages such as Java and Python use automatic garbage collection, so manual deallocation is unnecessary. In C and C++, memory must be released manually.

    Q: A deque looks like two stacks joined together. What is it useful for?

    A deque resembles a combination of a stack and a queue, or two stacks joined together. It combines stack and queue behavior, supporting all their applications with greater flexibility.

    Q: How exactly do undo and redo work?

    Use two stacks: A for undo and B for redo.

    1. Whenever the user performs an action, push it onto A and clear B.
    2. To undo, pop the most recent action from A and push it onto B.
    3. To redo, pop the most recent action from B and push it onto A.
    ","path":["Chapter 5.   Stacks and queues","5.4   Summary"],"tags":[]},{"location":"chapter_tree/","level":1,"title":"Chapter 7.   Trees","text":"

    Abstract

    A towering tree brims with life, its roots deep, its leaves lush, and its branches spreading wide.

    It gives vivid form to the idea of dividing data to conquer a problem.

    ","path":["Chapter 7.   Trees"],"tags":[]},{"location":"chapter_tree/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 7.1   Binary trees
    • 7.2   Traversing a binary tree
    • 7.3   Array representation of a binary tree
    • 7.4   Binary search trees
    • 7.5   AVL trees *
    • 7.6   Summary
    • 7.7   Exercises
    ","path":["Chapter 7.   Trees"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/","level":1,"title":"7.3   Array representation of a binary tree","text":"

    In a linked representation, a binary tree stores data in TreeNode nodes connected by pointers. The previous section introduced basic operations on binary trees in this form.

    Can we represent a binary tree with an array instead? Yes.

    ","path":["Chapter 7.   Trees","7.3   Array representation of a binary tree"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#731-representing-a-perfect-binary-tree","level":2,"title":"7.3.1   Representing a perfect binary tree","text":"

    Start with a simple case. Store every node of a perfect binary tree in an array in level order. Each node then corresponds to a unique array index.

    Level-order traversal gives us a mapping between parent and child indices: if a node has index \\(i\\), its left child has index \\(2i + 1\\) and its right child has index \\(2i + 2\\). Figure 7-12 illustrates this relationship.

    Figure 7-12   Array representation of a perfect binary tree

    The mapping formulas serve the same role as node references or pointers in a linked representation. Given any node in the array, we can use them to access its left or right child.

    ","path":["Chapter 7.   Trees","7.3   Array representation of a binary tree"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#732-representing-an-arbitrary-binary-tree","level":2,"title":"7.3.2   Representing an arbitrary binary tree","text":"

    A perfect binary tree is a special case. Other binary trees often contain many missing nodes, None, at intermediate levels. Ordinary level-order traversal omits these positions, so its sequence alone cannot tell us their number or locations. Multiple tree structures can therefore produce the same level-order sequence.

    As shown below, the array representation above no longer works for a non-perfect binary tree.

    Figure 7-13   Multiple binary trees can share a level-order sequence

    To solve this, include every None position explicitly in the level-order sequence. The sequence then represents the tree uniquely, as shown below. Example code follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    # Array representation of a binary tree\n# Use None for empty positions\ntree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]\n
    /* Array representation of a binary tree */\n// Use INT_MAX, the maximum int value, for empty positions\nvector<int> tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n
    /* Array representation of a binary tree */\n// Use the wrapper class Integer so that null can mark empty positions\nInteger[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };\n
    /* Array representation of a binary tree */\n// Use the nullable type int? so that null can mark empty positions\nint?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
    /* Array representation of a binary tree */\n// Use a slice of any so that nil can mark empty positions\ntree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15}\n
    /* Array representation of a binary tree */\n// Use the optional type Int? so that nil can mark empty positions\nlet tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n
    /* Array representation of a binary tree */\n// Use null for empty positions\nlet tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
    /* Array representation of a binary tree */\n// Use null for empty positions\nlet tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
    /* Array representation of a binary tree */\n// Use the nullable type int? so that null can mark empty positions\nList<int?> tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
    /* Array representation of a binary tree */\n// Use None for empty positions\nlet tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)];\n
    /* Array representation of a binary tree */\n// Use the maximum int value for empty positions, so node values must not equal INT_MAX\nint tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n
    /* Array representation of a binary tree */\n// Use null for empty positions\nval tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )\n
    ### Array representation of a binary tree ###\n# Use nil for empty positions\ntree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n

    Figure 7-14   Array representation of an arbitrary binary tree

    Complete binary trees are particularly well suited to array representation. By definition, missing positions occur only at the right end of the lowest level, so all None entries appear at the end of the level-order sequence.

    We can therefore omit all None entries when storing a complete binary tree in an array. An example is shown below.

    Figure 7-15   Array representation of a complete binary tree

    The following code implements an array-based binary tree with these operations.

    • Given a node, get its value, left or right child, and parent.
    • Obtain preorder, inorder, postorder, and level-order traversal sequences.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_binary_tree.py
    class ArrayBinaryTree:\n    \"\"\"Binary tree class using an array representation\"\"\"\n\n    def __init__(self, arr: list[int | None]):\n        \"\"\"Constructor\"\"\"\n        self._tree = list(arr)\n\n    def size(self):\n        \"\"\"Length of the backing list\"\"\"\n        return len(self._tree)\n\n    def val(self, i: int) -> int | None:\n        \"\"\"Get the value of the node at index i\"\"\"\n        # If the index is out of bounds, return None to represent an empty position\n        if i < 0 or i >= self.size():\n            return None\n        return self._tree[i]\n\n    def left(self, i: int) -> int | None:\n        \"\"\"Get the left child index of the node at index i\"\"\"\n        return 2 * i + 1\n\n    def right(self, i: int) -> int | None:\n        \"\"\"Get the right child index of the node at index i\"\"\"\n        return 2 * i + 2\n\n    def parent(self, i: int) -> int | None:\n        \"\"\"Get the parent index of the node at index i\"\"\"\n        return (i - 1) // 2\n\n    def level_order(self) -> list[int]:\n        \"\"\"Level-order traversal\"\"\"\n        self.res = []\n        # Traverse the array directly\n        for i in range(self.size()):\n            if self.val(i) is not None:\n                self.res.append(self.val(i))\n        return self.res\n\n    def dfs(self, i: int, order: str):\n        \"\"\"Depth-first traversal\"\"\"\n        if self.val(i) is None:\n            return\n        # Preorder traversal\n        if order == \"pre\":\n            self.res.append(self.val(i))\n        self.dfs(self.left(i), order)\n        # Inorder traversal\n        if order == \"in\":\n            self.res.append(self.val(i))\n        self.dfs(self.right(i), order)\n        # Postorder traversal\n        if order == \"post\":\n            self.res.append(self.val(i))\n\n    def pre_order(self) -> list[int]:\n        \"\"\"Preorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"pre\")\n        return self.res\n\n    def in_order(self) -> list[int]:\n        \"\"\"Inorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"in\")\n        return self.res\n\n    def post_order(self) -> list[int]:\n        \"\"\"Postorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"post\")\n        return self.res\n
    array_binary_tree.cpp
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n  public:\n    /* Constructor */\n    ArrayBinaryTree(vector<int> arr) {\n        tree = arr;\n    }\n\n    /* Number of positions in the array */\n    int size() {\n        return tree.size();\n    }\n\n    /* Get the value of the node at index i */\n    int val(int i) {\n        // If the index is out of bounds, return INT_MAX to represent an empty position\n        if (i < 0 || i >= size())\n            return INT_MAX;\n        return tree[i];\n    }\n\n    /* Get the left child index of the node at index i */\n    int left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    int right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    int parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    vector<int> levelOrder() {\n        vector<int> res;\n        // Traverse the array directly\n        for (int i = 0; i < size(); i++) {\n            if (val(i) != INT_MAX)\n                res.push_back(val(i));\n        }\n        return res;\n    }\n\n    /* Preorder traversal */\n    vector<int> preOrder() {\n        vector<int> res;\n        dfs(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    vector<int> inOrder() {\n        vector<int> res;\n        dfs(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    vector<int> postOrder() {\n        vector<int> res;\n        dfs(0, \"post\", res);\n        return res;\n    }\n\n  private:\n    vector<int> tree;\n\n    /* Depth-first traversal */\n    void dfs(int i, string order, vector<int> &res) {\n        // Return if this position is empty\n        if (val(i) == INT_MAX)\n            return;\n        // Preorder traversal\n        if (order == \"pre\")\n            res.push_back(val(i));\n        dfs(left(i), order, res);\n        // Inorder traversal\n        if (order == \"in\")\n            res.push_back(val(i));\n        dfs(right(i), order, res);\n        // Postorder traversal\n        if (order == \"post\")\n            res.push_back(val(i));\n    }\n};\n
    array_binary_tree.java
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n    private List<Integer> tree;\n\n    /* Constructor */\n    public ArrayBinaryTree(List<Integer> arr) {\n        tree = new ArrayList<>(arr);\n    }\n\n    /* List size */\n    public int size() {\n        return tree.size();\n    }\n\n    /* Get the value of the node at index i */\n    public Integer val(int i) {\n        // If the index is out of bounds, return null to represent an empty position\n        if (i < 0 || i >= size())\n            return null;\n        return tree.get(i);\n    }\n\n    /* Get the left child index of the node at index i */\n    public Integer left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    public Integer right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    public Integer parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    public List<Integer> levelOrder() {\n        List<Integer> res = new ArrayList<>();\n        // Traverse the array directly\n        for (int i = 0; i < size(); i++) {\n            if (val(i) != null)\n                res.add(val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    private void dfs(Integer i, String order, List<Integer> res) {\n        // Return if this position is empty\n        if (val(i) == null)\n            return;\n        // Preorder traversal\n        if (\"pre\".equals(order))\n            res.add(val(i));\n        dfs(left(i), order, res);\n        // Inorder traversal\n        if (\"in\".equals(order))\n            res.add(val(i));\n        dfs(right(i), order, res);\n        // Postorder traversal\n        if (\"post\".equals(order))\n            res.add(val(i));\n    }\n\n    /* Preorder traversal */\n    public List<Integer> preOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    public List<Integer> inOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    public List<Integer> postOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"post\", res);\n        return res;\n    }\n}\n
    array_binary_tree.cs
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree(List<int?> arr) {\n    List<int?> tree = new(arr);\n\n    /* List size */\n    public int Size() {\n        return tree.Count;\n    }\n\n    /* Get the value of the node at index i */\n    public int? Val(int i) {\n        // If the index is out of bounds, return null to represent an empty position\n        if (i < 0 || i >= Size())\n            return null;\n        return tree[i];\n    }\n\n    /* Get the left child index of the node at index i */\n    public int Left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    public int Right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    public int Parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    public List<int> LevelOrder() {\n        List<int> res = [];\n        // Traverse the array directly\n        for (int i = 0; i < Size(); i++) {\n            if (Val(i).HasValue)\n                res.Add(Val(i)!.Value);\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    void DFS(int i, string order, List<int> res) {\n        // Return if this position is empty\n        if (!Val(i).HasValue)\n            return;\n        // Preorder traversal\n        if (order == \"pre\")\n            res.Add(Val(i)!.Value);\n        DFS(Left(i), order, res);\n        // Inorder traversal\n        if (order == \"in\")\n            res.Add(Val(i)!.Value);\n        DFS(Right(i), order, res);\n        // Postorder traversal\n        if (order == \"post\")\n            res.Add(Val(i)!.Value);\n    }\n\n    /* Preorder traversal */\n    public List<int> PreOrder() {\n        List<int> res = [];\n        DFS(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    public List<int> InOrder() {\n        List<int> res = [];\n        DFS(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    public List<int> PostOrder() {\n        List<int> res = [];\n        DFS(0, \"post\", res);\n        return res;\n    }\n}\n
    array_binary_tree.go
    /* Binary tree type using an array representation */\ntype arrayBinaryTree struct {\n    tree []any\n}\n\n/* Constructor */\nfunc newArrayBinaryTree(arr []any) *arrayBinaryTree {\n    return &arrayBinaryTree{\n        tree: arr,\n    }\n}\n\n/* Length of the backing slice */\nfunc (abt *arrayBinaryTree) size() int {\n    return len(abt.tree)\n}\n\n/* Get the value of the node at index i */\nfunc (abt *arrayBinaryTree) val(i int) any {\n    // If the index is out of bounds, return nil to represent an empty position\n    if i < 0 || i >= abt.size() {\n        return nil\n    }\n    return abt.tree[i]\n}\n\n/* Get the left child index of the node at index i */\nfunc (abt *arrayBinaryTree) left(i int) int {\n    return 2*i + 1\n}\n\n/* Get the right child index of the node at index i */\nfunc (abt *arrayBinaryTree) right(i int) int {\n    return 2*i + 2\n}\n\n/* Get the parent index of the node at index i */\nfunc (abt *arrayBinaryTree) parent(i int) int {\n    return (i - 1) / 2\n}\n\n/* Level-order traversal */\nfunc (abt *arrayBinaryTree) levelOrder() []any {\n    var res []any\n    // Traverse the array directly\n    for i := 0; i < abt.size(); i++ {\n        if abt.val(i) != nil {\n            res = append(res, abt.val(i))\n        }\n    }\n    return res\n}\n\n/* Depth-first traversal */\nfunc (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) {\n    // Return if this position is empty\n    if abt.val(i) == nil {\n        return\n    }\n    // Preorder traversal\n    if order == \"pre\" {\n        *res = append(*res, abt.val(i))\n    }\n    abt.dfs(abt.left(i), order, res)\n    // Inorder traversal\n    if order == \"in\" {\n        *res = append(*res, abt.val(i))\n    }\n    abt.dfs(abt.right(i), order, res)\n    // Postorder traversal\n    if order == \"post\" {\n        *res = append(*res, abt.val(i))\n    }\n}\n\n/* Preorder traversal */\nfunc (abt *arrayBinaryTree) preOrder() []any {\n    var res []any\n    abt.dfs(0, \"pre\", &res)\n    return res\n}\n\n/* Inorder traversal */\nfunc (abt *arrayBinaryTree) inOrder() []any {\n    var res []any\n    abt.dfs(0, \"in\", &res)\n    return res\n}\n\n/* Postorder traversal */\nfunc (abt *arrayBinaryTree) postOrder() []any {\n    var res []any\n    abt.dfs(0, \"post\", &res)\n    return res\n}\n
    array_binary_tree.swift
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n    private var tree: [Int?]\n\n    /* Constructor */\n    init(arr: [Int?]) {\n        tree = arr\n    }\n\n    /* Length of the backing array */\n    func size() -> Int {\n        tree.count\n    }\n\n    /* Get the value of the node at index i */\n    func val(i: Int) -> Int? {\n        // If the index is out of bounds, return nil to represent an empty position\n        if i < 0 || i >= size() {\n            return nil\n        }\n        return tree[i]\n    }\n\n    /* Get the left child index of the node at index i */\n    func left(i: Int) -> Int {\n        2 * i + 1\n    }\n\n    /* Get the right child index of the node at index i */\n    func right(i: Int) -> Int {\n        2 * i + 2\n    }\n\n    /* Get the parent index of the node at index i */\n    func parent(i: Int) -> Int {\n        (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    func levelOrder() -> [Int] {\n        var res: [Int] = []\n        // Traverse the array directly\n        for i in 0 ..< size() {\n            if let val = val(i: i) {\n                res.append(val)\n            }\n        }\n        return res\n    }\n\n    /* Depth-first traversal */\n    private func dfs(i: Int, order: String, res: inout [Int]) {\n        // Return if this position is empty\n        guard let val = val(i: i) else {\n            return\n        }\n        // Preorder traversal\n        if order == \"pre\" {\n            res.append(val)\n        }\n        dfs(i: left(i: i), order: order, res: &res)\n        // Inorder traversal\n        if order == \"in\" {\n            res.append(val)\n        }\n        dfs(i: right(i: i), order: order, res: &res)\n        // Postorder traversal\n        if order == \"post\" {\n            res.append(val)\n        }\n    }\n\n    /* Preorder traversal */\n    func preOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"pre\", res: &res)\n        return res\n    }\n\n    /* Inorder traversal */\n    func inOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"in\", res: &res)\n        return res\n    }\n\n    /* Postorder traversal */\n    func postOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"post\", res: &res)\n        return res\n    }\n}\n
    array_binary_tree.js
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n    #tree;\n\n    /* Constructor */\n    constructor(arr) {\n        this.#tree = arr;\n    }\n\n    /* Array length */\n    size() {\n        return this.#tree.length;\n    }\n\n    /* Get the value of the node at index i */\n    val(i) {\n        // If the index is out of bounds, return null to indicate an empty position\n        if (i < 0 || i >= this.size()) return null;\n        return this.#tree[i];\n    }\n\n    /* Get the left child index of the node at index i */\n    left(i) {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    right(i) {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    parent(i) {\n        return Math.floor((i - 1) / 2); // Floor division\n    }\n\n    /* Level-order traversal */\n    levelOrder() {\n        let res = [];\n        // Traverse the array directly\n        for (let i = 0; i < this.size(); i++) {\n            if (this.val(i) !== null) res.push(this.val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    #dfs(i, order, res) {\n        // Return if this position is empty\n        if (this.val(i) === null) return;\n        // Preorder traversal\n        if (order === 'pre') res.push(this.val(i));\n        this.#dfs(this.left(i), order, res);\n        // Inorder traversal\n        if (order === 'in') res.push(this.val(i));\n        this.#dfs(this.right(i), order, res);\n        // Postorder traversal\n        if (order === 'post') res.push(this.val(i));\n    }\n\n    /* Preorder traversal */\n    preOrder() {\n        const res = [];\n        this.#dfs(0, 'pre', res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    inOrder() {\n        const res = [];\n        this.#dfs(0, 'in', res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    postOrder() {\n        const res = [];\n        this.#dfs(0, 'post', res);\n        return res;\n    }\n}\n
    array_binary_tree.ts
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n    #tree: (number | null)[];\n\n    /* Constructor */\n    constructor(arr: (number | null)[]) {\n        this.#tree = arr;\n    }\n\n    /* Array length, including empty positions */\n    size(): number {\n        return this.#tree.length;\n    }\n\n    /* Get the value of the node at index i */\n    val(i: number): number | null {\n        // If the index is out of bounds, return null to indicate an empty position\n        if (i < 0 || i >= this.size()) return null;\n        return this.#tree[i];\n    }\n\n    /* Get the left child index of the node at index i */\n    left(i: number): number {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    right(i: number): number {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    parent(i: number): number {\n        return Math.floor((i - 1) / 2); // Floor division\n    }\n\n    /* Level-order traversal */\n    levelOrder(): number[] {\n        let res = [];\n        // Traverse the array directly\n        for (let i = 0; i < this.size(); i++) {\n            if (this.val(i) !== null) res.push(this.val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    #dfs(i: number, order: Order, res: (number | null)[]): void {\n        // Return if this position is empty\n        if (this.val(i) === null) return;\n        // Preorder traversal\n        if (order === 'pre') res.push(this.val(i));\n        this.#dfs(this.left(i), order, res);\n        // Inorder traversal\n        if (order === 'in') res.push(this.val(i));\n        this.#dfs(this.right(i), order, res);\n        // Postorder traversal\n        if (order === 'post') res.push(this.val(i));\n    }\n\n    /* Preorder traversal */\n    preOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'pre', res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    inOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'in', res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    postOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'post', res);\n        return res;\n    }\n}\n
    array_binary_tree.dart
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n  late List<int?> _tree;\n\n  /* Constructor */\n  ArrayBinaryTree(this._tree);\n\n  /* Array length, including empty positions */\n  int size() {\n    return _tree.length;\n  }\n\n  /* Get the value of the node at index i */\n  int? val(int i) {\n    // If the index is out of bounds, return null to indicate an empty position\n    if (i < 0 || i >= size()) {\n      return null;\n    }\n    return _tree[i];\n  }\n\n  /* Get the left child index of the node at index i */\n  int? left(int i) {\n    return 2 * i + 1;\n  }\n\n  /* Get the right child index of the node at index i */\n  int? right(int i) {\n    return 2 * i + 2;\n  }\n\n  /* Get the parent index of the node at index i */\n  int? parent(int i) {\n    return (i - 1) ~/ 2;\n  }\n\n  /* Level-order traversal */\n  List<int> levelOrder() {\n    List<int> res = [];\n    for (int i = 0; i < size(); i++) {\n      if (val(i) != null) {\n        res.add(val(i)!);\n      }\n    }\n    return res;\n  }\n\n  /* Depth-first traversal */\n  void dfs(int i, String order, List<int?> res) {\n    // Return if this position is empty\n    if (val(i) == null) {\n      return;\n    }\n    // Preorder traversal\n    if (order == 'pre') {\n      res.add(val(i));\n    }\n    dfs(left(i)!, order, res);\n    // Inorder traversal\n    if (order == 'in') {\n      res.add(val(i));\n    }\n    dfs(right(i)!, order, res);\n    // Postorder traversal\n    if (order == 'post') {\n      res.add(val(i));\n    }\n  }\n\n  /* Preorder traversal */\n  List<int?> preOrder() {\n    List<int?> res = [];\n    dfs(0, 'pre', res);\n    return res;\n  }\n\n  /* Inorder traversal */\n  List<int?> inOrder() {\n    List<int?> res = [];\n    dfs(0, 'in', res);\n    return res;\n  }\n\n  /* Postorder traversal */\n  List<int?> postOrder() {\n    List<int?> res = [];\n    dfs(0, 'post', res);\n    return res;\n  }\n}\n
    array_binary_tree.rs
    /* Binary tree type using an array representation */\nstruct ArrayBinaryTree {\n    tree: Vec<Option<i32>>,\n}\n\nimpl ArrayBinaryTree {\n    /* Constructor */\n    fn new(arr: Vec<Option<i32>>) -> Self {\n        Self { tree: arr }\n    }\n\n    /* Number of positions in the array */\n    fn size(&self) -> i32 {\n        self.tree.len() as i32\n    }\n\n    /* Get the value of the node at index i */\n    fn val(&self, i: i32) -> Option<i32> {\n        // If the index is out of bounds, return None to represent an empty position\n        if i < 0 || i >= self.size() {\n            None\n        } else {\n            self.tree[i as usize]\n        }\n    }\n\n    /* Get the left child index of the node at index i */\n    fn left(&self, i: i32) -> i32 {\n        2 * i + 1\n    }\n\n    /* Get the right child index of the node at index i */\n    fn right(&self, i: i32) -> i32 {\n        2 * i + 2\n    }\n\n    /* Get the parent index of the node at index i */\n    fn parent(&self, i: i32) -> i32 {\n        (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    fn level_order(&self) -> Vec<i32> {\n        self.tree.iter().filter_map(|&x| x).collect()\n    }\n\n    /* Depth-first traversal */\n    fn dfs(&self, i: i32, order: &'static str, res: &mut Vec<i32>) {\n        if self.val(i).is_none() {\n            return;\n        }\n        let val = self.val(i).unwrap();\n        // Preorder traversal\n        if order == \"pre\" {\n            res.push(val);\n        }\n        self.dfs(self.left(i), order, res);\n        // Inorder traversal\n        if order == \"in\" {\n            res.push(val);\n        }\n        self.dfs(self.right(i), order, res);\n        // Postorder traversal\n        if order == \"post\" {\n            res.push(val);\n        }\n    }\n\n    /* Preorder traversal */\n    fn pre_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"pre\", &mut res);\n        res\n    }\n\n    /* Inorder traversal */\n    fn in_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"in\", &mut res);\n        res\n    }\n\n    /* Postorder traversal */\n    fn post_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"post\", &mut res);\n        res\n    }\n}\n
    array_binary_tree.c
    /* Binary tree structure represented by an array */\ntypedef struct {\n    int *tree;\n    int size;\n} ArrayBinaryTree;\n\n/* Constructor */\nArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) {\n    ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree));\n    abt->tree = malloc(sizeof(int) * arrSize);\n    memcpy(abt->tree, arr, sizeof(int) * arrSize);\n    abt->size = arrSize;\n    return abt;\n}\n\n/* Destructor */\nvoid delArrayBinaryTree(ArrayBinaryTree *abt) {\n    free(abt->tree);\n    free(abt);\n}\n\n/* Array length */\nint size(ArrayBinaryTree *abt) {\n    return abt->size;\n}\n\n/* Get the value of the node at index i */\nint val(ArrayBinaryTree *abt, int i) {\n    // If the index is out of bounds, return INT_MAX to represent an empty position\n    if (i < 0 || i >= size(abt))\n        return INT_MAX;\n    return abt->tree[i];\n}\n\n/* Level-order traversal */\nint *levelOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    // Traverse the array directly\n    for (int i = 0; i < size(abt); i++) {\n        if (val(abt, i) != INT_MAX)\n            res[index++] = val(abt, i);\n    }\n    *returnSize = index;\n    return res;\n}\n\n/* Depth-first traversal */\nvoid dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) {\n    // Return if this position is empty\n    if (val(abt, i) == INT_MAX)\n        return;\n    // Preorder traversal\n    if (strcmp(order, \"pre\") == 0)\n        res[(*index)++] = val(abt, i);\n    dfs(abt, left(i), order, res, index);\n    // Inorder traversal\n    if (strcmp(order, \"in\") == 0)\n        res[(*index)++] = val(abt, i);\n    dfs(abt, right(i), order, res, index);\n    // Postorder traversal\n    if (strcmp(order, \"post\") == 0)\n        res[(*index)++] = val(abt, i);\n}\n\n/* Preorder traversal */\nint *preOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"pre\", res, &index);\n    *returnSize = index;\n    return res;\n}\n\n/* Inorder traversal */\nint *inOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"in\", res, &index);\n    *returnSize = index;\n    return res;\n}\n\n/* Postorder traversal */\nint *postOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"post\", res, &index);\n    *returnSize = index;\n    return res;\n}\n
    array_binary_tree.kt
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree(val tree: MutableList<Int?>) {\n    /* List size */\n    fun size(): Int {\n        return tree.size\n    }\n\n    /* Get the value of the node at index i */\n    fun _val(i: Int): Int? {\n        // If the index is out of bounds, return null to represent an empty position\n        if (i < 0 || i >= size()) return null\n        return tree[i]\n    }\n\n    /* Get the left child index of the node at index i */\n    fun left(i: Int): Int {\n        return 2 * i + 1\n    }\n\n    /* Get the right child index of the node at index i */\n    fun right(i: Int): Int {\n        return 2 * i + 2\n    }\n\n    /* Get the parent index of the node at index i */\n    fun parent(i: Int): Int {\n        return (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    fun levelOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        // Traverse the array directly\n        for (i in 0..<size()) {\n            if (_val(i) != null)\n                res.add(_val(i))\n        }\n        return res\n    }\n\n    /* Depth-first traversal */\n    fun dfs(i: Int, order: String, res: MutableList<Int?>) {\n        // Return if this position is empty\n        if (_val(i) == null)\n            return\n        // Preorder traversal\n        if (\"pre\" == order)\n            res.add(_val(i))\n        dfs(left(i), order, res)\n        // Inorder traversal\n        if (\"in\" == order)\n            res.add(_val(i))\n        dfs(right(i), order, res)\n        // Postorder traversal\n        if (\"post\" == order)\n            res.add(_val(i))\n    }\n\n    /* Preorder traversal */\n    fun preOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"pre\", res)\n        return res\n    }\n\n    /* Inorder traversal */\n    fun inOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"in\", res)\n        return res\n    }\n\n    /* Postorder traversal */\n    fun postOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"post\", res)\n        return res\n    }\n}\n
    array_binary_tree.rb
    ### Binary tree class using an array representation ###\nclass ArrayBinaryTree\n  ### Constructor ###\n  def initialize(arr)\n    @tree = arr.to_a\n  end\n\n  ### Length of the array representation ###\n  def size\n    @tree.length\n  end\n\n  ### Get the value of the node at index i ###\n  def val(i)\n    # If the index is out of bounds, return nil to indicate an empty position\n    return if i < 0 || i >= size\n\n    @tree[i]\n  end\n\n  ### Get the left child index of the node at index i ###\n  def left(i)\n    2 * i + 1\n  end\n\n  ### Get the right child index of the node at index i ###\n  def right(i)\n    2 * i + 2\n  end\n\n  ### Get the parent index of the node at index i ###\n  def parent(i)\n    (i - 1) / 2\n  end\n\n  ### Level-order traversal ###\n  def level_order\n    @res = []\n\n    # Traverse the array directly\n    for i in 0...size\n      @res << val(i) unless val(i).nil?\n    end\n\n    @res\n  end\n\n  ### Depth-first traversal ###\n  def dfs(i, order)\n    return if val(i).nil?\n    # Preorder traversal\n    @res << val(i) if order == :pre\n    dfs(left(i), order)\n    # Inorder traversal\n    @res << val(i) if order == :in\n    dfs(right(i), order)\n    # Postorder traversal\n    @res << val(i) if order == :post\n  end\n\n  ### Preorder traversal ###\n  def pre_order\n    @res = []\n    dfs(0, :pre)\n    @res\n  end\n\n  ### Inorder traversal ###\n  def in_order\n    @res = []\n    dfs(0, :in)\n    @res\n  end\n\n  ### Postorder traversal ###\n  def post_order\n    @res = []\n    dfs(0, :post)\n    @res\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.3   Array representation of a binary tree"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#733-strengths-and-limitations","level":2,"title":"7.3.3   Strengths and limitations","text":"

    Array representation offers the following advantages.

    • Contiguous memory is cache-friendly, making access and traversal fast.
    • No pointers need to be stored, saving space.
    • Nodes can be accessed randomly by index.

    However, it also has limitations.

    • Arrays require contiguous memory, making them unsuitable for extremely large trees.
    • Adding and removing nodes requires array insertion and deletion operations, which are inefficient.
    • If the tree has many missing nodes, actual node data occupies only a small fraction of the array, wasting space.
    ","path":["Chapter 7.   Trees","7.3   Array representation of a binary tree"],"tags":[]},{"location":"chapter_tree/avl_tree/","level":1,"title":"7.5   AVL trees *","text":"

    The binary-search-tree section explained that repeated insertions and deletions can make a tree degenerate into a linked list. All operations then degrade from \\(O(\\log n)\\) to \\(O(n)\\) time.

    In the example below, just two deletions turn the binary search tree into a linked list.

    Figure 7-24   An AVL-tree example degenerating after deletions

    Likewise, inserting two nodes into the perfect binary tree below makes it heavily skewed to the left, degrading lookup performance.

    Figure 7-25   An AVL-tree example degenerating after insertions

    In 1962, G. M. Adelson-Velsky and E. M. Landis introduced the AVL tree in their paper “An algorithm for the organization of information.” They described operations that prevent degeneration through repeated insertions and deletions, keeping the time complexity of operations at \\(O(\\log n)\\). AVL trees therefore maintain efficient data operations when insertions, deletions, lookups, and updates are frequent, making them valuable in practice.

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#751-avl-tree-terminology","level":2,"title":"7.5.1   AVL tree terminology","text":"

    An AVL tree is both a binary search tree and a balanced binary tree, satisfying the properties of both. It is therefore a balanced binary search tree.

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-node-height","level":3,"title":"1.   Node height","text":"

    AVL operations need node heights, so add a height variable to the node class:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class TreeNode:\n    \"\"\"AVL tree node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                 # Node value\n        self.height: int = 0                # Node height\n        self.left: TreeNode | None = None   # Left child reference\n        self.right: TreeNode | None = None  # Right child reference\n
    /* AVL tree node class */\nstruct TreeNode {\n    int val{};          // Node value\n    int height = 0;     // Node height\n    TreeNode *left{};   // Left child\n    TreeNode *right{};  // Right child\n    TreeNode() = default;\n    explicit TreeNode(int x) : val(x){}\n};\n
    /* AVL tree node class */\nclass TreeNode {\n    public int val;        // Node value\n    public int height;     // Node height\n    public TreeNode left;  // Left child\n    public TreeNode right; // Right child\n    public TreeNode(int x) { val = x; }\n}\n
    /* AVL tree node class */\nclass TreeNode(int? x) {\n    public int? val = x;    // Node value\n    public int height;      // Node height\n    public TreeNode? left;  // Left child reference\n    public TreeNode? right; // Right child reference\n}\n
    /* AVL tree node struct */\ntype TreeNode struct {\n    Val    int       // Node value\n    Height int       // Node height\n    Left   *TreeNode // Left child reference\n    Right  *TreeNode // Right child reference\n}\n
    /* AVL tree node class */\nclass TreeNode {\n    var val: Int // Node value\n    var height: Int // Node height\n    var left: TreeNode? // Left child\n    var right: TreeNode? // Right child\n\n    init(x: Int) {\n        val = x\n        height = 0\n    }\n}\n
    /* AVL tree node class */\nclass TreeNode {\n    val; // Node value\n    height; //Node height\n    left; // Left child pointer\n    right; // Right child pointer\n    constructor(val, left, right, height) {\n        this.val = val === undefined ? 0 : val;\n        this.height = height === undefined ? 0 : height;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
    /* AVL tree node class */\nclass TreeNode {\n    val: number;            // Node value\n    height: number;         // Node height\n    left: TreeNode | null;  // Left child pointer\n    right: TreeNode | null; // Right child pointer\n    constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) {\n        this.val = val === undefined ? 0 : val;\n        this.height = height === undefined ? 0 : height;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
    /* AVL tree node class */\nclass TreeNode {\n  int val;         // Node value\n  int height;      // Node height\n  TreeNode? left;  // Left child\n  TreeNode? right; // Right child\n  TreeNode(this.val, [this.height = 0, this.left, this.right]);\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* AVL tree node struct */\nstruct TreeNode {\n    val: i32,                               // Node value\n    height: i32,                            // Node height\n    left: Option<Rc<RefCell<TreeNode>>>,    // Left child\n    right: Option<Rc<RefCell<TreeNode>>>,   // Right child\n}\n\nimpl TreeNode {\n    /* Constructor */\n    fn new(val: i32) -> Rc<RefCell<Self>> {\n        Rc::new(RefCell::new(Self {\n            val,\n            height: 0,\n            left: None,\n            right: None\n        }))\n    }\n}\n
    /* AVL tree node struct */\ntypedef struct TreeNode {\n    int val;\n    int height;\n    struct TreeNode *left;\n    struct TreeNode *right;\n} TreeNode;\n\n/* Constructor */\nTreeNode *newTreeNode(int val) {\n    TreeNode *node;\n\n    node = (TreeNode *)malloc(sizeof(TreeNode));\n    node->val = val;\n    node->height = 0;\n    node->left = NULL;\n    node->right = NULL;\n    return node;\n}\n
    /* AVL tree node class */\nclass TreeNode(val _val: Int) {  // Node value\n    var height: Int = 0          // Node height\n    var left: TreeNode? = null   // Left child\n    var right: TreeNode? = null  // Right child\n}\n
    ### AVL tree node class ###\nclass TreeNode\n  attr_accessor :val    # Node value\n  attr_accessor :height # Node height\n  attr_accessor :left   # Left child reference\n  attr_accessor :right  # Right child reference\n\n  def initialize(val)\n    @val = val\n    @height = 0\n  end\nend\n

    A node's height is the distance to its farthest leaf, measured in edges. A leaf has height \\(0\\), while a missing node has height \\(-1\\). We create two helper functions to retrieve and update a node's height:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def height(self, node: TreeNode | None) -> int:\n    \"\"\"Get the node height\"\"\"\n    # An empty subtree has height -1; a leaf has height 0\n    if node is not None:\n        return node.height\n    return -1\n\ndef update_height(self, node: TreeNode | None):\n    \"\"\"Update the node height\"\"\"\n    # The node height equals the height of its taller subtree + 1\n    node.height = max([self.height(node.left), self.height(node.right)]) + 1\n
    avl_tree.cpp
    /* Get the node height */\nint height(TreeNode *node) {\n    // A null node has height -1; a leaf has height 0\n    return node == nullptr ? -1 : node->height;\n}\n\n/* Update the node height */\nvoid updateHeight(TreeNode *node) {\n    // The node height equals the height of its taller subtree + 1\n    node->height = max(height(node->left), height(node->right)) + 1;\n}\n
    avl_tree.java
    /* Get the node height */\nint height(TreeNode node) {\n    // A null node has height -1; a leaf has height 0\n    return node == null ? -1 : node.height;\n}\n\n/* Update the node height */\nvoid updateHeight(TreeNode node) {\n    // The node height equals the height of its taller subtree + 1\n    node.height = Math.max(height(node.left), height(node.right)) + 1;\n}\n
    avl_tree.cs
    /* Get the node height */\nint Height(TreeNode? node) {\n    // A null node has height -1; a leaf has height 0\n    return node == null ? -1 : node.height;\n}\n\n/* Update the node height */\nvoid UpdateHeight(TreeNode node) {\n    // The node height equals the height of its taller subtree + 1\n    node.height = Math.Max(Height(node.left), Height(node.right)) + 1;\n}\n
    avl_tree.go
    /* Get the node height */\nfunc (t *aVLTree) height(node *TreeNode) int {\n    // A nil node has height -1; a leaf has height 0\n    if node != nil {\n        return node.Height\n    }\n    return -1\n}\n\n/* Update the node height */\nfunc (t *aVLTree) updateHeight(node *TreeNode) {\n    lh := t.height(node.Left)\n    rh := t.height(node.Right)\n    // The node height equals the height of its taller subtree + 1\n    if lh > rh {\n        node.Height = lh + 1\n    } else {\n        node.Height = rh + 1\n    }\n}\n
    avl_tree.swift
    /* Get the node height */\nfunc height(node: TreeNode?) -> Int {\n    // A nil node has height -1; a leaf has height 0\n    node?.height ?? -1\n}\n\n/* Update the node height */\nfunc updateHeight(node: TreeNode?) {\n    // The node height equals the height of its taller subtree + 1\n    node?.height = max(height(node: node?.left), height(node: node?.right)) + 1\n}\n
    avl_tree.js
    /* Get the node height */\nheight(node) {\n    // A null node has height -1; a leaf has height 0\n    return node === null ? -1 : node.height;\n}\n\n/* Update the node height */\n#updateHeight(node) {\n    // The node height equals the height of its taller subtree + 1\n    node.height =\n        Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
    avl_tree.ts
    /* Get the node height */\nheight(node: TreeNode): number {\n    // A null node has height -1; a leaf has height 0\n    return node === null ? -1 : node.height;\n}\n\n/* Update the node height */\nupdateHeight(node: TreeNode): void {\n    // The node height equals the height of its taller subtree + 1\n    node.height =\n        Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
    avl_tree.dart
    /* Get the node height */\nint height(TreeNode? node) {\n  // A null node has height -1; a leaf has height 0\n  return node == null ? -1 : node.height;\n}\n\n/* Update the node height */\nvoid updateHeight(TreeNode? node) {\n  // The node height equals the height of its taller subtree + 1\n  node!.height = max(height(node.left), height(node.right)) + 1;\n}\n
    avl_tree.rs
    /* Get the node height */\nfn height(node: OptionTreeNodeRc) -> i32 {\n    // A null node has height -1; a leaf has height 0\n    match node {\n        Some(node) => node.borrow().height,\n        None => -1,\n    }\n}\n\n/* Update the node height */\nfn update_height(node: OptionTreeNodeRc) {\n    if let Some(node) = node {\n        let left = node.borrow().left.clone();\n        let right = node.borrow().right.clone();\n        // The node height equals the height of its taller subtree + 1\n        node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1;\n    }\n}\n
    avl_tree.c
    /* Get the node height */\nint height(TreeNode *node) {\n    // A null node has height -1; a leaf has height 0\n    if (node != NULL) {\n        return node->height;\n    }\n    return -1;\n}\n\n/* Update the node height */\nvoid updateHeight(TreeNode *node) {\n    int lh = height(node->left);\n    int rh = height(node->right);\n    // The node height equals the height of its taller subtree + 1\n    if (lh > rh) {\n        node->height = lh + 1;\n    } else {\n        node->height = rh + 1;\n    }\n}\n
    avl_tree.kt
    /* Get the node height */\nfun height(node: TreeNode?): Int {\n    // A null node has height -1; a leaf has height 0\n    return node?.height ?: -1\n}\n\n/* Update the node height */\nfun updateHeight(node: TreeNode?) {\n    // The node height equals the height of its taller subtree + 1\n    node?.height = max(height(node?.left), height(node?.right)) + 1\n}\n
    avl_tree.rb
    ### Get the node height ###\ndef height(node)\n  # A nil node has height -1; a leaf has height 0\n  return node.height unless node.nil?\n\n  -1\nend\n\n### Update the node height ###\ndef update_height(node)\n  # The node height equals the height of its taller subtree + 1\n  node.height = [height(node.left), height(node.right)].max + 1\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-balance-factor","level":3,"title":"2.   Balance factor","text":"

    A node's balance factor is its left subtree's height minus its right subtree's height. A missing node has balance factor \\(0\\). Wrap this calculation in a function for convenient use:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def balance_factor(self, node: TreeNode | None) -> int:\n    \"\"\"Get the balance factor\"\"\"\n    # An empty subtree has balance factor 0\n    if node is None:\n        return 0\n    # Node balance factor = left subtree height - right subtree height\n    return self.height(node.left) - self.height(node.right)\n
    avl_tree.cpp
    /* Get the balance factor */\nint balanceFactor(TreeNode *node) {\n    // A null node has balance factor 0\n    if (node == nullptr)\n        return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return height(node->left) - height(node->right);\n}\n
    avl_tree.java
    /* Get the balance factor */\nint balanceFactor(TreeNode node) {\n    // A null node has balance factor 0\n    if (node == null)\n        return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return height(node.left) - height(node.right);\n}\n
    avl_tree.cs
    /* Get the balance factor */\nint BalanceFactor(TreeNode? node) {\n    // A null node has balance factor 0\n    if (node == null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return Height(node.left) - Height(node.right);\n}\n
    avl_tree.go
    /* Get the balance factor */\nfunc (t *aVLTree) balanceFactor(node *TreeNode) int {\n    // A nil node has balance factor 0\n    if node == nil {\n        return 0\n    }\n    // Node balance factor = left subtree height - right subtree height\n    return t.height(node.Left) - t.height(node.Right)\n}\n
    avl_tree.swift
    /* Get the balance factor */\nfunc balanceFactor(node: TreeNode?) -> Int {\n    // A nil node has balance factor 0\n    guard let node = node else { return 0 }\n    // Node balance factor = left subtree height - right subtree height\n    return height(node: node.left) - height(node: node.right)\n}\n
    avl_tree.js
    /* Get the balance factor */\nbalanceFactor(node) {\n    // A null node has balance factor 0\n    if (node === null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return this.height(node.left) - this.height(node.right);\n}\n
    avl_tree.ts
    /* Get the balance factor */\nbalanceFactor(node: TreeNode): number {\n    // A null node has balance factor 0\n    if (node === null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return this.height(node.left) - this.height(node.right);\n}\n
    avl_tree.dart
    /* Get the balance factor */\nint balanceFactor(TreeNode? node) {\n  // A null node has balance factor 0\n  if (node == null) return 0;\n  // Node balance factor = left subtree height - right subtree height\n  return height(node.left) - height(node.right);\n}\n
    avl_tree.rs
    /* Get the balance factor */\nfn balance_factor(node: OptionTreeNodeRc) -> i32 {\n    match node {\n        // A null node has balance factor 0\n        None => 0,\n        // Node balance factor = left subtree height - right subtree height\n        Some(node) => {\n            Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone())\n        }\n    }\n}\n
    avl_tree.c
    /* Get the balance factor */\nint balanceFactor(TreeNode *node) {\n    // A null node has balance factor 0\n    if (node == NULL) {\n        return 0;\n    }\n    // Node balance factor = left subtree height - right subtree height\n    return height(node->left) - height(node->right);\n}\n
    avl_tree.kt
    /* Get the balance factor */\nfun balanceFactor(node: TreeNode?): Int {\n    // A null node has balance factor 0\n    if (node == null) return 0\n    // Node balance factor = left subtree height - right subtree height\n    return height(node.left) - height(node.right)\n}\n
    avl_tree.rb
    ### Get the balance factor ###\ndef balance_factor(node)\n  # A nil node has balance factor 0\n  return 0 if node.nil?\n\n  # Node balance factor = left subtree height - right subtree height\n  height(node.left) - height(node.right)\nend\n

    Tip

    If a node's balance factor is \\(f\\), every node in an AVL tree satisfies \\(-1 \\le f \\le 1\\).

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#752-avl-tree-rotations","level":2,"title":"7.5.2   AVL tree rotations","text":"

    The defining operation of an AVL tree is rotation, which restores balance without changing the inorder traversal sequence. A rotation preserves the binary-search-tree property while restoring the balanced-tree property.

    A node whose balance factor has absolute value \\(> 1\\) is “unbalanced.” Depending on the imbalance, we use one of four rotations: right, left, right-then-left, or left-then-right. Let us examine them in detail.

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-right-rotation","level":3,"title":"1.   Right rotation","text":"

    In Figure 7-26, balance factors appear beneath the nodes. Viewed from the bottom up, node 3 is the first unbalanced node. Focus on the subtree rooted there, naming its root node and its left child child. A right rotation restores balance while preserving the binary-search-tree property.

    <1><2><3><4>

    Figure 7-26   Steps of a right rotation

    If child has a right child, denoted by grand_child, add one step to the rotation: make grand_child the left child of node, as shown below.

    Figure 7-27   A right rotation with grand_child

    “Rotating right” is a visual description; the implementation updates node pointers:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def right_rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Right rotation\"\"\"\n    child = node.left\n    grand_child = child.right\n    # Rotate node to the right around child\n    child.right = node\n    node.left = grand_child\n    # Update the node height\n    self.update_height(node)\n    self.update_height(child)\n    # Return the root of the rotated subtree\n    return child\n
    avl_tree.cpp
    /* Right rotation */\nTreeNode *rightRotate(TreeNode *node) {\n    TreeNode *child = node->left;\n    TreeNode *grandChild = child->right;\n    // Rotate node to the right around child\n    child->right = node;\n    node->left = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.java
    /* Right rotation */\nTreeNode rightRotate(TreeNode node) {\n    TreeNode child = node.left;\n    TreeNode grandChild = child.right;\n    // Rotate node to the right around child\n    child.right = node;\n    node.left = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.cs
    /* Right rotation */\nTreeNode? RightRotate(TreeNode? node) {\n    TreeNode? child = node?.left;\n    TreeNode? grandChild = child?.right;\n    // Rotate node to the right around child\n    child.right = node;\n    node.left = grandChild;\n    // Update the node height\n    UpdateHeight(node);\n    UpdateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.go
    /* Right rotation */\nfunc (t *aVLTree) rightRotate(node *TreeNode) *TreeNode {\n    child := node.Left\n    grandChild := child.Right\n    // Rotate node to the right around child\n    child.Right = node\n    node.Left = grandChild\n    // Update the node height\n    t.updateHeight(node)\n    t.updateHeight(child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.swift
    /* Right rotation */\nfunc rightRotate(node: TreeNode?) -> TreeNode? {\n    let child = node?.left\n    let grandChild = child?.right\n    // Rotate node to the right around child\n    child?.right = node\n    node?.left = grandChild\n    // Update the node height\n    updateHeight(node: node)\n    updateHeight(node: child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.js
    /* Right rotation */\n#rightRotate(node) {\n    const child = node.left;\n    const grandChild = child.right;\n    // Rotate node to the right around child\n    child.right = node;\n    node.left = grandChild;\n    // Update the node height\n    this.#updateHeight(node);\n    this.#updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.ts
    /* Right rotation */\nrightRotate(node: TreeNode): TreeNode {\n    const child = node.left;\n    const grandChild = child.right;\n    // Rotate node to the right around child\n    child.right = node;\n    node.left = grandChild;\n    // Update the node height\n    this.updateHeight(node);\n    this.updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.dart
    /* Right rotation */\nTreeNode? rightRotate(TreeNode? node) {\n  TreeNode? child = node!.left;\n  TreeNode? grandChild = child!.right;\n  // Rotate node to the right around child\n  child.right = node;\n  node.left = grandChild;\n  // Update the node height\n  updateHeight(node);\n  updateHeight(child);\n  // Return the root of the rotated subtree\n  return child;\n}\n
    avl_tree.rs
    /* Right rotation */\nfn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    match node {\n        Some(node) => {\n            let child = node.borrow().left.clone().unwrap();\n            let grand_child = child.borrow().right.clone();\n            // Rotate node to the right around child\n            child.borrow_mut().right = Some(node.clone());\n            node.borrow_mut().left = grand_child;\n            // Update the node height\n            Self::update_height(Some(node));\n            Self::update_height(Some(child.clone()));\n            // Return the root of the rotated subtree\n            Some(child)\n        }\n        None => None,\n    }\n}\n
    avl_tree.c
    /* Right rotation */\nTreeNode *rightRotate(TreeNode *node) {\n    TreeNode *child, *grandChild;\n    child = node->left;\n    grandChild = child->right;\n    // Rotate node to the right around child\n    child->right = node;\n    node->left = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.kt
    /* Right rotation */\nfun rightRotate(node: TreeNode?): TreeNode {\n    val child = node!!.left\n    val grandChild = child!!.right\n    // Rotate node to the right around child\n    child.right = node\n    node.left = grandChild\n    // Update the node height\n    updateHeight(node)\n    updateHeight(child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.rb
    ### Right rotation ###\ndef right_rotate(node)\n  child = node.left\n  grand_child = child.right\n  # Rotate node to the right around child\n  child.right = node\n  node.left = grand_child\n  # Update the node height\n  update_height(node)\n  update_height(child)\n  # Return the root of the rotated subtree\n  child\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-left-rotation","level":3,"title":"2.   Left rotation","text":"

    For the mirror image of the unbalanced tree above, perform a left rotation, as shown below.

    Figure 7-28   A left rotation

    Similarly, if child has a left child, denoted by grand_child, make it the right child of node during the rotation.

    Figure 7-29   A left rotation with grand_child

    Right and left rotations are mirror images, as are the imbalances they resolve. To obtain left-rotation code, exchange every left and right in the right-rotation implementation:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def left_rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Left rotation\"\"\"\n    child = node.right\n    grand_child = child.left\n    # Rotate node to the left around child\n    child.left = node\n    node.right = grand_child\n    # Update the node height\n    self.update_height(node)\n    self.update_height(child)\n    # Return the root of the rotated subtree\n    return child\n
    avl_tree.cpp
    /* Left rotation */\nTreeNode *leftRotate(TreeNode *node) {\n    TreeNode *child = node->right;\n    TreeNode *grandChild = child->left;\n    // Rotate node to the left around child\n    child->left = node;\n    node->right = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.java
    /* Left rotation */\nTreeNode leftRotate(TreeNode node) {\n    TreeNode child = node.right;\n    TreeNode grandChild = child.left;\n    // Rotate node to the left around child\n    child.left = node;\n    node.right = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.cs
    /* Left rotation */\nTreeNode? LeftRotate(TreeNode? node) {\n    TreeNode? child = node?.right;\n    TreeNode? grandChild = child?.left;\n    // Rotate node to the left around child\n    child.left = node;\n    node.right = grandChild;\n    // Update the node height\n    UpdateHeight(node);\n    UpdateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.go
    /* Left rotation */\nfunc (t *aVLTree) leftRotate(node *TreeNode) *TreeNode {\n    child := node.Right\n    grandChild := child.Left\n    // Rotate node to the left around child\n    child.Left = node\n    node.Right = grandChild\n    // Update the node height\n    t.updateHeight(node)\n    t.updateHeight(child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.swift
    /* Left rotation */\nfunc leftRotate(node: TreeNode?) -> TreeNode? {\n    let child = node?.right\n    let grandChild = child?.left\n    // Rotate node to the left around child\n    child?.left = node\n    node?.right = grandChild\n    // Update the node height\n    updateHeight(node: node)\n    updateHeight(node: child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.js
    /* Left rotation */\n#leftRotate(node) {\n    const child = node.right;\n    const grandChild = child.left;\n    // Rotate node to the left around child\n    child.left = node;\n    node.right = grandChild;\n    // Update the node height\n    this.#updateHeight(node);\n    this.#updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.ts
    /* Left rotation */\nleftRotate(node: TreeNode): TreeNode {\n    const child = node.right;\n    const grandChild = child.left;\n    // Rotate node to the left around child\n    child.left = node;\n    node.right = grandChild;\n    // Update the node height\n    this.updateHeight(node);\n    this.updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.dart
    /* Left rotation */\nTreeNode? leftRotate(TreeNode? node) {\n  TreeNode? child = node!.right;\n  TreeNode? grandChild = child!.left;\n  // Rotate node to the left around child\n  child.left = node;\n  node.right = grandChild;\n  // Update the node height\n  updateHeight(node);\n  updateHeight(child);\n  // Return the root of the rotated subtree\n  return child;\n}\n
    avl_tree.rs
    /* Left rotation */\nfn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    match node {\n        Some(node) => {\n            let child = node.borrow().right.clone().unwrap();\n            let grand_child = child.borrow().left.clone();\n            // Rotate node to the left around child\n            child.borrow_mut().left = Some(node.clone());\n            node.borrow_mut().right = grand_child;\n            // Update the node height\n            Self::update_height(Some(node));\n            Self::update_height(Some(child.clone()));\n            // Return the root of the rotated subtree\n            Some(child)\n        }\n        None => None,\n    }\n}\n
    avl_tree.c
    /* Left rotation */\nTreeNode *leftRotate(TreeNode *node) {\n    TreeNode *child, *grandChild;\n    child = node->right;\n    grandChild = child->left;\n    // Rotate node to the left around child\n    child->left = node;\n    node->right = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.kt
    /* Left rotation */\nfun leftRotate(node: TreeNode?): TreeNode {\n    val child = node!!.right\n    val grandChild = child!!.left\n    // Rotate node to the left around child\n    child.left = node\n    node.right = grandChild\n    // Update the node height\n    updateHeight(node)\n    updateHeight(child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.rb
    ### Left rotation ###\ndef left_rotate(node)\n  child = node.right\n  grand_child = child.left\n  # Rotate node to the left around child\n  child.left = node\n  node.right = grand_child\n  # Update the node height\n  update_height(node)\n  update_height(child)\n  # Return the root of the rotated subtree\n  child\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3-left-then-right-rotation","level":3,"title":"3.   Left-then-right rotation","text":"

    For unbalanced node 3 below, neither a left nor a right rotation alone restores balance. First rotate child to the left, then rotate node to the right.

    Figure 7-30   A left-then-right rotation

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#4-right-then-left-rotation","level":3,"title":"4.   Right-then-left rotation","text":"

    For the mirror-image case below, first rotate child to the right, then rotate node to the left.

    Figure 7-31   A right-then-left rotation

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#5-choosing-a-rotation","level":3,"title":"5.   Choosing a rotation","text":"

    The four cases below correspond to the examples above and require, respectively, right, left-then-right, right-then-left, and left rotations.

    Figure 7-32   The four AVL rotation cases

    As Table 7-3 shows, the signs of the unbalanced node's balance factor and that of the child on its taller side determine the case.

    Table 7-3   Conditions for choosing a rotation

    Unbalanced node's balance factor Child's balance factor Rotation \\(> 1\\) (left-heavy) \\(\\geq 0\\) Right \\(> 1\\) (left-heavy) \\(<0\\) Left, then right \\(< -1\\) (right-heavy) \\(\\leq 0\\) Left \\(< -1\\) (right-heavy) \\(>0\\) Right, then left

    Wrap the rotation logic in one function. It can then handle every imbalance and restore the affected node's balance. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Perform rotations to restore balance in this subtree\"\"\"\n    # Get the balance factor of node\n    balance_factor = self.balance_factor(node)\n    # Left-heavy tree\n    if balance_factor > 1:\n        if self.balance_factor(node.left) >= 0:\n            # Rotate right\n            return self.right_rotate(node)\n        else:\n            # Rotate left, then right\n            node.left = self.left_rotate(node.left)\n            return self.right_rotate(node)\n    # Right-heavy tree\n    elif balance_factor < -1:\n        if self.balance_factor(node.right) <= 0:\n            # Rotate left\n            return self.left_rotate(node)\n        else:\n            # Rotate right, then left\n            node.right = self.right_rotate(node.right)\n            return self.left_rotate(node)\n    # The tree is balanced; return without rotating\n    return node\n
    avl_tree.cpp
    /* Perform rotations to restore balance in this subtree */\nTreeNode *rotate(TreeNode *node) {\n    // Get the balance factor of node\n    int _balanceFactor = balanceFactor(node);\n    // Left-heavy tree\n    if (_balanceFactor > 1) {\n        if (balanceFactor(node->left) >= 0) {\n            // Rotate right\n            return rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node->left = leftRotate(node->left);\n            return rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (_balanceFactor < -1) {\n        if (balanceFactor(node->right) <= 0) {\n            // Rotate left\n            return leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node->right = rightRotate(node->right);\n            return leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.java
    /* Perform rotations to restore balance in this subtree */\nTreeNode rotate(TreeNode node) {\n    // Get the balance factor of node\n    int balanceFactor = balanceFactor(node);\n    // Left-heavy tree\n    if (balanceFactor > 1) {\n        if (balanceFactor(node.left) >= 0) {\n            // Rotate right\n            return rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node.left = leftRotate(node.left);\n            return rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactor < -1) {\n        if (balanceFactor(node.right) <= 0) {\n            // Rotate left\n            return leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node.right = rightRotate(node.right);\n            return leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.cs
    /* Perform rotations to restore balance in this subtree */\nTreeNode? Rotate(TreeNode? node) {\n    // Get the balance factor of node\n    int balanceFactorInt = BalanceFactor(node);\n    // Left-heavy tree\n    if (balanceFactorInt > 1) {\n        if (BalanceFactor(node?.left) >= 0) {\n            // Rotate right\n            return RightRotate(node);\n        } else {\n            // Rotate left, then right\n            node!.left = LeftRotate(node!.left);\n            return RightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactorInt < -1) {\n        if (BalanceFactor(node?.right) <= 0) {\n            // Rotate left\n            return LeftRotate(node);\n        } else {\n            // Rotate right, then left\n            node!.right = RightRotate(node!.right);\n            return LeftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.go
    /* Perform rotations to restore balance in this subtree */\nfunc (t *aVLTree) rotate(node *TreeNode) *TreeNode {\n    // Get the balance factor of node\n    // Go favors short local variable names; bf holds the result of t.balanceFactor(node)\n    bf := t.balanceFactor(node)\n    // Left-heavy tree\n    if bf > 1 {\n        if t.balanceFactor(node.Left) >= 0 {\n            // Rotate right\n            return t.rightRotate(node)\n        } else {\n            // Rotate left, then right\n            node.Left = t.leftRotate(node.Left)\n            return t.rightRotate(node)\n        }\n    }\n    // Right-heavy tree\n    if bf < -1 {\n        if t.balanceFactor(node.Right) <= 0 {\n            // Rotate left\n            return t.leftRotate(node)\n        } else {\n            // Rotate right, then left\n            node.Right = t.rightRotate(node.Right)\n            return t.leftRotate(node)\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node\n}\n
    avl_tree.swift
    /* Perform rotations to restore balance in this subtree */\nfunc rotate(node: TreeNode?) -> TreeNode? {\n    // Get the balance factor of node\n    let balanceFactor = balanceFactor(node: node)\n    // Left-heavy tree\n    if balanceFactor > 1 {\n        if self.balanceFactor(node: node?.left) >= 0 {\n            // Rotate right\n            return rightRotate(node: node)\n        } else {\n            // Rotate left, then right\n            node?.left = leftRotate(node: node?.left)\n            return rightRotate(node: node)\n        }\n    }\n    // Right-heavy tree\n    if balanceFactor < -1 {\n        if self.balanceFactor(node: node?.right) <= 0 {\n            // Rotate left\n            return leftRotate(node: node)\n        } else {\n            // Rotate right, then left\n            node?.right = rightRotate(node: node?.right)\n            return leftRotate(node: node)\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node\n}\n
    avl_tree.js
    /* Perform rotations to restore balance in this subtree */\n#rotate(node) {\n    // Get the balance factor of node\n    const balanceFactor = this.balanceFactor(node);\n    // Left-heavy tree\n    if (balanceFactor > 1) {\n        if (this.balanceFactor(node.left) >= 0) {\n            // Rotate right\n            return this.#rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node.left = this.#leftRotate(node.left);\n            return this.#rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactor < -1) {\n        if (this.balanceFactor(node.right) <= 0) {\n            // Rotate left\n            return this.#leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node.right = this.#rightRotate(node.right);\n            return this.#leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.ts
    /* Perform rotations to restore balance in this subtree */\nrotate(node: TreeNode): TreeNode {\n    // Get the balance factor of node\n    const balanceFactor = this.balanceFactor(node);\n    // Left-heavy tree\n    if (balanceFactor > 1) {\n        if (this.balanceFactor(node.left) >= 0) {\n            // Rotate right\n            return this.rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node.left = this.leftRotate(node.left);\n            return this.rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactor < -1) {\n        if (this.balanceFactor(node.right) <= 0) {\n            // Rotate left\n            return this.leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node.right = this.rightRotate(node.right);\n            return this.leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.dart
    /* Perform rotations to restore balance in this subtree */\nTreeNode? rotate(TreeNode? node) {\n  // Get the balance factor of node\n  int factor = balanceFactor(node);\n  // Left-heavy tree\n  if (factor > 1) {\n    if (balanceFactor(node!.left) >= 0) {\n      // Rotate right\n      return rightRotate(node);\n    } else {\n      // Rotate left, then right\n      node.left = leftRotate(node.left);\n      return rightRotate(node);\n    }\n  }\n  // Right-heavy tree\n  if (factor < -1) {\n    if (balanceFactor(node!.right) <= 0) {\n      // Rotate left\n      return leftRotate(node);\n    } else {\n      // Rotate right, then left\n      node.right = rightRotate(node.right);\n      return leftRotate(node);\n    }\n  }\n  // The tree is balanced; return without rotating\n  return node;\n}\n
    avl_tree.rs
    /* Perform rotations to restore balance in this subtree */\nfn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    // Get the balance factor of node\n    let balance_factor = Self::balance_factor(node.clone());\n    // Left-heavy tree\n    if balance_factor > 1 {\n        let node = node.unwrap();\n        if Self::balance_factor(node.borrow().left.clone()) >= 0 {\n            // Rotate right\n            Self::right_rotate(Some(node))\n        } else {\n            // Rotate left, then right\n            let left = node.borrow().left.clone();\n            node.borrow_mut().left = Self::left_rotate(left);\n            Self::right_rotate(Some(node))\n        }\n    }\n    // Right-heavy tree\n    else if balance_factor < -1 {\n        let node = node.unwrap();\n        if Self::balance_factor(node.borrow().right.clone()) <= 0 {\n            // Rotate left\n            Self::left_rotate(Some(node))\n        } else {\n            // Rotate right, then left\n            let right = node.borrow().right.clone();\n            node.borrow_mut().right = Self::right_rotate(right);\n            Self::left_rotate(Some(node))\n        }\n    } else {\n        // The tree is balanced; return without rotating\n        node\n    }\n}\n
    avl_tree.c
    /* Perform rotations to restore balance in this subtree */\nTreeNode *rotate(TreeNode *node) {\n    // Get the balance factor of node\n    int bf = balanceFactor(node);\n    // Left-heavy tree\n    if (bf > 1) {\n        if (balanceFactor(node->left) >= 0) {\n            // Rotate right\n            return rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node->left = leftRotate(node->left);\n            return rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (bf < -1) {\n        if (balanceFactor(node->right) <= 0) {\n            // Rotate left\n            return leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node->right = rightRotate(node->right);\n            return leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.kt
    /* Perform rotations to restore balance in this subtree */\nfun rotate(node: TreeNode): TreeNode {\n    // Get the balance factor of node\n    val balanceFactor = balanceFactor(node)\n    // Left-heavy tree\n    if (balanceFactor > 1) {\n        if (balanceFactor(node.left) >= 0) {\n            // Rotate right\n            return rightRotate(node)\n        } else {\n            // Rotate left, then right\n            node.left = leftRotate(node.left)\n            return rightRotate(node)\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactor < -1) {\n        if (balanceFactor(node.right) <= 0) {\n            // Rotate left\n            return leftRotate(node)\n        } else {\n            // Rotate right, then left\n            node.right = rightRotate(node.right)\n            return leftRotate(node)\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node\n}\n
    avl_tree.rb
    ### Perform rotations to restore balance in this subtree ###\ndef rotate(node)\n  # Get the balance factor of node\n  balance_factor = balance_factor(node)\n  # Left-heavy tree\n  if balance_factor > 1\n    if balance_factor(node.left) >= 0\n      # Rotate right\n      return right_rotate(node)\n    else\n      # Rotate left, then right\n      node.left = left_rotate(node.left)\n      return right_rotate(node)\n    end\n  # Right-heavy tree\n  elsif balance_factor < -1\n    if balance_factor(node.right) <= 0\n      # Rotate left\n      return left_rotate(node)\n    else\n      # Rotate right, then left\n      node.right = right_rotate(node.right)\n      return left_rotate(node)\n    end\n  end\n  # The tree is balanced; return without rotating\n  node\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#753-common-avl-tree-operations","level":2,"title":"7.5.3   Common AVL tree operations","text":"","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-inserting-a-node","level":3,"title":"1.   Inserting a node","text":"

    Insertion largely follows the binary-search-tree procedure. The difference is that inserting a node may unbalance nodes along the path to the root. Starting at the inserted node, work upward and perform rotations to restore balance at every affected node:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def insert(self, val):\n    \"\"\"Insert a node\"\"\"\n    self._root = self.insert_helper(self._root, val)\n\ndef insert_helper(self, node: TreeNode | None, val: int) -> TreeNode:\n    \"\"\"Insert a node recursively (helper method)\"\"\"\n    if node is None:\n        return TreeNode(val)\n    # 1. Find the insertion position and insert the node\n    if val < node.val:\n        node.left = self.insert_helper(node.left, val)\n    elif val > node.val:\n        node.right = self.insert_helper(node.right, val)\n    else:\n        # Return without inserting a duplicate node\n        return node\n    # Update the node height\n    self.update_height(node)\n    # 2. Perform rotations to restore balance in this subtree\n    return self.rotate(node)\n
    avl_tree.cpp
    /* Insert a node */\nvoid insert(int val) {\n    root = insertHelper(root, val);\n}\n\n/* Insert a node recursively (helper method) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n    if (node == nullptr)\n        return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node->val)\n        node->left = insertHelper(node->left, val);\n    else if (val > node->val)\n        node->right = insertHelper(node->right, val);\n    else\n        return node;    // Return without inserting a duplicate node\n    updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.java
    /* Insert a node */\nvoid insert(int val) {\n    root = insertHelper(root, val);\n}\n\n/* Insert a node recursively (helper method) */\nTreeNode insertHelper(TreeNode node, int val) {\n    if (node == null)\n        return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node.val)\n        node.left = insertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = insertHelper(node.right, val);\n    else\n        return node; // Return without inserting a duplicate node\n    updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the subtree root\n    return node;\n}\n
    avl_tree.cs
    /* Insert a node */\nvoid Insert(int val) {\n    root = InsertHelper(root, val);\n}\n\n/* Insert a node recursively (helper method) */\nTreeNode? InsertHelper(TreeNode? node, int val) {\n    if (node == null) return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node.val)\n        node.left = InsertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = InsertHelper(node.right, val);\n    else\n        return node;     // Return without inserting a duplicate node\n    UpdateHeight(node);  // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = Rotate(node);\n    // Return the subtree root\n    return node;\n}\n
    avl_tree.go
    /* Insert a node */\nfunc (t *aVLTree) insert(val int) {\n    t.root = t.insertHelper(t.root, val)\n}\n\n/* Insert a node recursively (helper function) */\nfunc (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode {\n    if node == nil {\n        return NewTreeNode(val)\n    }\n    /* 1. Find the insertion position and insert the node */\n    if val < node.Val.(int) {\n        node.Left = t.insertHelper(node.Left, val)\n    } else if val > node.Val.(int) {\n        node.Right = t.insertHelper(node.Right, val)\n    } else {\n        // Return without inserting a duplicate node\n        return node\n    }\n    // Update the node height\n    t.updateHeight(node)\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = t.rotate(node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.swift
    /* Insert a node */\nfunc insert(val: Int) {\n    root = insertHelper(node: root, val: val)\n}\n\n/* Insert a node recursively (helper method) */\nfunc insertHelper(node: TreeNode?, val: Int) -> TreeNode? {\n    var node = node\n    if node == nil {\n        return TreeNode(x: val)\n    }\n    /* 1. Find the insertion position and insert the node */\n    if val < node!.val {\n        node?.left = insertHelper(node: node?.left, val: val)\n    } else if val > node!.val {\n        node?.right = insertHelper(node: node?.right, val: val)\n    } else {\n        return node // Return without inserting a duplicate node\n    }\n    updateHeight(node: node) // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node: node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.js
    /* Insert a node */\ninsert(val) {\n    this.root = this.#insertHelper(this.root, val);\n}\n\n/* Insert a node recursively (helper method) */\n#insertHelper(node, val) {\n    if (node === null) return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node.val) node.left = this.#insertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = this.#insertHelper(node.right, val);\n    else return node; // Return without inserting a duplicate node\n    this.#updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = this.#rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.ts
    /* Insert a node */\ninsert(val: number): void {\n    this.root = this.insertHelper(this.root, val);\n}\n\n/* Insert a node recursively (helper method) */\ninsertHelper(node: TreeNode, val: number): TreeNode {\n    if (node === null) return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node.val) {\n        node.left = this.insertHelper(node.left, val);\n    } else if (val > node.val) {\n        node.right = this.insertHelper(node.right, val);\n    } else {\n        return node; // Return without inserting a duplicate node\n    }\n    this.updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = this.rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.dart
    /* Insert a node */\nvoid insert(int val) {\n  root = insertHelper(root, val);\n}\n\n/* Insert a node recursively (helper method) */\nTreeNode? insertHelper(TreeNode? node, int val) {\n  if (node == null) return TreeNode(val);\n  /* 1. Find the insertion position and insert the node */\n  if (val < node.val)\n    node.left = insertHelper(node.left, val);\n  else if (val > node.val)\n    node.right = insertHelper(node.right, val);\n  else\n    return node; // Return without inserting a duplicate node\n  updateHeight(node); // Update the node height\n  /* 2. Perform rotations to restore balance in this subtree */\n  node = rotate(node);\n  // Return the root of the subtree\n  return node;\n}\n
    avl_tree.rs
    /* Insert a node */\nfn insert(&mut self, val: i32) {\n    self.root = Self::insert_helper(self.root.clone(), val);\n}\n\n/* Insert a node recursively (helper method) */\nfn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {\n    match node {\n        Some(mut node) => {\n            /* 1. Find the insertion position and insert the node */\n            match {\n                let node_val = node.borrow().val;\n                node_val\n            }\n            .cmp(&val)\n            {\n                Ordering::Greater => {\n                    let left = node.borrow().left.clone();\n                    node.borrow_mut().left = Self::insert_helper(left, val);\n                }\n                Ordering::Less => {\n                    let right = node.borrow().right.clone();\n                    node.borrow_mut().right = Self::insert_helper(right, val);\n                }\n                Ordering::Equal => {\n                    return Some(node); // Return without inserting a duplicate node\n                }\n            }\n            Self::update_height(Some(node.clone())); // Update the node height\n\n            /* 2. Perform rotations to restore balance in this subtree */\n            node = Self::rotate(Some(node)).unwrap();\n            // Return the root of the subtree\n            Some(node)\n        }\n        None => Some(TreeNode::new(val)),\n    }\n}\n
    avl_tree.c
    /* Insert a node */\nvoid insert(AVLTree *tree, int val) {\n    tree->root = insertHelper(tree->root, val);\n}\n\n/* Recursively insert a node (helper function) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n    if (node == NULL) {\n        return newTreeNode(val);\n    }\n    /* 1. Find the insertion position and insert the node */\n    if (val < node->val) {\n        node->left = insertHelper(node->left, val);\n    } else if (val > node->val) {\n        node->right = insertHelper(node->right, val);\n    } else {\n        // Return without inserting a duplicate node\n        return node;\n    }\n    // Update the node height\n    updateHeight(node);\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.kt
    /* Insert a node */\nfun insert(_val: Int) {\n    root = insertHelper(root, _val)\n}\n\n/* Insert a node recursively (helper method) */\nfun insertHelper(n: TreeNode?, _val: Int): TreeNode {\n    if (n == null)\n        return TreeNode(_val)\n    var node = n\n    /* 1. Find the insertion position and insert the node */\n    if (_val < node._val)\n        node.left = insertHelper(node.left, _val)\n    else if (_val > node._val)\n        node.right = insertHelper(node.right, _val)\n    else\n        return node // Return without inserting a duplicate node\n    updateHeight(node) // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.rb
    ### Insert a node ###\ndef insert(val)\n  @root = insert_helper(@root, val)\nend\n\n### Insert a node recursively (helper method)###\ndef insert_helper(node, val)\n  return TreeNode.new(val) if node.nil?\n  # 1. Find the insertion position and insert the node\n  if val < node.val\n    node.left = insert_helper(node.left, val)\n  elsif val > node.val\n    node.right = insert_helper(node.right, val)\n  else\n    # Return without inserting a duplicate node\n    return node\n  end\n  # Update the node height\n  update_height(node)\n  # 2. Perform rotations to restore balance in this subtree\n  rotate(node)\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-deleting-a-node","level":3,"title":"2.   Deleting a node","text":"

    Similarly, extend binary-search-tree deletion with rotations from the bottom up to restore balance at every affected node:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def remove(self, val: int):\n    \"\"\"Remove a node\"\"\"\n    self._root = self.remove_helper(self._root, val)\n\ndef remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None:\n    \"\"\"Remove a node recursively (helper method)\"\"\"\n    if node is None:\n        return None\n    # 1. Find and remove the node\n    if val < node.val:\n        node.left = self.remove_helper(node.left, val)\n    elif val > node.val:\n        node.right = self.remove_helper(node.right, val)\n    else:\n        if node.left is None or node.right is None:\n            child = node.left or node.right\n            # Number of children = 0: remove node and return\n            if child is None:\n                return None\n            # Number of children = 1: remove node directly\n            else:\n                node = child\n        else:\n            # Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            temp = node.right\n            while temp.left is not None:\n                temp = temp.left\n            node.right = self.remove_helper(node.right, temp.val)\n            node.val = temp.val\n    # Update the node height\n    self.update_height(node)\n    # 2. Perform rotations to restore balance in this subtree\n    return self.rotate(node)\n
    avl_tree.cpp
    /* Remove a node */\nvoid remove(int val) {\n    root = removeHelper(root, val);\n}\n\n/* Remove a node recursively (helper method) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n    if (node == nullptr)\n        return nullptr;\n    /* 1. Find and remove the node */\n    if (val < node->val)\n        node->left = removeHelper(node->left, val);\n    else if (val > node->val)\n        node->right = removeHelper(node->right, val);\n    else {\n        if (node->left == nullptr || node->right == nullptr) {\n            TreeNode *child = node->left != nullptr ? node->left : node->right;\n            // Number of children = 0: remove node and return\n            if (child == nullptr) {\n                delete node;\n                return nullptr;\n            }\n            // Number of children = 1: remove node directly\n            else {\n                delete node;\n                node = child;\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            TreeNode *temp = node->right;\n            while (temp->left != nullptr) {\n                temp = temp->left;\n            }\n            int tempVal = temp->val;\n            node->right = removeHelper(node->right, temp->val);\n            node->val = tempVal;\n        }\n    }\n    updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.java
    /* Remove a node */\nvoid remove(int val) {\n    root = removeHelper(root, val);\n}\n\n/* Remove a node recursively (helper method) */\nTreeNode removeHelper(TreeNode node, int val) {\n    if (node == null)\n        return null;\n    /* 1. Find and remove the node */\n    if (val < node.val)\n        node.left = removeHelper(node.left, val);\n    else if (val > node.val)\n        node.right = removeHelper(node.right, val);\n    else {\n        if (node.left == null || node.right == null) {\n            TreeNode child = node.left != null ? node.left : node.right;\n            // Number of children = 0: remove node and return\n            if (child == null)\n                return null;\n            // Number of children = 1: remove node directly\n            else\n                node = child;\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            TreeNode temp = node.right;\n            while (temp.left != null) {\n                temp = temp.left;\n            }\n            node.right = removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the subtree root\n    return node;\n}\n
    avl_tree.cs
    /* Remove a node */\nvoid Remove(int val) {\n    root = RemoveHelper(root, val);\n}\n\n/* Remove a node recursively (helper method) */\nTreeNode? RemoveHelper(TreeNode? node, int val) {\n    if (node == null) return null;\n    /* 1. Find and remove the node */\n    if (val < node.val)\n        node.left = RemoveHelper(node.left, val);\n    else if (val > node.val)\n        node.right = RemoveHelper(node.right, val);\n    else {\n        if (node.left == null || node.right == null) {\n            TreeNode? child = node.left ?? node.right;\n            // Number of children = 0: remove node and return\n            if (child == null)\n                return null;\n            // Number of children = 1: remove node directly\n            else\n                node = child;\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            TreeNode? temp = node.right;\n            while (temp.left != null) {\n                temp = temp.left;\n            }\n            node.right = RemoveHelper(node.right, temp.val!.Value);\n            node.val = temp.val;\n        }\n    }\n    UpdateHeight(node);  // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = Rotate(node);\n    // Return the subtree root\n    return node;\n}\n
    avl_tree.go
    /* Remove a node */\nfunc (t *aVLTree) remove(val int) {\n    t.root = t.removeHelper(t.root, val)\n}\n\n/* Remove a node recursively (helper function) */\nfunc (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode {\n    if node == nil {\n        return nil\n    }\n    /* 1. Find and remove the node */\n    if val < node.Val.(int) {\n        node.Left = t.removeHelper(node.Left, val)\n    } else if val > node.Val.(int) {\n        node.Right = t.removeHelper(node.Right, val)\n    } else {\n        if node.Left == nil || node.Right == nil {\n            child := node.Left\n            if node.Right != nil {\n                child = node.Right\n            }\n            if child == nil {\n                // Number of children = 0: remove node and return\n                return nil\n            } else {\n                // Number of children = 1: remove node directly\n                node = child\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            temp := node.Right\n            for temp.Left != nil {\n                temp = temp.Left\n            }\n            node.Right = t.removeHelper(node.Right, temp.Val.(int))\n            node.Val = temp.Val\n        }\n    }\n    // Update the node height\n    t.updateHeight(node)\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = t.rotate(node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.swift
    /* Remove a node */\nfunc remove(val: Int) {\n    root = removeHelper(node: root, val: val)\n}\n\n/* Remove a node recursively (helper method) */\nfunc removeHelper(node: TreeNode?, val: Int) -> TreeNode? {\n    var node = node\n    if node == nil {\n        return nil\n    }\n    /* 1. Find and remove the node */\n    if val < node!.val {\n        node?.left = removeHelper(node: node?.left, val: val)\n    } else if val > node!.val {\n        node?.right = removeHelper(node: node?.right, val: val)\n    } else {\n        if node?.left == nil || node?.right == nil {\n            let child = node?.left ?? node?.right\n            // Number of children = 0: remove node and return\n            if child == nil {\n                return nil\n            }\n            // Number of children = 1: remove node directly\n            else {\n                node = child\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            var temp = node?.right\n            while temp?.left != nil {\n                temp = temp?.left\n            }\n            node?.right = removeHelper(node: node?.right, val: temp!.val)\n            node?.val = temp!.val\n        }\n    }\n    updateHeight(node: node) // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node: node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.js
    /* Remove a node */\nremove(val) {\n    this.root = this.#removeHelper(this.root, val);\n}\n\n/* Remove a node recursively (helper method) */\n#removeHelper(node, val) {\n    if (node === null) return null;\n    /* 1. Find and remove the node */\n    if (val < node.val) node.left = this.#removeHelper(node.left, val);\n    else if (val > node.val)\n        node.right = this.#removeHelper(node.right, val);\n    else {\n        if (node.left === null || node.right === null) {\n            const child = node.left !== null ? node.left : node.right;\n            // Number of children = 0: remove node and return\n            if (child === null) return null;\n            // Number of children = 1: remove node directly\n            else node = child;\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            let temp = node.right;\n            while (temp.left !== null) {\n                temp = temp.left;\n            }\n            node.right = this.#removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    this.#updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = this.#rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.ts
    /* Remove a node */\nremove(val: number): void {\n    this.root = this.removeHelper(this.root, val);\n}\n\n/* Remove a node recursively (helper method) */\nremoveHelper(node: TreeNode, val: number): TreeNode {\n    if (node === null) return null;\n    /* 1. Find and remove the node */\n    if (val < node.val) {\n        node.left = this.removeHelper(node.left, val);\n    } else if (val > node.val) {\n        node.right = this.removeHelper(node.right, val);\n    } else {\n        if (node.left === null || node.right === null) {\n            const child = node.left !== null ? node.left : node.right;\n            // Number of children = 0: remove node and return\n            if (child === null) {\n                return null;\n            } else {\n                // Number of children = 1: remove node directly\n                node = child;\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            let temp = node.right;\n            while (temp.left !== null) {\n                temp = temp.left;\n            }\n            node.right = this.removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    this.updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = this.rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.dart
    /* Remove a node */\nvoid remove(int val) {\n  root = removeHelper(root, val);\n}\n\n/* Remove a node recursively (helper method) */\nTreeNode? removeHelper(TreeNode? node, int val) {\n  if (node == null) return null;\n  /* 1. Find and remove the node */\n  if (val < node.val)\n    node.left = removeHelper(node.left, val);\n  else if (val > node.val)\n    node.right = removeHelper(node.right, val);\n  else {\n    if (node.left == null || node.right == null) {\n      TreeNode? child = node.left ?? node.right;\n      // Number of children = 0: remove node and return\n      if (child == null)\n        return null;\n      // Number of children = 1: remove node directly\n      else\n        node = child;\n    } else {\n      // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n      TreeNode? temp = node.right;\n      while (temp!.left != null) {\n        temp = temp.left;\n      }\n      node.right = removeHelper(node.right, temp.val);\n      node.val = temp.val;\n    }\n  }\n  updateHeight(node); // Update the node height\n  /* 2. Perform rotations to restore balance in this subtree */\n  node = rotate(node);\n  // Return the root of the subtree\n  return node;\n}\n
    avl_tree.rs
    /* Remove a node */\nfn remove(&self, val: i32) {\n    Self::remove_helper(self.root.clone(), val);\n}\n\n/* Remove a node recursively (helper method) */\nfn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {\n    match node {\n        Some(mut node) => {\n            /* 1. Find and remove the node */\n            if val < node.borrow().val {\n                let left = node.borrow().left.clone();\n                node.borrow_mut().left = Self::remove_helper(left, val);\n            } else if val > node.borrow().val {\n                let right = node.borrow().right.clone();\n                node.borrow_mut().right = Self::remove_helper(right, val);\n            } else if node.borrow().left.is_none() || node.borrow().right.is_none() {\n                let child = if node.borrow().left.is_some() {\n                    node.borrow().left.clone()\n                } else {\n                    node.borrow().right.clone()\n                };\n                match child {\n                    // Number of children = 0: remove node and return\n                    None => {\n                        return None;\n                    }\n                    // Number of children = 1: remove node directly\n                    Some(child) => node = child,\n                }\n            } else {\n                // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n                let mut temp = node.borrow().right.clone().unwrap();\n                loop {\n                    let temp_left = temp.borrow().left.clone();\n                    if temp_left.is_none() {\n                        break;\n                    }\n                    temp = temp_left.unwrap();\n                }\n                let right = node.borrow().right.clone();\n                node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val);\n                node.borrow_mut().val = temp.borrow().val;\n            }\n            Self::update_height(Some(node.clone())); // Update the node height\n\n            /* 2. Perform rotations to restore balance in this subtree */\n            node = Self::rotate(Some(node)).unwrap();\n            // Return the root of the subtree\n            Some(node)\n        }\n        None => None,\n    }\n}\n
    avl_tree.c
    /* Remove a node */\n// The name remove conflicts with the function declared in stdio.h\nvoid removeItem(AVLTree *tree, int val) {\n    TreeNode *root = removeHelper(tree->root, val);\n}\n\n/* Recursively remove a node (helper function) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n    TreeNode *child, *grandChild;\n    if (node == NULL) {\n        return NULL;\n    }\n    /* 1. Find and remove the node */\n    if (val < node->val) {\n        node->left = removeHelper(node->left, val);\n    } else if (val > node->val) {\n        node->right = removeHelper(node->right, val);\n    } else {\n        if (node->left == NULL || node->right == NULL) {\n            child = node->left;\n            if (node->right != NULL) {\n                child = node->right;\n            }\n            // Number of children = 0: remove node and return\n            if (child == NULL) {\n                return NULL;\n            } else {\n                // Number of children = 1: remove node directly\n                node = child;\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            TreeNode *temp = node->right;\n            while (temp->left != NULL) {\n                temp = temp->left;\n            }\n            int tempVal = temp->val;\n            node->right = removeHelper(node->right, temp->val);\n            node->val = tempVal;\n        }\n    }\n    // Update the node height\n    updateHeight(node);\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.kt
    /* Remove a node */\nfun remove(_val: Int) {\n    root = removeHelper(root, _val)\n}\n\n/* Remove a node recursively (helper method) */\nfun removeHelper(n: TreeNode?, _val: Int): TreeNode? {\n    var node = n ?: return null\n    /* 1. Find and remove the node */\n    if (_val < node._val)\n        node.left = removeHelper(node.left, _val)\n    else if (_val > node._val)\n        node.right = removeHelper(node.right, _val)\n    else {\n        if (node.left == null || node.right == null) {\n            val child = if (node.left != null)\n                node.left\n            else\n                node.right\n            // Number of children = 0: remove node and return\n            if (child == null)\n                return null\n            // Number of children = 1: remove node directly\n            else\n                node = child\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            var temp = node.right\n            while (temp!!.left != null) {\n                temp = temp.left\n            }\n            node.right = removeHelper(node.right, temp._val)\n            node._val = temp._val\n        }\n    }\n    updateHeight(node) // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.rb
    ### Remove a node ###\ndef remove(val)\n  @root = remove_helper(@root, val)\nend\n\n### Remove a node recursively (helper method)###\ndef remove_helper(node, val)\n  return if node.nil?\n  # 1. Find and remove the node\n  if val < node.val\n    node.left = remove_helper(node.left, val)\n  elsif val > node.val\n    node.right = remove_helper(node.right, val)\n  else\n    if node.left.nil? || node.right.nil?\n      child = node.left || node.right\n      # Number of children = 0: remove node and return\n      return if child.nil?\n      # Number of children = 1: remove node directly\n      node = child\n    else\n      # Number of children = 2: remove the inorder successor and use its value to replace the current node value\n      temp = node.right\n      while !temp.left.nil?\n        temp = temp.left\n      end\n      node.right = remove_helper(node.right, temp.val)\n      node.val = temp.val\n    end\n  end\n  # Update the node height\n  update_height(node)\n  # 2. Perform rotations to restore balance in this subtree\n  rotate(node)\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3-finding-a-node","level":3,"title":"3.   Finding a node","text":"

    Lookup is identical to binary-search-tree lookup, so we will not repeat it here.

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#754-typical-applications","level":2,"title":"7.5.4   Typical applications","text":"
    • Organizing and storing large datasets, particularly when lookups are frequent and insertions and deletions are less frequent.
    • Building database indexing systems.
    • Red-black trees are another common kind of balanced binary search tree. Their balance requirements are looser than those of AVL trees, so insertion and deletion need fewer rotations and are more efficient on average.
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/binary_search_tree/","level":1,"title":"7.4   Binary search trees","text":"

    A binary search tree satisfies the following conditions, as shown below.

    1. Every value in the left subtree is \\(<\\) the root value, which is \\(<\\) every value in the right subtree.
    2. The left and right subtrees of every node are also binary search trees and satisfy condition 1..

    Figure 7-16   A binary search tree

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#741-binary-search-tree-operations","level":2,"title":"7.4.1   Binary search tree operations","text":"

    We encapsulate the tree in a BinarySearchTree class with a member variable root pointing to its root node.

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#1-finding-a-node","level":3,"title":"1.   Finding a node","text":"

    To find a target value num, use the tree's ordering property. As shown below, initialize a node reference cur at root and repeatedly compare cur.val with num.

    • If cur.val < num, the target lies in the right subtree, so set cur = cur.right.
    • If cur.val > num, the target lies in the left subtree, so set cur = cur.left.
    • If cur.val = num, the target has been found; exit the loop and return the node.
    <1><2><3><4>

    Figure 7-17   Finding a node in a binary search tree

    Searching a binary search tree follows the same principle as binary search: each round eliminates half the possibilities. The number of iterations is at most the tree's height, giving \\(O(\\log n)\\) time when the tree is balanced. An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
    def search(self, num: int) -> TreeNode | None:\n    \"\"\"Find a node\"\"\"\n    cur = self._root\n    # Search in a loop, stopping if cur becomes None\n    while cur is not None:\n        # If present, the target node is in the right subtree of cur\n        if cur.val < num:\n            cur = cur.right\n        # If present, the target node is in the left subtree of cur\n        elif cur.val > num:\n            cur = cur.left\n        # The target node was found; exit the loop\n        else:\n            break\n    return cur\n
    binary_search_tree.cpp
    /* Find a node */\nTreeNode *search(int num) {\n    TreeNode *cur = root;\n    // Continue searching while the current node pointer is not null\n    while (cur != nullptr) {\n        // The target node lies in the right subtree of cur\n        if (cur->val < num)\n            cur = cur->right;\n        // The target node lies in the left subtree of cur\n        else if (cur->val > num)\n            cur = cur->left;\n        // The target node was found; exit the loop\n        else\n            break;\n    }\n    // Return the target node, or nullptr if it was not found\n    return cur;\n}\n
    binary_search_tree.java
    /* Find a node */\nTreeNode search(int num) {\n    TreeNode cur = root;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != null) {\n        // The target node lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The target node lies in the left subtree of cur\n        else if (cur.val > num)\n            cur = cur.left;\n        // The target node was found; exit the loop\n        else\n            break;\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.cs
    /* Find a node */\nTreeNode? Search(int num) {\n    TreeNode? cur = root;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur != null) {\n        // The target node lies in the right subtree of cur\n        if (cur.val < num) cur =\n            cur.right;\n        // The target node lies in the left subtree of cur\n        else if (cur.val > num)\n            cur = cur.left;\n        // The target node was found; exit the loop\n        else\n            break;\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.go
    /* Find a node */\nfunc (bst *binarySearchTree) search(num int) *TreeNode {\n    node := bst.root\n    // Continue searching while the current node is not nil\n    for node != nil {\n        if node.Val.(int) < num {\n            // If the target node exists, it lies in the right subtree of node\n            node = node.Right\n        } else if node.Val.(int) > num {\n            // If the target node exists, it lies in the left subtree of node\n            node = node.Left\n        } else {\n            // The target node was found; exit the loop\n            break\n        }\n    }\n    // Return the target node, or nil if it was not found\n    return node\n}\n
    binary_search_tree.swift
    /* Find a node */\nfunc search(num: Int) -> TreeNode? {\n    var cur = root\n    // Search in a loop, stopping if cur becomes nil\n    while cur != nil {\n        // If present, the target node lies in the right subtree of cur\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // If present, the target node lies in the left subtree of cur\n        else if cur!.val > num {\n            cur = cur?.left\n        }\n        // The target node was found; exit the loop\n        else {\n            break\n        }\n    }\n    // Return the target node, or nil if it was not found\n    return cur\n}\n
    binary_search_tree.js
    /* Find a node */\nsearch(num) {\n    let cur = this.root;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur !== null) {\n        // The target node lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The target node lies in the left subtree of cur\n        else if (cur.val > num) cur = cur.left;\n        // The target node was found; exit the loop\n        else break;\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.ts
    /* Find a node */\nsearch(num: number): TreeNode | null {\n    let cur = this.root;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur !== null) {\n        // The target node lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The target node lies in the left subtree of cur\n        else if (cur.val > num) cur = cur.left;\n        // The target node was found; exit the loop\n        else break;\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.dart
    /* Find a node */\nTreeNode? search(int _num) {\n  TreeNode? cur = _root;\n  // Search in a loop, stopping if a null reference is reached\n  while (cur != null) {\n    // The target node lies in the right subtree of cur\n    if (cur.val < _num)\n      cur = cur.right;\n    // The target node lies in the left subtree of cur\n    else if (cur.val > _num)\n      cur = cur.left;\n    // The target node was found; exit the loop\n    else\n      break;\n  }\n  // Return the target node\n  return cur;\n}\n
    binary_search_tree.rs
    /* Find a node */\npub fn search(&self, num: i32) -> OptionTreeNodeRc {\n    let mut cur = self.root.clone();\n    // Search while cur refers to a node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // The target node lies in the right subtree of cur\n            Ordering::Greater => cur = node.borrow().right.clone(),\n            // The target node lies in the left subtree of cur\n            Ordering::Less => cur = node.borrow().left.clone(),\n            // The target node was found; exit the loop\n            Ordering::Equal => break,\n        }\n    }\n\n    // Return the matching node, or None if no match was found\n    cur\n}\n
    binary_search_tree.c
    /* Find a node */\nTreeNode *search(BinarySearchTree *bst, int num) {\n    TreeNode *cur = bst->root;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != NULL) {\n        if (cur->val < num) {\n            // The target node lies in the right subtree of cur\n            cur = cur->right;\n        } else if (cur->val > num) {\n            // The target node lies in the left subtree of cur\n            cur = cur->left;\n        } else {\n            // The target node was found; exit the loop\n            break;\n        }\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.kt
    /* Find a node */\nfun search(num: Int): TreeNode? {\n    var cur = root\n    // Continue searching while cur is not null\n    while (cur != null) {\n        // The target node lies in the right subtree of cur\n        cur = if (cur._val < num)\n            cur.right\n        // The target node lies in the left subtree of cur\n        else if (cur._val > num)\n            cur.left\n        // The target node was found; exit the loop\n        else\n            break\n    }\n    // Return the target node\n    return cur\n}\n
    binary_search_tree.rb
    ### Find a node ###\ndef search(num)\n  cur = @root\n\n  # Search in a loop, stopping if cur becomes nil\n  while !cur.nil?\n    # The target node lies in the right subtree of cur\n    if cur.val < num\n      cur = cur.right\n    # The target node lies in the left subtree of cur\n    elsif cur.val > num\n      cur = cur.left\n    # The target node was found; exit the loop\n    else\n      break\n    end\n  end\n\n  cur\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#2-inserting-a-node","level":3,"title":"2.   Inserting a node","text":"

    To insert num while preserving the ordering “left subtree < root < right subtree,” follow the process below.

    1. Find the insertion position: as in a search, start at the root and move down according to comparisons with num. Stop when the traversal passes a leaf and reaches None.
    2. Insert at that position: create a node containing num and place it at the None position.

    Figure 7-18   Inserting a node into a binary search tree

    Keep two details in mind when implementing this.

    • Duplicate values are not allowed under this definition. If the value already exists, return without inserting it.
    • Keep a reference pre to the node from the previous iteration. When the traversal reaches None, pre gives its parent so that the new node can be attached.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
    def insert(self, num: int):\n    \"\"\"Insert a node\"\"\"\n    # If the tree is empty, initialize the root node\n    if self._root is None:\n        self._root = TreeNode(num)\n        return\n    # Search in a loop, stopping if cur becomes None\n    cur, pre = self._root, None\n    while cur is not None:\n        # Return immediately if a duplicate node is found\n        if cur.val == num:\n            return\n        pre = cur\n        # The insertion position lies in the right subtree of cur\n        if cur.val < num:\n            cur = cur.right\n        # The insertion position lies in the left subtree of cur\n        else:\n            cur = cur.left\n    # Insert a node\n    node = TreeNode(num)\n    if pre.val < num:\n        pre.right = node\n    else:\n        pre.left = node\n
    binary_search_tree.cpp
    /* Insert a node */\nvoid insert(int num) {\n    // If the tree is empty, initialize the root node\n    if (root == nullptr) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode *cur = root, *pre = nullptr;\n    // Continue searching while the current node pointer is not null\n    while (cur != nullptr) {\n        // Return immediately if a duplicate node is found\n        if (cur->val == num)\n            return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur->val < num)\n            cur = cur->right;\n        // The insertion position lies in the left subtree of cur\n        else\n            cur = cur->left;\n    }\n    // Insert a node\n    TreeNode *node = new TreeNode(num);\n    if (pre->val < num)\n        pre->right = node;\n    else\n        pre->left = node;\n}\n
    binary_search_tree.java
    /* Insert a node */\nvoid insert(int num) {\n    // If the tree is empty, initialize the root node\n    if (root == null) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode cur = root, pre = null;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != null) {\n        // Return immediately if a duplicate node is found\n        if (cur.val == num)\n            return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The insertion position lies in the left subtree of cur\n        else\n            cur = cur.left;\n    }\n    // Insert a node\n    TreeNode node = new TreeNode(num);\n    if (pre.val < num)\n        pre.right = node;\n    else\n        pre.left = node;\n}\n
    binary_search_tree.cs
    /* Insert a node */\nvoid Insert(int num) {\n    // If the tree is empty, initialize the root node\n    if (root == null) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode? cur = root, pre = null;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur != null) {\n        // Return immediately if a duplicate node is found\n        if (cur.val == num)\n            return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The insertion position lies in the left subtree of cur\n        else\n            cur = cur.left;\n    }\n\n    // Insert a node\n    TreeNode node = new(num);\n    if (pre != null) {\n        if (pre.val < num)\n            pre.right = node;\n        else\n            pre.left = node;\n    }\n}\n
    binary_search_tree.go
    /* Insert a node */\nfunc (bst *binarySearchTree) insert(num int) {\n    cur := bst.root\n    // If the tree is empty, initialize the root node\n    if cur == nil {\n        bst.root = NewTreeNode(num)\n        return\n    }\n    // Track the parent of the node to insert\n    var pre *TreeNode = nil\n    // Continue searching while the current node is not nil\n    for cur != nil {\n        if cur.Val == num {\n            return\n        }\n        pre = cur\n        if cur.Val.(int) < num {\n            cur = cur.Right\n        } else {\n            cur = cur.Left\n        }\n    }\n    // Insert a node\n    node := NewTreeNode(num)\n    if pre.Val.(int) < num {\n        pre.Right = node\n    } else {\n        pre.Left = node\n    }\n}\n
    binary_search_tree.swift
    /* Insert a node */\nfunc insert(num: Int) {\n    // If the tree is empty, initialize the root node\n    if root == nil {\n        root = TreeNode(x: num)\n        return\n    }\n    var cur = root\n    var pre: TreeNode?\n    // Search in a loop, stopping if cur becomes nil\n    while cur != nil {\n        // Return immediately if a duplicate node is found\n        if cur!.val == num {\n            return\n        }\n        pre = cur\n        // The insertion position lies in the right subtree of cur\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // The insertion position lies in the left subtree of cur\n        else {\n            cur = cur?.left\n        }\n    }\n    // Insert a node\n    let node = TreeNode(x: num)\n    if pre!.val < num {\n        pre?.right = node\n    } else {\n        pre?.left = node\n    }\n}\n
    binary_search_tree.js
    /* Insert a node */\ninsert(num) {\n    // If the tree is empty, initialize the root node\n    if (this.root === null) {\n        this.root = new TreeNode(num);\n        return;\n    }\n    let cur = this.root,\n        pre = null;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur !== null) {\n        // Return immediately if a duplicate node is found\n        if (cur.val === num) return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The insertion position lies in the left subtree of cur\n        else cur = cur.left;\n    }\n    // Insert a node\n    const node = new TreeNode(num);\n    if (pre.val < num) pre.right = node;\n    else pre.left = node;\n}\n
    binary_search_tree.ts
    /* Insert a node */\ninsert(num: number): void {\n    // If the tree is empty, initialize the root node\n    if (this.root === null) {\n        this.root = new TreeNode(num);\n        return;\n    }\n    let cur: TreeNode | null = this.root,\n        pre: TreeNode | null = null;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur !== null) {\n        // Return immediately if a duplicate node is found\n        if (cur.val === num) return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The insertion position lies in the left subtree of cur\n        else cur = cur.left;\n    }\n    // Insert a node\n    const node = new TreeNode(num);\n    if (pre!.val < num) pre!.right = node;\n    else pre!.left = node;\n}\n
    binary_search_tree.dart
    /* Insert a node */\nvoid insert(int _num) {\n  // If the tree is empty, initialize the root node\n  if (_root == null) {\n    _root = TreeNode(_num);\n    return;\n  }\n  TreeNode? cur = _root;\n  TreeNode? pre = null;\n  // Search in a loop, stopping if a null reference is reached\n  while (cur != null) {\n    // Return immediately if a duplicate node is found\n    if (cur.val == _num) return;\n    pre = cur;\n    // The insertion position lies in the right subtree of cur\n    if (cur.val < _num)\n      cur = cur.right;\n    // The insertion position lies in the left subtree of cur\n    else\n      cur = cur.left;\n  }\n  // Insert a node\n  TreeNode? node = TreeNode(_num);\n  if (pre!.val < _num)\n    pre.right = node;\n  else\n    pre.left = node;\n}\n
    binary_search_tree.rs
    /* Insert a node */\npub fn insert(&mut self, num: i32) {\n    // If the tree is empty, initialize the root node\n    if self.root.is_none() {\n        self.root = Some(TreeNode::new(num));\n        return;\n    }\n    let mut cur = self.root.clone();\n    let mut pre = None;\n    // Search while cur refers to a node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // Return immediately if a duplicate node is found\n            Ordering::Equal => return,\n            // The insertion position lies in the right subtree of cur\n            Ordering::Greater => {\n                pre = cur.clone();\n                cur = node.borrow().right.clone();\n            }\n            // The insertion position lies in the left subtree of cur\n            Ordering::Less => {\n                pre = cur.clone();\n                cur = node.borrow().left.clone();\n            }\n        }\n    }\n    // Insert a node\n    let pre = pre.unwrap();\n    let node = Some(TreeNode::new(num));\n    if num > pre.borrow().val {\n        pre.borrow_mut().right = node;\n    } else {\n        pre.borrow_mut().left = node;\n    }\n}\n
    binary_search_tree.c
    /* Insert a node */\nvoid insert(BinarySearchTree *bst, int num) {\n    // If the tree is empty, initialize the root node\n    if (bst->root == NULL) {\n        bst->root = newTreeNode(num);\n        return;\n    }\n    TreeNode *cur = bst->root, *pre = NULL;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != NULL) {\n        // Return immediately if a duplicate node is found\n        if (cur->val == num) {\n            return;\n        }\n        pre = cur;\n        if (cur->val < num) {\n            // The insertion position lies in the right subtree of cur\n            cur = cur->right;\n        } else {\n            // The insertion position lies in the left subtree of cur\n            cur = cur->left;\n        }\n    }\n    // Insert a node\n    TreeNode *node = newTreeNode(num);\n    if (pre->val < num) {\n        pre->right = node;\n    } else {\n        pre->left = node;\n    }\n}\n
    binary_search_tree.kt
    /* Insert a node */\nfun insert(num: Int) {\n    // If the tree is empty, initialize the root node\n    if (root == null) {\n        root = TreeNode(num)\n        return\n    }\n    var cur = root\n    var pre: TreeNode? = null\n    // Continue searching while cur is not null\n    while (cur != null) {\n        // Return immediately if a duplicate node is found\n        if (cur._val == num)\n            return\n        pre = cur\n        // The insertion position lies in the right subtree of cur\n        cur = if (cur._val < num)\n            cur.right\n        // The insertion position lies in the left subtree of cur\n        else\n            cur.left\n    }\n    // Insert a node\n    val node = TreeNode(num)\n    if (pre?._val!! < num)\n        pre.right = node\n    else\n        pre.left = node\n}\n
    binary_search_tree.rb
    ### Insert a node ###\ndef insert(num)\n  # If the tree is empty, initialize the root node\n  if @root.nil?\n    @root = TreeNode.new(num)\n    return\n  end\n\n  # Search in a loop, stopping if cur becomes nil\n  cur, pre = @root, nil\n  while !cur.nil?\n    # Return immediately if a duplicate node is found\n    return if cur.val == num\n\n    pre = cur\n    # The insertion position lies in the right subtree of cur\n    if cur.val < num\n      cur = cur.right\n    # The insertion position lies in the left subtree of cur\n    else\n      cur = cur.left\n    end\n  end\n\n  # Insert a node\n  node = TreeNode.new(num)\n  if pre.val < num\n    pre.right = node\n  else\n    pre.left = node\n  end\nend\n
    Visualize execution

    View full screen >

    Like lookup, insertion takes \\(O(\\log n)\\) time.

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#3-deleting-a-node","level":3,"title":"3.   Deleting a node","text":"

    First locate the target node, then remove it while preserving “left subtree < root < right subtree.” The procedure depends on whether the target has 0, 1, or 2 children.

    As shown below, a node of degree \\(0\\) is a leaf and can be removed directly.

    Figure 7-19   Deleting a node of degree 0

    As shown below, replace a node of degree \\(1\\) with its only child.

    Figure 7-20   Deleting a node of degree 1

    A node of degree \\(2\\) cannot simply be removed; another node must replace it. To preserve “left subtree \\(<\\) root \\(<\\) right subtree,” use either the smallest node in the right subtree or the largest node in the left subtree.

    Suppose we choose the smallest node in the right subtree, the next node in inorder traversal. The process is shown below.

    1. Find the target node's successor in inorder traversal and call it tmp.
    2. Replace the target node's value with tmp's value, then recursively delete tmp from the tree.
    <1><2><3><4>

    Figure 7-21   Deleting a node of degree 2

    Deletion also takes \\(O(\\log n)\\) time: locating the target takes \\(O(\\log n)\\), and finding its inorder successor takes \\(O(\\log n)\\). An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
    def remove(self, num: int):\n    \"\"\"Remove a node\"\"\"\n    # Return immediately if the tree is empty\n    if self._root is None:\n        return\n    # Search in a loop, stopping if cur becomes None\n    cur, pre = self._root, None\n    while cur is not None:\n        # The node to remove was found; exit the loop\n        if cur.val == num:\n            break\n        pre = cur\n        # If present, the node to remove is in the right subtree of cur\n        if cur.val < num:\n            cur = cur.right\n        # If present, the node to remove is in the left subtree of cur\n        else:\n            cur = cur.left\n    # Return immediately if the node to remove does not exist\n    if cur is None:\n        return\n\n    # Number of children = 0 or 1\n    if cur.left is None or cur.right is None:\n        # With 0 or 1 child, child is None or that child, respectively\n        child = cur.left or cur.right\n        # Remove node cur\n        if cur != self._root:\n            if pre.left == cur:\n                pre.left = child\n            else:\n                pre.right = child\n        else:\n            # If the removed node is the root, assign the new root\n            self._root = child\n    # Number of children = 2\n    else:\n        # Get the inorder successor of cur\n        tmp: TreeNode = cur.right\n        while tmp.left is not None:\n            tmp = tmp.left\n        # Recursively remove node tmp\n        self.remove(tmp.val)\n        # Replace the value of cur with the value of tmp\n        cur.val = tmp.val\n
    binary_search_tree.cpp
    /* Remove a node */\nvoid remove(int num) {\n    // Return immediately if the tree is empty\n    if (root == nullptr)\n        return;\n    TreeNode *cur = root, *pre = nullptr;\n    // Continue searching while the current node pointer is not null\n    while (cur != nullptr) {\n        // The node to remove was found; exit the loop\n        if (cur->val == num)\n            break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur->val < num)\n            cur = cur->right;\n        // The node to remove lies in the left subtree of cur\n        else\n            cur = cur->left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == nullptr)\n        return;\n    // Number of children = 0 or 1\n    if (cur->left == nullptr || cur->right == nullptr) {\n        // With 0 or 1 child, child is nullptr or that child, respectively\n        TreeNode *child = cur->left != nullptr ? cur->left : cur->right;\n        // Remove node cur\n        if (cur != root) {\n            if (pre->left == cur)\n                pre->left = child;\n            else\n                pre->right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child;\n        }\n        // Free memory\n        delete cur;\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        TreeNode *tmp = cur->right;\n        while (tmp->left != nullptr) {\n            tmp = tmp->left;\n        }\n        int tmpVal = tmp->val;\n        // Recursively remove node tmp\n        remove(tmp->val);\n        // Replace the value of cur with the value of tmp\n        cur->val = tmpVal;\n    }\n}\n
    binary_search_tree.java
    /* Remove a node */\nvoid remove(int num) {\n    // Return immediately if the tree is empty\n    if (root == null)\n        return;\n    TreeNode cur = root, pre = null;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != null) {\n        // The node to remove was found; exit the loop\n        if (cur.val == num)\n            break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The node to remove lies in the left subtree of cur\n        else\n            cur = cur.left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == null)\n        return;\n    // Number of children = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        TreeNode child = cur.left != null ? cur.left : cur.right;\n        // Remove node cur\n        if (cur != root) {\n            if (pre.left == cur)\n                pre.left = child;\n            else\n                pre.right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child;\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        TreeNode tmp = cur.right;\n        while (tmp.left != null) {\n            tmp = tmp.left;\n        }\n        // Recursively remove node tmp\n        remove(tmp.val);\n        // Replace the value of cur with the value of tmp\n        cur.val = tmp.val;\n    }\n}\n
    binary_search_tree.cs
    /* Remove a node */\nvoid Remove(int num) {\n    // Return immediately if the tree is empty\n    if (root == null)\n        return;\n    TreeNode? cur = root, pre = null;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur != null) {\n        // The node to remove was found; exit the loop\n        if (cur.val == num)\n            break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The node to remove lies in the left subtree of cur\n        else\n            cur = cur.left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == null)\n        return;\n    // Number of children = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        TreeNode? child = cur.left ?? cur.right;\n        // Remove node cur\n        if (cur != root) {\n            if (pre!.left == cur)\n                pre.left = child;\n            else\n                pre.right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child;\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        TreeNode? tmp = cur.right;\n        while (tmp.left != null) {\n            tmp = tmp.left;\n        }\n        // Recursively remove node tmp\n        Remove(tmp.val!.Value);\n        // Replace the value of cur with the value of tmp\n        cur.val = tmp.val;\n    }\n}\n
    binary_search_tree.go
    /* Remove a node */\nfunc (bst *binarySearchTree) remove(num int) {\n    cur := bst.root\n    // Return immediately if the tree is empty\n    if cur == nil {\n        return\n    }\n    // Track the parent of the node to remove\n    var pre *TreeNode = nil\n    // Continue searching while the current node is not nil\n    for cur != nil {\n        if cur.Val == num {\n            break\n        }\n        pre = cur\n        if cur.Val.(int) < num {\n            // The node to remove is in the right subtree\n            cur = cur.Right\n        } else {\n            // The node to remove is in the left subtree\n            cur = cur.Left\n        }\n    }\n    // Return immediately if the node to remove does not exist\n    if cur == nil {\n        return\n    }\n    // The node has 0 or 1 child\n    if cur.Left == nil || cur.Right == nil {\n        var child *TreeNode = nil\n        // Get the child of the node to remove\n        if cur.Left != nil {\n            child = cur.Left\n        } else {\n            child = cur.Right\n        }\n        // Remove node cur\n        if cur != bst.root {\n            if pre.Left == cur {\n                pre.Left = child\n            } else {\n                pre.Right = child\n            }\n        } else {\n            // If the removed node is the root, assign the new root\n            bst.root = child\n        }\n        // The node has 2 children\n    } else {\n        // Get the inorder successor of cur, the node to remove\n        tmp := cur.Right\n        for tmp.Left != nil {\n            tmp = tmp.Left\n        }\n        // Recursively remove node tmp\n        bst.remove(tmp.Val.(int))\n        // Replace the value of cur with the value of tmp\n        cur.Val = tmp.Val\n    }\n}\n
    binary_search_tree.swift
    /* Remove a node */\nfunc remove(num: Int) {\n    // Return immediately if the tree is empty\n    if root == nil {\n        return\n    }\n    var cur = root\n    var pre: TreeNode?\n    // Search in a loop, stopping if cur becomes nil\n    while cur != nil {\n        // The node to remove was found; exit the loop\n        if cur!.val == num {\n            break\n        }\n        pre = cur\n        // If present, the node to remove lies in the right subtree of cur\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // If present, the node to remove lies in the left subtree of cur\n        else {\n            cur = cur?.left\n        }\n    }\n    // Return immediately if the node to remove does not exist\n    if cur == nil {\n        return\n    }\n    // Number of children = 0 or 1\n    if cur?.left == nil || cur?.right == nil {\n        // With 0 / 1 children, child is nil / the child node, respectively\n        let child = cur?.left ?? cur?.right\n        // Remove node cur\n        if cur !== root {\n            if pre?.left === cur {\n                pre?.left = child\n            } else {\n                pre?.right = child\n            }\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        var tmp = cur?.right\n        while tmp?.left != nil {\n            tmp = tmp?.left\n        }\n        // Recursively remove node tmp\n        remove(num: tmp!.val)\n        // Replace the value of cur with the value of tmp\n        cur?.val = tmp!.val\n    }\n}\n
    binary_search_tree.js
    /* Remove a node */\nremove(num) {\n    // Return immediately if the tree is empty\n    if (this.root === null) return;\n    let cur = this.root,\n        pre = null;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur !== null) {\n        // The node to remove was found; exit the loop\n        if (cur.val === num) break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The node to remove lies in the left subtree of cur\n        else cur = cur.left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur === null) return;\n    // Number of children = 0 or 1\n    if (cur.left === null || cur.right === null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        const child = cur.left !== null ? cur.left : cur.right;\n        // Remove node cur\n        if (cur !== this.root) {\n            if (pre.left === cur) pre.left = child;\n            else pre.right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            this.root = child;\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        let tmp = cur.right;\n        while (tmp.left !== null) {\n            tmp = tmp.left;\n        }\n        // Recursively remove node tmp\n        this.remove(tmp.val);\n        // Replace the value of cur with the value of tmp\n        cur.val = tmp.val;\n    }\n}\n
    binary_search_tree.ts
    /* Remove a node */\nremove(num: number): void {\n    // Return immediately if the tree is empty\n    if (this.root === null) return;\n    let cur: TreeNode | null = this.root,\n        pre: TreeNode | null = null;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur !== null) {\n        // The node to remove was found; exit the loop\n        if (cur.val === num) break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The node to remove lies in the left subtree of cur\n        else cur = cur.left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur === null) return;\n    // Number of children = 0 or 1\n    if (cur.left === null || cur.right === null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        const child: TreeNode | null =\n            cur.left !== null ? cur.left : cur.right;\n        // Remove node cur\n        if (cur !== this.root) {\n            if (pre!.left === cur) pre!.left = child;\n            else pre!.right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            this.root = child;\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        let tmp: TreeNode | null = cur.right;\n        while (tmp!.left !== null) {\n            tmp = tmp!.left;\n        }\n        // Recursively remove node tmp\n        this.remove(tmp!.val);\n        // Replace the value of cur with the value of tmp\n        cur.val = tmp!.val;\n    }\n}\n
    binary_search_tree.dart
    /* Remove a node */\nvoid remove(int _num) {\n  // Return immediately if the tree is empty\n  if (_root == null) return;\n  TreeNode? cur = _root;\n  TreeNode? pre = null;\n  // Search in a loop, stopping if a null reference is reached\n  while (cur != null) {\n    // The node to remove was found; exit the loop\n    if (cur.val == _num) break;\n    pre = cur;\n    // The node to remove lies in the right subtree of cur\n    if (cur.val < _num)\n      cur = cur.right;\n    // The node to remove lies in the left subtree of cur\n    else\n      cur = cur.left;\n  }\n  // Return if the node to remove does not exist\n  if (cur == null) return;\n  // Number of children = 0 or 1\n  if (cur.left == null || cur.right == null) {\n    // With 0 / 1 children, child is null / the child node, respectively\n    TreeNode? child = cur.left ?? cur.right;\n    // Remove node cur\n    if (cur != _root) {\n      if (pre!.left == cur)\n        pre.left = child;\n      else\n        pre.right = child;\n    } else {\n      // If the removed node is the root, assign the new root\n      _root = child;\n    }\n  } else {\n    // Number of children = 2\n    // Get the inorder successor of cur\n    TreeNode? tmp = cur.right;\n    while (tmp!.left != null) {\n      tmp = tmp.left;\n    }\n    // Recursively remove node tmp\n    remove(tmp.val);\n    // Replace the value of cur with the value of tmp\n    cur.val = tmp.val;\n  }\n}\n
    binary_search_tree.rs
    /* Remove a node */\npub fn remove(&mut self, num: i32) {\n    // Return immediately if the tree is empty\n    if self.root.is_none() {\n        return;\n    }\n    let mut cur = self.root.clone();\n    let mut pre = None;\n    // Search while cur refers to a node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // The node to remove was found; exit the loop\n            Ordering::Equal => break,\n            // The node to remove lies in the right subtree of cur\n            Ordering::Greater => {\n                pre = cur.clone();\n                cur = node.borrow().right.clone();\n            }\n            // The node to remove lies in the left subtree of cur\n            Ordering::Less => {\n                pre = cur.clone();\n                cur = node.borrow().left.clone();\n            }\n        }\n    }\n    // Return immediately if the node to remove does not exist\n    if cur.is_none() {\n        return;\n    }\n    let cur = cur.unwrap();\n    let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone());\n    match (left_child.clone(), right_child.clone()) {\n        // Number of children = 0 or 1\n        (None, None) | (Some(_), None) | (None, Some(_)) => {\n            // With 0 or 1 child, child is None or that child, respectively\n            let child = left_child.or(right_child);\n            let pre = pre.unwrap();\n            // Remove node cur\n            if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) {\n                let left = pre.borrow().left.clone();\n                if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) {\n                    pre.borrow_mut().left = child;\n                } else {\n                    pre.borrow_mut().right = child;\n                }\n            } else {\n                // If the removed node is the root, assign the new root\n                self.root = child;\n            }\n        }\n        // Number of children = 2\n        (Some(_), Some(_)) => {\n            // Get the inorder successor of cur\n            let mut tmp = cur.borrow().right.clone();\n            while let Some(node) = tmp.clone() {\n                if node.borrow().left.is_some() {\n                    tmp = node.borrow().left.clone();\n                } else {\n                    break;\n                }\n            }\n            let tmp_val = tmp.unwrap().borrow().val;\n            // Recursively remove node tmp\n            self.remove(tmp_val);\n            // Replace the value of cur with the value of tmp\n            cur.borrow_mut().val = tmp_val;\n        }\n    }\n}\n
    binary_search_tree.c
    /* Remove a node */\n// The name remove conflicts with the function declared in stdio.h\nvoid removeItem(BinarySearchTree *bst, int num) {\n    // Return immediately if the tree is empty\n    if (bst->root == NULL)\n        return;\n    TreeNode *cur = bst->root, *pre = NULL;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != NULL) {\n        // The node to remove was found; exit the loop\n        if (cur->val == num)\n            break;\n        pre = cur;\n        if (cur->val < num) {\n            // The node to remove is in cur's right subtree\n            cur = cur->right;\n        } else {\n            // The node to remove is in cur's left subtree\n            cur = cur->left;\n        }\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == NULL)\n        return;\n    // Check whether the node to remove has children\n    if (cur->left == NULL || cur->right == NULL) {\n        /* Number of children = 0 or 1 */\n        // With 0 or 1 child, child is NULL or that child, respectively\n        TreeNode *child = cur->left != NULL ? cur->left : cur->right;\n        // Remove node cur\n        if (pre->left == cur) {\n            pre->left = child;\n        } else {\n            pre->right = child;\n        }\n        // Free memory\n        free(cur);\n    } else {\n        /* Number of children = 2 */\n        // Get the inorder successor of cur\n        TreeNode *tmp = cur->right;\n        while (tmp->left != NULL) {\n            tmp = tmp->left;\n        }\n        int tmpVal = tmp->val;\n        // Recursively remove node tmp\n        removeItem(bst, tmp->val);\n        // Replace the value of cur with the value of tmp\n        cur->val = tmpVal;\n    }\n}\n
    binary_search_tree.kt
    /* Remove a node */\nfun remove(num: Int) {\n    // Return immediately if the tree is empty\n    if (root == null)\n        return\n    var cur = root\n    var pre: TreeNode? = null\n    // Continue searching while cur is not null\n    while (cur != null) {\n        // The node to remove was found; exit the loop\n        if (cur._val == num)\n            break\n        pre = cur\n        // The node to remove lies in the right subtree of cur\n        cur = if (cur._val < num)\n            cur.right\n        // The node to remove lies in the left subtree of cur\n        else\n            cur.left\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == null)\n        return\n    // Number of children = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        val child = if (cur.left != null)\n            cur.left\n        else\n            cur.right\n        // Remove node cur\n        if (cur != root) {\n            if (pre!!.left == cur)\n                pre.left = child\n            else\n                pre.right = child\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child\n        }\n        // Number of children = 2\n    } else {\n        // Get the inorder successor of cur\n        var tmp = cur.right\n        while (tmp!!.left != null) {\n            tmp = tmp.left\n        }\n        // Recursively remove node tmp\n        remove(tmp._val)\n        // Replace the value of cur with the value of tmp\n        cur._val = tmp._val\n    }\n}\n
    binary_search_tree.rb
    ### Remove a node ###\ndef remove(num)\n  # Return immediately if the tree is empty\n  return if @root.nil?\n\n  # Search in a loop, stopping if cur becomes nil\n  cur, pre = @root, nil\n  while !cur.nil?\n    # The node to remove was found; exit the loop\n    break if cur.val == num\n\n    pre = cur\n    # The node to remove lies in the right subtree of cur\n    if cur.val < num\n      cur = cur.right\n    # The node to remove lies in the left subtree of cur\n    else\n      cur = cur.left\n    end\n  end\n  # Return immediately if the node to remove does not exist\n  return if cur.nil?\n\n  # Number of children = 0 or 1\n  if cur.left.nil? || cur.right.nil?\n    # With 0 / 1 children, child is nil / the child node, respectively\n    child = cur.left || cur.right\n    # Remove node cur\n    if cur != @root\n      if pre.left == cur\n        pre.left = child\n      else\n        pre.right = child\n      end\n    else\n      # If the removed node is the root, assign the new root\n      @root = child\n    end\n  # Number of children = 2\n  else\n    # Get the inorder successor of cur\n    tmp = cur.right\n    while !tmp.left.nil?\n      tmp = tmp.left\n    end\n    # Recursively remove node tmp\n    remove(tmp.val)\n    # Replace the value of cur with the value of tmp\n    cur.val = tmp.val\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#4-inorder-traversal-produces-sorted-data","level":3,"title":"4.   Inorder traversal produces sorted data","text":"

    As shown below, inorder traversal visits “left \\(\\rightarrow\\) root \\(\\rightarrow\\) right,” while a binary search tree obeys “left child \\(<\\) root \\(<\\) right child.”

    Thus, inorder traversal always visits the next smallest node, giving an important property: the inorder traversal of a binary search tree is in ascending order.

    This lets us retrieve sorted data from the tree in just \\(O(n)\\) time, with no additional sorting.

    Figure 7-22   Inorder traversal of a binary search tree

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#742-efficiency","level":2,"title":"7.4.2   Efficiency","text":"

    Suppose we want to store a dataset in an array or a binary search tree. As Table 7-2 shows, the tree's operations take logarithmic time, offering consistent and efficient performance. An array is faster only when insertions are frequent but lookups and deletions are infrequent.

    Table 7-2   Efficiency of arrays and search trees

    Unsorted array Binary search tree Find an element \\(O(n)\\) \\(O(\\log n)\\) Insert an element \\(O(1)\\) \\(O(\\log n)\\) Delete an element \\(O(n)\\) \\(O(\\log n)\\)

    Ideally, the tree is balanced, allowing any node to be found within \\(\\log n\\) iterations.

    However, repeated insertions and deletions may cause it to degenerate into a linked list, as shown below. The time complexity of its operations then degrades to \\(O(n)\\).

    Figure 7-23   Degeneration of a binary search tree

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#743-common-applications","level":2,"title":"7.4.3   Common applications","text":"
    • Multilevel indexes in systems, supporting efficient lookup, insertion, and deletion.
    • The underlying data structure for certain search algorithms.
    • Storing a data stream while maintaining its order.
    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_tree/","level":1,"title":"7.1   Binary trees","text":"

    A binary tree is a nonlinear data structure representing relationships between ancestors and descendants. It embodies the divide-and-conquer idea of splitting in two. Like a linked list, it is built from nodes, each containing a value and references to its left and right children.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class TreeNode:\n    \"\"\"Binary tree node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                # Node value\n        self.left: TreeNode | None = None  # Left child reference\n        self.right: TreeNode | None = None # Right child reference\n
    /* Binary tree node struct */\nstruct TreeNode {\n    int val;          // Node value\n    TreeNode *left;   // Left child pointer\n    TreeNode *right;  // Right child pointer\n    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}\n};\n
    /* Binary tree node class */\nclass TreeNode {\n    int val;         // Node value\n    TreeNode left;   // Left child reference\n    TreeNode right;  // Right child reference\n    TreeNode(int x) { val = x; }\n}\n
    /* Binary tree node class */\nclass TreeNode(int? x) {\n    public int? val = x;    // Node value\n    public TreeNode? left;  // Left child reference\n    public TreeNode? right; // Right child reference\n}\n
    /* Binary tree node struct */\ntype TreeNode struct {\n    Val   int\n    Left  *TreeNode\n    Right *TreeNode\n}\n/* Constructor */\nfunc NewTreeNode(v int) *TreeNode {\n    return &TreeNode{\n        Left:  nil, // Left child pointer\n        Right: nil, // Right child pointer\n        Val:   v,   // Node value\n    }\n}\n
    /* Binary tree node class */\nclass TreeNode {\n    var val: Int // Node value\n    var left: TreeNode? // Left child reference\n    var right: TreeNode? // Right child reference\n\n    init(x: Int) {\n        val = x\n    }\n}\n
    /* Binary tree node class */\nclass TreeNode {\n    val; // Node value\n    left; // Left child pointer\n    right; // Right child pointer\n    constructor(val, left, right) {\n        this.val = val === undefined ? 0 : val;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
    /* Binary tree node class */\nclass TreeNode {\n    val: number;\n    left: TreeNode | null;\n    right: TreeNode | null;\n\n    constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.left = left === undefined ? null : left; // Left child reference\n        this.right = right === undefined ? null : right; // Right child reference\n    }\n}\n
    /* Binary tree node class */\nclass TreeNode {\n  int val;         // Node value\n  TreeNode? left;  // Left child reference\n  TreeNode? right; // Right child reference\n  TreeNode(this.val, [this.left, this.right]);\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Binary tree node struct */\nstruct TreeNode {\n    val: i32,                               // Node value\n    left: Option<Rc<RefCell<TreeNode>>>,    // Left child reference\n    right: Option<Rc<RefCell<TreeNode>>>,   // Right child reference\n}\n\nimpl TreeNode {\n    /* Constructor */\n    fn new(val: i32) -> Rc<RefCell<Self>> {\n        Rc::new(RefCell::new(Self {\n            val,\n            left: None,\n            right: None\n        }))\n    }\n}\n
    /* Binary tree node struct */\ntypedef struct TreeNode {\n    int val;                // Node value\n    int height;             // Node height\n    struct TreeNode *left;  // Left child pointer\n    struct TreeNode *right; // Right child pointer\n} TreeNode;\n\n/* Constructor */\nTreeNode *newTreeNode(int val) {\n    TreeNode *node;\n\n    node = (TreeNode *)malloc(sizeof(TreeNode));\n    node->val = val;\n    node->height = 0;\n    node->left = NULL;\n    node->right = NULL;\n    return node;\n}\n
    /* Binary tree node class */\nclass TreeNode(val _val: Int) {  // Node value\n    var left: TreeNode? = null   // Left child reference\n    var right: TreeNode? = null  // Right child reference\n}\n
    ### Binary tree node class ###\nclass TreeNode\n  attr_accessor :val    # Node value\n  attr_accessor :left   # Left child reference\n  attr_accessor :right  # Right child reference\n\n  def initialize(val)\n    @val = val\n  end\nend\n

    Each node has two references or pointers, one to its left child and one to its right child. The node is their parent. Its left child and all nodes below that child form its left subtree; the right subtree is defined similarly.

    Every non-leaf node has children and at least one nonempty subtree. In Figure 7-1, if node 2 is the parent, its left and right children are nodes 4 and 5. Its left subtree consists of node 4 and everything below it, and its right subtree consists of node 5 and everything below it.

    Figure 7-1   Parents, children, and subtrees

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#711-common-terminology","level":2,"title":"7.1.1   Common terminology","text":"

    Figure 7-2 illustrates common binary-tree terminology.

    • Root: the topmost node, which has no parent.
    • Leaf: a node with no children; both of its child references point to None.
    • Edge: a connection between two nodes, represented by a reference or pointer.
    • A node's level: levels increase from top to bottom, with the root at level 1.
    • A node's degree: its number of children, which is 0, 1, or 2 in a binary tree.
    • A tree's height: the number of edges from the root to its farthest leaf.
    • A node's depth: the number of edges from the root to that node.
    • A node's height: the number of edges from that node to its farthest leaf.

    Figure 7-2   Common binary-tree terminology

    Tip

    Height and depth are usually measured in edges, but some problems or textbooks count nodes instead. Under that convention, add 1 to both height and depth.

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#712-basic-operations","level":2,"title":"7.1.2   Basic operations","text":"","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#1-initializing-a-binary-tree","level":3,"title":"1.   Initializing a binary tree","text":"

    As with a linked list, first create the nodes, then connect them with references or pointers.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py
    # Initialize a binary tree\n# Initialize nodes\nn1 = TreeNode(val=1)\nn2 = TreeNode(val=2)\nn3 = TreeNode(val=3)\nn4 = TreeNode(val=4)\nn5 = TreeNode(val=5)\n# Connect nodes with references (pointers)\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
    binary_tree.cpp
    /* Initialize a binary tree */\n// Initialize nodes\nTreeNode* n1 = new TreeNode(1);\nTreeNode* n2 = new TreeNode(2);\nTreeNode* n3 = new TreeNode(3);\nTreeNode* n4 = new TreeNode(4);\nTreeNode* n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1->left = n2;\nn1->right = n3;\nn2->left = n4;\nn2->right = n5;\n
    binary_tree.java
    // Initialize nodes\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.cs
    /* Initialize a binary tree */\n// Initialize nodes\nTreeNode n1 = new(1);\nTreeNode n2 = new(2);\nTreeNode n3 = new(3);\nTreeNode n4 = new(4);\nTreeNode n5 = new(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.go
    /* Initialize a binary tree */\n// Initialize nodes\nn1 := NewTreeNode(1)\nn2 := NewTreeNode(2)\nn3 := NewTreeNode(3)\nn4 := NewTreeNode(4)\nn5 := NewTreeNode(5)\n// Connect nodes with references (pointers)\nn1.Left = n2\nn1.Right = n3\nn2.Left = n4\nn2.Right = n5\n
    binary_tree.swift
    // Initialize nodes\nlet n1 = TreeNode(x: 1)\nlet n2 = TreeNode(x: 2)\nlet n3 = TreeNode(x: 3)\nlet n4 = TreeNode(x: 4)\nlet n5 = TreeNode(x: 5)\n// Connect nodes with references (pointers)\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
    binary_tree.js
    /* Initialize a binary tree */\n// Initialize nodes\nlet n1 = new TreeNode(1),\n    n2 = new TreeNode(2),\n    n3 = new TreeNode(3),\n    n4 = new TreeNode(4),\n    n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.ts
    /* Initialize a binary tree */\n// Initialize nodes\nlet n1 = new TreeNode(1),\n    n2 = new TreeNode(2),\n    n3 = new TreeNode(3),\n    n4 = new TreeNode(4),\n    n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.dart
    /* Initialize a binary tree */\n// Initialize nodes\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.rs
    // Initialize nodes\nlet n1 = TreeNode::new(1);\nlet n2 = TreeNode::new(2);\nlet n3 = TreeNode::new(3);\nlet n4 = TreeNode::new(4);\nlet n5 = TreeNode::new(5);\n// Connect nodes with references (pointers)\nn1.borrow_mut().left = Some(n2.clone());\nn1.borrow_mut().right = Some(n3);\nn2.borrow_mut().left = Some(n4);\nn2.borrow_mut().right = Some(n5);\n
    binary_tree.c
    /* Initialize a binary tree */\n// Initialize nodes\nTreeNode *n1 = newTreeNode(1);\nTreeNode *n2 = newTreeNode(2);\nTreeNode *n3 = newTreeNode(3);\nTreeNode *n4 = newTreeNode(4);\nTreeNode *n5 = newTreeNode(5);\n// Connect nodes with references (pointers)\nn1->left = n2;\nn1->right = n3;\nn2->left = n4;\nn2->right = n5;\n
    binary_tree.kt
    // Initialize nodes\nval n1 = TreeNode(1)\nval n2 = TreeNode(2)\nval n3 = TreeNode(3)\nval n4 = TreeNode(4)\nval n5 = TreeNode(5)\n// Connect nodes with references (pointers)\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
    binary_tree.rb
    # Initialize a binary tree\n# Initialize nodes\nn1 = TreeNode.new(1)\nn2 = TreeNode.new(2)\nn3 = TreeNode.new(3)\nn4 = TreeNode.new(4)\nn5 = TreeNode.new(5)\n# Connect nodes with references (pointers)\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#2-inserting-and-deleting-nodes","level":3,"title":"2.   Inserting and deleting nodes","text":"

    As with linked lists, insertion and deletion can be implemented by changing pointers. An example is shown below.

    Figure 7-3   Inserting and deleting a binary-tree node

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py
    # Insert and delete a node\np = TreeNode(0)\n# Insert node P between n1 and n2\nn1.left = p\np.left = n2\n# Delete node P\nn1.left = n2\n
    binary_tree.cpp
    /* Insert and delete a node */\nTreeNode* P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1->left = P;\nP->left = n2;\n// Delete node P\nn1->left = n2;\n// Free the memory\ndelete P;\n
    binary_tree.java
    TreeNode P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.cs
    /* Insert and delete a node */\nTreeNode P = new(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.go
    /* Insert and delete a node */\n// Insert node P between n1 and n2\np := NewTreeNode(0)\nn1.Left = p\np.Left = n2\n// Delete node P\nn1.Left = n2\n
    binary_tree.swift
    let P = TreeNode(x: 0)\n// Insert node P between n1 and n2\nn1.left = P\nP.left = n2\n// Delete node P\nn1.left = n2\n
    binary_tree.js
    /* Insert and delete a node */\nlet P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.ts
    /* Insert and delete a node */\nconst P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.dart
    /* Insert and delete a node */\nTreeNode P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.rs
    let p = TreeNode::new(0);\n// Insert node P between n1 and n2\nn1.borrow_mut().left = Some(p.clone());\np.borrow_mut().left = Some(n2.clone());\n// Delete node p\nn1.borrow_mut().left = Some(n2);\n
    binary_tree.c
    /* Insert and delete a node */\nTreeNode *P = newTreeNode(0);\n// Insert node P between n1 and n2\nn1->left = P;\nP->left = n2;\n// Delete node P\nn1->left = n2;\n// Free the memory\nfree(P);\n
    binary_tree.kt
    val P = TreeNode(0)\n// Insert node P between n1 and n2\nn1.left = P\nP.left = n2\n// Delete node P\nn1.left = n2\n
    binary_tree.rb
    # Insert and delete a node\n_p = TreeNode.new(0)\n# Insert node _p between n1 and n2\nn1.left = _p\n_p.left = n2\n# Delete the node\nn1.left = n2\n
    Visualize execution

    View full screen >

    Tip

    Inserting a node may change the tree's logical structure, while deleting a node generally means deleting it and all its subtrees. Meaningful insertion and deletion operations therefore usually require several coordinated steps.

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#713-common-types-of-binary-trees","level":2,"title":"7.1.3   Common types of binary trees","text":"","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#1-perfect-binary-trees","level":3,"title":"1.   Perfect binary trees","text":"

    In a perfect binary tree, every level is completely filled, as shown below. Leaves have degree \\(0\\), and all other nodes have degree \\(2\\). A tree of height \\(h\\) has \\(2^{h+1} - 1\\) nodes, an exponential relationship reminiscent of cell division in nature.

    Tip

    In Chinese-language resources, a perfect binary tree is often called 满二叉树.

    Figure 7-4   A perfect binary tree

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#2-complete-binary-trees","level":3,"title":"2.   Complete binary trees","text":"

    Only the lowest level of a complete binary tree may be incomplete, and its nodes must fill that level consecutively from left to right, as shown below. Every perfect binary tree is also complete.

    Figure 7-5   A complete binary tree

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#3-full-binary-trees","level":3,"title":"3.   Full binary trees","text":"

    In a full binary tree, every node other than a leaf has exactly two children, as shown below.

    Figure 7-6   A full binary tree

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#4-balanced-binary-trees","level":3,"title":"4.   Balanced binary trees","text":"

    In a balanced binary tree, the heights of the left and right subtrees of every node differ by at most 1, as shown below.

    Figure 7-7   A balanced binary tree

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#714-degeneration-of-a-binary-tree","level":2,"title":"7.1.4   Degeneration of a binary tree","text":"

    Figure 7-8 shows ideal and degenerate tree structures. When every level is full, the tree is perfect. When every node lies along one side, the tree degenerates into a linked list.

    • A perfect binary tree is the ideal case, making full use of divide and conquer.
    • A linked list is the opposite extreme: operations become linear, with time complexity degrading to \\(O(n)\\).

    Figure 7-8   Best and worst binary-tree structures

    As Table 7-1 shows, quantities such as leaf count, total node count, and height reach their maximum or minimum values in these best and worst structures.

    Table 7-1   Best and worst binary-tree structures

    Perfect binary tree Linked list Nodes at level \\(i\\) \\(2^{i-1}\\) \\(1\\) Leaves in a tree of height \\(h\\) \\(2^h\\) \\(1\\) Total nodes in a tree of height \\(h\\) \\(2^{h+1} - 1\\) \\(h + 1\\) Height of a tree with \\(n\\) nodes \\(\\log_2 (n+1) - 1\\) \\(n - 1\\)","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/","level":1,"title":"7.2   Traversing a binary tree","text":"

    In its physical representation, a tree is a linked data structure, so traversal follows pointers from node to node. Unlike a linked list, however, a tree is nonlinear. Traversing it is more complex and requires a search algorithm.

    Common binary-tree traversals include level-order, preorder, inorder, and postorder traversal.

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#721-level-order-traversal","level":2,"title":"7.2.1   Level-order traversal","text":"

    As shown below, level-order traversal visits the tree level by level from top to bottom, moving from left to right within each level.

    Level-order traversal is a form of breadth-first traversal, also called breadth-first search (BFS). It explores outward one layer at a time.

    Figure 7-9   Level-order traversal of a binary tree

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1-implementation","level":3,"title":"1.   Implementation","text":"

    Breadth-first traversal usually uses a queue. A queue's first-in, first-out rule matches the traversal's level-by-level progression. The implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_bfs.py
    def level_order(root: TreeNode | None) -> list[int]:\n    \"\"\"Level-order traversal\"\"\"\n    # Initialize a queue and enqueue the root\n    queue: deque[TreeNode] = deque()\n    queue.append(root)\n    # Initialize a list to store the traversal sequence\n    res = []\n    while queue:\n        node: TreeNode = queue.popleft()  # Dequeue from the queue\n        res.append(node.val)  # Store the node value\n        if node.left is not None:\n            queue.append(node.left)  # Enqueue the left child\n        if node.right is not None:\n            queue.append(node.right)  # Enqueue the right child\n    return res\n
    binary_tree_bfs.cpp
    /* Level-order traversal */\nvector<int> levelOrder(TreeNode *root) {\n    // Initialize a queue and enqueue the root\n    queue<TreeNode *> queue;\n    queue.push(root);\n    // Initialize a list to store the traversal sequence\n    vector<int> vec;\n    while (!queue.empty()) {\n        TreeNode *node = queue.front();\n        queue.pop();              // Dequeue from the queue\n        vec.push_back(node->val); // Store the node value\n        if (node->left != nullptr)\n            queue.push(node->left); // Enqueue the left child\n        if (node->right != nullptr)\n            queue.push(node->right); // Enqueue the right child\n    }\n    return vec;\n}\n
    binary_tree_bfs.java
    /* Level-order traversal */\nList<Integer> levelOrder(TreeNode root) {\n    // Initialize a queue and enqueue the root\n    Queue<TreeNode> queue = new LinkedList<>();\n    queue.add(root);\n    // Initialize a list to store the traversal sequence\n    List<Integer> list = new ArrayList<>();\n    while (!queue.isEmpty()) {\n        TreeNode node = queue.poll(); // Dequeue from the queue\n        list.add(node.val);           // Store the node value\n        if (node.left != null)\n            queue.offer(node.left);   // Enqueue the left child\n        if (node.right != null)\n            queue.offer(node.right);  // Enqueue the right child\n    }\n    return list;\n}\n
    binary_tree_bfs.cs
    /* Level-order traversal */\nList<int> LevelOrder(TreeNode root) {\n    // Initialize a queue and enqueue the root\n    Queue<TreeNode> queue = new();\n    queue.Enqueue(root);\n    // Initialize a list to store the traversal sequence\n    List<int> list = [];\n    while (queue.Count != 0) {\n        TreeNode node = queue.Dequeue(); // Dequeue from the queue\n        list.Add(node.val!.Value);       // Store the node value\n        if (node.left != null)\n            queue.Enqueue(node.left);    // Enqueue the left child\n        if (node.right != null)\n            queue.Enqueue(node.right);   // Enqueue the right child\n    }\n    return list;\n}\n
    binary_tree_bfs.go
    /* Level-order traversal */\nfunc levelOrder(root *TreeNode) []any {\n    // Initialize a queue and enqueue the root\n    queue := list.New()\n    queue.PushBack(root)\n    // Initialize a slice to store the traversal sequence\n    nums := make([]any, 0)\n    for queue.Len() > 0 {\n        // Dequeue from the queue\n        node := queue.Remove(queue.Front()).(*TreeNode)\n        // Store the node value\n        nums = append(nums, node.Val)\n        if node.Left != nil {\n            // Enqueue the left child\n            queue.PushBack(node.Left)\n        }\n        if node.Right != nil {\n            // Enqueue the right child\n            queue.PushBack(node.Right)\n        }\n    }\n    return nums\n}\n
    binary_tree_bfs.swift
    /* Level-order traversal */\nfunc levelOrder(root: TreeNode) -> [Int] {\n    // Initialize a queue and enqueue the root\n    var queue: [TreeNode] = [root]\n    // Initialize a list to store the traversal sequence\n    var list: [Int] = []\n    while !queue.isEmpty {\n        let node = queue.removeFirst() // Dequeue from the queue\n        list.append(node.val) // Store the node value\n        if let left = node.left {\n            queue.append(left) // Enqueue the left child\n        }\n        if let right = node.right {\n            queue.append(right) // Enqueue the right child\n        }\n    }\n    return list\n}\n
    binary_tree_bfs.js
    /* Level-order traversal */\nfunction levelOrder(root) {\n    // Initialize a queue and enqueue the root\n    const queue = [root];\n    // Initialize a list to store the traversal sequence\n    const list = [];\n    while (queue.length) {\n        let node = queue.shift(); // Dequeue from the queue\n        list.push(node.val); // Store the node value\n        if (node.left) queue.push(node.left); // Enqueue the left child\n        if (node.right) queue.push(node.right); // Enqueue the right child\n    }\n    return list;\n}\n
    binary_tree_bfs.ts
    /* Level-order traversal */\nfunction levelOrder(root: TreeNode | null): number[] {\n    // Initialize a queue and enqueue the root\n    const queue = [root];\n    // Initialize a list to store the traversal sequence\n    const list: number[] = [];\n    while (queue.length) {\n        let node = queue.shift() as TreeNode; // Dequeue from the queue\n        list.push(node.val); // Store the node value\n        if (node.left) {\n            queue.push(node.left); // Enqueue the left child\n        }\n        if (node.right) {\n            queue.push(node.right); // Enqueue the right child\n        }\n    }\n    return list;\n}\n
    binary_tree_bfs.dart
    /* Level-order traversal */\nList<int> levelOrder(TreeNode? root) {\n  // Initialize a queue and enqueue the root\n  Queue<TreeNode?> queue = Queue();\n  queue.add(root);\n  // Initialize a list to store the traversal sequence\n  List<int> res = [];\n  while (queue.isNotEmpty) {\n    TreeNode? node = queue.removeFirst(); // Dequeue from the queue\n    res.add(node!.val); // Store the node value\n    if (node.left != null) queue.add(node.left); // Enqueue the left child\n    if (node.right != null) queue.add(node.right); // Enqueue the right child\n  }\n  return res;\n}\n
    binary_tree_bfs.rs
    /* Level-order traversal */\nfn level_order(root: &Rc<RefCell<TreeNode>>) -> Vec<i32> {\n    // Initialize a queue and enqueue the root\n    let mut que = VecDeque::new();\n    que.push_back(root.clone());\n    // Initialize a list to store the traversal sequence\n    let mut vec = Vec::new();\n\n    while let Some(node) = que.pop_front() {\n        // Dequeue from the queue\n        vec.push(node.borrow().val); // Store the node value\n        if let Some(left) = node.borrow().left.as_ref() {\n            que.push_back(left.clone()); // Enqueue the left child\n        }\n        if let Some(right) = node.borrow().right.as_ref() {\n            que.push_back(right.clone()); // Enqueue the right child\n        };\n    }\n    vec\n}\n
    binary_tree_bfs.c
    /* Level-order traversal */\nint *levelOrder(TreeNode *root, int *size) {\n    /* Auxiliary queue */\n    int front, rear;\n    int index, *arr;\n    TreeNode *node;\n    TreeNode **queue;\n\n    /* Auxiliary queue */\n    queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE);\n    // Queue indices\n    front = 0, rear = 0;\n    // Enqueue the root node\n    queue[rear++] = root;\n    // Initialize a list to store the traversal sequence\n    /* Auxiliary array */\n    arr = (int *)malloc(sizeof(int) * MAX_SIZE);\n    // Array index\n    index = 0;\n    while (front < rear) {\n        // Dequeue from the queue\n        node = queue[front++];\n        // Store the node value\n        arr[index++] = node->val;\n        if (node->left != NULL) {\n            // Enqueue the left child\n            queue[rear++] = node->left;\n        }\n        if (node->right != NULL) {\n            // Enqueue the right child\n            queue[rear++] = node->right;\n        }\n    }\n    // Update the array length\n    *size = index;\n    arr = realloc(arr, sizeof(int) * (*size));\n\n    // Free the auxiliary queue array\n    free(queue);\n    return arr;\n}\n
    binary_tree_bfs.kt
    /* Level-order traversal */\nfun levelOrder(root: TreeNode?): MutableList<Int> {\n    // Initialize a queue and enqueue the root\n    val queue = LinkedList<TreeNode?>()\n    queue.add(root)\n    // Initialize a list to store the traversal sequence\n    val list = mutableListOf<Int>()\n    while (queue.isNotEmpty()) {\n        val node = queue.poll()      // Dequeue from the queue\n        list.add(node?._val!!)       // Store the node value\n        if (node.left != null)\n            queue.offer(node.left)   // Enqueue the left child\n        if (node.right != null)\n            queue.offer(node.right)  // Enqueue the right child\n    }\n    return list\n}\n
    binary_tree_bfs.rb
    ### Level-order traversal ###\ndef level_order(root)\n  # Initialize a queue and enqueue the root\n  queue = [root]\n  # Initialize a list to store the traversal sequence\n  res = []\n  while !queue.empty?\n    node = queue.shift # Dequeue from the queue\n    res << node.val # Store the node value\n    queue << node.left unless node.left.nil? # Enqueue the left child\n    queue << node.right unless node.right.nil? # Enqueue the right child\n  end\n  res\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2-complexity-analysis","level":3,"title":"2.   Complexity analysis","text":"
    • Time complexity: \\(O(n)\\). Each of the \\(n\\) nodes is visited once, taking \\(O(n)\\) time.
    • Space complexity: \\(O(n)\\). In the worst case, a perfect binary tree, the queue holds up to \\((n + 1) / 2\\) nodes just before the lowest level is visited, requiring \\(O(n)\\) space.
    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#722-preorder-inorder-and-postorder-traversal","level":2,"title":"7.2.2   Preorder, inorder, and postorder traversal","text":"

    Preorder, inorder, and postorder traversal are forms of depth-first traversal, also called depth-first search (DFS). They follow a path to its end, then backtrack and continue.

    Figure 7-10 illustrates depth-first traversal. Imagine walking once around the outside of the entire binary tree. At each node, you encounter three positions corresponding to preorder, inorder, and postorder visits.

    Figure 7-10   Preorder, inorder, and postorder traversal of a binary search tree

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1-implementation_1","level":3,"title":"1.   Implementation","text":"

    Depth-first search is usually implemented recursively:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_dfs.py
    def pre_order(root: TreeNode | None):\n    \"\"\"Preorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit order: root -> left subtree -> right subtree\n    res.append(root.val)\n    pre_order(root=root.left)\n    pre_order(root=root.right)\n\ndef in_order(root: TreeNode | None):\n    \"\"\"Inorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit order: left subtree -> root -> right subtree\n    in_order(root=root.left)\n    res.append(root.val)\n    in_order(root=root.right)\n\ndef post_order(root: TreeNode | None):\n    \"\"\"Postorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit order: left subtree -> right subtree -> root\n    post_order(root=root.left)\n    post_order(root=root.right)\n    res.append(root.val)\n
    binary_tree_dfs.cpp
    /* Preorder traversal */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit order: root -> left subtree -> right subtree\n    vec.push_back(root->val);\n    preOrder(root->left);\n    preOrder(root->right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root->left);\n    vec.push_back(root->val);\n    inOrder(root->right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root->left);\n    postOrder(root->right);\n    vec.push_back(root->val);\n}\n
    binary_tree_dfs.java
    /* Preorder traversal */\nvoid preOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit order: root -> left subtree -> right subtree\n    list.add(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root.left);\n    list.add(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root.left);\n    postOrder(root.right);\n    list.add(root.val);\n}\n
    binary_tree_dfs.cs
    /* Preorder traversal */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit order: root -> left subtree -> right subtree\n    list.Add(root.val!.Value);\n    PreOrder(root.left);\n    PreOrder(root.right);\n}\n\n/* Inorder traversal */\nvoid InOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit order: left subtree -> root -> right subtree\n    InOrder(root.left);\n    list.Add(root.val!.Value);\n    InOrder(root.right);\n}\n\n/* Postorder traversal */\nvoid PostOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit order: left subtree -> right subtree -> root\n    PostOrder(root.left);\n    PostOrder(root.right);\n    list.Add(root.val!.Value);\n}\n
    binary_tree_dfs.go
    /* Preorder traversal */\nfunc preOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit order: root -> left subtree -> right subtree\n    nums = append(nums, node.Val)\n    preOrder(node.Left)\n    preOrder(node.Right)\n}\n\n/* Inorder traversal */\nfunc inOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(node.Left)\n    nums = append(nums, node.Val)\n    inOrder(node.Right)\n}\n\n/* Postorder traversal */\nfunc postOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(node.Left)\n    postOrder(node.Right)\n    nums = append(nums, node.Val)\n}\n
    binary_tree_dfs.swift
    /* Preorder traversal */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit order: root -> left subtree -> right subtree\n    list.append(root.val)\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n}\n\n/* Inorder traversal */\nfunc inOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root: root.left)\n    list.append(root.val)\n    inOrder(root: root.right)\n}\n\n/* Postorder traversal */\nfunc postOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root: root.left)\n    postOrder(root: root.right)\n    list.append(root.val)\n}\n
    binary_tree_dfs.js
    /* Preorder traversal */\nfunction preOrder(root) {\n    if (root === null) return;\n    // Visit order: root -> left subtree -> right subtree\n    list.push(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nfunction inOrder(root) {\n    if (root === null) return;\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root.left);\n    list.push(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nfunction postOrder(root) {\n    if (root === null) return;\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root.left);\n    postOrder(root.right);\n    list.push(root.val);\n}\n
    binary_tree_dfs.ts
    /* Preorder traversal */\nfunction preOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit order: root -> left subtree -> right subtree\n    list.push(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nfunction inOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root.left);\n    list.push(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nfunction postOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root.left);\n    postOrder(root.right);\n    list.push(root.val);\n}\n
    binary_tree_dfs.dart
    /* Preorder traversal */\nvoid preOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit order: root -> left subtree -> right subtree\n  list.add(node.val);\n  preOrder(node.left);\n  preOrder(node.right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit order: left subtree -> root -> right subtree\n  inOrder(node.left);\n  list.add(node.val);\n  inOrder(node.right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit order: left subtree -> right subtree -> root\n  postOrder(node.left);\n  postOrder(node.right);\n  list.add(node.val);\n}\n
    binary_tree_dfs.rs
    /* Preorder traversal */\nfn pre_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit order: root -> left subtree -> right subtree\n            let node = node.borrow();\n            res.push(node.val);\n            dfs(node.left.as_ref(), res);\n            dfs(node.right.as_ref(), res);\n        }\n    }\n    dfs(root, &mut result);\n\n    result\n}\n\n/* Inorder traversal */\nfn in_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit order: left subtree -> root -> right subtree\n            let node = node.borrow();\n            dfs(node.left.as_ref(), res);\n            res.push(node.val);\n            dfs(node.right.as_ref(), res);\n        }\n    }\n    dfs(root, &mut result);\n\n    result\n}\n\n/* Postorder traversal */\nfn post_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit order: left subtree -> right subtree -> root\n            let node = node.borrow();\n            dfs(node.left.as_ref(), res);\n            dfs(node.right.as_ref(), res);\n            res.push(node.val);\n        }\n    }\n\n    dfs(root, &mut result);\n\n    result\n}\n
    binary_tree_dfs.c
    /* Preorder traversal */\nvoid preOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit order: root -> left subtree -> right subtree\n    arr[(*size)++] = root->val;\n    preOrder(root->left, size);\n    preOrder(root->right, size);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root->left, size);\n    arr[(*size)++] = root->val;\n    inOrder(root->right, size);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root->left, size);\n    postOrder(root->right, size);\n    arr[(*size)++] = root->val;\n}\n
    binary_tree_dfs.kt
    /* Preorder traversal */\nfun preOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit order: root -> left subtree -> right subtree\n    list.add(root._val)\n    preOrder(root.left)\n    preOrder(root.right)\n}\n\n/* Inorder traversal */\nfun inOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root.left)\n    list.add(root._val)\n    inOrder(root.right)\n}\n\n/* Postorder traversal */\nfun postOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root.left)\n    postOrder(root.right)\n    list.add(root._val)\n}\n
    binary_tree_dfs.rb
    ### Preorder traversal ###\ndef pre_order(root)\n  return if root.nil?\n\n  # Visit order: root -> left subtree -> right subtree\n  $res << root.val\n  pre_order(root.left)\n  pre_order(root.right)\nend\n\n### Inorder traversal ###\ndef in_order(root)\n  return if root.nil?\n\n  # Visit order: left subtree -> root -> right subtree\n  in_order(root.left)\n  $res << root.val\n  in_order(root.right)\nend\n\n### Postorder traversal ###\ndef post_order(root)\n  return if root.nil?\n\n  # Visit order: left subtree -> right subtree -> root\n  post_order(root.left)\n  post_order(root.right)\n  $res << root.val\nend\n
    Visualize execution

    View full screen >

    Tip

    Depth-first search can also be implemented iteratively. Interested readers can explore this approach themselves.

    Figure 7-11 shows the recursive process for preorder traversal, with two opposing phases: descending into calls and returning from them.

    1. Descending starts a new function call and visits the next node.
    2. Returning ends a function call, indicating that the current node has been fully processed.
    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 7-11   The recursive process of preorder traversal

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2-complexity-analysis_1","level":3,"title":"2.   Complexity analysis","text":"
    • Time complexity: \\(O(n)\\). Every node is visited once, taking \\(O(n)\\) time.
    • Space complexity: \\(O(n)\\). In the worst case, the tree degenerates into a linked list, recursion reaches depth \\(n\\), and stack frames require \\(O(n)\\) space.
    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/exercises/","level":1,"title":"7.7   Exercises","text":"","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#771-check-your-understanding","level":2,"title":"7.7.1   Check your understanding","text":"","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#1-complete-full-and-perfect-binary-trees","level":3,"title":"1.   Complete, full, and perfect binary trees","text":"

    The following arrays represent binary trees in level order, with None marking an empty position:

    • Tree A: [1, 2, 3, 4, 5, 6]
    • Tree B: [1, 2, 3, None, None, 6, 7]
    1. Which tree is a complete binary tree?
    2. Which is a full binary tree, meaning that every non-leaf node has two children?
    3. Is either tree a perfect binary tree? Explain for each tree.
    Reference answer
    1. Tree A is complete. Only its bottom level is not full, and the nodes occupy consecutive positions from left to right. Tree B is not complete: its bottom level has empty positions on the left but still has nodes farther to the right.

    2. Tree B is full: nodes 1 and 3 each have two children, and all the other nodes are leaves. Tree A is not full because node 3 has only its left child, 6.

    3. Neither tree is perfect, because neither has a completely filled bottom level.

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#2-three-traversal-orders-for-the-same-tree","level":3,"title":"2.   Three traversal orders for the same tree","text":"

    Store the array [1, 2, 3, 4, 5, 6, 7] in a complete binary tree in level order.

    1. Draw the tree.
    2. Write its preorder, inorder, and postorder traversal sequences.
    3. In the inorder sequence, which parts of the tree do the sequences to the left and right of root node 1 correspond to?
    Reference answer
    1. The tree is:

            1\n    /   \\\n   2     3\n  / \\   / \\\n 4   5 6   7\n
    2. Preorder: 1, 2, 4, 5, 3, 6, 7; inorder: 4, 2, 5, 1, 6, 3, 7; postorder: 4, 5, 2, 6, 7, 3, 1.

    3. The sequence 4, 2, 5 to the left of root node 1 is the inorder traversal of the left subtree. The sequence 6, 3, 7 to its right is the inorder traversal of the right subtree.

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#3-comparing-two-binary-search-trees","level":3,"title":"3.   Comparing two binary search trees","text":"

    Insert each of the following sequences from left to right into an empty binary search tree:

    • Sequence A: [4, 2, 6, 1, 3, 5, 7]
    • Sequence B: [1, 2, 3, 4, 5, 6, 7]
    1. Write the nodes visited when searching for 7 in each tree.
    2. If height is measured by the number of edges from the root to the farthest leaf, what is each tree's height?
    3. Based on your answers, do the two trees search for 7 equally efficiently? Explain using their shapes and search paths.
    Reference answer
    1. In the tree built from sequence A, the search path is 4 → 6 → 7. In the tree built from sequence B, it is 1 → 2 → 3 → 4 → 5 → 6 → 7.

    2. The first tree has every level filled and a height of 2. The second tree has only right children and a height of 6.

    3. The searches for 7 are not equally efficient. Insertion order changes the shape and height of a binary search tree. Searching for 7 visits only three nodes in the first tree, but must visit seven in sequence in the second. The taller the tree, the more nodes may need to be compared along a path in the worst case.

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#772-programming-exercises","level":2,"title":"7.7.2   Programming exercises","text":"","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#1-maximum-depth-of-a-binary-tree","level":3,"title":"1.   Maximum depth of a binary tree","text":"

    You are given the root node root of a binary tree. Each node contains an integer value and references to its left and right children.

    The maximum depth is the number of nodes on the path from the root to the farthest leaf. Return the maximum depth of the tree; an empty tree has maximum depth 0. Use recursion.

    Hints
    1. Depth is counted in nodes here. A tree containing only a root has maximum depth 1.
    2. Have the recursive function return the maximum depth of the subtree rooted at the current node.
    3. Return 0 for an empty node; otherwise, return max(depth(left), depth(right)) + 1.

    LeetCode

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#2-traversing-a-binary-tree-level-by-level","level":3,"title":"2.   Traversing a binary tree level by level","text":"

    Given the root node root of a binary tree, use a queue to visit all nodes level by level from top to bottom, visiting each level from left to right.

    Return a two-dimensional array: the first subarray contains the values on the root's level, the second contains those on the next level, and so on. Return an empty array if the tree is empty.

    Hints
    1. Level-order traversal visits earlier enqueued nodes first, so use a queue.
    2. At the start of each round, the nodes in the queue belong to exactly one level.
    3. Record the queue's length first, then dequeue that many nodes and enqueue their children.

    LeetCode

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#3-the-kth-smallest-element-in-a-binary-search-tree","level":3,"title":"3.   The kth smallest element in a binary search tree","text":"

    A binary search tree contains n nodes with distinct values. If all node values are sorted in ascending order, their positions are numbered starting from 1.

    Given the root node root and an integer k satisfying 1 <= k <= n, return the node value in position k. Find the answer directly during inorder traversal, without first collecting all node values.

    Hints
    1. An inorder traversal of a binary search tree visits node values in ascending order.
    2. Inorder traversal processes the left subtree, the current node, and then the right subtree. Increment a counter by 1 when visiting the current node.
    3. When the counter first reaches k, the current node's value is the answer; there is no need to continue traversing.

    LeetCode

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/summary/","level":1,"title":"7.6   Summary","text":"","path":["Chapter 7.   Trees","7.6   Summary"],"tags":[]},{"location":"chapter_tree/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • A binary tree is a nonlinear data structure that embodies the divide-and-conquer idea of splitting in two. Each node contains a value and two pointers, one to each child.
    • A node's left or right child, together with everything below it, forms its left or right subtree.
    • Tree terminology includes root, leaf, level, degree, edge, height, and depth.
    • Initializing a binary tree and inserting or deleting nodes resemble linked-list operations.
    • Common types include perfect, complete, full, and balanced binary trees. A perfect tree is the ideal form; a linked list is the worst degenerate form.
    • A binary tree can be represented by an array of node values and empty positions in level order. Index mappings between parents and children replace pointers.
    • Level-order traversal is breadth-first search: it expands outward one layer at a time and usually uses a queue.
    • Preorder, inorder, and postorder traversal are depth-first search: they follow paths to their ends before backtracking and usually use recursion.
    • A binary search tree supports efficient lookup, insertion, and deletion in \\(O(\\log n)\\) time. If it degenerates into a linked list, all these operations degrade to \\(O(n)\\).
    • An AVL tree is a balanced binary search tree that uses rotations to maintain balance through insertions and deletions.
    • AVL rotations include right rotation, left rotation, right-then-left rotation, and left-then-right rotation. After an insertion or deletion, rotations are performed from the bottom up to restore balance.
    ","path":["Chapter 7.   Trees","7.6   Summary"],"tags":[]},{"location":"chapter_tree/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: For a binary tree with only one node, are both the tree's height and the root's depth \\(0\\)?

    Yes. Height and depth are usually defined by the number of edges traversed.

    Q: The text says binary-tree insertion and deletion usually involve a sequence of coordinated operations. What does that mean? Does it refer to releasing resources held by child nodes?

    For example, deleting a node from a binary search tree has three cases, and each case requires several steps involving nodes.

    Q: Why does depth-first traversal have preorder, inorder, and postorder variants? What is each used for?

    Just as an array can be traversed forward or backward, these are three ways to visit a binary tree and obtain a particular ordering. In a binary search tree, values satisfy left child value < root value < right child value, so visiting “left \\(\\rightarrow\\) root \\(\\rightarrow\\) right” produces a sorted node sequence.

    Q: A right rotation adjusts node, child, and grand_child. What happens to the link between node and its parent? Doesn't the rotation break it?

    View the operation recursively. right_rotate(root) receives a subtree's root and ends with return child, returning the new root of that subtree. The connection to its parent is updated after the function returns; it is outside the rotation function's responsibility.

    Q: In C++, how do you decide which methods are private and which are public? Why is height() public but updateHeight() private?

    The main consideration is where the method is used. A method used only within the class should be private. Calling updateHeight() alone is not meaningful to a user; it is just one step of insertion or deletion. In contrast, height() retrieves a node's height, much like vector.size(), so it is public for convenient access.

    Q: How do you build a binary search tree from input data? Is choosing the root important?

    Yes. The construction is provided by the build_tree() method in the binary-search-tree code. Usually, we sort the input, choose its middle element as the root, and recursively construct the left and right subtrees. This keeps the tree as balanced as possible.

    Q: Must strings in Java always be compared with equals()?

    For primitive types in Java, == compares variable values. For reference types, the two forms work differently.

    • == compares whether two variables refer to the same object, meaning the same location in memory.
    • equals() compares whether two objects have equal values.

    Use equals() when comparing values. However, strings initialized with String a = \"hi\"; String b = \"hi\"; are stored in the string pool and refer to the same object, so a == b also works for these strings.

    Q: Just before breadth-first traversal reaches the lowest level, does the queue contain \\(2^h\\) nodes?

    Yes. For example, a perfect binary tree of height \\(h = 2\\) has \\(n = 7\\) nodes, with \\(4 = 2^h = (n + 1) / 2\\) nodes on its lowest level.

    ","path":["Chapter 7.   Trees","7.6   Summary"],"tags":[]}]} \ No newline at end of file +{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"chapter_appendix/","level":1,"title":"Chapter 16.   Appendix","text":"","path":["Chapter 16.   Appendix"],"tags":[]},{"location":"chapter_appendix/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 16.1   Setting up a programming environment
    • 16.2   Contribute to the book
    • 16.3   Glossary
    ","path":["Chapter 16.   Appendix"],"tags":[]},{"location":"chapter_appendix/contribution/","level":1,"title":"16.2   Contribute to the book","text":"

    Despite the author's best efforts, this book may contain omissions and errors. Thank you for your understanding. If you find typos, broken links, missing content, ambiguous wording, unclear explanations, or poorly organized passages, please help us correct them so that we can provide a better learning resource.

    To thank everyone for their generous contributions to the open-source community, the GitHub IDs of all contributors are displayed on the home pages of the book's repository, website, and PDF edition.

    The beauty of open source

    Printed books often have long intervals between print runs, making their content difficult to update.

    In this open-source book, updates can be made within days or even hours.

    ","path":["Chapter 16.   Appendix","16.2   Contribute to the book"],"tags":[]},{"location":"chapter_appendix/contribution/#1-small-edits","level":3,"title":"1.   Small edits","text":"

    As shown in Figure 16-3, each page has an edit icon in its upper-right corner. Follow these steps to edit text or code.

    1. Click the edit icon. If you are prompted to fork the repository, agree to do so.
    2. Edit the Markdown source file, check that the content is correct, and keep the formatting as consistent as possible.
    3. Enter a description of your changes at the bottom of the page, then click \"Propose file change\". On the next page, click \"Create pull request\" to submit your pull request.

    Figure 16-3   Page edit button

    Images cannot be edited directly. Please describe the problem in a new issue or a comment, and we will redraw and replace the image as soon as possible.

    ","path":["Chapter 16.   Appendix","16.2   Contribute to the book"],"tags":[]},{"location":"chapter_appendix/contribution/#2-creating-content","level":3,"title":"2.   Creating content","text":"

    If you would like to contribute to this open-source project, for example by translating code into another programming language or expanding an article, follow this pull request workflow.

    1. Sign in to GitHub and fork the book's code repository to your own account.
    2. Open your fork's repository page and use git clone to clone it to your computer.
    3. Create your content locally and test it thoroughly to verify that the code is correct.
    4. Commit your local changes, then push them to your remote repository.
    5. Refresh the repository page and click \"Create pull request\" to submit your pull request.
    ","path":["Chapter 16.   Appendix","16.2   Contribute to the book"],"tags":[]},{"location":"chapter_appendix/contribution/#3-deploying-with-docker","level":3,"title":"3.   Deploying with Docker","text":"

    Run the following Docker command in the hello-algo root directory to access the English edition at http://localhost:8000/en/:

    docker-compose up -d\n

    Use the following command to remove the deployment:

    docker-compose down\n
    ","path":["Chapter 16.   Appendix","16.2   Contribute to the book"],"tags":[]},{"location":"chapter_appendix/installation/","level":1,"title":"16.1   Setting up a programming environment","text":"","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#1611-install-an-ide","level":2,"title":"16.1.1   Install an IDE","text":"

    We recommend VS Code, a lightweight, open-source integrated development environment (IDE). Visit the VS Code website, then download and install the version for your operating system.

    Figure 16-1   Downloading VS Code from the official website

    VS Code has an extensive ecosystem of extensions that support running and debugging code in most programming languages. For example, installing the Python Extension Pack lets you debug Python code. The installation steps are shown in Figure 16-2.

    Figure 16-2   Installing VS Code extensions

    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#1612-install-a-language-environment","level":2,"title":"16.1.2   Install a language environment","text":"","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#1-python","level":3,"title":"1.   Python","text":"
    1. Download and install Miniconda3. Python 3.10 or later is required.
    2. Search for python in the VS Code Extensions Marketplace and install Python Extension Pack.
    3. (Optional) Run pip install black in a terminal to install a code formatter.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#2-cc","level":3,"title":"2.   C/C++","text":"
    1. On Windows, install MinGW (setup guide). macOS comes with Clang, so no installation is needed.
    2. Search for c++ in the VS Code Extensions Marketplace and install C/C++ Extension Pack.
    3. (Optional) Open Settings, search for the Clang_format_fallback Style formatting option, and set it to { BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#3-java","level":3,"title":"3.   Java","text":"
    1. Download and install OpenJDK (a version later than JDK 9 is required).
    2. Search for java in the VS Code Extensions Marketplace and install Extension Pack for Java.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#4-c","level":3,"title":"4.   C#","text":"
    1. Download and install .NET 8.0.
    2. Search for C# Dev Kit in the VS Code Extensions Marketplace and install C# Dev Kit (setup guide).
    3. You can also use Visual Studio (installation guide).
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#5-go","level":3,"title":"5.   Go","text":"
    1. Download and install Go.
    2. Search for go in the VS Code Extensions Marketplace and install Go.
    3. Press Ctrl + Shift + P to open the Command Palette, enter go, select Go: Install/Update Tools, then select and install all the tools.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#6-swift","level":3,"title":"6.   Swift","text":"
    1. Download and install Swift.
    2. Search for swift in the VS Code Extensions Marketplace and install Swift for Visual Studio Code.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#7-javascript","level":3,"title":"7.   JavaScript","text":"
    1. Download and install Node.js.
    2. (Optional) Search for Prettier in the VS Code Extensions Marketplace and install this code formatter.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#8-typescript","level":3,"title":"8.   TypeScript","text":"
    1. Follow the JavaScript environment setup steps.
    2. Install TypeScript Execute (tsx).
    3. Search for typescript in the VS Code Extensions Marketplace and install Pretty TypeScript Errors.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#9-dart","level":3,"title":"9.   Dart","text":"
    1. Download and install Dart.
    2. Search for dart in the VS Code Extensions Marketplace and install Dart.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/installation/#10-rust","level":3,"title":"10.   Rust","text":"
    1. Download and install Rust.
    2. Search for rust in the VS Code Extensions Marketplace and install rust-analyzer.
    ","path":["Chapter 16.   Appendix","16.1   Setting up a programming environment"],"tags":[]},{"location":"chapter_appendix/terminology/","level":1,"title":"16.3   Glossary","text":"

    Table 16-1 lists important terms used in this book. Familiarity with their English names will help when reading technical literature in English.

    Table 16-1   Key terms in data structures and algorithms

    English Simplified Chinese algorithm 算法 data structure 数据结构 code 代码 file 文件 function 函数 method 方法 variable 变量 asymptotic complexity analysis 渐近复杂度分析 time complexity 时间复杂度 space complexity 空间复杂度 loop 循环 iteration 迭代 recursion 递归 tail recursion 尾递归 recursion tree 递归树 big-\\(O\\) notation 大 \\(O\\) 记号 asymptotic upper bound 渐近上界 sign-magnitude 原码 one's complement 反码 two's complement 补码 array 数组 index 索引 linked list 链表 linked list node, list node 链表节点 head node 头节点 tail node 尾节点 list 列表 dynamic array 动态数组 hard disk 硬盘 random-access memory (RAM) 内存 cache memory 缓存 cache miss 缓存未命中 cache hit rate 缓存命中率 stack 栈 top of the stack 栈顶 bottom of the stack 栈底 queue 队列 double-ended queue 双向队列 front of the queue 队首 rear of the queue 队尾 hash table 哈希表 hash set 哈希集合 bucket 桶 hash function 哈希函数 hash collision 哈希冲突 load factor 负载因子 separate chaining 链式地址 open addressing 开放寻址 linear probing 线性探测 lazy deletion 懒删除 binary tree 二叉树 tree node 树节点 left-child node 左子节点 right-child node 右子节点 parent node 父节点 left subtree 左子树 right subtree 右子树 root node 根节点 leaf node 叶节点 edge 边 level 层 degree 度 height 高度 depth 深度 perfect binary tree 完美二叉树 complete binary tree 完全二叉树 full binary tree 完满二叉树 balanced binary tree 平衡二叉树 binary search tree 二叉搜索树 AVL tree AVL 树 red-black tree 红黑树 level-order traversal 层序遍历 breadth-first traversal 广度优先遍历 depth-first traversal 深度优先遍历 preorder traversal 前序遍历 inorder traversal 中序遍历 postorder traversal 后序遍历 balanced binary search tree 平衡二叉搜索树 balance factor 平衡因子 heap 堆 max heap 大顶堆 min heap 小顶堆 priority queue 优先队列 heapify 堆化 top-\\(k\\) problem Top-\\(k\\) 问题 graph 图 vertex 顶点 undirected graph 无向图 directed graph 有向图 connected graph 连通图 disconnected graph 非连通图 weighted graph 有权图 adjacency 邻接 path 路径 in-degree 入度 out-degree 出度 adjacency matrix 邻接矩阵 adjacency list 邻接表 breadth-first search 广度优先搜索 depth-first search 深度优先搜索 binary search 二分查找 searching algorithm 搜索算法 sorting algorithm 排序算法 selection sort 选择排序 bubble sort 冒泡排序 insertion sort 插入排序 quick sort 快速排序 merge sort 归并排序 heap sort 堆排序 bucket sort 桶排序 counting sort 计数排序 radix sort 基数排序 divide and conquer 分治 Tower of Hanoi problem 汉诺塔问题 backtracking algorithm 回溯算法 constraint 约束 solution 解 state 状态 pruning 剪枝 permutations problem 全排列问题 subset-sum problem 子集和问题 \\(n\\)-queens problem \\(n\\) 皇后问题 dynamic programming 动态规划 initial state 初始状态 state transition equation 状态转移方程 knapsack problem 背包问题 edit distance problem 编辑距离问题 greedy algorithm 贪心算法","path":["Chapter 16.   Appendix","16.3   Glossary"],"tags":[]},{"location":"chapter_array_and_linkedlist/","level":1,"title":"Chapter 4.   Arrays and linked lists","text":"

    Abstract

    The world of data structures is like a solid brick wall.

    In an array, the bricks line up neatly, each right beside the next. In a linked list, the bricks are scattered, with connecting vines weaving freely through the gaps.

    ","path":["Chapter 4.   Arrays and linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 4.1   Arrays
    • 4.2   Linked lists
    • 4.3   Lists
    • 4.4   Memory and cache *
    • 4.5   Summary
    • 4.6   Exercises
    ","path":["Chapter 4.   Arrays and linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/","level":1,"title":"4.1   Arrays","text":"

    An array is a linear data structure that stores elements of the same type in contiguous memory. An element's position in the array is its index. Figure 4-1 shows the main concepts and storage layout.

    Figure 4-1   Array definition and storage layout

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#411-common-array-operations","level":2,"title":"4.1.1   Common array operations","text":"","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#1-initializing-an-array","level":3,"title":"1.   Initializing an array","text":"

    We can initialize an array with or without explicit values. When values are not specified, most programming languages initialize the elements to \\(0\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    # Initialize arrays\narr: list[int] = [0] * 5  # [ 0, 0, 0, 0, 0 ]\nnums: list[int] = [1, 3, 2, 5, 4]\n
    array.cpp
    /* Initialize arrays */\n// Stored on the stack\nint arr[5] = {};\nint nums[5] = { 1, 3, 2, 5, 4 };\n// Stored on the heap (memory must be freed manually)\nint* arr1 = new int[5] {};\nint* nums1 = new int[5] { 1, 3, 2, 5, 4 };\n
    array.java
    /* Initialize arrays */\nint[] arr = new int[5]; // { 0, 0, 0, 0, 0 }\nint[] nums = { 1, 3, 2, 5, 4 };\n
    array.cs
    /* Initialize arrays */\nint[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ]\nint[] nums = [1, 3, 2, 5, 4];\n
    array.go
    /* Initialize arrays */\nvar arr [5]int\n// In Go, [5]int is an array with a specified length; []int is a slice\n// Go array lengths are determined at compile time and must be constants\n// To make extend() easier to implement, we treat slices as arrays below\nnums := []int{1, 3, 2, 5, 4}\n
    array.swift
    /* Initialize arrays */\nlet arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]\nlet nums = [1, 3, 2, 5, 4]\n
    array.js
    /* Initialize arrays */\nvar arr = new Array(5).fill(0);\nvar nums = [1, 3, 2, 5, 4];\n
    array.ts
    /* Initialize arrays */\nlet arr: number[] = new Array(5).fill(0);\nlet nums: number[] = [1, 3, 2, 5, 4];\n
    array.dart
    /* Initialize arrays */\nList<int> arr = List.filled(5, 0); // [0, 0, 0, 0, 0]\nList<int> nums = [1, 3, 2, 5, 4];\n
    array.rs
    /* Initialize arrays */\nlet arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0]\nlet slice: &[i32] = &[0; 5];\n// In Rust, [i32; 5] is an array with a specified length; &[i32] is a slice\n// Rust array lengths are determined at compile time and must be constants\n// Vector is Rust's usual dynamic array type\n// To make extend() easier to implement, we treat a vector as an array below\nlet nums: Vec<i32> = vec![1, 3, 2, 5, 4];\n
    array.c
    /* Initialize arrays */\nint arr[5] = { 0 }; // { 0, 0, 0, 0, 0 }\nint nums[5] = { 1, 3, 2, 5, 4 };\n
    array.kt
    /* Initialize arrays */\nvar arr = IntArray(5) // { 0, 0, 0, 0, 0 }\nvar nums = intArrayOf(1, 3, 2, 5, 4)\n
    array.rb
    # Initialize arrays\narr = Array.new(5, 0)\nnums = [1, 3, 2, 5, 4]\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#2-accessing-an-element","level":3,"title":"2.   Accessing an element","text":"

    Because array elements occupy contiguous memory, their addresses are easy to calculate. Given the array's address (the address of its first element) and an index, the formula in Figure 4-2 gives the element's address, allowing direct access.

    Figure 4-2   Calculating an array element's memory address

    The first element has index \\(0\\), which may seem unintuitive because counting from \\(1\\) feels more natural. However, an index essentially describes an offset from the starting memory address. The first element's offset is \\(0\\), so index \\(0\\) makes sense.

    Array access is very efficient: any element can be accessed directly in \\(O(1)\\) time.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def random_access(nums: list[int]) -> int:\n    \"\"\"Access an element at a random index\"\"\"\n    # Choose a random number in the range [0, len(nums)-1]\n    random_index = random.randint(0, len(nums) - 1)\n    # Get and return the randomly selected element\n    random_num = nums[random_index]\n    return random_num\n
    array.cpp
    /* Access an element at a random index */\nint randomAccess(int *nums, int size) {\n    // Choose a random number in [0, size)\n    int randomIndex = rand() % size;\n    // Get and return the randomly selected element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
    array.java
    /* Access an element at a random index */\nint randomAccess(int[] nums) {\n    // Choose a random number in the interval [0, nums.length)\n    int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length);\n    // Get and return the randomly selected element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
    array.cs
    /* Access an element at a random index */\nint RandomAccess(int[] nums) {\n    Random random = new();\n    // Randomly select a number from [0, nums.Length)\n    int randomIndex = random.Next(nums.Length);\n    // Get and return the randomly selected element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
    array.go
    /* Access an element at a random index */\nfunc randomAccess(nums []int) (randomNum int) {\n    // Choose a random number in the interval [0, len(nums))\n    randomIndex := rand.Intn(len(nums))\n    // Get and return the randomly selected element\n    randomNum = nums[randomIndex]\n    return\n}\n
    array.swift
    /* Access an element at a random index */\nfunc randomAccess(nums: [Int]) -> Int {\n    // Pick a random number in the interval [0, nums.count)\n    let randomIndex = nums.indices.randomElement()!\n    // Get and return the randomly selected element\n    let randomNum = nums[randomIndex]\n    return randomNum\n}\n
    array.js
    /* Access an element at a random index */\nfunction randomAccess(nums) {\n    // Choose a random index in the interval [0, nums.length)\n    const random_index = Math.floor(Math.random() * nums.length);\n    // Get and return the randomly selected element\n    const random_num = nums[random_index];\n    return random_num;\n}\n
    array.ts
    /* Access an element at a random index */\nfunction randomAccess(nums: number[]): number {\n    // Choose a random index in the interval [0, nums.length)\n    const random_index = Math.floor(Math.random() * nums.length);\n    // Get and return the randomly selected element\n    const random_num = nums[random_index];\n    return random_num;\n}\n
    array.dart
    /* Access an element at a random index */\nint randomAccess(List<int> nums) {\n  // Choose a random index in the interval [0, nums.length)\n  int randomIndex = Random().nextInt(nums.length);\n  // Get and return the randomly selected element\n  int randomNum = nums[randomIndex];\n  return randomNum;\n}\n
    array.rs
    /* Access an element at a random index */\nfn random_access(nums: &[i32]) -> i32 {\n    // Choose a random index in [0, nums.len())\n    let random_index = rand::thread_rng().gen_range(0..nums.len());\n    // Get and return the randomly selected element\n    let random_num = nums[random_index];\n    random_num\n}\n
    array.c
    /* Access an element at a random index */\nint randomAccess(int *nums, int size) {\n    // Choose a random number in [0, size)\n    int randomIndex = rand() % size;\n    // Get and return the randomly selected element\n    int randomNum = nums[randomIndex];\n    return randomNum;\n}\n
    array.kt
    /* Access an element at a random index */\nfun randomAccess(nums: IntArray): Int {\n    // Randomly select a number in [0, nums.size)\n    val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size)\n    // Get and return the randomly selected element\n    val randomNum = nums[randomIndex]\n    return randomNum\n}\n
    array.rb
    ### Access an element at a random index ###\ndef random_access(nums)\n  # Choose a random index in the interval [0, nums.length)\n  random_index = Random.rand(0...nums.length)\n\n  # Get and return the randomly selected element\n  nums[random_index]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#3-inserting-an-element","level":3,"title":"3.   Inserting an element","text":"

    Array elements sit next to one another, with no room between them for more data. As shown in Figure 4-3, inserting into the middle requires shifting every element at or after the insertion point one position toward the end, then assigning the new element at the desired index.

    Figure 4-3   Inserting an element into an array

    Because an array's length is fixed, inserting an element causes the last element to be lost. We will discuss the solution in the section on lists.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def insert(nums: list[int], num: int, index: int):\n    \"\"\"Insert element num at index `index` in the array\"\"\"\n    # Shift all elements at and after index `index` one position to the right\n    for i in range(len(nums) - 1, index, -1):\n        nums[i] = nums[i - 1]\n    # Assign num to the element at index `index`\n    nums[index] = num\n
    array.cpp
    /* Insert element num at index `index` in the array */\nvoid insert(int *nums, int size, int num, int index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (int i = size - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.java
    /* Insert element num at index `index` in the array */\nvoid insert(int[] nums, int num, int index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (int i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.cs
    /* Insert element num at index `index` in the array */\nvoid Insert(int[] nums, int num, int index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (int i = nums.Length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.go
    /* Insert element num at index `index` in the array */\nfunc insert(nums []int, num int, index int) {\n    // Shift all elements at and after index `index` one position to the right\n    for i := len(nums) - 1; i > index; i-- {\n        nums[i] = nums[i-1]\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num\n}\n
    array.swift
    /* Insert element num at index `index` in the array */\nfunc insert(nums: inout [Int], num: Int, index: Int) {\n    // Shift all elements at and after index `index` one position to the right\n    for i in stride(from: nums.count - 1, to: index, by: -1) {\n        nums[i] = nums[i - 1]\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num\n}\n
    array.js
    /* Insert element num at index `index` in the array */\nfunction insert(nums, num, index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (let i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.ts
    /* Insert element num at index `index` in the array */\nfunction insert(nums: number[], num: number, index: number): void {\n    // Shift all elements at and after index `index` one position to the right\n    for (let i = nums.length - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.dart
    /* Insert element _num at index in the array */\nvoid insert(List<int> nums, int _num, int index) {\n  // Shift all elements at and after index `index` one position to the right\n  for (var i = nums.length - 1; i > index; i--) {\n    nums[i] = nums[i - 1];\n  }\n  // Assign _num to the element at index\n  nums[index] = _num;\n}\n
    array.rs
    /* Insert element num at index `index` in the array */\nfn insert(nums: &mut [i32], num: i32, index: usize) {\n    // Shift all elements at and after index `index` one position to the right\n    for i in (index + 1..nums.len()).rev() {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.c
    /* Insert element num at index `index` in the array */\nvoid insert(int *nums, int size, int num, int index) {\n    // Shift all elements at and after index `index` one position to the right\n    for (int i = size - 1; i > index; i--) {\n        nums[i] = nums[i - 1];\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num;\n}\n
    array.kt
    /* Insert element num at index `index` in the array */\nfun insert(nums: IntArray, num: Int, index: Int) {\n    // Shift all elements at and after index `index` one position to the right\n    for (i in nums.size - 1 downTo index + 1) {\n        nums[i] = nums[i - 1]\n    }\n    // Assign num to the element at index `index`\n    nums[index] = num\n}\n
    array.rb
    ### Insert element num at index `index` in the array ###\ndef insert(nums, num, index)\n  # Shift all elements at and after index `index` one position to the right\n  for i in (nums.length - 1).downto(index + 1)\n    nums[i] = nums[i - 1]\n  end\n\n  # Assign num to the element at index `index`\n  nums[index] = num\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#4-removing-an-element","level":3,"title":"4.   Removing an element","text":"

    Similarly, as shown in Figure 4-4, removing the element at index \\(i\\) requires shifting every element after \\(i\\) one position toward the beginning.

    Figure 4-4   Removing an element from an array

    After removal, the old final entry is no longer meaningful, so we do not need to change it explicitly.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def remove(nums: list[int], index: int):\n    \"\"\"Remove the element at index `index`\"\"\"\n    # Shift all elements after index `index` one position to the left\n    for i in range(index, len(nums) - 1):\n        nums[i] = nums[i + 1]\n
    array.cpp
    /* Remove the element at index `index` */\nvoid remove(int *nums, int size, int index) {\n    // Shift all elements after index `index` one position to the left\n    for (int i = index; i < size - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.java
    /* Remove the element at index `index` */\nvoid remove(int[] nums, int index) {\n    // Shift all elements after index `index` one position to the left\n    for (int i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.cs
    /* Remove the element at index `index` */\nvoid Remove(int[] nums, int index) {\n    // Shift all elements after index `index` one position to the left\n    for (int i = index; i < nums.Length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.go
    /* Remove the element at index `index` */\nfunc remove(nums []int, index int) {\n    // Shift all elements after index `index` one position to the left\n    for i := index; i < len(nums)-1; i++ {\n        nums[i] = nums[i+1]\n    }\n}\n
    array.swift
    /* Remove the element at index `index` */\nfunc remove(nums: inout [Int], index: Int) {\n    // Shift all elements after index `index` one position to the left\n    for i in nums.indices.dropFirst(index).dropLast() {\n        nums[i] = nums[i + 1]\n    }\n}\n
    array.js
    /* Remove the element at index `index` */\nfunction remove(nums, index) {\n    // Shift all elements after index `index` one position to the left\n    for (let i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.ts
    /* Remove the element at index `index` */\nfunction remove(nums: number[], index: number): void {\n    // Shift all elements after index `index` one position to the left\n    for (let i = index; i < nums.length - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.dart
    /* Remove the element at index `index` */\nvoid remove(List<int> nums, int index) {\n  // Shift all elements after index `index` one position to the left\n  for (var i = index; i < nums.length - 1; i++) {\n    nums[i] = nums[i + 1];\n  }\n}\n
    array.rs
    /* Remove the element at index `index` */\nfn remove(nums: &mut [i32], index: usize) {\n    // Shift all elements after index `index` one position to the left\n    for i in index..nums.len() - 1 {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.c
    /* Remove the element at index `index` */\n// Note: stdio.h already declares a function named remove\nvoid removeItem(int *nums, int size, int index) {\n    // Shift all elements after index `index` one position to the left\n    for (int i = index; i < size - 1; i++) {\n        nums[i] = nums[i + 1];\n    }\n}\n
    array.kt
    /* Remove the element at index `index` */\nfun remove(nums: IntArray, index: Int) {\n    // Shift all elements after index `index` one position to the left\n    for (i in index..<nums.size - 1) {\n        nums[i] = nums[i + 1]\n    }\n}\n
    array.rb
    ### Remove the element at index `index` ###\ndef remove(nums, index)\n  # Shift all elements after index `index` one position to the left\n  for i in index...(nums.length - 1)\n    nums[i] = nums[i + 1]\n  end\nend\n
    Visualize execution

    View full screen >

    Overall, inserting and removing array elements have these drawbacks.

    • High time complexity: Both operations take \\(O(n)\\) time on average, where \\(n\\) is the array length.
    • Lost elements: Since the length is fixed, insertion loses any element pushed beyond the array's bounds.
    • Wasted memory: We can allocate a longer array and use only its beginning, so any final entries lost during insertion are unused. However, this leaves some memory wasted.
    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#5-traversing-an-array","level":3,"title":"5.   Traversing an array","text":"

    Most programming languages let us traverse an array either by index or by visiting its elements directly:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def traverse(nums: list[int]):\n    \"\"\"Traverse the array\"\"\"\n    count = 0\n    # Traverse the array by index\n    for i in range(len(nums)):\n        count += nums[i]\n    # Iterate directly over the array elements\n    for num in nums:\n        count += num\n    # Iterate over both the indices and the elements\n    for i, num in enumerate(nums):\n        count += nums[i]\n        count += num\n
    array.cpp
    /* Traverse the array */\nvoid traverse(int *nums, int size) {\n    int count = 0;\n    // Traverse the array by index\n    for (int i = 0; i < size; i++) {\n        count += nums[i];\n    }\n}\n
    array.java
    /* Traverse the array */\nvoid traverse(int[] nums) {\n    int count = 0;\n    // Traverse the array by index\n    for (int i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Iterate directly over the array elements\n    for (int num : nums) {\n        count += num;\n    }\n}\n
    array.cs
    /* Traverse the array */\nvoid Traverse(int[] nums) {\n    int count = 0;\n    // Traverse the array by index\n    for (int i = 0; i < nums.Length; i++) {\n        count += nums[i];\n    }\n    // Iterate directly over the array elements\n    foreach (int num in nums) {\n        count += num;\n    }\n}\n
    array.go
    /* Traverse the array */\nfunc traverse(nums []int) {\n    count := 0\n    // Traverse the array by index\n    for i := 0; i < len(nums); i++ {\n        count += nums[i]\n    }\n    count = 0\n    // Iterate directly over the array elements\n    for _, num := range nums {\n        count += num\n    }\n    // Iterate over both the indices and the elements\n    for i, num := range nums {\n        count += nums[i]\n        count += num\n    }\n}\n
    array.swift
    /* Traverse the array */\nfunc traverse(nums: [Int]) {\n    var count = 0\n    // Traverse the array by index\n    for i in nums.indices {\n        count += nums[i]\n    }\n    // Iterate directly over the array elements\n    for num in nums {\n        count += num\n    }\n    // Iterate over both the indices and the elements\n    for (i, num) in nums.enumerated() {\n        count += nums[i]\n        count += num\n    }\n}\n
    array.js
    /* Traverse the array */\nfunction traverse(nums) {\n    let count = 0;\n    // Traverse the array by index\n    for (let i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Iterate directly over the array elements\n    for (const num of nums) {\n        count += num;\n    }\n}\n
    array.ts
    /* Traverse the array */\nfunction traverse(nums: number[]): void {\n    let count = 0;\n    // Traverse the array by index\n    for (let i = 0; i < nums.length; i++) {\n        count += nums[i];\n    }\n    // Iterate directly over the array elements\n    for (const num of nums) {\n        count += num;\n    }\n}\n
    array.dart
    /* Traverse the array elements */\nvoid traverse(List<int> nums) {\n  int count = 0;\n  // Traverse the array by index\n  for (var i = 0; i < nums.length; i++) {\n    count += nums[i];\n  }\n  // Iterate directly over the array elements\n  for (int _num in nums) {\n    count += _num;\n  }\n  // Traverse the array using forEach\n  nums.forEach((_num) {\n    count += _num;\n  });\n}\n
    array.rs
    /* Traverse the array */\nfn traverse(nums: &[i32]) {\n    let mut _count = 0;\n    // Traverse the array by index\n    for i in 0..nums.len() {\n        _count += nums[i];\n    }\n    // Iterate directly over the array elements\n    _count = 0;\n    for &num in nums {\n        _count += num;\n    }\n}\n
    array.c
    /* Traverse the array */\nvoid traverse(int *nums, int size) {\n    int count = 0;\n    // Traverse the array by index\n    for (int i = 0; i < size; i++) {\n        count += nums[i];\n    }\n}\n
    array.kt
    /* Traverse the array */\nfun traverse(nums: IntArray) {\n    var count = 0\n    // Traverse the array by index\n    for (i in nums.indices) {\n        count += nums[i]\n    }\n    // Iterate directly over the array elements\n    for (j in nums) {\n        count += j\n    }\n}\n
    array.rb
    ### Traverse the array ###\ndef traverse(nums)\n  count = 0\n\n  # Traverse the array by index\n  for i in 0...nums.length\n    count += nums[i]\n  end\n\n  # Iterate directly over the array elements\n  for num in nums\n    count += num\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#6-finding-an-element","level":3,"title":"6.   Finding an element","text":"

    To find a specified element, traverse the array and compare each value with the target. Return the index when a match is found.

    Because an array is a linear data structure, this is called linear search.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def find(nums: list[int], target: int) -> int:\n    \"\"\"Find a specified element in the array\"\"\"\n    for i in range(len(nums)):\n        if nums[i] == target:\n            return i\n    return -1\n
    array.cpp
    /* Find a specified element in the array */\nint find(int *nums, int size, int target) {\n    for (int i = 0; i < size; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
    array.java
    /* Find a specified element in the array */\nint find(int[] nums, int target) {\n    for (int i = 0; i < nums.length; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
    array.cs
    /* Find a specified element in the array */\nint Find(int[] nums, int target) {\n    for (int i = 0; i < nums.Length; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
    array.go
    /* Find a specified element in the array */\nfunc find(nums []int, target int) (index int) {\n    index = -1\n    for i := 0; i < len(nums); i++ {\n        if nums[i] == target {\n            index = i\n            break\n        }\n    }\n    return\n}\n
    array.swift
    /* Find a specified element in the array */\nfunc find(nums: [Int], target: Int) -> Int {\n    for i in nums.indices {\n        if nums[i] == target {\n            return i\n        }\n    }\n    return -1\n}\n
    array.js
    /* Find a specified element in the array */\nfunction find(nums, target) {\n    for (let i = 0; i < nums.length; i++) {\n        if (nums[i] === target) return i;\n    }\n    return -1;\n}\n
    array.ts
    /* Find a specified element in the array */\nfunction find(nums: number[], target: number): number {\n    for (let i = 0; i < nums.length; i++) {\n        if (nums[i] === target) {\n            return i;\n        }\n    }\n    return -1;\n}\n
    array.dart
    /* Find a specified element in the array */\nint find(List<int> nums, int target) {\n  for (var i = 0; i < nums.length; i++) {\n    if (nums[i] == target) return i;\n  }\n  return -1;\n}\n
    array.rs
    /* Find a specified element in the array */\nfn find(nums: &[i32], target: i32) -> Option<usize> {\n    for i in 0..nums.len() {\n        if nums[i] == target {\n            return Some(i);\n        }\n    }\n    None\n}\n
    array.c
    /* Find a specified element in the array */\nint find(int *nums, int size, int target) {\n    for (int i = 0; i < size; i++) {\n        if (nums[i] == target)\n            return i;\n    }\n    return -1;\n}\n
    array.kt
    /* Find a specified element in the array */\nfun find(nums: IntArray, target: Int): Int {\n    for (i in nums.indices) {\n        if (nums[i] == target)\n            return i\n    }\n    return -1\n}\n
    array.rb
    ### Find a specified element in the array ###\ndef find(nums, target)\n  for i in 0...nums.length\n    return i if nums[i] == target\n  end\n\n  -1\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#7-expanding-an-array","level":3,"title":"7.   Expanding an array","text":"

    In a complex system, a program cannot ensure that memory immediately after an array is available, so extending it in place is unsafe. Therefore, in most programming languages, an array's length is fixed.

    To expand an array, create a larger one and copy every element into it. This takes \\(O(n)\\) time and can be expensive for a large array. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py
    def extend(nums: list[int], enlarge: int) -> list[int]:\n    \"\"\"Extend the array length\"\"\"\n    # Initialize an array with the extended length\n    res = [0] * (len(nums) + enlarge)\n    # Copy all elements from the original array to the new array\n    for i in range(len(nums)):\n        res[i] = nums[i]\n    # Return the new, extended array\n    return res\n
    array.cpp
    /* Extend the array length */\nint *extend(int *nums, int size, int enlarge) {\n    // Initialize an array with the extended length\n    int *res = new int[size + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < size; i++) {\n        res[i] = nums[i];\n    }\n    // Free memory\n    delete[] nums;\n    // Return the new, extended array\n    return res;\n}\n
    array.java
    /* Extend the array length */\nint[] extend(int[] nums, int enlarge) {\n    // Initialize an array with the extended length\n    int[] res = new int[nums.length + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.cs
    /* Extend the array length */\nint[] Extend(int[] nums, int enlarge) {\n    // Initialize an array with the extended length\n    int[] res = new int[nums.Length + enlarge];\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < nums.Length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.go
    /* Extend the array length */\nfunc extend(nums []int, enlarge int) []int {\n    // Initialize an array with the extended length\n    res := make([]int, len(nums)+enlarge)\n    // Copy all elements from the original array to the new array\n    for i, num := range nums {\n        res[i] = num\n    }\n    // Return the new, extended array\n    return res\n}\n
    array.swift
    /* Extend the array length */\nfunc extend(nums: [Int], enlarge: Int) -> [Int] {\n    // Initialize an array with the extended length\n    var res = Array(repeating: 0, count: nums.count + enlarge)\n    // Copy all elements from the original array to the new array\n    for i in nums.indices {\n        res[i] = nums[i]\n    }\n    // Return the new, extended array\n    return res\n}\n
    array.js
    /* Extend the array length */\n// JavaScript Array is a dynamic array and can be extended directly\n// For learning purposes, this function treats Array as a fixed-length array\nfunction extend(nums, enlarge) {\n    // Initialize an array with the extended length\n    const res = new Array(nums.length + enlarge).fill(0);\n    // Copy all elements from the original array to the new array\n    for (let i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.ts
    /* Extend the array length */\n// Note: TypeScript Array is a dynamic array and can grow directly\n// For learning purposes, this function treats Array as a fixed-length array\nfunction extend(nums: number[], enlarge: number): number[] {\n    // Initialize an array with the extended length\n    const res = new Array(nums.length + enlarge).fill(0);\n    // Copy all elements from the original array to the new array\n    for (let i = 0; i < nums.length; i++) {\n        res[i] = nums[i];\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.dart
    /* Extend the array length */\nList<int> extend(List<int> nums, int enlarge) {\n  // Initialize an array with the extended length\n  List<int> res = List.filled(nums.length + enlarge, 0);\n  // Copy all elements from the original array to the new array\n  for (var i = 0; i < nums.length; i++) {\n    res[i] = nums[i];\n  }\n  // Return the new, extended array\n  return res;\n}\n
    array.rs
    /* Extend the array length */\nfn extend(nums: &[i32], enlarge: usize) -> Vec<i32> {\n    // Initialize an array with the extended length\n    let mut res: Vec<i32> = vec![0; nums.len() + enlarge];\n    // Copy all elements from the original array to the new one\n    res[0..nums.len()].copy_from_slice(nums);\n\n    // Return the new, extended array\n    res\n}\n
    array.c
    /* Extend the array length */\nint *extend(int *nums, int size, int enlarge) {\n    // Initialize an array with the extended length\n    int *res = (int *)malloc(sizeof(int) * (size + enlarge));\n    // Copy all elements from the original array to the new array\n    for (int i = 0; i < size; i++) {\n        res[i] = nums[i];\n    }\n    // Initialize the newly added space\n    for (int i = size; i < size + enlarge; i++) {\n        res[i] = 0;\n    }\n    // Return the new, extended array\n    return res;\n}\n
    array.kt
    /* Extend the array length */\nfun extend(nums: IntArray, enlarge: Int): IntArray {\n    // Initialize an array with the extended length\n    val res = IntArray(nums.size + enlarge)\n    // Copy all elements from the original array to the new array\n    for (i in nums.indices) {\n        res[i] = nums[i]\n    }\n    // Return the new, extended array\n    return res\n}\n
    array.rb
    ### Extend the array length ###\n# Ruby Array is a dynamic array that can grow directly\n# For learning purposes, this function treats Array as a fixed-length array\ndef extend(nums, enlarge)\n  # Initialize an array with the extended length\n  res = Array.new(nums.length + enlarge, 0)\n\n  # Copy all elements from the original array to the new array\n  for i in 0...nums.length\n    res[i] = nums[i]\n  end\n\n  # Return the new, extended array\n  res\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#412-strengths-and-limitations-of-arrays","level":2,"title":"4.1.2   Strengths and limitations of arrays","text":"

    An array stores elements of one type in contiguous memory. These known properties give the system opportunities to optimize operations.

    • Space efficiency: Data occupies a contiguous block with no extra structural overhead.
    • Random access: Any element can be accessed in \\(O(1)\\) time.
    • Cache locality: Accessing an element loads not only that element but also nearby data into the cache, speeding up later operations.

    Contiguous storage also has limitations.

    • Slow insertion and deletion: With many elements, these operations require substantial shifting.
    • Fixed length: Once initialized, an array cannot change length; expansion requires copying all data into a new array.
    • Wasted space: Any allocated space beyond actual needs is unused.
    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#413-typical-applications-of-arrays","level":2,"title":"4.1.3   Typical applications of arrays","text":"

    Arrays are fundamental, widely used data structures. They appear throughout algorithms and underpin more complex structures.

    • Random access: To draw random samples, store data in an array and generate random indices to select elements.
    • Sorting and searching: Arrays are the most common structures for these algorithms. Quick sort, merge sort, and binary search operate primarily on arrays.
    • Lookup tables: An array can provide fast access to an element or a mapping. For example, a character-to-ASCII mapping can use a character's ASCII value as an index and store its corresponding element there.
    • Machine learning: Neural networks make extensive use of linear algebra on vectors, matrices, and tensors, all built from arrays. Arrays are the most common data structure in neural network programming.
    • Implementing data structures: Arrays can implement stacks, queues, hash tables, heaps, graphs, and more. A graph's adjacency matrix, for example, is a two-dimensional array.
    ","path":["Chapter 4.   Arrays and linked lists","4.1   Arrays"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/","level":1,"title":"4.6   Exercises","text":"","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#461-check-your-understanding","level":2,"title":"4.6.1   Check your understanding","text":"","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#1-finding-elements-in-arrays-and-linked-lists","level":3,"title":"1.   Finding elements in arrays and linked lists","text":"

    An array and a singly linked list both store [A, B, C, D, E] in order. We want to read the fourth element, D:

    1. Which index can we use to access it directly in the array?
    2. Starting at the head node A, which nodes must the singly linked list visit by following next?
    3. As the requested element moves farther toward the end, how does the number of steps change for each structure? Which is better suited to repeated access by position, and why?
    Reference answer
    1. With zero-based indexing, the fourth element has index 3, so the array can read arr[3] directly.

    2. The singly linked list must start at the head and follow the path A → B → C → D, advancing along next three times.

    3. An array can locate an element directly from its starting address and the index, giving time complexity \\(O(1)\\) for access by position. To access the \\(k\\)-th node of a singly linked list, we must start at the head and follow next \\(k-1\\) times, taking \\(O(n)\\) time in the worst case.

      This comparison concerns access by position only; it does not mean that linked lists are slower for every operation.

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#2-inserting-elements-into-arrays-and-linked-lists","level":3,"title":"2.   Inserting elements into arrays and linked lists","text":"

    An array and a singly linked list both store A, B, C, D. We want to insert X after B:

    • The array has capacity 5 and currently contains [A, B, C, D, _].
    • The linked list is A → B → C → D, and we already have a reference to node B.
    1. Which elements must the array move? Write out the array after insertion.
    2. In what order should the linked list update X.next and B.next? Write out the resulting list.
    3. Why must a comparison of insertion efficiency specify that we already have a reference to node B?
    Reference answer
    1. First move D one position to the right, then move C one position to the right, and finally put X at index 2. The result is [A, B, X, C, D].

    2. Initially, B.next points to C. First set X.next = B.next, making X point to C. Then set B.next = X. The result is A → B → X → C → D. If we overwrite B.next first without saving the original link, we may lose access to C.

    3. Once the location of B is known, insertion into the linked list requires changing only two links and takes \\(O(1)\\) time. If we first need to search for B from the head, that search itself may take \\(O(n)\\) time.

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#3-how-does-a-lists-capacity-grow","level":3,"title":"3.   How does a list's capacity grow?","text":"

    An array-based list currently contains [A, B, C], with length size = 3 and capacity capacity = 4. When capacity runs out, the new array is allocated with twice the previous capacity.

    1. After appending D, what are the list's length and capacity? Does it need to grow?
    2. When E is appended next, what does the capacity become? How many existing elements must be copied?
    3. If the length of the underlying array cannot change, why does the list's capacity appear to grow?
    Reference answer
    1. D fits in the last empty position. The contents become [A, B, C, D], with size = 4 and capacity = 4. No capacity increase is needed.

    2. Appending E finds no empty position, so a new array of capacity 8 is created. The four existing elements A, B, C, D are copied into it, and then E is added. The list now has size = 5 and capacity = 8.

    3. The original array itself does not become longer. The list creates a larger array, copies the existing elements, and switches to using the new array for storage. From the user's perspective, its capacity has grown.

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#462-programming-exercises","level":2,"title":"4.6.2   Programming exercises","text":"","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#1-adding-one-to-a-large-integer-stored-in-an-array","level":3,"title":"1.   Adding one to a large integer stored in an array","text":"

    The array digits stores the digits of a nonnegative integer from left to right. For example, [3, 0, 8] represents 308. Zero is represented by [0]; all other inputs have a nonzero first digit.

    Simulate the usual digit-by-digit method for decimal addition to add 1 to this integer, and return the result in the same array format. You may modify digits directly. If a carry creates a new leading digit, you may return a longer array.

    Hints
    1. Start at the last digit of the array, just as you would when adding by hand.
    2. If the current digit is less than 9, increment it and return immediately.
    3. If the current digit is 9, change it to 0. If all digits are 9, add a leading 1.

    LeetCode

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#2-reversing-a-singly-linked-list","level":3,"title":"2.   Reversing a singly linked list","text":"

    You are given the head node head of a singly linked list. Each node contains a value and a next reference to the following node.

    Use iteration to reverse every link between nodes, and return the new head. Do not create any new linked-list nodes.

    Hints
    1. First draw three connected nodes and the two pointers prev and cur on paper.
    2. Before changing cur.next, save the original next node in nxt.
    3. After reversing cur.next, set prev = cur and then cur = nxt to continue with the next node in the original list.

    LeetCode

    ","path":["Chapter 4.   Arrays and linked lists","4.6   Exercises"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/","level":1,"title":"4.2   Linked lists","text":"

    Memory is a resource used by all programs, and in a complex system, free space may be scattered throughout it. An array requires contiguous storage, which may be unavailable for a very large array. This is where a linked list's flexibility helps.

    A linked list is a linear data structure whose elements are node objects connected by references. Each reference stores the next node's memory address, allowing us to reach it from the current node.

    This design lets nodes occupy scattered memory locations; their addresses need not be contiguous.

    Figure 4-5   Linked list definition and storage layout

    As shown above, the basic unit of a linked list is a node object. Each node holds a value and a reference to the next node.

    • The first node is called the head, and the last is called the tail.
    • The tail points to a null value: null in Java, nullptr in C++, and None in Python.
    • In languages supporting pointers, such as C, C++, Go, and Rust, these references are expressed as pointers.

    As the code below shows, each ListNode stores a reference (pointer) in addition to its value. Thus, a linked list uses more memory than an array for the same amount of data.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class ListNode:\n    \"\"\"Linked list node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val               # Node value\n        self.next: ListNode | None = None # Reference to the next node\n
    /* Linked list node structure */\nstruct ListNode {\n    int val;         // Node value\n    ListNode *next;  // Pointer to the next node\n    ListNode(int x) : val(x), next(nullptr) {}  // Constructor\n};\n
    /* Linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode next;  // Reference to the next node\n    ListNode(int x) { val = x; }  // Constructor\n}\n
    /* Linked list node class */\nclass ListNode(int x) {  // Constructor\n    int val = x;         // Node value\n    ListNode? next;      // Reference to the next node\n}\n
    /* Linked list node structure */\ntype ListNode struct {\n    Val  int       // Node value\n    Next *ListNode // Pointer to the next node\n}\n\n// NewListNode constructor: create a new linked list\nfunc NewListNode(val int) *ListNode {\n    return &ListNode{\n        Val:  val,\n        Next: nil,\n    }\n}\n
    /* Linked list node class */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the next node\n\n    init(x: Int) { // Constructor\n        val = x\n    }\n}\n
    /* Linked list node class */\nclass ListNode {\n    constructor(val, next) {\n        this.val = (val === undefined ? 0 : val);       // Node value\n        this.next = (next === undefined ? null : next); // Reference to the next node\n    }\n}\n
    /* Linked list node class */\nclass ListNode {\n    val: number;\n    next: ListNode | null;\n    constructor(val?: number, next?: ListNode | null) {\n        this.val = val === undefined ? 0 : val;        // Node value\n        this.next = next === undefined ? null : next;  // Reference to the next node\n    }\n}\n
    /* Linked list node class */\nclass ListNode {\n  int val; // Node value\n  ListNode? next; // Reference to the next node\n  ListNode(this.val, [this.next]); // Constructor\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n/* Linked list node class */\n#[derive(Debug)]\nstruct ListNode {\n    val: i32, // Node value\n    next: Option<Rc<RefCell<ListNode>>>, // Pointer to the next node\n}\n
    /* Linked list node structure */\ntypedef struct ListNode {\n    int val;               // Node value\n    struct ListNode *next; // Pointer to the next node\n} ListNode;\n\n/* Constructor */\nListNode *newListNode(int val) {\n    ListNode *node;\n    node = (ListNode *) malloc(sizeof(ListNode));\n    node->val = val;\n    node->next = NULL;\n    return node;\n}\n
    /* Linked list node class */\n// Constructor\nclass ListNode(x: Int) {\n    val _val: Int = x          // Node value\n    var next: ListNode? = null // Reference to the next node\n}\n
    # Linked list node class\nclass ListNode\n  attr_accessor :val  # Node value\n  attr_accessor :next # Reference to the next node\n\n  def initialize(val=0, next_node=nil)\n    @val = val\n    @next = next_node\n  end\nend\n
    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#421-common-linked-list-operations","level":2,"title":"4.2.1   Common linked list operations","text":"","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#1-initializing-a-linked-list","level":3,"title":"1.   Initializing a linked list","text":"

    Building a linked list takes two steps: initialize the node objects, then connect them with references. Afterward, start at the head and follow next references to visit every node in order.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    # Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4\n# Initialize the nodes\nn0 = ListNode(1)\nn1 = ListNode(3)\nn2 = ListNode(2)\nn3 = ListNode(5)\nn4 = ListNode(4)\n# Link the nodes with references\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
    linked_list.cpp
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nListNode* n0 = new ListNode(1);\nListNode* n1 = new ListNode(3);\nListNode* n2 = new ListNode(2);\nListNode* n3 = new ListNode(5);\nListNode* n4 = new ListNode(4);\n// Link the nodes with references\nn0->next = n1;\nn1->next = n2;\nn2->next = n3;\nn3->next = n4;\n
    linked_list.java
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nListNode n0 = new ListNode(1);\nListNode n1 = new ListNode(3);\nListNode n2 = new ListNode(2);\nListNode n3 = new ListNode(5);\nListNode n4 = new ListNode(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.cs
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nListNode n0 = new(1);\nListNode n1 = new(3);\nListNode n2 = new(2);\nListNode n3 = new(5);\nListNode n4 = new(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.go
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nn0 := NewListNode(1)\nn1 := NewListNode(3)\nn2 := NewListNode(2)\nn3 := NewListNode(5)\nn4 := NewListNode(4)\n// Link the nodes with references\nn0.Next = n1\nn1.Next = n2\nn2.Next = n3\nn3.Next = n4\n
    linked_list.swift
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nlet n0 = ListNode(x: 1)\nlet n1 = ListNode(x: 3)\nlet n2 = ListNode(x: 2)\nlet n3 = ListNode(x: 5)\nlet n4 = ListNode(x: 4)\n// Link the nodes with references\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
    linked_list.js
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.ts
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.dart
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\\\n// Initialize the nodes\nListNode n0 = ListNode(1);\nListNode n1 = ListNode(3);\nListNode n2 = ListNode(2);\nListNode n3 = ListNode(5);\nListNode n4 = ListNode(4);\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.rs
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nlet n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None }));\nlet n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None }));\nlet n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None }));\nlet n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None }));\nlet n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None }));\n\n// Link the nodes with references\nn0.borrow_mut().next = Some(n1.clone());\nn1.borrow_mut().next = Some(n2.clone());\nn2.borrow_mut().next = Some(n3.clone());\nn3.borrow_mut().next = Some(n4.clone());\n
    linked_list.c
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nListNode* n0 = newListNode(1);\nListNode* n1 = newListNode(3);\nListNode* n2 = newListNode(2);\nListNode* n3 = newListNode(5);\nListNode* n4 = newListNode(4);\n// Link the nodes with references\nn0->next = n1;\nn1->next = n2;\nn2->next = n3;\nn3->next = n4;\n
    linked_list.kt
    /* Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4 */\n// Initialize the nodes\nval n0 = ListNode(1)\nval n1 = ListNode(3)\nval n2 = ListNode(2)\nval n3 = ListNode(5)\nval n4 = ListNode(4)\n// Link the nodes with references\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n
    linked_list.rb
    # Initialize the linked list 1 -> 3 -> 2 -> 5 -> 4\n# Initialize the nodes\nn0 = ListNode.new(1)\nn1 = ListNode.new(3)\nn2 = ListNode.new(2)\nn3 = ListNode.new(5)\nn4 = ListNode.new(4)\n# Link the nodes with references\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n
    Visualize execution

    View full screen >

    An array is a single variable, such as nums, containing entries nums[0], nums[1], and so on. A linked list consists of separate node objects. We usually refer to the list by its head node; for example, the list above can be called n0.

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#2-inserting-a-node","level":3,"title":"2.   Inserting a node","text":"

    Inserting a node is straightforward. As shown in Figure 4-6, inserting a new node P between adjacent nodes n0 and n1 requires changing only two references (pointers), taking \\(O(1)\\) time.

    In contrast, inserting an array element takes \\(O(n)\\) time and is slower for large datasets.

    Figure 4-6   Inserting a node into a linked list

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    def insert(n0: ListNode, P: ListNode):\n    \"\"\"Insert node P after node n0 in the linked list\"\"\"\n    n1 = n0.next\n    P.next = n1\n    n0.next = P\n
    linked_list.cpp
    /* Insert node P after node n0 in the linked list */\nvoid insert(ListNode *n0, ListNode *P) {\n    ListNode *n1 = n0->next;\n    P->next = n1;\n    n0->next = P;\n}\n
    linked_list.java
    /* Insert node P after node n0 in the linked list */\nvoid insert(ListNode n0, ListNode P) {\n    ListNode n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
    linked_list.cs
    /* Insert node P after node n0 in the linked list */\nvoid Insert(ListNode n0, ListNode P) {\n    ListNode? n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
    linked_list.go
    /* Insert node P after node n0 in the linked list */\nfunc insertNode(n0 *ListNode, P *ListNode) {\n    n1 := n0.Next\n    P.Next = n1\n    n0.Next = P\n}\n
    linked_list.swift
    /* Insert node P after node n0 in the linked list */\nfunc insert(n0: ListNode, P: ListNode) {\n    let n1 = n0.next\n    P.next = n1\n    n0.next = P\n}\n
    linked_list.js
    /* Insert node P after node n0 in the linked list */\nfunction insert(n0, P) {\n    const n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
    linked_list.ts
    /* Insert node P after node n0 in the linked list */\nfunction insert(n0: ListNode, P: ListNode): void {\n    const n1 = n0.next;\n    P.next = n1;\n    n0.next = P;\n}\n
    linked_list.dart
    /* Insert node P after node n0 in the linked list */\nvoid insert(ListNode n0, ListNode P) {\n  ListNode? n1 = n0.next;\n  P.next = n1;\n  n0.next = P;\n}\n
    linked_list.rs
    /* Insert node P after node n0 in the linked list */\n#[allow(non_snake_case)]\npub fn insert<T>(n0: &Rc<RefCell<ListNode<T>>>, P: Rc<RefCell<ListNode<T>>>) {\n    let n1 = n0.borrow_mut().next.take();\n    P.borrow_mut().next = n1;\n    n0.borrow_mut().next = Some(P);\n}\n
    linked_list.c
    /* Insert node P after node n0 in the linked list */\nvoid insert(ListNode *n0, ListNode *P) {\n    ListNode *n1 = n0->next;\n    P->next = n1;\n    n0->next = P;\n}\n
    linked_list.kt
    /* Insert node p after node n0 in the linked list */\nfun insert(n0: ListNode?, p: ListNode?) {\n    val n1 = n0?.next\n    p?.next = n1\n    n0?.next = p\n}\n
    linked_list.rb
    ### Insert node _p after node n0 in the linked list ###\n# In Ruby, `p` is a built-in method and `P` denotes a constant, so use `_p` instead\ndef insert(n0, _p)\n  n1 = n0.next\n  _p.next = n1\n  n0.next = _p\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#3-removing-a-node","level":3,"title":"3.   Removing a node","text":"

    Removing a node is also straightforward: as shown in Figure 4-7, only one reference (pointer) needs to change.

    Although P still points to n1 after removal, traversing the list can no longer reach P. It therefore no longer belongs to the list.

    Figure 4-7   Removing a linked list node

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    def remove(n0: ListNode):\n    \"\"\"Remove the first node after node n0 in the linked list\"\"\"\n    if not n0.next:\n        return\n    # n0 -> P -> n1\n    P = n0.next\n    n1 = P.next\n    n0.next = n1\n
    linked_list.cpp
    /* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode *n0) {\n    if (n0->next == nullptr)\n        return;\n    // n0 -> P -> n1\n    ListNode *P = n0->next;\n    ListNode *n1 = P->next;\n    n0->next = n1;\n    // Free memory\n    delete P;\n}\n
    linked_list.java
    /* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode n0) {\n    if (n0.next == null)\n        return;\n    // n0 -> P -> n1\n    ListNode P = n0.next;\n    ListNode n1 = P.next;\n    n0.next = n1;\n}\n
    linked_list.cs
    /* Remove the first node after node n0 in the linked list */\nvoid Remove(ListNode n0) {\n    if (n0.next == null)\n        return;\n    // n0 -> P -> n1\n    ListNode P = n0.next;\n    ListNode? n1 = P.next;\n    n0.next = n1;\n}\n
    linked_list.go
    /* Remove the first node after node n0 in the linked list */\nfunc removeItem(n0 *ListNode) {\n    if n0.Next == nil {\n        return\n    }\n    // n0 -> P -> n1\n    P := n0.Next\n    n1 := P.Next\n    n0.Next = n1\n}\n
    linked_list.swift
    /* Remove the first node after node n0 in the linked list */\nfunc remove(n0: ListNode) {\n    if n0.next == nil {\n        return\n    }\n    // n0 -> P -> n1\n    let P = n0.next\n    let n1 = P?.next\n    n0.next = n1\n}\n
    linked_list.js
    /* Remove the first node after node n0 in the linked list */\nfunction remove(n0) {\n    if (!n0.next) return;\n    // n0 -> P -> n1\n    const P = n0.next;\n    const n1 = P.next;\n    n0.next = n1;\n}\n
    linked_list.ts
    /* Remove the first node after node n0 in the linked list */\nfunction remove(n0: ListNode): void {\n    if (!n0.next) {\n        return;\n    }\n    // n0 -> P -> n1\n    const P = n0.next;\n    const n1 = P.next;\n    n0.next = n1;\n}\n
    linked_list.dart
    /* Remove the first node after node n0 in the linked list */\nvoid remove(ListNode n0) {\n  if (n0.next == null) return;\n  // n0 -> P -> n1\n  ListNode P = n0.next!;\n  ListNode? n1 = P.next;\n  n0.next = n1;\n}\n
    linked_list.rs
    /* Remove the first node after node n0 in the linked list */\n#[allow(non_snake_case)]\npub fn remove<T>(n0: &Rc<RefCell<ListNode<T>>>) {\n    // n0 -> P -> n1\n    let P = n0.borrow_mut().next.take();\n    if let Some(node) = P {\n        let n1 = node.borrow_mut().next.take();\n        n0.borrow_mut().next = n1;\n    }\n}\n
    linked_list.c
    /* Remove the first node after node n0 in the linked list */\n// Note: stdio.h already declares a function named remove\nvoid removeItem(ListNode *n0) {\n    if (!n0->next)\n        return;\n    // n0 -> P -> n1\n    ListNode *P = n0->next;\n    ListNode *n1 = P->next;\n    n0->next = n1;\n    // Free memory\n    free(P);\n}\n
    linked_list.kt
    /* Remove the first node after node n0 in the linked list */\nfun remove(n0: ListNode?) {\n    if (n0?.next == null)\n        return\n    // n0 -> P -> n1\n    val p = n0.next\n    val n1 = p?.next\n    n0.next = n1\n}\n
    linked_list.rb
    ### Remove the first node after node n0 in the linked list ###\ndef remove(n0)\n  return if n0.next.nil?\n\n  # n0 -> remove_node -> n1\n  remove_node = n0.next\n  n1 = remove_node.next\n  n0.next = n1\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#4-accessing-a-node","level":3,"title":"4.   Accessing a node","text":"

    Accessing a node in a linked list is relatively slow. An array provides access to any element in \\(O(1)\\) time, but a linked list must be traversed from its head until the desired node is reached. Accessing the \\(i\\)-th node requires \\(i - 1\\) iterations, giving \\(O(n)\\) time complexity.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    def access(head: ListNode, index: int) -> ListNode | None:\n    \"\"\"Access the node at index `index` in the linked list\"\"\"\n    for _ in range(index):\n        if not head:\n            return None\n        head = head.next\n    return head\n
    linked_list.cpp
    /* Access the node at index `index` in the linked list */\nListNode *access(ListNode *head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == nullptr)\n            return nullptr;\n        head = head->next;\n    }\n    return head;\n}\n
    linked_list.java
    /* Access the node at index `index` in the linked list */\nListNode access(ListNode head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == null)\n            return null;\n        head = head.next;\n    }\n    return head;\n}\n
    linked_list.cs
    /* Access the node at index `index` in the linked list */\nListNode? Access(ListNode? head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == null)\n            return null;\n        head = head.next;\n    }\n    return head;\n}\n
    linked_list.go
    /* Access the node at index `index` in the linked list */\nfunc access(head *ListNode, index int) *ListNode {\n    for i := 0; i < index; i++ {\n        if head == nil {\n            return nil\n        }\n        head = head.Next\n    }\n    return head\n}\n
    linked_list.swift
    /* Access the node at index `index` in the linked list */\nfunc access(head: ListNode, index: Int) -> ListNode? {\n    var head: ListNode? = head\n    for _ in 0 ..< index {\n        if head == nil {\n            return nil\n        }\n        head = head?.next\n    }\n    return head\n}\n
    linked_list.js
    /* Access the node at index `index` in the linked list */\nfunction access(head, index) {\n    for (let i = 0; i < index; i++) {\n        if (!head) {\n            return null;\n        }\n        head = head.next;\n    }\n    return head;\n}\n
    linked_list.ts
    /* Access the node at index `index` in the linked list */\nfunction access(head: ListNode | null, index: number): ListNode | null {\n    for (let i = 0; i < index; i++) {\n        if (!head) {\n            return null;\n        }\n        head = head.next;\n    }\n    return head;\n}\n
    linked_list.dart
    /* Access the node at index `index` in the linked list */\nListNode? access(ListNode? head, int index) {\n  for (var i = 0; i < index; i++) {\n    if (head == null) return null;\n    head = head.next;\n  }\n  return head;\n}\n
    linked_list.rs
    /* Access the node at index `index` in the linked list */\npub fn access<T>(head: Rc<RefCell<ListNode<T>>>, index: i32) -> Option<Rc<RefCell<ListNode<T>>>> {\n    fn dfs<T>(\n        head: Option<&Rc<RefCell<ListNode<T>>>>,\n        index: i32,\n    ) -> Option<Rc<RefCell<ListNode<T>>>> {\n        if index <= 0 {\n            return head.cloned();\n        }\n\n        if let Some(node) = head {\n            dfs(node.borrow().next.as_ref(), index - 1)\n        } else {\n            None\n        }\n    }\n\n    dfs(Some(head).as_ref(), index)\n}\n
    linked_list.c
    /* Access the node at index `index` in the linked list */\nListNode *access(ListNode *head, int index) {\n    for (int i = 0; i < index; i++) {\n        if (head == NULL)\n            return NULL;\n        head = head->next;\n    }\n    return head;\n}\n
    linked_list.kt
    /* Access the node at index `index` in the linked list */\nfun access(head: ListNode?, index: Int): ListNode? {\n    var h = head\n    for (i in 0..<index) {\n        if (h == null)\n            return null\n        h = h.next\n    }\n    return h\n}\n
    linked_list.rb
    ### Access the node at index `index` in the linked list ###\ndef access(head, index)\n  for i in 0...index\n    return nil if head.nil?\n    head = head.next\n  end\n\n  head\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#5-finding-a-node","level":3,"title":"5.   Finding a node","text":"

    Traverse the list to find a node with value target, then return its index. This is also linear search. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py
    def find(head: ListNode, target: int) -> int:\n    \"\"\"Find the first node with value target in the linked list\"\"\"\n    index = 0\n    while head:\n        if head.val == target:\n            return index\n        head = head.next\n        index += 1\n    return -1\n
    linked_list.cpp
    /* Find the first node with value target in the linked list */\nint find(ListNode *head, int target) {\n    int index = 0;\n    while (head != nullptr) {\n        if (head->val == target)\n            return index;\n        head = head->next;\n        index++;\n    }\n    return -1;\n}\n
    linked_list.java
    /* Find the first node with value target in the linked list */\nint find(ListNode head, int target) {\n    int index = 0;\n    while (head != null) {\n        if (head.val == target)\n            return index;\n        head = head.next;\n        index++;\n    }\n    return -1;\n}\n
    linked_list.cs
    /* Find the first node with value target in the linked list */\nint Find(ListNode? head, int target) {\n    int index = 0;\n    while (head != null) {\n        if (head.val == target)\n            return index;\n        head = head.next;\n        index++;\n    }\n    return -1;\n}\n
    linked_list.go
    /* Find the first node with value target in the linked list */\nfunc findNode(head *ListNode, target int) int {\n    index := 0\n    for head != nil {\n        if head.Val == target {\n            return index\n        }\n        head = head.Next\n        index++\n    }\n    return -1\n}\n
    linked_list.swift
    /* Find the first node with value target in the linked list */\nfunc find(head: ListNode, target: Int) -> Int {\n    var head: ListNode? = head\n    var index = 0\n    while head != nil {\n        if head?.val == target {\n            return index\n        }\n        head = head?.next\n        index += 1\n    }\n    return -1\n}\n
    linked_list.js
    /* Find the first node with value target in the linked list */\nfunction find(head, target) {\n    let index = 0;\n    while (head !== null) {\n        if (head.val === target) {\n            return index;\n        }\n        head = head.next;\n        index += 1;\n    }\n    return -1;\n}\n
    linked_list.ts
    /* Find the first node with value target in the linked list */\nfunction find(head: ListNode | null, target: number): number {\n    let index = 0;\n    while (head !== null) {\n        if (head.val === target) {\n            return index;\n        }\n        head = head.next;\n        index += 1;\n    }\n    return -1;\n}\n
    linked_list.dart
    /* Find the first node with value target in the linked list */\nint find(ListNode? head, int target) {\n  int index = 0;\n  while (head != null) {\n    if (head.val == target) {\n      return index;\n    }\n    head = head.next;\n    index++;\n  }\n  return -1;\n}\n
    linked_list.rs
    /* Find the first node with value target in the linked list */\npub fn find<T: PartialEq>(head: Rc<RefCell<ListNode<T>>>, target: T) -> i32 {\n    fn find<T: PartialEq>(head: Option<&Rc<RefCell<ListNode<T>>>>, target: T, idx: i32) -> i32 {\n        if let Some(node) = head {\n            if node.borrow().val == target {\n                return idx;\n            }\n            return find(node.borrow().next.as_ref(), target, idx + 1);\n        } else {\n            -1\n        }\n    }\n\n    find(Some(head).as_ref(), target, 0)\n}\n
    linked_list.c
    /* Find the first node with value target in the linked list */\nint find(ListNode *head, int target) {\n    int index = 0;\n    while (head) {\n        if (head->val == target)\n            return index;\n        head = head->next;\n        index++;\n    }\n    return -1;\n}\n
    linked_list.kt
    /* Find the first node with value target in the linked list */\nfun find(head: ListNode?, target: Int): Int {\n    var index = 0\n    var h = head\n    while (h != null) {\n        if (h._val == target)\n            return index\n        h = h.next\n        index++\n    }\n    return -1\n}\n
    linked_list.rb
    ### Find the first node with value target in the linked list ###\ndef find(head, target)\n  index = 0\n  while head\n    return index if head.val == target\n    head = head.next\n    index += 1\n  end\n\n  -1\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#422-arrays-versus-linked-lists","level":2,"title":"4.2.2   Arrays versus linked lists","text":"

    Table 4-1 compares their properties and operation costs. Their contrasting storage strategies give them contrasting strengths.

    Table 4-1   Comparing array and linked list efficiency

    Array Linked list Storage Contiguous memory Scattered memory Expansion Fixed length Flexible expansion Memory efficiency Less memory per element, but may waste space More memory per element Access an element \\(O(1)\\) \\(O(n)\\) Add an element \\(O(n)\\) \\(O(1)\\) Remove an element \\(O(n)\\) \\(O(1)\\)","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#423-common-types-of-linked-lists","level":2,"title":"4.2.3   Common types of linked lists","text":"

    Figure 4-8 shows three common types.

    • Singly linked list: The ordinary linked list introduced above. Each node stores a value and a reference to the next node. The first and last nodes are the head and tail, and the tail points to None.
    • Circular linked list: Connecting a singly linked list's tail to its head forms a circular list. Any node can then be treated as the head.
    • Doubly linked list: Each node stores references (pointers) in both directions, to its successor and predecessor. This makes it more flexible and allows traversal in either direction, at the cost of additional memory.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class ListNode:\n    \"\"\"Doubly linked list node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                # Node value\n        self.next: ListNode | None = None  # Reference to the successor\n        self.prev: ListNode | None = None  # Reference to the predecessor\n
    /* Doubly linked list node structure */\nstruct ListNode {\n    int val;         // Node value\n    ListNode *next;  // Pointer to the successor\n    ListNode *prev;  // Pointer to the predecessor\n    ListNode(int x) : val(x), next(nullptr), prev(nullptr) {}  // Constructor\n};\n
    /* Doubly linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode next;  // Reference to the successor\n    ListNode prev;  // Reference to the predecessor\n    ListNode(int x) { val = x; }  // Constructor\n}\n
    /* Doubly linked list node class */\nclass ListNode(int x) {  // Constructor\n    int val = x;    // Node value\n    ListNode next;  // Reference to the successor\n    ListNode prev;  // Reference to the predecessor\n}\n
    /* Doubly linked list node structure */\ntype DoublyListNode struct {\n    Val  int             // Node value\n    Next *DoublyListNode // Pointer to the successor\n    Prev *DoublyListNode // Pointer to the predecessor\n}\n\n// Initialize a node with NewDoublyListNode\nfunc NewDoublyListNode(val int) *DoublyListNode {\n    return &DoublyListNode{\n        Val:  val,\n        Next: nil,\n        Prev: nil,\n    }\n}\n
    /* Doubly linked list node class */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the successor\n    var prev: ListNode? // Reference to the predecessor\n\n    init(x: Int) { // Constructor\n        val = x\n    }\n}\n
    /* Doubly linked list node class */\nclass ListNode {\n    constructor(val, next, prev) {\n        this.val = val  ===  undefined ? 0 : val;        // Node value\n        this.next = next  ===  undefined ? null : next;  // Reference to the successor\n        this.prev = prev  ===  undefined ? null : prev;  // Reference to the predecessor\n    }\n}\n
    /* Doubly linked list node class */\nclass ListNode {\n    val: number;\n    next: ListNode | null;\n    prev: ListNode | null;\n    constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {\n        this.val = val  ===  undefined ? 0 : val;        // Node value\n        this.next = next  ===  undefined ? null : next;  // Reference to the successor\n        this.prev = prev  ===  undefined ? null : prev;  // Reference to the predecessor\n    }\n}\n
    /* Doubly linked list node class */\nclass ListNode {\n    int val;        // Node value\n    ListNode? next;  // Reference to the successor\n    ListNode? prev;  // Reference to the predecessor\n    ListNode(this.val, [this.next, this.prev]);  // Constructor\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Doubly linked list node type */\n#[derive(Debug)]\nstruct ListNode {\n    val: i32, // Node value\n    next: Option<Rc<RefCell<ListNode>>>, // Pointer to the successor\n    prev: Option<Rc<RefCell<ListNode>>>, // Pointer to the predecessor\n}\n\n/* Constructor */\nimpl ListNode {\n    fn new(val: i32) -> Self {\n        ListNode {\n            val,\n            next: None,\n            prev: None,\n        }\n    }\n}\n
    /* Doubly linked list node structure */\ntypedef struct ListNode {\n    int val;               // Node value\n    struct ListNode *next; // Pointer to the successor\n    struct ListNode *prev; // Pointer to the predecessor\n} ListNode;\n\n/* Constructor */\nListNode *newListNode(int val) {\n    ListNode *node;\n    node = (ListNode *) malloc(sizeof(ListNode));\n    node->val = val;\n    node->next = NULL;\n    node->prev = NULL;\n    return node;\n}\n
    /* Doubly linked list node class */\n// Constructor\nclass ListNode(x: Int) {\n    val _val: Int = x           // Node value\n    var next: ListNode? = null  // Reference to the successor\n    var prev: ListNode? = null  // Reference to the predecessor\n}\n
    # Doubly linked list node class\nclass ListNode\n  attr_accessor :val    # Node value\n  attr_accessor :next   # Reference to the successor\n  attr_accessor :prev   # Reference to the predecessor\n\n  def initialize(val=0, next_node=nil, prev_node=nil)\n    @val = val\n    @next = next_node\n    @prev = prev_node\n  end\nend\n

    Figure 4-8   Common types of linked lists

    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#424-typical-applications-of-linked-lists","level":2,"title":"4.2.4   Typical applications of linked lists","text":"

    Singly linked lists commonly implement stacks, queues, hash tables, and graphs.

    • Stacks and queues: Inserting and removing at the same end gives last-in, first-out behavior, forming a stack. Inserting at one end and removing at the other gives first-in, first-out behavior, forming a queue.
    • Hash tables: Separate chaining is a common collision-handling strategy that places colliding elements in a linked list.
    • Graphs: An adjacency list associates each vertex with a linked list whose elements represent the other vertices connected to it.

    Doubly linked lists are useful when both the previous and next elements must be found quickly.

    • Advanced data structures: In structures such as red-black trees and B-trees, a node may need access to its parent. Storing a parent reference is similar to the backward link in a doubly linked list.
    • Browser history: The forward and back buttons require access to the next and previous visited pages. A doubly linked list makes this straightforward.
    • LRU caching: Least recently used eviction needs fast access to the least recently used data and efficient insertion and removal, making doubly linked lists a good fit.

    Circular linked lists suit cyclic operations, such as operating-system resource scheduling.

    • Round-robin scheduling: This common CPU scheduling algorithm cycles through processes, giving each a time slice before moving to the next. A circular linked list can implement this cycle.
    • Data buffers: Some buffers use circular linked lists. Audio and video players, for example, may divide a stream into buffer blocks arranged in a circular list for seamless playback.
    ","path":["Chapter 4.   Arrays and linked lists","4.2   Linked lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/","level":1,"title":"4.3   Lists","text":"

    A list is an abstract data structure representing an ordered collection. It supports access, modification, insertion, removal, and traversal without requiring users to manage capacity limits. Lists can be implemented with linked lists or arrays.

    • A linked list naturally serves as a list, supporting these operations and flexible dynamic growth.
    • An array also supports these operations, but its fixed length makes it a list with a length limit.

    When an array implements a list, its fixed length limits the list's usefulness. We often cannot predict how much data we will store. Too small an allocation may not meet our needs; too large an allocation wastes memory.

    A dynamic array solves this problem. It retains the advantages of an array while allowing capacity to grow during execution.

    Many standard-library lists use dynamic arrays, including Python's list, Java's ArrayList, C++'s vector, and C#'s List. In the discussion that follows, we treat lists and dynamic arrays as equivalent concepts.

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#431-common-list-operations","level":2,"title":"4.3.1   Common list operations","text":"","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#1-initializing-a-list","level":3,"title":"1.   Initializing a list","text":"

    We usually initialize a list either without elements or with explicit initial values:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Initialize lists\n# Without initial values\nnums1: list[int] = []\n# With initial values\nnums: list[int] = [1, 3, 2, 5, 4]\n
    list.cpp
    /* Initialize lists */\n// In C++, vector is the list type used for nums here\n// Without initial values\nvector<int> nums1;\n// With initial values\nvector<int> nums = { 1, 3, 2, 5, 4 };\n
    list.java
    /* Initialize lists */\n// Without initial values\nList<Integer> nums1 = new ArrayList<>();\n// With initial values (use Integer[], the boxed counterpart of int[])\nInteger[] numbers = new Integer[] { 1, 3, 2, 5, 4 };\nList<Integer> nums = new ArrayList<>(Arrays.asList(numbers));\n
    list.cs
    /* Initialize lists */\n// Without initial values\nList<int> nums1 = [];\n// With initial values\nint[] numbers = [1, 3, 2, 5, 4];\nList<int> nums = [.. numbers];\n
    list_test.go
    /* Initialize lists */\n// Without initial values\nnums1 := []int{}\n// With initial values\nnums := []int{1, 3, 2, 5, 4}\n
    list.swift
    /* Initialize lists */\n// Without initial values\nlet nums1: [Int] = []\n// With initial values\nvar nums = [1, 3, 2, 5, 4]\n
    list.js
    /* Initialize lists */\n// Without initial values\nconst nums1 = [];\n// With initial values\nconst nums = [1, 3, 2, 5, 4];\n
    list.ts
    /* Initialize lists */\n// Without initial values\nconst nums1: number[] = [];\n// With initial values\nconst nums: number[] = [1, 3, 2, 5, 4];\n
    list.dart
    /* Initialize lists */\n// Without initial values\nList<int> nums1 = [];\n// With initial values\nList<int> nums = [1, 3, 2, 5, 4];\n
    list.rs
    /* Initialize lists */\n// Without initial values\nlet nums1: Vec<i32> = Vec::new();\n// With initial values\nlet nums: Vec<i32> = vec![1, 3, 2, 5, 4];\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Initialize lists */\n// Without initial values\nvar nums1 = listOf<Int>()\n// With initial values\nvar numbers = arrayOf(1, 3, 2, 5, 4)\nvar nums = numbers.toMutableList()\n
    list.rb
    # Initialize lists\n# Without initial values\nnums1 = []\n# With initial values\nnums = [1, 3, 2, 5, 4]\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#2-accessing-an-element","level":3,"title":"2.   Accessing an element","text":"

    Since the list is based on an array, accessing and updating an element are efficient \\(O(1)\\) operations.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Access an element\nnum: int = nums[1]  # Access the element at index 1\n\n# Update an element\nnums[1] = 0    # Set the element at index 1 to 0\n
    list.cpp
    /* Access an element */\nint num = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list.java
    /* Access an element */\nint num = nums.get(1);  // Access the element at index 1\n\n/* Update an element */\nnums.set(1, 0);  // Set the element at index 1 to 0\n
    list.cs
    /* Access an element */\nint num = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list_test.go
    /* Access an element */\nnum := nums[1]  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0     // Set the element at index 1 to 0\n
    list.swift
    /* Access an element */\nlet num = nums[1] // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0 // Set the element at index 1 to 0\n
    list.js
    /* Access an element */\nconst num = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list.ts
    /* Access an element */\nconst num: number = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list.dart
    /* Access an element */\nint num = nums[1];  // Access the element at index 1\n\n/* Update an element */\nnums[1] = 0;  // Set the element at index 1 to 0\n
    list.rs
    /* Access an element */\nlet num: i32 = nums[1];  // Access the element at index 1\n/* Update an element */\nnums[1] = 0;             // Set the element at index 1 to 0\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Access an element */\nval num = nums[1]       // Access the element at index 1\n/* Update an element */\nnums[1] = 0             // Set the element at index 1 to 0\n
    list.rb
    # Access an element\nnum = nums[1] # Access the element at index 1\n# Update an element\nnums[1] = 0 # Set the element at index 1 to 0\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#3-inserting-and-removing-elements","level":3,"title":"3.   Inserting and removing elements","text":"

    Unlike a fixed array, a list can freely add and remove elements. Appending takes \\(O(1)\\) time, while insertion and removal retain the array's \\(O(n)\\) time complexity.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Clear the list\nnums.clear()\n\n# Append elements\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n# Insert an element in the middle\nnums.insert(3, 6)  # Insert 6 at index 3\n\n# Remove an element\nnums.pop(3)        # Remove the element at index 3\n
    list.cpp
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.push_back(1);\nnums.push_back(3);\nnums.push_back(2);\nnums.push_back(5);\nnums.push_back(4);\n\n/* Insert an element in the middle */\nnums.insert(nums.begin() + 3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.erase(nums.begin() + 3);      // Remove the element at index 3\n
    list.java
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.add(3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.remove(3);  // Remove the element at index 3\n
    list.cs
    /* Clear the list */\nnums.Clear();\n\n/* Append elements */\nnums.Add(1);\nnums.Add(3);\nnums.Add(2);\nnums.Add(5);\nnums.Add(4);\n\n/* Insert an element in the middle */\nnums.Insert(3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.RemoveAt(3);  // Remove the element at index 3\n
    list_test.go
    /* Clear the list */\nnums = nil\n\n/* Append elements */\nnums = append(nums, 1)\nnums = append(nums, 3)\nnums = append(nums, 2)\nnums = append(nums, 5)\nnums = append(nums, 4)\n\n/* Insert an element in the middle */\nnums = append(nums[:3], append([]int{6}, nums[3:]...)...) // Insert 6 at index 3\n\n/* Remove an element */\nnums = append(nums[:3], nums[4:]...) // Remove the element at index 3\n
    list.swift
    /* Clear the list */\nnums.removeAll()\n\n/* Append elements */\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n/* Insert an element in the middle */\nnums.insert(6, at: 3) // Insert 6 at index 3\n\n/* Remove an element */\nnums.remove(at: 3) // Remove the element at index 3\n
    list.js
    /* Clear the list */\nnums.length = 0;\n\n/* Append elements */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.splice(3, 0, 6); // Insert 6 at index 3\n\n/* Remove an element */\nnums.splice(3, 1);  // Remove the element at index 3\n
    list.ts
    /* Clear the list */\nnums.length = 0;\n\n/* Append elements */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.splice(3, 0, 6); // Insert 6 at index 3\n\n/* Remove an element */\nnums.splice(3, 1);  // Remove the element at index 3\n
    list.dart
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.insert(3, 6); // Insert 6 at index 3\n\n/* Remove an element */\nnums.removeAt(3); // Remove the element at index 3\n
    list.rs
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* Insert an element in the middle */\nnums.insert(3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.remove(3);    // Remove the element at index 3\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Clear the list */\nnums.clear();\n\n/* Append elements */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* Insert an element in the middle */\nnums.add(3, 6);  // Insert 6 at index 3\n\n/* Remove an element */\nnums.remove(3);  // Remove the element at index 3\n
    list.rb
    # Clear the list\nnums.clear\n\n# Append elements\nnums << 1\nnums << 3\nnums << 2\nnums << 5\nnums << 4\n\n# Insert an element in the middle\nnums.insert(3, 6) # Insert 6 at index 3\n\n# Remove an element\nnums.delete_at(3) # Remove the element at index 3\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#4-traversing-a-list","level":3,"title":"4.   Traversing a list","text":"

    Like an array, a list can be traversed by index or by visiting its elements directly.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Traverse the list by index\ncount = 0\nfor i in range(len(nums)):\n    count += nums[i]\n\n# Traverse list elements directly\nfor num in nums:\n    count += num\n
    list.cpp
    /* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.size(); i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (int num : nums) {\n    count += num;\n}\n
    list.java
    /* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.size(); i++) {\n    count += nums.get(i);\n}\n\n/* Traverse list elements directly */\nfor (int num : nums) {\n    count += num;\n}\n
    list.cs
    /* Traverse the list by index */\nint count = 0;\nfor (int i = 0; i < nums.Count; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nforeach (int num in nums) {\n    count += num;\n}\n
    list_test.go
    /* Traverse the list by index */\ncount := 0\nfor i := 0; i < len(nums); i++ {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\ncount = 0\nfor _, num := range nums {\n    count += num\n}\n
    list.swift
    /* Traverse the list by index */\nvar count = 0\nfor i in nums.indices {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\ncount = 0\nfor num in nums {\n    count += num\n}\n
    list.js
    /* Traverse the list by index */\nlet count = 0;\nfor (let i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (const num of nums) {\n    count += num;\n}\n
    list.ts
    /* Traverse the list by index */\nlet count = 0;\nfor (let i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (const num of nums) {\n    count += num;\n}\n
    list.dart
    /* Traverse the list by index */\nint count = 0;\nfor (var i = 0; i < nums.length; i++) {\n    count += nums[i];\n}\n\n/* Traverse list elements directly */\ncount = 0;\nfor (var num in nums) {\n    count += num;\n}\n
    list.rs
    // Traverse the list by index\nlet mut _count = 0;\nfor i in 0..nums.len() {\n    _count += nums[i];\n}\n\n// Traverse list elements directly\n_count = 0;\nfor num in &nums {\n    _count += num;\n}\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Traverse the list by index */\nvar count = 0\nfor (i in nums.indices) {\n    count += nums[i]\n}\n\n/* Traverse list elements directly */\nfor (num in nums) {\n    count += num\n}\n
    list.rb
    # Traverse the list by index\ncount = 0\nfor i in 0...nums.length\n    count += nums[i]\nend\n\n# Traverse list elements directly\ncount = 0\nfor num in nums\n    count += num\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#5-concatenating-lists","level":3,"title":"5.   Concatenating lists","text":"

    Given another list nums1, we can append its elements to the end of the original list.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Concatenate two lists\nnums1: list[int] = [6, 8, 7, 10, 9]\nnums += nums1  # Append nums1 to nums\n
    list.cpp
    /* Concatenate two lists */\nvector<int> nums1 = { 6, 8, 7, 10, 9 };\n// Append nums1 to nums\nnums.insert(nums.end(), nums1.begin(), nums1.end());\n
    list.java
    /* Concatenate two lists */\nList<Integer> nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));\nnums.addAll(nums1);  // Append nums1 to nums\n
    list.cs
    /* Concatenate two lists */\nList<int> nums1 = [6, 8, 7, 10, 9];\nnums.AddRange(nums1);  // Append nums1 to nums\n
    list_test.go
    /* Concatenate two lists */\nnums1 := []int{6, 8, 7, 10, 9}\nnums = append(nums, nums1...)  // Append nums1 to nums\n
    list.swift
    /* Concatenate two lists */\nlet nums1 = [6, 8, 7, 10, 9]\nnums.append(contentsOf: nums1) // Append nums1 to nums\n
    list.js
    /* Concatenate two lists */\nconst nums1 = [6, 8, 7, 10, 9];\nnums.push(...nums1);  // Append nums1 to nums\n
    list.ts
    /* Concatenate two lists */\nconst nums1: number[] = [6, 8, 7, 10, 9];\nnums.push(...nums1);  // Append nums1 to nums\n
    list.dart
    /* Concatenate two lists */\nList<int> nums1 = [6, 8, 7, 10, 9];\nnums.addAll(nums1);  // Append nums1 to nums\n
    list.rs
    /* Concatenate two lists */\nlet nums1: Vec<i32> = vec![6, 8, 7, 10, 9];\nnums.extend(nums1);\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Concatenate two lists */\nval nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList()\nnums.addAll(nums1)  // Append nums1 to nums\n
    list.rb
    # Concatenate two lists\nnums1 = [6, 8, 7, 10, 9]\nnums += nums1\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#6-sorting-a-list","level":3,"title":"6.   Sorting a list","text":"

    Once the list is sorted, we can apply binary search and two-pointer algorithms, which often appear in array problems.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py
    # Sort the list\nnums.sort()  # After sorting, elements are in ascending order\n
    list.cpp
    /* Sort the list */\nsort(nums.begin(), nums.end());  // After sorting, elements are in ascending order\n
    list.java
    /* Sort the list */\nCollections.sort(nums);  // After sorting, elements are in ascending order\n
    list.cs
    /* Sort the list */\nnums.Sort(); // After sorting, elements are in ascending order\n
    list_test.go
    /* Sort the list */\nsort.Ints(nums)  // After sorting, elements are in ascending order\n
    list.swift
    /* Sort the list */\nnums.sort() // After sorting, elements are in ascending order\n
    list.js
    /* Sort the list */\nnums.sort((a, b) => a - b);  // After sorting, elements are in ascending order\n
    list.ts
    /* Sort the list */\nnums.sort((a, b) => a - b);  // After sorting, elements are in ascending order\n
    list.dart
    /* Sort the list */\nnums.sort(); // After sorting, elements are in ascending order\n
    list.rs
    /* Sort the list */\nnums.sort(); // After sorting, elements are in ascending order\n
    list.c
    // C does not provide a built-in dynamic array\n
    list.kt
    /* Sort the list */\nnums.sort() // After sorting, elements are in ascending order\n
    list.rb
    # Sort the list\nnums = nums.sort { |a, b| a <=> b } # After sorting, elements are in ascending order\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#432-implementing-a-list","level":2,"title":"4.3.2   Implementing a list","text":"

    Many languages, including Java, C++, and Python, provide lists. Their implementations are sophisticated, with carefully chosen parameters such as initial capacity and growth factor. Interested readers can study their source code.

    To understand how a list works, let's implement a simplified version with three key design choices.

    • Initial capacity: Choose a reasonable starting array capacity. We use 10 here.
    • Element count: Maintain size, the number of elements, updating it on insertion and removal. It identifies the list's end and tells us when expansion is needed.
    • Growth mechanism: If insertion finds the capacity full, allocate a larger array using a growth factor and copy all current elements into it. Here, capacity doubles each time.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_list.py
    class MyList:\n    \"\"\"List class\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._capacity: int = 10  # List capacity\n        self._arr: list[int] = [0] * self._capacity  # Array (stores the list elements)\n        self._size: int = 0  # List length (current number of elements)\n        self._extend_ratio: int = 2  # Factor by which the list capacity grows each time\n\n    def size(self) -> int:\n        \"\"\"Get the list length (current number of elements)\"\"\"\n        return self._size\n\n    def capacity(self) -> int:\n        \"\"\"Get the list capacity\"\"\"\n        return self._capacity\n\n    def get(self, index: int) -> int:\n        \"\"\"Access an element\"\"\"\n        # Raise an exception if the index is out of bounds; the same applies below\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        return self._arr[index]\n\n    def set(self, num: int, index: int):\n        \"\"\"Update an element\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        self._arr[index] = num\n\n    def add(self, num: int):\n        \"\"\"Append an element at the end\"\"\"\n        # Grow the capacity when adding an element would exceed it\n        if self.size() == self.capacity():\n            self.extend_capacity()\n        self._arr[self._size] = num\n        self._size += 1\n\n    def insert(self, num: int, index: int):\n        \"\"\"Insert an element in the middle\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        # Grow the capacity when adding an element would exceed it\n        if self._size == self.capacity():\n            self.extend_capacity()\n        # Shift all elements at and after index `index` one position to the right\n        for j in range(self._size - 1, index - 1, -1):\n            self._arr[j + 1] = self._arr[j]\n        self._arr[index] = num\n        # Update the number of elements\n        self._size += 1\n\n    def remove(self, index: int) -> int:\n        \"\"\"Remove an element\"\"\"\n        if index < 0 or index >= self._size:\n            raise IndexError(\"Index out of bounds\")\n        num = self._arr[index]\n        # Shift all elements after index `index` one position to the left\n        for j in range(index, self._size - 1):\n            self._arr[j] = self._arr[j + 1]\n        # Update the number of elements\n        self._size -= 1\n        # Return the removed element\n        return num\n\n    def extend_capacity(self):\n        \"\"\"Grow the list capacity\"\"\"\n        # Create a new array with _extend_ratio times the original length and copy the original array into it\n        self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)\n        # Update the list capacity\n        self._capacity = len(self._arr)\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return the portion of the list that contains elements\"\"\"\n        return self._arr[: self._size]\n
    my_list.cpp
    /* List class */\nclass MyList {\n  private:\n    int *arr;             // Array (stores the list elements)\n    int arrCapacity = 10; // List capacity\n    int arrSize = 0;      // List length (current number of elements)\n    int extendRatio = 2;   // Factor by which the list capacity grows each time\n\n  public:\n    /* Constructor */\n    MyList() {\n        arr = new int[arrCapacity];\n    }\n\n    /* Destructor */\n    ~MyList() {\n        delete[] arr;\n    }\n\n    /* Get the list length (current number of elements)*/\n    int size() {\n        return arrSize;\n    }\n\n    /* Get the list capacity */\n    int capacity() {\n        return arrCapacity;\n    }\n\n    /* Access an element */\n    int get(int index) {\n        // Raise an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Update an element */\n    void set(int index, int num) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Append an element at the end */\n    void add(int num) {\n        // Grow the capacity when adding an element would exceed it\n        if (size() == capacity())\n            extendCapacity();\n        arr[size()] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Insert an element in the middle */\n    void insert(int index, int num) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        // Grow the capacity when adding an element would exceed it\n        if (size() == capacity())\n            extendCapacity();\n        // Shift all elements at and after index `index` one position to the right\n        for (int j = size() - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Remove an element */\n    int remove(int index) {\n        if (index < 0 || index >= size())\n            throw out_of_range(\"Index out of bounds\");\n        int num = arr[index];\n        // Shift all elements after index `index` one position to the left\n        for (int j = index; j < size() - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        arrSize--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    void extendCapacity() {\n        // Create a new array with extendRatio times the original array's length\n        int newCapacity = capacity() * extendRatio;\n        int *tmp = arr;\n        arr = new int[newCapacity];\n        // Copy all elements from the original array to the new array\n        for (int i = 0; i < size(); i++) {\n            arr[i] = tmp[i];\n        }\n        // Free memory\n        delete[] tmp;\n        arrCapacity = newCapacity;\n    }\n\n    /* Convert the list to a vector for printing */\n    vector<int> toVector() {\n        // Convert only the list elements within the occupied range\n        vector<int> vec(size());\n        for (int i = 0; i < size(); i++) {\n            vec[i] = arr[i];\n        }\n        return vec;\n    }\n};\n
    my_list.java
    /* List class */\nclass MyList {\n    private int[] arr; // Array (stores the list elements)\n    private int capacity = 10; // List capacity\n    private int size = 0; // List length (current number of elements)\n    private int extendRatio = 2; // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    public MyList() {\n        arr = new int[capacity];\n    }\n\n    /* Get the list length (current number of elements) */\n    public int size() {\n        return size;\n    }\n\n    /* Get the list capacity */\n    public int capacity() {\n        return capacity;\n    }\n\n    /* Access an element */\n    public int get(int index) {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Update an element */\n    public void set(int index, int num) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Append at the end */\n    public void add(int num) {\n        // Grow the capacity when adding an element would exceed it\n        if (size == capacity())\n            extendCapacity();\n        arr[size] = num;\n        // Update the number of elements\n        size++;\n    }\n\n    /* Insert an element in the middle */\n    public void insert(int index, int num) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        // Grow the capacity when adding an element would exceed it\n        if (size == capacity())\n            extendCapacity();\n        // Shift all elements at and after index `index` one position to the right\n        for (int j = size - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        size++;\n    }\n\n    /* Remove an element */\n    public int remove(int index) {\n        if (index < 0 || index >= size)\n            throw new IndexOutOfBoundsException(\"Index out of bounds\");\n        int num = arr[index];\n        // Shift all elements after index `index` one position to the left\n        for (int j = index; j < size - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    public void extendCapacity() {\n        // Create an array extendRatio times as long as the original and copy the original array into it\n        arr = Arrays.copyOf(arr, capacity() * extendRatio);\n        // Update the list capacity\n        capacity = arr.length;\n    }\n\n    /* Convert the list to an array */\n    public int[] toArray() {\n        int size = size();\n        // Convert only the list elements within the occupied range\n        int[] arr = new int[size];\n        for (int i = 0; i < size; i++) {\n            arr[i] = get(i);\n        }\n        return arr;\n    }\n}\n
    my_list.cs
    /* List class */\nclass MyList {\n    private int[] arr;           // Array (stores the list elements)\n    private int arrCapacity = 10;    // List capacity\n    private int arrSize = 0;         // List length (current number of elements)\n    private readonly int extendRatio = 2;  // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    public MyList() {\n        arr = new int[arrCapacity];\n    }\n\n    /* Get the list length (current number of elements)*/\n    public int Size() {\n        return arrSize;\n    }\n\n    /* Get the list capacity */\n    public int Capacity() {\n        return arrCapacity;\n    }\n\n    /* Access an element */\n    public int Get(int index) {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        return arr[index];\n    }\n\n    /* Update an element */\n    public void Set(int index, int num) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        arr[index] = num;\n    }\n\n    /* Append at the end */\n    public void Add(int num) {\n        // Grow the capacity when adding an element would exceed it\n        if (arrSize == arrCapacity)\n            ExtendCapacity();\n        arr[arrSize] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Insert an element in the middle */\n    public void Insert(int index, int num) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        // Grow the capacity when adding an element would exceed it\n        if (arrSize == arrCapacity)\n            ExtendCapacity();\n        // Shift all elements at and after index `index` one position to the right\n        for (int j = arrSize - 1; j >= index; j--) {\n            arr[j + 1] = arr[j];\n        }\n        arr[index] = num;\n        // Update the number of elements\n        arrSize++;\n    }\n\n    /* Remove an element */\n    public int Remove(int index) {\n        if (index < 0 || index >= arrSize)\n            throw new IndexOutOfRangeException(\"Index out of bounds\");\n        int num = arr[index];\n        // Shift all elements after index `index` one position to the left\n        for (int j = index; j < arrSize - 1; j++) {\n            arr[j] = arr[j + 1];\n        }\n        // Update the number of elements\n        arrSize--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    public void ExtendCapacity() {\n        // Create an array of length arrCapacity * extendRatio and copy the original array into it\n        Array.Resize(ref arr, arrCapacity * extendRatio);\n        // Update the list capacity\n        arrCapacity = arr.Length;\n    }\n\n    /* Convert the list to an array */\n    public int[] ToArray() {\n        // Convert only the list elements within the occupied range\n        int[] arr = new int[arrSize];\n        for (int i = 0; i < arrSize; i++) {\n            arr[i] = Get(i);\n        }\n        return arr;\n    }\n}\n
    my_list.go
    /* List type */\ntype myList struct {\n    arrCapacity int\n    arr         []int\n    arrSize     int\n    extendRatio int\n}\n\n/* Constructor */\nfunc newMyList() *myList {\n    return &myList{\n        arrCapacity: 10,              // List capacity\n        arr:         make([]int, 10), // Array (stores the list elements)\n        arrSize:     0,               // List length (current number of elements)\n        extendRatio: 2,               // Factor by which the list capacity grows each time\n    }\n}\n\n/* Get the list length (current number of elements) */\nfunc (l *myList) size() int {\n    return l.arrSize\n}\n\n/*  Get the list capacity */\nfunc (l *myList) capacity() int {\n    return l.arrCapacity\n}\n\n/* Access an element */\nfunc (l *myList) get(index int) int {\n    // Panic if the index is out of bounds; the same applies below\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    return l.arr[index]\n}\n\n/* Update an element */\nfunc (l *myList) set(num, index int) {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    l.arr[index] = num\n}\n\n/* Append at the end */\nfunc (l *myList) add(num int) {\n    // Grow the capacity when adding an element would exceed it\n    if l.arrSize == l.arrCapacity {\n        l.extendCapacity()\n    }\n    l.arr[l.arrSize] = num\n    // Update the number of elements\n    l.arrSize++\n}\n\n/* Insert an element in the middle */\nfunc (l *myList) insert(num, index int) {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    // Grow the capacity when adding an element would exceed it\n    if l.arrSize == l.arrCapacity {\n        l.extendCapacity()\n    }\n    // Shift all elements at and after index `index` one position to the right\n    for j := l.arrSize - 1; j >= index; j-- {\n        l.arr[j+1] = l.arr[j]\n    }\n    l.arr[index] = num\n    // Update the number of elements\n    l.arrSize++\n}\n\n/* Remove an element */\nfunc (l *myList) remove(index int) int {\n    if index < 0 || index >= l.arrSize {\n        panic(\"Index out of bounds\")\n    }\n    num := l.arr[index]\n    // Shift all elements after index `index` one position to the left\n    for j := index; j < l.arrSize-1; j++ {\n        l.arr[j] = l.arr[j+1]\n    }\n    // Update the number of elements\n    l.arrSize--\n    // Return the removed element\n    return num\n}\n\n/* Grow the list capacity */\nfunc (l *myList) extendCapacity() {\n    // Extend the backing slice to extendRatio times its original length, preserving the existing elements\n    l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...)\n    // Update the list capacity\n    l.arrCapacity = len(l.arr)\n}\n\n/* Return the portion of the list that contains elements */\nfunc (l *myList) toArray() []int {\n    // Return a slice containing only the occupied elements\n    return l.arr[:l.arrSize]\n}\n
    my_list.swift
    /* List class */\nclass MyList {\n    private var arr: [Int] // Array (stores the list elements)\n    private var _capacity: Int // List capacity\n    private var _size: Int // List length (current number of elements)\n    private let extendRatio: Int // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    init() {\n        _capacity = 10\n        _size = 0\n        extendRatio = 2\n        arr = Array(repeating: 0, count: _capacity)\n    }\n\n    /* Get the list length (current number of elements)*/\n    func size() -> Int {\n        _size\n    }\n\n    /* Get the list capacity */\n    func capacity() -> Int {\n        _capacity\n    }\n\n    /* Access an element */\n    func get(index: Int) -> Int {\n        // An out-of-bounds index triggers a fatal error; the same check is used below\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        return arr[index]\n    }\n\n    /* Update an element */\n    func set(index: Int, num: Int) {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        arr[index] = num\n    }\n\n    /* Append at the end */\n    func add(num: Int) {\n        // Grow the capacity when adding an element would exceed it\n        if size() == capacity() {\n            extendCapacity()\n        }\n        arr[size()] = num\n        // Update the number of elements\n        _size += 1\n    }\n\n    /* Insert an element in the middle */\n    func insert(index: Int, num: Int) {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        // Grow the capacity when adding an element would exceed it\n        if size() == capacity() {\n            extendCapacity()\n        }\n        // Shift all elements at and after index `index` one position to the right\n        for j in (index ..< size()).reversed() {\n            arr[j + 1] = arr[j]\n        }\n        arr[index] = num\n        // Update the number of elements\n        _size += 1\n    }\n\n    /* Remove an element */\n    @discardableResult\n    func remove(index: Int) -> Int {\n        if index < 0 || index >= size() {\n            fatalError(\"Index out of bounds\")\n        }\n        let num = arr[index]\n        // Shift all elements after index `index` one position to the left\n        for j in index ..< (size() - 1) {\n            arr[j] = arr[j + 1]\n        }\n        // Update the number of elements\n        _size -= 1\n        // Return the removed element\n        return num\n    }\n\n    /* Grow the list capacity */\n    func extendCapacity() {\n        // Create an array extendRatio times as long as the original and copy the original array into it\n        arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1))\n        // Update the list capacity\n        _capacity = arr.count\n    }\n\n    /* Convert the list to an array */\n    func toArray() -> [Int] {\n        Array(arr.prefix(size()))\n    }\n}\n
    my_list.js
    /* List class */\nclass MyList {\n    #arr = new Array(); // Array (stores the list elements)\n    #capacity = 10; // List capacity\n    #size = 0; // List length (current number of elements)\n    #extendRatio = 2; // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    constructor() {\n        this.#arr = new Array(this.#capacity);\n    }\n\n    /* Get the list length (current number of elements)*/\n    size() {\n        return this.#size;\n    }\n\n    /* Get the list capacity */\n    capacity() {\n        return this.#capacity;\n    }\n\n    /* Access an element */\n    get(index) {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        return this.#arr[index];\n    }\n\n    /* Update an element */\n    set(index, num) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        this.#arr[index] = num;\n    }\n\n    /* Append an element at the end */\n    add(num) {\n        // Grow the capacity if the length equals the capacity\n        if (this.#size === this.#capacity) {\n            this.extendCapacity();\n        }\n        // Append the new element to the end of the list\n        this.#arr[this.#size] = num;\n        this.#size++;\n    }\n\n    /* Insert an element in the middle */\n    insert(index, num) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        // Grow the capacity when adding an element would exceed it\n        if (this.#size === this.#capacity) {\n            this.extendCapacity();\n        }\n        // Shift all elements at and after index `index` one position to the right\n        for (let j = this.#size - 1; j >= index; j--) {\n            this.#arr[j + 1] = this.#arr[j];\n        }\n        // Update the number of elements\n        this.#arr[index] = num;\n        this.#size++;\n    }\n\n    /* Remove an element */\n    remove(index) {\n        if (index < 0 || index >= this.#size) throw new Error('Index out of bounds');\n        let num = this.#arr[index];\n        // Shift all elements after index `index` one position to the left\n        for (let j = index; j < this.#size - 1; j++) {\n            this.#arr[j] = this.#arr[j + 1];\n        }\n        // Update the number of elements\n        this.#size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    extendCapacity() {\n        // Create an array extendRatio times the original length and copy the original array into it\n        this.#arr = this.#arr.concat(\n            new Array(this.capacity() * (this.#extendRatio - 1))\n        );\n        // Update the list capacity\n        this.#capacity = this.#arr.length;\n    }\n\n    /* Convert the list to an array */\n    toArray() {\n        let size = this.size();\n        // Convert only the list elements within the occupied range\n        const arr = new Array(size);\n        for (let i = 0; i < size; i++) {\n            arr[i] = this.get(i);\n        }\n        return arr;\n    }\n}\n
    my_list.ts
    /* List class */\nclass MyList {\n    private arr: Array<number>; // Array (stores the list elements)\n    private _capacity: number = 10; // List capacity\n    private _size: number = 0; // List length (current number of elements)\n    private extendRatio: number = 2; // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    constructor() {\n        this.arr = new Array(this._capacity);\n    }\n\n    /* Get the list length (current number of elements)*/\n    public size(): number {\n        return this._size;\n    }\n\n    /* Get the list capacity */\n    public capacity(): number {\n        return this._capacity;\n    }\n\n    /* Access an element */\n    public get(index: number): number {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        return this.arr[index];\n    }\n\n    /* Update an element */\n    public set(index: number, num: number): void {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        this.arr[index] = num;\n    }\n\n    /* Append an element at the end */\n    public add(num: number): void {\n        // Grow the capacity if the length equals the capacity\n        if (this._size === this._capacity) this.extendCapacity();\n        // Append the new element to the end of the list\n        this.arr[this._size] = num;\n        this._size++;\n    }\n\n    /* Insert an element in the middle */\n    public insert(index: number, num: number): void {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        // Grow the capacity when adding an element would exceed it\n        if (this._size === this._capacity) {\n            this.extendCapacity();\n        }\n        // Shift all elements at and after index `index` one position to the right\n        for (let j = this._size - 1; j >= index; j--) {\n            this.arr[j + 1] = this.arr[j];\n        }\n        // Update the number of elements\n        this.arr[index] = num;\n        this._size++;\n    }\n\n    /* Remove an element */\n    public remove(index: number): number {\n        if (index < 0 || index >= this._size) throw new Error('Index out of bounds');\n        let num = this.arr[index];\n        // Shift all elements after index one position to the left\n        for (let j = index; j < this._size - 1; j++) {\n            this.arr[j] = this.arr[j + 1];\n        }\n        // Update the number of elements\n        this._size--;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    public extendCapacity(): void {\n        // Create a larger array by appending empty slots to a copy of the original array\n        this.arr = this.arr.concat(\n            new Array(this.capacity() * (this.extendRatio - 1))\n        );\n        // Update the list capacity\n        this._capacity = this.arr.length;\n    }\n\n    /* Convert the list to an array */\n    public toArray(): number[] {\n        let size = this.size();\n        // Convert only the list elements within the occupied range\n        const arr = new Array(size);\n        for (let i = 0; i < size; i++) {\n            arr[i] = this.get(i);\n        }\n        return arr;\n    }\n}\n
    my_list.dart
    /* List class */\nclass MyList {\n  late List<int> _arr; // Array (stores the list elements)\n  int _capacity = 10; // List capacity\n  int _size = 0; // List length (current number of elements)\n  int _extendRatio = 2; // Factor by which the list capacity grows each time\n\n  /* Constructor */\n  MyList() {\n    _arr = List.filled(_capacity, 0);\n  }\n\n  /* Get the list length (current number of elements)*/\n  int size() => _size;\n\n  /* Get the list capacity */\n  int capacity() => _capacity;\n\n  /* Access an element */\n  int get(int index) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    return _arr[index];\n  }\n\n  /* Update an element */\n  void set(int index, int _num) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    _arr[index] = _num;\n  }\n\n  /* Append an element at the end */\n  void add(int _num) {\n    // Grow the capacity when adding an element would exceed it\n    if (_size == _capacity) extendCapacity();\n    _arr[_size] = _num;\n    // Update the number of elements\n    _size++;\n  }\n\n  /* Insert an element in the middle */\n  void insert(int index, int _num) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    // Grow the capacity when adding an element would exceed it\n    if (_size == _capacity) extendCapacity();\n    // Shift all elements at and after index `index` one position to the right\n    for (var j = _size - 1; j >= index; j--) {\n      _arr[j + 1] = _arr[j];\n    }\n    _arr[index] = _num;\n    // Update the number of elements\n    _size++;\n  }\n\n  /* Remove an element */\n  int remove(int index) {\n    if (index >= _size) throw RangeError('Index out of bounds');\n    int _num = _arr[index];\n    // Shift all elements after index one position to the left\n    for (var j = index; j < _size - 1; j++) {\n      _arr[j] = _arr[j + 1];\n    }\n    // Update the number of elements\n    _size--;\n    // Return the removed element\n    return _num;\n  }\n\n  /* Grow the list capacity */\n  void extendCapacity() {\n    // Create a new array with _extendRatio times the length of the original array\n    final _newNums = List.filled(_capacity * _extendRatio, 0);\n    // Copy the original array into the new array\n    List.copyRange(_newNums, 0, _arr);\n    // Update the reference _arr\n    _arr = _newNums;\n    // Update the list capacity\n    _capacity = _arr.length;\n  }\n\n  /* Convert the list to an array */\n  List<int> toArray() {\n    List<int> arr = [];\n    for (var i = 0; i < _size; i++) {\n      arr.add(get(i));\n    }\n    return arr;\n  }\n}\n
    my_list.rs
    /* List type */\n#[allow(dead_code)]\nstruct MyList {\n    arr: Vec<i32>,       // Array (stores the list elements)\n    capacity: usize,     // List capacity\n    size: usize,         // List length (current number of elements)\n    extend_ratio: usize, // Factor by which the list capacity grows each time\n}\n\n#[allow(unused, unused_comparisons)]\nimpl MyList {\n    /* Constructor */\n    pub fn new(capacity: usize) -> Self {\n        let mut vec = vec![0; capacity];\n        Self {\n            arr: vec,\n            capacity,\n            size: 0,\n            extend_ratio: 2,\n        }\n    }\n\n    /* Get the list length (current number of elements)*/\n    pub fn size(&self) -> usize {\n        return self.size;\n    }\n\n    /* Get the list capacity */\n    pub fn capacity(&self) -> usize {\n        return self.capacity;\n    }\n\n    /* Access an element */\n    pub fn get(&self, index: usize) -> i32 {\n        // Panic if the index is out of bounds; the same applies below\n        if index >= self.size {\n            panic!(\"Index out of bounds\")\n        };\n        return self.arr[index];\n    }\n\n    /* Update an element */\n    pub fn set(&mut self, index: usize, num: i32) {\n        if index >= self.size {\n            panic!(\"Index out of bounds\")\n        };\n        self.arr[index] = num;\n    }\n\n    /* Append an element at the end */\n    pub fn add(&mut self, num: i32) {\n        // Grow the capacity when adding an element would exceed it\n        if self.size == self.capacity() {\n            self.extend_capacity();\n        }\n        self.arr[self.size] = num;\n        // Update the number of elements\n        self.size += 1;\n    }\n\n    /* Insert an element in the middle */\n    pub fn insert(&mut self, index: usize, num: i32) {\n        if index >= self.size() {\n            panic!(\"Index out of bounds\")\n        };\n        // Grow the capacity when adding an element would exceed it\n        if self.size == self.capacity() {\n            self.extend_capacity();\n        }\n        // Shift all elements at and after index `index` one position to the right\n        for j in (index..self.size).rev() {\n            self.arr[j + 1] = self.arr[j];\n        }\n        self.arr[index] = num;\n        // Update the number of elements\n        self.size += 1;\n    }\n\n    /* Remove an element */\n    pub fn remove(&mut self, index: usize) -> i32 {\n        if index >= self.size() {\n            panic!(\"Index out of bounds\")\n        };\n        let num = self.arr[index];\n        // Shift all elements after index `index` one position to the left\n        for j in index..self.size - 1 {\n            self.arr[j] = self.arr[j + 1];\n        }\n        // Update the number of elements\n        self.size -= 1;\n        // Return the removed element\n        return num;\n    }\n\n    /* Grow the list capacity */\n    pub fn extend_capacity(&mut self) {\n        // Grow the backing vector to extend_ratio times its original length, preserving its elements\n        let new_capacity = self.capacity * self.extend_ratio;\n        self.arr.resize(new_capacity, 0);\n        // Update the list capacity\n        self.capacity = new_capacity;\n    }\n\n    /* Copy the list elements into a vector */\n    pub fn to_array(&self) -> Vec<i32> {\n        // Convert only the list elements within the occupied range\n        let mut arr = Vec::new();\n        for i in 0..self.size {\n            arr.push(self.get(i));\n        }\n        arr\n    }\n}\n
    my_list.c
    /* List structure */\ntypedef struct {\n    int *arr;        // Array (stores the list elements)\n    int capacity;    // List capacity\n    int size;        // List size\n    int extendRatio; // List capacity growth factor\n} MyList;\n\n/* Constructor */\nMyList *newMyList() {\n    MyList *nums = malloc(sizeof(MyList));\n    nums->capacity = 10;\n    nums->arr = malloc(sizeof(int) * nums->capacity);\n    nums->size = 0;\n    nums->extendRatio = 2;\n    return nums;\n}\n\n/* Destructor */\nvoid delMyList(MyList *nums) {\n    free(nums->arr);\n    free(nums);\n}\n\n/* Get the list size */\nint size(MyList *nums) {\n    return nums->size;\n}\n\n/* Get the list capacity */\nint capacity(MyList *nums) {\n    return nums->capacity;\n}\n\n/* Access an element */\nint get(MyList *nums, int index) {\n    assert(index >= 0 && index < nums->size);\n    return nums->arr[index];\n}\n\n/* Update an element */\nvoid set(MyList *nums, int index, int num) {\n    assert(index >= 0 && index < nums->size);\n    nums->arr[index] = num;\n}\n\n/* Append an element at the end */\nvoid add(MyList *nums, int num) {\n    if (size(nums) == capacity(nums)) {\n        extendCapacity(nums); // Grow the capacity\n    }\n    nums->arr[size(nums)] = num;\n    nums->size++;\n}\n\n/* Insert an element in the middle */\nvoid insert(MyList *nums, int index, int num) {\n    assert(index >= 0 && index < size(nums));\n    // Grow the capacity when adding an element would exceed it\n    if (size(nums) == capacity(nums)) {\n        extendCapacity(nums); // Grow the capacity\n    }\n    for (int i = size(nums); i > index; --i) {\n        nums->arr[i] = nums->arr[i - 1];\n    }\n    nums->arr[index] = num;\n    nums->size++;\n}\n\n/* Remove an element */\n// Note: stdio.h already declares a function named remove\nint removeItem(MyList *nums, int index) {\n    assert(index >= 0 && index < size(nums));\n    int num = nums->arr[index];\n    for (int i = index; i < size(nums) - 1; i++) {\n        nums->arr[i] = nums->arr[i + 1];\n    }\n    nums->size--;\n    return num;\n}\n\n/* Grow the list capacity */\nvoid extendCapacity(MyList *nums) {\n    // Allocate new space first\n    int newCapacity = capacity(nums) * nums->extendRatio;\n    int *extend = (int *)malloc(sizeof(int) * newCapacity);\n    int *temp = nums->arr;\n\n    // Copy the old data into the new array\n    for (int i = 0; i < size(nums); i++)\n        extend[i] = nums->arr[i];\n\n    // Free the old array\n    free(temp);\n\n    // Update the array pointer and capacity\n    nums->arr = extend;\n    nums->capacity = newCapacity;\n}\n\n/* Convert the list to an array for printing */\nint *toArray(MyList *nums) {\n    return nums->arr;\n}\n
    my_list.kt
    /* List class */\nclass MyList {\n    private var arr: IntArray = intArrayOf() // Array (stores the list elements)\n    private var capacity: Int = 10 // List capacity\n    private var size: Int = 0 // List length (current number of elements)\n    private var extendRatio: Int = 2 // Factor by which the list capacity grows each time\n\n    /* Constructor */\n    init {\n        arr = IntArray(capacity)\n    }\n\n    /* Get the list length (current number of elements) */\n    fun size(): Int {\n        return size\n    }\n\n    /* Get the list capacity */\n    fun capacity(): Int {\n        return capacity\n    }\n\n    /* Access an element */\n    fun get(index: Int): Int {\n        // Throw an exception if the index is out of bounds; the same applies below\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        return arr[index]\n    }\n\n    /* Update an element */\n    fun set(index: Int, num: Int) {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        arr[index] = num\n    }\n\n    /* Append at the end */\n    fun add(num: Int) {\n        // Grow the capacity when adding an element would exceed it\n        if (size == capacity())\n            extendCapacity()\n        arr[size] = num\n        // Update the number of elements\n        size++\n    }\n\n    /* Insert an element in the middle */\n    fun insert(index: Int, num: Int) {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        // Grow the capacity when adding an element would exceed it\n        if (size == capacity())\n            extendCapacity()\n        // Shift all elements at and after index `index` one position to the right\n        for (j in size - 1 downTo index)\n            arr[j + 1] = arr[j]\n        arr[index] = num\n        // Update the number of elements\n        size++\n    }\n\n    /* Remove an element */\n    fun remove(index: Int): Int {\n        if (index < 0 || index >= size)\n            throw IndexOutOfBoundsException(\"Index out of bounds\")\n        val num = arr[index]\n        // Shift all elements after index `index` one position to the left\n        for (j in index..<size - 1)\n            arr[j] = arr[j + 1]\n        // Update the number of elements\n        size--\n        // Return the removed element\n        return num\n    }\n\n    /* Grow the list capacity */\n    fun extendCapacity() {\n        // Create an array extendRatio times as long as the original and copy the original array into it\n        arr = arr.copyOf(capacity() * extendRatio)\n        // Update the list capacity\n        capacity = arr.size\n    }\n\n    /* Convert the list to an array */\n    fun toArray(): IntArray {\n        val size = size()\n        // Convert only the list elements within the occupied range\n        val arr = IntArray(size)\n        for (i in 0..<size) {\n            arr[i] = get(i)\n        }\n        return arr\n    }\n}\n
    my_list.rb
    ### List class ###\nclass MyList\n  attr_reader :size       # Get the list length (current number of elements)\n  attr_reader :capacity   # Get the list capacity\n\n  ### Constructor ###\n  def initialize\n    @capacity = 10\n    @size = 0\n    @extend_ratio = 2\n    @arr = Array.new(capacity)\n  end\n\n  ### Access an element ###\n  def get(index)\n    # Raise an exception if the index is out of bounds; the same applies below\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    @arr[index]\n  end\n\n  ### Update an element ###\n  def set(index, num)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    @arr[index] = num\n  end\n\n  ### Append an element at the end ###\n  def add(num)\n    # Grow the capacity when adding an element would exceed it\n    extend_capacity if size == capacity\n    @arr[size] = num\n\n    # Update the number of elements\n    @size += 1\n  end\n\n  ### Insert an element in the middle ###\n  def insert(index, num)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n\n    # Grow the capacity when adding an element would exceed it\n    extend_capacity if size == capacity\n\n    # Shift all elements at and after index `index` one position to the right\n    for j in (size - 1).downto(index)\n      @arr[j + 1] = @arr[j]\n    end\n    @arr[index] = num\n\n    # Update the number of elements\n    @size += 1\n  end\n\n  ### Remove an element ###\n  def remove(index)\n    raise IndexError, \"Index out of bounds\" if index < 0 || index >= size\n    num = @arr[index]\n\n    # Shift all elements after index one position to the left\n    for j in index...size\n      @arr[j] = @arr[j + 1]\n    end\n\n    # Update the number of elements\n    @size -= 1\n\n    # Return the removed element\n    num\n  end\n\n  ### Grow the list capacity ###\n  def extend_capacity\n    # Duplicate the original array and append capacity * (extend_ratio - 1) empty slots\n    arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1))\n    # Update the list capacity\n    @capacity = arr.length\n  end\n\n  ### Convert the list to an array ###\n  def to_array\n    sz = size\n    # Convert only the list elements within the occupied range\n    arr = Array.new(sz)\n    for i in 0...sz\n      arr[i] = get(i)\n    end\n    arr\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 4.   Arrays and linked lists","4.3   Lists"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/","level":1,"title":"4.4   Memory and cache *","text":"

    The first two sections of this chapter introduced arrays and linked lists, two fundamental structures representing contiguous and scattered storage, respectively.

    Physical structure largely determines how efficiently a program uses memory and cache, affecting its overall performance.

    ","path":["Chapter 4.   Arrays and linked lists","4.4   Memory and cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#441-computer-storage-devices","level":2,"title":"4.4.1   Computer storage devices","text":"

    Computers have three types of storage: disk storage, random-access memory (RAM), and cache memory. Table 4-2 compares their roles and performance characteristics.

    Table 4-2   Computer storage devices

    Disk storage RAM Cache Purpose Long-term storage of the operating system, programs, files, and other data Temporary storage of running programs and data being processed Stores frequently accessed data and instructions to reduce CPU accesses to RAM Volatility Retains data without power Loses data without power Loses data without power Capacity Large, on the TB scale Smaller, on the GB scale Very small, on the MB scale Speed Slower, hundreds to thousands of MB/s Faster, tens of GB/s Very fast, tens to hundreds of GB/s Price (Chinese yuan) Inexpensive, fractions of a yuan to several yuan per GB More expensive, tens to hundreds of yuan per GB Very expensive, priced as part of the CPU

    We can picture computer storage as the pyramid in Figure 4-9. Nearer the top, storage is faster, smaller, and more expensive. This hierarchy is a deliberate design by computer scientists and engineers.

    • RAM cannot readily replace disk storage. It loses data when power is removed, making it unsuitable for long-term storage. It also costs tens of times more than disk storage, making such a replacement impractical for consumers.
    • Cache capacity and speed are difficult to maximize together. From L1 to L2 to L3, increasing capacity means greater physical size and distance from the CPU core, increasing transfer time and access latency. With current technology, a multilevel cache offers the best balance of capacity, speed, and cost.

    Figure 4-9   Computer storage hierarchy

    Tip

    The storage hierarchy balances speed, capacity, and cost. Such tradeoffs occur throughout industry, requiring us to find the best balance among competing strengths and limitations.

    In general, disks store large amounts of data long term, RAM temporarily holds data being processed, and caches hold frequently accessed data and instructions to speed up execution. The three work together to keep the computer efficient.

    As shown in Figure 4-10, a running program reads data from disk into RAM for the CPU to process. The cache can be viewed as part of the CPU: it intelligently loads data from RAM and provides faster access, improving execution speed and reducing dependence on slower memory.

    Figure 4-10   Data flow between disk, RAM, and cache

    ","path":["Chapter 4.   Arrays and linked lists","4.4   Memory and cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#442-memory-efficiency-of-data-structures","level":2,"title":"4.4.2   Memory efficiency of data structures","text":"

    Arrays and linked lists each have strengths and limitations in memory use.

    On one hand, memory is finite, and the same region cannot be shared by multiple programs, so data structures should use it efficiently. Arrays pack elements tightly without extra references (pointers) between nodes, making them more space-efficient. However, they require a sufficiently large contiguous allocation, which can waste memory, and expansion costs extra time and space. Linked lists instead allocate and reclaim memory node by node, providing greater flexibility.

    On the other hand, repeated allocation and deallocation increasingly fragment free memory, reducing its usability. Arrays' contiguous storage is less prone to causing fragmentation. Linked lists store elements in scattered locations, so frequent insertions and deletions are more likely to fragment memory.

    ","path":["Chapter 4.   Arrays and linked lists","4.4   Memory and cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#443-cache-efficiency-of-data-structures","level":2,"title":"4.4.3   Cache efficiency of data structures","text":"

    A cache is much smaller than RAM but far faster, making it crucial to execution speed. Its limited capacity holds only a small portion of frequently accessed data. If the CPU requests data not in the cache, a cache miss occurs, and the data must be fetched from slower RAM.

    Clearly, fewer cache misses mean more efficient CPU reads and writes and better program performance. The fraction of accesses successfully served by the cache is the cache hit rate, a common measure of cache efficiency.

    Caches use the following mechanisms to improve efficiency.

    • Cache lines: Data is stored and loaded in cache-line units rather than one byte at a time, making transfers more efficient.
    • Prefetching: The processor tries to predict access patterns, such as sequential access or fixed strides, and loads matching data into the cache to improve the hit rate.
    • Spatial locality: Accessing data suggests that nearby data may soon be needed, so the cache loads neighboring data as well.
    • Temporal locality: Recently accessed data is likely to be accessed again soon, so keeping it in the cache improves the hit rate.

    Arrays and linked lists use the cache differently, mainly in the following ways.

    • Storage size: Linked list elements require more space, leaving less room for useful data in the cache.
    • Cache lines: Linked list data is scattered, so loading a whole cache line brings in a larger proportion of unneeded data.
    • Prefetching: Array access patterns are more predictable, making it easier to anticipate the next data needed.
    • Spatial locality: Array elements are stored together, so data near a loaded element is more likely to be accessed soon.

    Overall, arrays have higher cache hit rates and usually faster operations than linked lists. This makes array-based structures popular for algorithmic problems.

    However, better cache efficiency does not make arrays superior in every situation. Choose a structure based on the requirements. Both arrays and linked lists can implement a stack, discussed in the next chapter, but suit different situations.

    • For algorithm exercises, an array-based stack is often preferable because of faster operations and random access, at the cost of allocating some memory in advance.
    • If the dataset is very large, changes frequently, and the stack's size is hard to predict, a linked-list-based stack may be more suitable. It can distribute data throughout memory and avoid the extra cost of array expansion.
    ","path":["Chapter 4.   Arrays and linked lists","4.4   Memory and cache *"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/","level":1,"title":"4.5   Summary","text":"","path":["Chapter 4.   Arrays and linked lists","4.5   Summary"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Arrays and linked lists are fundamental structures representing contiguous and scattered memory storage. Their strengths are complementary.
    • Arrays support random access and use less memory, but insertion and deletion are slow, and their length is fixed after initialization.
    • Linked lists efficiently insert and remove nodes by changing references (pointers), and their length is flexible. However, accessing nodes is slow and they use more memory. Common forms include singly linked, circular, and doubly linked lists.
    • A list is an ordered collection supporting insertion, removal, access, and modification. It is usually implemented as a dynamic array, retaining array advantages while allowing its length to change.
    • Lists make arrays much more practical, though they may waste some memory.
    • During execution, data is mainly stored in RAM. Arrays use memory more efficiently, while linked lists offer more flexible allocation.
    • Cache lines, prefetching, spatial locality, and temporal locality provide fast CPU access to data, substantially improving execution speed.
    • Arrays usually outperform linked lists because of higher cache hit rates. Choose a structure according to the actual requirements and setting.
    ","path":["Chapter 4.   Arrays and linked lists","4.5   Summary"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Does storing an array on the stack or heap affect time and space efficiency?

    Both use contiguous memory, so data operations have essentially the same efficiency. However, the stack and heap differ in these ways.

    1. Allocation and deallocation: Stack memory is relatively small and allocated automatically by the compiler. The heap is larger, supports dynamic allocation in code, and is more prone to fragmentation. Heap allocation and deallocation are therefore usually slower.
    2. Size limits: Stack space is relatively small, while the heap is generally limited by available memory, making it better suited to large arrays.
    3. Flexibility: Stack array sizes must be known at compile time, while heap array sizes can be determined at runtime.

    Q: Why must array elements have the same type, while this requirement is not emphasized for linked lists?

    Linked lists consist of nodes connected by references (pointers). Different nodes can hold different types, such as int, double, string, and object.

    Array elements must have the same type so that their positions can be calculated from offsets. If an array contained both int and long, occupying 4 and 8 bytes respectively, the following formula would not work because there would be two element sizes.

    # Element address = array address (first element's address) + element size * element index\n

    Q: After removing node P, must we set P.next to None?

    Leaving P.next unchanged is acceptable. A traversal from the list's head to its tail no longer encounters P, so it has been removed. Whatever P points to no longer affects the list.

    For algorithm exercises, leaving it connected is fine as long as the logic is correct. For a standard library, disconnecting it is safer and clearer: if the removed node is not reclaimed properly, its reference may prevent its successor from being reclaimed.

    Q: Insertion and deletion in a linked list take \\(O(1)\\) time, but locating the element first takes \\(O(n)\\). Why is the overall complexity not \\(O(n)\\)?

    Finding an element and then deleting it does take \\(O(n)\\) time. However, \\(O(1)\\) insertion and deletion are useful in other settings. A deque, for example, can use a linked list with pointers maintained at the head and tail, giving \\(O(1)\\) operations.

    Q: In the linked list storage diagram, does the light-blue pointer occupy a separate memory location, or share half the node's space with its value?

    The diagram is qualitative. Exact sizes depend on the circumstances.

    • Node values of different types, such as int, long, double, and objects, occupy different amounts of space.
    • A pointer's size depends on the operating system and compilation environment, and is usually 8 or 4 bytes.

    Q: Does appending to a list always take \\(O(1)\\) time?

    If appending exceeds the available capacity, the list must first expand. The system allocates new memory and copies all existing elements, making that operation \\(O(n)\\).

    Q: When lists waste memory, does this mean the extra variables for capacity, length, and growth factor?

    The main waste has two sources. First, the initial allocation may exceed what we need. Second, to avoid frequent resizing, capacity usually grows by a factor such as \\(\\times 1.5\\), leaving empty slots that may never all be filled.

    Q: In Python, after n = [1, 2, 3], the three elements seem to have consecutive addresses. But in m = [2, 1, 3], their IDs are not consecutive and match those in n. Is m still an array?

    Consider storing linked list nodes instead: n = [n1, n2, n3, n4, n5]. These five objects are usually scattered in memory, yet an index still gives a node's address in \\(O(1)\\) time. The array stores references to the nodes, not the nodes themselves.

    Unlike many languages, Python also wraps numbers in objects. A list stores references to those objects rather than the numbers themselves. Thus, the same numbers in the two arrays can have the same IDs, and their addresses need not be contiguous.

    Q: C++ STL's std::list already implements a doubly linked list. Why do algorithm books rarely use it directly? Does it have limitations?

    First, we often favor arrays and use linked lists only when needed, mainly for two reasons.

    • Space overhead: Each element needs two extra pointers, to its predecessor and successor, so std::list usually consumes more space than std::vector.
    • Poor cache locality: Its data is not contiguous, making cache use less efficient. In general, std::vector performs better.

    Second, linked structures are mainly needed for binary trees and graphs. For stacks and queues, we usually use the language's provided stack and queue rather than a linked list directly.

    Q: Does res = [[0]] * n create a two-dimensional list with independent [0] entries?

    No. Every [0] is a reference to the same object. Modifying an element through one reference changes the corresponding element seen through all the others.

    To create independent [0] lists, use res = [[0] for _ in range(n)]. This initializes \\(n\\) separate list objects.

    Q: Does res = [0] * n create independent integer zeros?

    All zeros in the list refer to the same object. Python caches small integers, usually -5 through 256, to maximize reuse and improve performance.

    Even so, each list entry can be changed independently because Python integers are immutable. Assigning a new value switches the entry to a different object reference instead of modifying the original object.

    When an element is mutable, such as a list, dictionary, or class instance, modifying the object itself is visible through every reference to it.

    ","path":["Chapter 4.   Arrays and linked lists","4.5   Summary"],"tags":[]},{"location":"chapter_backtracking/","level":1,"title":"Chapter 13.   Backtracking","text":"

    Abstract

    Like explorers in a maze, we may encounter obstacles as we move forward.

    Backtracking lets us start again, try new paths, and eventually find our way out into the light.

    ","path":["Chapter 13.   Backtracking"],"tags":[]},{"location":"chapter_backtracking/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 13.1   Backtracking algorithms
    • 13.2   Permutations problem
    • 13.3   Subset sum problem
    • 13.4   N-queens problem
    • 13.5   Summary
    • 13.6   Exercises
    ","path":["Chapter 13.   Backtracking"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/","level":1,"title":"13.1   Backtracking algorithms","text":"

    A backtracking algorithm solves a problem through exhaustive search. Starting from an initial state, it explores possible solutions by brute force, recording valid solutions as it finds them, until a solution is found or every possible choice has been tried without success.

    Backtracking usually traverses the solution space through depth-first search. In the binary tree chapter, we learned that preorder, inorder, and postorder traversal are all forms of depth-first search. We will use preorder traversal to build a backtracking problem and explore the approach step by step.

    Example 1

    Given a binary tree, find and record every node with value \\(7\\). Return a list of these nodes.

    Traverse the tree in preorder and check whether the current node's value is \\(7\\). If so, add that node's value to the result list res. The code and figure below show the process:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_i_compact.py
    def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: example 1\"\"\"\n    if root is None:\n        return\n    if root.val == 7:\n        # Record the solution\n        res.append(root)\n    pre_order(root.left)\n    pre_order(root.right)\n
    preorder_traversal_i_compact.cpp
    /* Preorder traversal: example 1 */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr) {\n        return;\n    }\n    if (root->val == 7) {\n        // Record the solution\n        res.push_back(root);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n}\n
    preorder_traversal_i_compact.java
    /* Preorder traversal: example 1 */\nvoid preOrder(TreeNode root) {\n    if (root == null) {\n        return;\n    }\n    if (root.val == 7) {\n        // Record the solution\n        res.add(root);\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n}\n
    preorder_traversal_i_compact.cs
    /* Preorder traversal: example 1 */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) {\n        return;\n    }\n    if (root.val == 7) {\n        // Record the solution\n        res.Add(root);\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n}\n
    preorder_traversal_i_compact.go
    /* Preorder traversal: example 1 */\nfunc preOrderI(root *TreeNode, res *[]*TreeNode) {\n    if root == nil {\n        return\n    }\n    if (root.Val).(int) == 7 {\n        // Record the solution\n        *res = append(*res, root)\n    }\n    preOrderI(root.Left, res)\n    preOrderI(root.Right, res)\n}\n
    preorder_traversal_i_compact.swift
    /* Preorder traversal: example 1 */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    if root.val == 7 {\n        // Record the solution\n        res.append(root)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n}\n
    preorder_traversal_i_compact.js
    /* Preorder traversal: example 1 */\nfunction preOrder(root, res) {\n    if (root === null) {\n        return;\n    }\n    if (root.val === 7) {\n        // Record the solution\n        res.push(root);\n    }\n    preOrder(root.left, res);\n    preOrder(root.right, res);\n}\n
    preorder_traversal_i_compact.ts
    /* Preorder traversal: example 1 */\nfunction preOrder(root: TreeNode | null, res: TreeNode[]): void {\n    if (root === null) {\n        return;\n    }\n    if (root.val === 7) {\n        // Record the solution\n        res.push(root);\n    }\n    preOrder(root.left, res);\n    preOrder(root.right, res);\n}\n
    preorder_traversal_i_compact.dart
    /* Preorder traversal: example 1 */\nvoid preOrder(TreeNode? root, List<TreeNode> res) {\n  if (root == null) {\n    return;\n  }\n  if (root.val == 7) {\n    // Record the solution\n    res.add(root);\n  }\n  preOrder(root.left, res);\n  preOrder(root.right, res);\n}\n
    preorder_traversal_i_compact.rs
    /* Preorder traversal: example 1 */\nfn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<&Rc<RefCell<TreeNode>>>) {\n    if root.is_none() {\n        return;\n    }\n    if let Some(node) = root {\n        if node.borrow().val == 7 {\n            // Record the solution\n            res.push(node.clone());\n        }\n        pre_order(res, node.borrow().left.as_ref());\n        pre_order(res, node.borrow().right.as_ref());\n    }\n}\n
    preorder_traversal_i_compact.c
    /* Preorder traversal: example 1 */\nvoid preOrder(TreeNode *root) {\n    if (root == NULL) {\n        return;\n    }\n    if (root->val == 7) {\n        // Record the solution\n        res[resSize++] = root;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n}\n
    preorder_traversal_i_compact.kt
    /* Preorder traversal: example 1 */\nfun preOrder(root: TreeNode?) {\n    if (root == null) {\n        return\n    }\n    if (root._val == 7) {\n        // Record the solution\n        res!!.add(root)\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n}\n
    preorder_traversal_i_compact.rb
    ### Preorder traversal: example 1 ###\ndef pre_order(root)\n  return unless root\n\n  # Record the solution\n  $res << root if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\nend\n
    Visualize execution

    View full screen >

    Figure 13-1   Finding nodes during preorder traversal

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1311-trying-choices-and-backtracking","level":2,"title":"13.1.1   Trying choices and backtracking","text":"

    Backtracking gets its name from its pattern of trying choices and undoing them while searching the solution space. When the search reaches a state that cannot advance or lead to a valid solution, it undoes the last choice, returns to the previous state, and tries other possibilities.

    In Example 1, visiting a node is a trial, while a return after passing a leaf or returning to a parent is backtracking.

    Backtracking involves more than returning from a function. To see why, let's extend Example 1.

    Example 2

    Find every node with value \\(7\\) in a binary tree, and return the paths from the root to those nodes.

    Extend the first solution with a list path recording the current path of visited nodes. When a node with value \\(7\\) is reached, append a copy of path to the result list res. After traversal, res contains all solutions. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_ii_compact.py
    def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: example 2\"\"\"\n    if root is None:\n        return\n    # Try\n    path.append(root)\n    if root.val == 7:\n        # Record the solution\n        res.append(list(path))\n    pre_order(root.left)\n    pre_order(root.right)\n    # Backtrack\n    path.pop()\n
    preorder_traversal_ii_compact.cpp
    /* Preorder traversal: example 2 */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr) {\n        return;\n    }\n    // Try\n    path.push_back(root);\n    if (root->val == 7) {\n        // Record the solution\n        res.push_back(path);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    path.pop_back();\n}\n
    preorder_traversal_ii_compact.java
    /* Preorder traversal: example 2 */\nvoid preOrder(TreeNode root) {\n    if (root == null) {\n        return;\n    }\n    // Try\n    path.add(root);\n    if (root.val == 7) {\n        // Record the solution\n        res.add(new ArrayList<>(path));\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n    // Backtrack\n    path.remove(path.size() - 1);\n}\n
    preorder_traversal_ii_compact.cs
    /* Preorder traversal: example 2 */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) {\n        return;\n    }\n    // Try\n    path.Add(root);\n    if (root.val == 7) {\n        // Record the solution\n        res.Add(new List<TreeNode>(path));\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n    // Backtrack\n    path.RemoveAt(path.Count - 1);\n}\n
    preorder_traversal_ii_compact.go
    /* Preorder traversal: example 2 */\nfunc preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n    if root == nil {\n        return\n    }\n    // Try\n    *path = append(*path, root)\n    if root.Val.(int) == 7 {\n        // Record the solution\n        *res = append(*res, append([]*TreeNode{}, *path...))\n    }\n    preOrderII(root.Left, res, path)\n    preOrderII(root.Right, res, path)\n    // Backtrack\n    *path = (*path)[:len(*path)-1]\n}\n
    preorder_traversal_ii_compact.swift
    /* Preorder traversal: example 2 */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Try\n    path.append(root)\n    if root.val == 7 {\n        // Record the solution\n        res.append(path)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n    // Backtrack\n    path.removeLast()\n}\n
    preorder_traversal_ii_compact.js
    /* Preorder traversal: example 2 */\nfunction preOrder(root, path, res) {\n    if (root === null) {\n        return;\n    }\n    // Try\n    path.push(root);\n    if (root.val === 7) {\n        // Record the solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
    preorder_traversal_ii_compact.ts
    /* Preorder traversal: example 2 */\nfunction preOrder(\n    root: TreeNode | null,\n    path: TreeNode[],\n    res: TreeNode[][]\n): void {\n    if (root === null) {\n        return;\n    }\n    // Try\n    path.push(root);\n    if (root.val === 7) {\n        // Record the solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
    preorder_traversal_ii_compact.dart
    /* Preorder traversal: example 2 */\nvoid preOrder(\n  TreeNode? root,\n  List<TreeNode> path,\n  List<List<TreeNode>> res,\n) {\n  if (root == null) {\n    return;\n  }\n\n  // Try\n  path.add(root);\n  if (root.val == 7) {\n    // Record the solution\n    res.add(List.from(path));\n  }\n  preOrder(root.left, path, res);\n  preOrder(root.right, path, res);\n  // Backtrack\n  path.removeLast();\n}\n
    preorder_traversal_ii_compact.rs
    /* Preorder traversal: example 2 */\nfn pre_order(\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n    path: &mut Vec<Rc<RefCell<TreeNode>>>,\n    root: Option<&Rc<RefCell<TreeNode>>>,\n) {\n    if root.is_none() {\n        return;\n    }\n    if let Some(node) = root {\n        // Try\n        path.push(node.clone());\n        if node.borrow().val == 7 {\n            // Record the solution\n            res.push(path.clone());\n        }\n        pre_order(res, path, node.borrow().left.as_ref());\n        pre_order(res, path, node.borrow().right.as_ref());\n        // Backtrack\n        path.pop();\n    }\n}\n
    preorder_traversal_ii_compact.c
    /* Preorder traversal: example 2 */\nvoid preOrder(TreeNode *root) {\n    if (root == NULL) {\n        return;\n    }\n    // Try\n    path[pathSize++] = root;\n    if (root->val == 7) {\n        // Record the solution\n        for (int i = 0; i < pathSize; ++i) {\n            res[resSize][i] = path[i];\n        }\n        resSize++;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    pathSize--;\n}\n
    preorder_traversal_ii_compact.kt
    /* Preorder traversal: example 2 */\nfun preOrder(root: TreeNode?) {\n    if (root == null) {\n        return\n    }\n    // Try\n    path!!.add(root)\n    if (root._val == 7) {\n        // Record the solution\n        res!!.add(path!!.toMutableList())\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n    // Backtrack\n    path!!.removeAt(path!!.size - 1)\n}\n
    preorder_traversal_ii_compact.rb
    ### Preorder traversal: example 2 ###\ndef pre_order(root)\n  return unless root\n\n  # Try\n  $path << root\n\n  # Record the solution\n  $res << $path.dup if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\n\n  # Backtrack\n  $path.pop\nend\n
    Visualize execution

    View full screen >

    Each time we try a node, append it to path. Before backtracking, pop that node from path to restore the state from before the trial.

    As Figure 13-2 illustrates, trying a choice and backtracking are like moving forward and undoing that move: they are inverse operations.

    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 13-2   Trying choices and backtracking

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1312-pruning","level":2,"title":"13.1.2   Pruning","text":"

    Complex backtracking problems usually have one or more constraints, which can often be used for pruning.

    Example 3

    Find every node with value \\(7\\) in a binary tree and return the paths from the root to those nodes, requiring that no path contain a node with value \\(3\\).

    To enforce this constraint, add a pruning step: when a node with value \\(3\\) is encountered, return immediately without searching farther. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_compact.py
    def pre_order(root: TreeNode):\n    \"\"\"Preorder traversal: example 3\"\"\"\n    # Prune\n    if root is None or root.val == 3:\n        return\n    # Try\n    path.append(root)\n    if root.val == 7:\n        # Record the solution\n        res.append(list(path))\n    pre_order(root.left)\n    pre_order(root.right)\n    # Backtrack\n    path.pop()\n
    preorder_traversal_iii_compact.cpp
    /* Preorder traversal: example 3 */\nvoid preOrder(TreeNode *root) {\n    // Prune\n    if (root == nullptr || root->val == 3) {\n        return;\n    }\n    // Try\n    path.push_back(root);\n    if (root->val == 7) {\n        // Record the solution\n        res.push_back(path);\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    path.pop_back();\n}\n
    preorder_traversal_iii_compact.java
    /* Preorder traversal: example 3 */\nvoid preOrder(TreeNode root) {\n    // Prune\n    if (root == null || root.val == 3) {\n        return;\n    }\n    // Try\n    path.add(root);\n    if (root.val == 7) {\n        // Record the solution\n        res.add(new ArrayList<>(path));\n    }\n    preOrder(root.left);\n    preOrder(root.right);\n    // Backtrack\n    path.remove(path.size() - 1);\n}\n
    preorder_traversal_iii_compact.cs
    /* Preorder traversal: example 3 */\nvoid PreOrder(TreeNode? root) {\n    // Prune\n    if (root == null || root.val == 3) {\n        return;\n    }\n    // Try\n    path.Add(root);\n    if (root.val == 7) {\n        // Record the solution\n        res.Add(new List<TreeNode>(path));\n    }\n    PreOrder(root.left);\n    PreOrder(root.right);\n    // Backtrack\n    path.RemoveAt(path.Count - 1);\n}\n
    preorder_traversal_iii_compact.go
    /* Preorder traversal: example 3 */\nfunc preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n    // Prune\n    if root == nil || root.Val == 3 {\n        return\n    }\n    // Try\n    *path = append(*path, root)\n    if root.Val.(int) == 7 {\n        // Record the solution\n        *res = append(*res, append([]*TreeNode{}, *path...))\n    }\n    preOrderIII(root.Left, res, path)\n    preOrderIII(root.Right, res, path)\n    // Backtrack\n    *path = (*path)[:len(*path)-1]\n}\n
    preorder_traversal_iii_compact.swift
    /* Preorder traversal: example 3 */\nfunc preOrder(root: TreeNode?) {\n    // Prune\n    guard let root = root, root.val != 3 else {\n        return\n    }\n    // Try\n    path.append(root)\n    if root.val == 7 {\n        // Record the solution\n        res.append(path)\n    }\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n    // Backtrack\n    path.removeLast()\n}\n
    preorder_traversal_iii_compact.js
    /* Preorder traversal: example 3 */\nfunction preOrder(root, path, res) {\n    // Prune\n    if (root === null || root.val === 3) {\n        return;\n    }\n    // Try\n    path.push(root);\n    if (root.val === 7) {\n        // Record the solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
    preorder_traversal_iii_compact.ts
    /* Preorder traversal: example 3 */\nfunction preOrder(\n    root: TreeNode | null,\n    path: TreeNode[],\n    res: TreeNode[][]\n): void {\n    // Prune\n    if (root === null || root.val === 3) {\n        return;\n    }\n    // Try\n    path.push(root);\n    if (root.val === 7) {\n        // Record the solution\n        res.push([...path]);\n    }\n    preOrder(root.left, path, res);\n    preOrder(root.right, path, res);\n    // Backtrack\n    path.pop();\n}\n
    preorder_traversal_iii_compact.dart
    /* Preorder traversal: example 3 */\nvoid preOrder(\n  TreeNode? root,\n  List<TreeNode> path,\n  List<List<TreeNode>> res,\n) {\n  if (root == null || root.val == 3) {\n    return;\n  }\n\n  // Try\n  path.add(root);\n  if (root.val == 7) {\n    // Record the solution\n    res.add(List.from(path));\n  }\n  preOrder(root.left, path, res);\n  preOrder(root.right, path, res);\n  // Backtrack\n  path.removeLast();\n}\n
    preorder_traversal_iii_compact.rs
    /* Preorder traversal: example 3 */\nfn pre_order(\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n    path: &mut Vec<Rc<RefCell<TreeNode>>>,\n    root: Option<&Rc<RefCell<TreeNode>>>,\n) {\n    // Prune\n    if root.is_none() || root.as_ref().unwrap().borrow().val == 3 {\n        return;\n    }\n    if let Some(node) = root {\n        // Try\n        path.push(node.clone());\n        if node.borrow().val == 7 {\n            // Record the solution\n            res.push(path.clone());\n        }\n        pre_order(res, path, node.borrow().left.as_ref());\n        pre_order(res, path, node.borrow().right.as_ref());\n        // Backtrack\n        path.pop();\n    }\n}\n
    preorder_traversal_iii_compact.c
    /* Preorder traversal: example 3 */\nvoid preOrder(TreeNode *root) {\n    // Prune\n    if (root == NULL || root->val == 3) {\n        return;\n    }\n    // Try\n    path[pathSize++] = root;\n    if (root->val == 7) {\n        // Record the solution\n        for (int i = 0; i < pathSize; i++) {\n            res[resSize][i] = path[i];\n        }\n        resSize++;\n    }\n    preOrder(root->left);\n    preOrder(root->right);\n    // Backtrack\n    pathSize--;\n}\n
    preorder_traversal_iii_compact.kt
    /* Preorder traversal: example 3 */\nfun preOrder(root: TreeNode?) {\n    // Prune\n    if (root == null || root._val == 3) {\n        return\n    }\n    // Try\n    path!!.add(root)\n    if (root._val == 7) {\n        // Record the solution\n        res!!.add(path!!.toMutableList())\n    }\n    preOrder(root.left)\n    preOrder(root.right)\n    // Backtrack\n    path!!.removeAt(path!!.size - 1)\n}\n
    preorder_traversal_iii_compact.rb
    ### Preorder traversal: example 3 ###\ndef pre_order(root)\n  # Prune\n  return if !root || root.val == 3\n\n  # Try\n  $path.append(root)\n\n  # Record the solution\n  $res << $path.dup if root.val == 7\n\n  pre_order(root.left)\n  pre_order(root.right)\n\n  # Backtrack\n  $path.pop\nend\n
    Visualize execution

    View full screen >

    Pruning is an apt name. As shown in Figure 13-3, we cut off search branches that violate the constraints, avoiding pointless trials and improving efficiency.

    Figure 13-3   Pruning according to constraints

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1313-a-code-template","level":2,"title":"13.1.3   A code template","text":"

    We will now extract the core pattern of trying, undoing, and pruning into a more general backtracking template.

    In the following template, state is the current problem state, and choices contains the choices available from that state:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def backtrack(state: State, choices: list[choice], res: list[state]):\n    \"\"\"Backtracking template\"\"\"\n    # Check whether the state is a solution\n    if is_solution(state):\n        # Record the solution\n        record_solution(state, res)\n        # Stop searching this branch\n        return\n    # Try every choice\n    for choice in choices:\n        # Prune: check whether the choice is valid\n        if is_valid(state, choice):\n            # Try: make the choice and update the state\n            make_choice(state, choice)\n            backtrack(state, choices, res)\n            # Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice)\n
    /* Backtracking template */\nvoid backtrack(State *state, vector<Choice *> &choices, vector<State *> &res) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (Choice choice : choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nvoid backtrack(State state, List<Choice> choices, List<State> res) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (Choice choice : choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nvoid Backtrack(State state, List<Choice> choices, List<State> res) {\n    // Check whether the state is a solution\n    if (IsSolution(state)) {\n        // Record the solution\n        RecordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    foreach (Choice choice in choices) {\n        // Prune: check whether the choice is valid\n        if (IsValid(state, choice)) {\n            // Try: make the choice and update the state\n            MakeChoice(state, choice);\n            Backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            UndoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nfunc backtrack(state *State, choices []Choice, res *[]State) {\n    // Check whether the state is a solution\n    if isSolution(state) {\n        // Record the solution\n        recordSolution(state, res)\n        // Stop searching this branch\n        return\n    }\n    // Try every choice\n    for _, choice := range choices {\n        // Prune: check whether the choice is valid\n        if isValid(state, choice) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice)\n            backtrack(state, choices, res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
    /* Backtracking template */\nfunc backtrack(state: inout State, choices: [Choice], res: inout [State]) {\n    // Check whether the state is a solution\n    if isSolution(state: state) {\n        // Record the solution\n        recordSolution(state: state, res: &res)\n        // Stop searching this branch\n        return\n    }\n    // Try every choice\n    for choice in choices {\n        // Prune: check whether the choice is valid\n        if isValid(state: state, choice: choice) {\n            // Try: make the choice and update the state\n            makeChoice(state: &state, choice: choice)\n            backtrack(state: &state, choices: choices, res: &res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state: &state, choice: choice)\n        }\n    }\n}\n
    /* Backtracking template */\nfunction backtrack(state, choices, res) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (let choice of choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nfunction backtrack(state: State, choices: Choice[], res: State[]): void {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (let choice of choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nvoid backtrack(State state, List<Choice>, List<State> res) {\n  // Check whether the state is a solution\n  if (isSolution(state)) {\n    // Record the solution\n    recordSolution(state, res);\n    // Stop searching this branch\n    return;\n  }\n  // Try every choice\n  for (Choice choice in choices) {\n    // Prune: check whether the choice is valid\n    if (isValid(state, choice)) {\n      // Try: make the choice and update the state\n      makeChoice(state, choice);\n      backtrack(state, choices, res);\n      // Backtrack: undo the choice and restore the previous state\n      undoChoice(state, choice);\n    }\n  }\n}\n
    /* Backtracking template */\nfn backtrack(state: &mut State, choices: &Vec<Choice>, res: &mut Vec<State>) {\n    // Check whether the state is a solution\n    if is_solution(state) {\n        // Record the solution\n        record_solution(state, res);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for choice in choices {\n        // Prune: check whether the choice is valid\n        if is_valid(state, choice) {\n            // Try: make the choice and update the state\n            make_choice(state, choice);\n            backtrack(state, choices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice);\n        }\n    }\n}\n
    /* Backtracking template */\nvoid backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res, numRes);\n        // Stop searching this branch\n        return;\n    }\n    // Try every choice\n    for (int i = 0; i < numChoices; i++) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, &choices[i])) {\n            // Try: make the choice and update the state\n            makeChoice(state, &choices[i]);\n            backtrack(state, choices, numChoices, res, numRes);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, &choices[i]);\n        }\n    }\n}\n
    /* Backtracking template */\nfun backtrack(state: State?, choices: List<Choice?>, res: List<State?>?) {\n    // Check whether the state is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res)\n        // Stop searching this branch\n        return\n    }\n    // Try every choice\n    for (choice in choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make the choice and update the state\n            makeChoice(state, choice)\n            backtrack(state, choices, res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
    ### Backtracking template ###\ndef backtrack(state, choices, res)\n    # Check whether the state is a solution\n    if is_solution?(state)\n        # Record the solution\n        record_solution(state, res)\n        return\n    end\n\n    # Try every choice\n    for choice in choices\n        # Prune: check whether the choice is valid\n        if is_valid?(state, choice)\n            # Try: make the choice and update the state\n            make_choice(state, choice)\n            backtrack(state, choices, res)\n            # Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice)\n        end\n    end\nend\n

    Now apply this template to Example 3. The state is the current path of visited nodes, choices consists of the current node's left and right children, and res is the list of paths:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_template.py
    def is_solution(state: list[TreeNode]) -> bool:\n    \"\"\"Check whether the current state is a solution\"\"\"\n    return state and state[-1].val == 7\n\ndef record_solution(state: list[TreeNode], res: list[list[TreeNode]]):\n    \"\"\"Record the solution\"\"\"\n    res.append(list(state))\n\ndef is_valid(state: list[TreeNode], choice: TreeNode) -> bool:\n    \"\"\"Check whether the choice is valid in the current state\"\"\"\n    return choice is not None and choice.val != 3\n\ndef make_choice(state: list[TreeNode], choice: TreeNode):\n    \"\"\"Update the state\"\"\"\n    state.append(choice)\n\ndef undo_choice(state: list[TreeNode], choice: TreeNode):\n    \"\"\"Restore the state\"\"\"\n    state.pop()\n\ndef backtrack(\n    state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]\n):\n    \"\"\"Backtracking: example 3\"\"\"\n    # Check whether this is a solution\n    if is_solution(state):\n        # Record the solution\n        record_solution(state, res)\n    # Iterate over all choices\n    for choice in choices:\n        # Prune: check whether the choice is valid\n        if is_valid(state, choice):\n            # Try: make a choice and update the state\n            make_choice(state, choice)\n            # Proceed to the next choice\n            backtrack(state, [choice.left, choice.right], res)\n            # Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice)\n
    preorder_traversal_iii_template.cpp
    /* Check whether the current state is a solution */\nbool isSolution(vector<TreeNode *> &state) {\n    return !state.empty() && state.back()->val == 7;\n}\n\n/* Record the solution */\nvoid recordSolution(vector<TreeNode *> &state, vector<vector<TreeNode *>> &res) {\n    res.push_back(state);\n}\n\n/* Check whether the choice is valid in the current state */\nbool isValid(vector<TreeNode *> &state, TreeNode *choice) {\n    return choice != nullptr && choice->val != 3;\n}\n\n/* Update the state */\nvoid makeChoice(vector<TreeNode *> &state, TreeNode *choice) {\n    state.push_back(choice);\n}\n\n/* Restore the state */\nvoid undoChoice(vector<TreeNode *> &state, TreeNode *choice) {\n    state.pop_back();\n}\n\n/* Backtracking: example 3 */\nvoid backtrack(vector<TreeNode *> &state, vector<TreeNode *> &choices, vector<vector<TreeNode *>> &res) {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n    }\n    // Iterate over all choices\n    for (TreeNode *choice : choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice);\n            // Proceed to the next choice\n            vector<TreeNode *> nextChoices{choice->left, choice->right};\n            backtrack(state, nextChoices, res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    preorder_traversal_iii_template.java
    /* Check whether the current state is a solution */\nboolean isSolution(List<TreeNode> state) {\n    return !state.isEmpty() && state.get(state.size() - 1).val == 7;\n}\n\n/* Record the solution */\nvoid recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n    res.add(new ArrayList<>(state));\n}\n\n/* Check whether the choice is valid in the current state */\nboolean isValid(List<TreeNode> state, TreeNode choice) {\n    return choice != null && choice.val != 3;\n}\n\n/* Update the state */\nvoid makeChoice(List<TreeNode> state, TreeNode choice) {\n    state.add(choice);\n}\n\n/* Restore the state */\nvoid undoChoice(List<TreeNode> state, TreeNode choice) {\n    state.remove(state.size() - 1);\n}\n\n/* Backtracking: example 3 */\nvoid backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n    }\n    // Iterate over all choices\n    for (TreeNode choice : choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice);\n            // Proceed to the next choice\n            backtrack(state, Arrays.asList(choice.left, choice.right), res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice);\n        }\n    }\n}\n
    preorder_traversal_iii_template.cs
    /* Check whether the current state is a solution */\nbool IsSolution(List<TreeNode> state) {\n    return state.Count != 0 && state[^1].val == 7;\n}\n\n/* Record the solution */\nvoid RecordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n    res.Add(new List<TreeNode>(state));\n}\n\n/* Check whether the choice is valid in the current state */\nbool IsValid(List<TreeNode> state, TreeNode choice) {\n    return choice != null && choice.val != 3;\n}\n\n/* Update the state */\nvoid MakeChoice(List<TreeNode> state, TreeNode choice) {\n    state.Add(choice);\n}\n\n/* Restore the state */\nvoid UndoChoice(List<TreeNode> state, TreeNode choice) {\n    state.RemoveAt(state.Count - 1);\n}\n\n/* Backtracking: example 3 */\nvoid Backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {\n    // Check whether this is a solution\n    if (IsSolution(state)) {\n        // Record the solution\n        RecordSolution(state, res);\n    }\n    // Iterate over all choices\n    foreach (TreeNode choice in choices) {\n        // Prune: check whether the choice is valid\n        if (IsValid(state, choice)) {\n            // Try: make a choice and update the state\n            MakeChoice(state, choice);\n            // Proceed to the next choice\n            Backtrack(state, [choice.left!, choice.right!], res);\n            // Backtrack: undo the choice and restore the previous state\n            UndoChoice(state, choice);\n        }\n    }\n}\n
    preorder_traversal_iii_template.go
    /* Check whether the current state is a solution */\nfunc isSolution(state *[]*TreeNode) bool {\n    return len(*state) != 0 && (*state)[len(*state)-1].Val == 7\n}\n\n/* Record the solution */\nfunc recordSolution(state *[]*TreeNode, res *[][]*TreeNode) {\n    *res = append(*res, append([]*TreeNode{}, *state...))\n}\n\n/* Check whether the choice is valid in the current state */\nfunc isValid(state *[]*TreeNode, choice *TreeNode) bool {\n    return choice != nil && choice.Val != 3\n}\n\n/* Update the state */\nfunc makeChoice(state *[]*TreeNode, choice *TreeNode) {\n    *state = append(*state, choice)\n}\n\n/* Restore the state */\nfunc undoChoice(state *[]*TreeNode, choice *TreeNode) {\n    *state = (*state)[:len(*state)-1]\n}\n\n/* Backtracking: example 3 */\nfunc backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) {\n    // Check whether this is a solution\n    if isSolution(state) {\n        // Record the solution\n        recordSolution(state, res)\n    }\n    // Iterate over all choices\n    for _, choice := range *choices {\n        // Prune: check whether the choice is valid\n        if isValid(state, choice) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice)\n            // Proceed to the next choice\n            temp := make([]*TreeNode, 0)\n            temp = append(temp, choice.Left, choice.Right)\n            backtrackIII(state, &temp, res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
    preorder_traversal_iii_template.swift
    /* Check whether the current state is a solution */\nfunc isSolution(state: [TreeNode]) -> Bool {\n    !state.isEmpty && state.last!.val == 7\n}\n\n/* Record the solution */\nfunc recordSolution(state: [TreeNode], res: inout [[TreeNode]]) {\n    res.append(state)\n}\n\n/* Check whether the choice is valid in the current state */\nfunc isValid(state: [TreeNode], choice: TreeNode?) -> Bool {\n    choice != nil && choice!.val != 3\n}\n\n/* Update the state */\nfunc makeChoice(state: inout [TreeNode], choice: TreeNode) {\n    state.append(choice)\n}\n\n/* Restore the state */\nfunc undoChoice(state: inout [TreeNode], choice: TreeNode) {\n    state.removeLast()\n}\n\n/* Backtracking: example 3 */\nfunc backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) {\n    // Check whether this is a solution\n    if isSolution(state: state) {\n        recordSolution(state: state, res: &res)\n    }\n    // Iterate over all choices\n    for choice in choices {\n        // Prune: check whether the choice is valid\n        if isValid(state: state, choice: choice) {\n            // Try: make a choice and update the state\n            makeChoice(state: &state, choice: choice)\n            // Proceed to the next choice\n            backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state: &state, choice: choice)\n        }\n    }\n}\n
    preorder_traversal_iii_template.js
    /* Check whether the current state is a solution */\nfunction isSolution(state) {\n    return state && state[state.length - 1]?.val === 7;\n}\n\n/* Record the solution */\nfunction recordSolution(state, res) {\n    res.push([...state]);\n}\n\n/* Check whether the choice is valid in the current state */\nfunction isValid(state, choice) {\n    return choice !== null && choice.val !== 3;\n}\n\n/* Update the state */\nfunction makeChoice(state, choice) {\n    state.push(choice);\n}\n\n/* Restore the state */\nfunction undoChoice(state) {\n    state.pop();\n}\n\n/* Backtracking: example 3 */\nfunction backtrack(state, choices, res) {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n    }\n    // Iterate over all choices\n    for (const choice of choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice);\n            // Proceed to the next choice\n            backtrack(state, [choice.left, choice.right], res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state);\n        }\n    }\n}\n
    preorder_traversal_iii_template.ts
    /* Check whether the current state is a solution */\nfunction isSolution(state: TreeNode[]): boolean {\n    return state && state[state.length - 1]?.val === 7;\n}\n\n/* Record the solution */\nfunction recordSolution(state: TreeNode[], res: TreeNode[][]): void {\n    res.push([...state]);\n}\n\n/* Check whether the choice is valid in the current state */\nfunction isValid(state: TreeNode[], choice: TreeNode): boolean {\n    return choice !== null && choice.val !== 3;\n}\n\n/* Update the state */\nfunction makeChoice(state: TreeNode[], choice: TreeNode): void {\n    state.push(choice);\n}\n\n/* Restore the state */\nfunction undoChoice(state: TreeNode[]): void {\n    state.pop();\n}\n\n/* Backtracking: example 3 */\nfunction backtrack(\n    state: TreeNode[],\n    choices: TreeNode[],\n    res: TreeNode[][]\n): void {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res);\n    }\n    // Iterate over all choices\n    for (const choice of choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice);\n            // Proceed to the next choice\n            backtrack(state, [choice.left, choice.right], res);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state);\n        }\n    }\n}\n
    preorder_traversal_iii_template.dart
    /* Check whether the current state is a solution */\nbool isSolution(List<TreeNode> state) {\n  return state.isNotEmpty && state.last.val == 7;\n}\n\n/* Record the solution */\nvoid recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {\n  res.add(List.from(state));\n}\n\n/* Check whether the choice is valid in the current state */\nbool isValid(List<TreeNode> state, TreeNode? choice) {\n  return choice != null && choice.val != 3;\n}\n\n/* Update the state */\nvoid makeChoice(List<TreeNode> state, TreeNode? choice) {\n  state.add(choice!);\n}\n\n/* Restore the state */\nvoid undoChoice(List<TreeNode> state, TreeNode? choice) {\n  state.removeLast();\n}\n\n/* Backtracking: example 3 */\nvoid backtrack(\n  List<TreeNode> state,\n  List<TreeNode?> choices,\n  List<List<TreeNode>> res,\n) {\n  // Check whether this is a solution\n  if (isSolution(state)) {\n    // Record the solution\n    recordSolution(state, res);\n  }\n  // Iterate over all choices\n  for (TreeNode? choice in choices) {\n    // Prune: check whether the choice is valid\n    if (isValid(state, choice)) {\n      // Try: make a choice and update the state\n      makeChoice(state, choice);\n      // Proceed to the next choice\n      backtrack(state, [choice!.left, choice.right], res);\n      // Backtrack: undo the choice and restore the previous state\n      undoChoice(state, choice);\n    }\n  }\n}\n
    preorder_traversal_iii_template.rs
    /* Check whether the current state is a solution */\nfn is_solution(state: &mut Vec<Rc<RefCell<TreeNode>>>) -> bool {\n    return !state.is_empty() && state.last().unwrap().borrow().val == 7;\n}\n\n/* Record the solution */\nfn record_solution(\n    state: &mut Vec<Rc<RefCell<TreeNode>>>,\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n) {\n    res.push(state.clone());\n}\n\n/* Check whether the choice is valid in the current state */\nfn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Option<&Rc<RefCell<TreeNode>>>) -> bool {\n    return choice.is_some() && choice.unwrap().borrow().val != 3;\n}\n\n/* Update the state */\nfn make_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Rc<RefCell<TreeNode>>) {\n    state.push(choice);\n}\n\n/* Restore the state */\nfn undo_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, _: Rc<RefCell<TreeNode>>) {\n    state.pop();\n}\n\n/* Backtracking: example 3 */\nfn backtrack(\n    state: &mut Vec<Rc<RefCell<TreeNode>>>,\n    choices: &Vec<Option<&Rc<RefCell<TreeNode>>>>,\n    res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,\n) {\n    // Check whether this is a solution\n    if is_solution(state) {\n        // Record the solution\n        record_solution(state, res);\n    }\n    // Iterate over all choices\n    for &choice in choices.iter() {\n        // Prune: check whether the choice is valid\n        if is_valid(state, choice) {\n            // Try: make a choice and update the state\n            make_choice(state, choice.unwrap().clone());\n            // Proceed to the next choice\n            backtrack(\n                state,\n                &vec![\n                    choice.unwrap().borrow().left.as_ref(),\n                    choice.unwrap().borrow().right.as_ref(),\n                ],\n                res,\n            );\n            // Backtrack: undo the choice and restore the previous state\n            undo_choice(state, choice.unwrap().clone());\n        }\n    }\n}\n
    preorder_traversal_iii_template.c
    /* Check whether the current state is a solution */\nbool isSolution(void) {\n    return pathSize > 0 && path[pathSize - 1]->val == 7;\n}\n\n/* Record the solution */\nvoid recordSolution(void) {\n    for (int i = 0; i < pathSize; i++) {\n        res[resSize][i] = path[i];\n    }\n    resSize++;\n}\n\n/* Check whether the choice is valid in the current state */\nbool isValid(TreeNode *choice) {\n    return choice != NULL && choice->val != 3;\n}\n\n/* Update the state */\nvoid makeChoice(TreeNode *choice) {\n    path[pathSize++] = choice;\n}\n\n/* Restore the state */\nvoid undoChoice(void) {\n    pathSize--;\n}\n\n/* Backtracking: example 3 */\nvoid backtrack(TreeNode *choices[2]) {\n    // Check whether this is a solution\n    if (isSolution()) {\n        // Record the solution\n        recordSolution();\n    }\n    // Iterate over all choices\n    for (int i = 0; i < 2; i++) {\n        TreeNode *choice = choices[i];\n        // Prune: check whether the choice is valid\n        if (isValid(choice)) {\n            // Try: make a choice and update the state\n            makeChoice(choice);\n            // Proceed to the next choice\n            TreeNode *nextChoices[2] = {choice->left, choice->right};\n            backtrack(nextChoices);\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice();\n        }\n    }\n}\n
    preorder_traversal_iii_template.kt
    /* Check whether the current state is a solution */\nfun isSolution(state: MutableList<TreeNode?>): Boolean {\n    return state.isNotEmpty() && state[state.size - 1]?._val == 7\n}\n\n/* Record the solution */\nfun recordSolution(state: MutableList<TreeNode?>?, res: MutableList<MutableList<TreeNode?>?>) {\n    res.add(state!!.toMutableList())\n}\n\n/* Check whether the choice is valid in the current state */\nfun isValid(state: MutableList<TreeNode?>?, choice: TreeNode?): Boolean {\n    return choice != null && choice._val != 3\n}\n\n/* Update the state */\nfun makeChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {\n    state.add(choice)\n}\n\n/* Restore the state */\nfun undoChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {\n    state.removeLast()\n}\n\n/* Backtracking: example 3 */\nfun backtrack(\n    state: MutableList<TreeNode?>,\n    choices: MutableList<TreeNode?>,\n    res: MutableList<MutableList<TreeNode?>?>\n) {\n    // Check whether this is a solution\n    if (isSolution(state)) {\n        // Record the solution\n        recordSolution(state, res)\n    }\n    // Iterate over all choices\n    for (choice in choices) {\n        // Prune: check whether the choice is valid\n        if (isValid(state, choice)) {\n            // Try: make a choice and update the state\n            makeChoice(state, choice)\n            // Proceed to the next choice\n            backtrack(state, mutableListOf(choice!!.left, choice.right), res)\n            // Backtrack: undo the choice and restore the previous state\n            undoChoice(state, choice)\n        }\n    }\n}\n
    preorder_traversal_iii_template.rb
    ### Check whether the current state is a solution ###\ndef is_solution?(state)\n  !state.empty? && state.last.val == 7\nend\n\n### Record the solution ###\ndef record_solution(state, res)\n  res << state.dup\nend\n\n### Check whether the choice is valid in the current state ###\ndef is_valid?(state, choice)\n  choice && choice.val != 3\nend\n\n### Update the state ###\ndef make_choice(state, choice)\n  state << choice\nend\n\n### Restore the state ###\ndef undo_choice(state, choice)\n  state.pop\nend\n\n### Backtracking: example 3 ###\ndef backtrack(state, choices, res)\n  # Check whether this is a solution\n  record_solution(state, res) if is_solution?(state)\n\n  # Iterate over all choices\n  for choice in choices\n    # Prune: check whether the choice is valid\n    if is_valid?(state, choice)\n      # Try: make a choice and update the state\n      make_choice(state, choice)\n      # Proceed to the next choice\n      backtrack(state, [choice.left, choice.right], res)\n      # Backtrack: undo the choice and restore the previous state\n      undo_choice(state, choice)\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    The problem requires us to keep searching after finding a node with value \\(7\\), so remove the return after recording a solution. Figure 13-4 compares the searches with and without that return.

    Figure 13-4   Comparing searches with and without return

    The template-based implementation is more verbose than the preorder version, but more general. Many backtracking problems can be solved within this template: define state and choices for the problem, then implement each helper method.

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1314-common-terminology","level":2,"title":"13.1.4   Common terminology","text":"

    Table 13-1 summarizes common backtracking terms and illustrates them with Example 3, helping us analyze problems more clearly.

    Table 13-1   Common backtracking terminology

    Term Definition Example 3 Solution An answer satisfying the problem's conditions; there may be one or more All paths from the root to a node valued \\(7\\) that satisfy the constraint Constraint A condition restricting which solutions are feasible, often used for pruning The path contains no node valued \\(3\\) State The situation at a particular point, including the choices already made The current path of visited nodes, stored in path Trial Exploring the solution space by making a choice, updating the state, and checking for a solution Recursively visit a left or right child, append it to path, and check whether its value is \\(7\\) Backtracking Undoing an earlier choice and returning to the previous state when a constraint cannot be met Stop searching and return after passing a leaf, finishing a node, or encountering value \\(3\\) Pruning Using problem properties and constraints to avoid pointless search paths and improve efficiency Stop searching when a node valued \\(3\\) is encountered

    Tip

    Concepts such as problems, solutions, and states are shared by divide and conquer, backtracking, dynamic programming, greedy algorithms, and other approaches.

    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1315-strengths-and-limitations","level":2,"title":"13.1.5   Strengths and limitations","text":"

    Backtracking is fundamentally a depth-first search that tries possible solutions until it finds ones meeting the conditions. It can find all possible solutions and can be highly efficient with effective pruning.

    However, for large or complex problems, backtracking may be unacceptably slow.

    • Time: Backtracking often explores every possibility in the state space, leading to exponential or factorial time complexity.
    • Space: Recursive calls must preserve state, such as the path and auxiliary pruning variables. Deep recursion can therefore require substantial space.

    Even so, backtracking remains the best approach for some search and constraint satisfaction problems. When we cannot predict which choices lead to valid solutions, we must explore every possibility. The key is then to improve efficiency, commonly through two techniques.

    • Pruning: Avoid paths that cannot produce a solution, saving time and space.
    • Heuristic search: Use strategies or estimates to explore paths most likely to yield valid solutions first.
    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1316-typical-backtracking-problems","level":2,"title":"13.1.6   Typical backtracking problems","text":"

    Backtracking can solve many search, constraint satisfaction, and combinatorial optimization problems.

    Search problems seek solutions satisfying specified conditions.

    • Permutations: Given a collection, find every possible ordering of its elements.
    • Subset sum: Given a collection and a target sum, find every subset whose sum equals the target.
    • Tower of Hanoi: Given three rods and disks of different sizes, move all disks from one rod to another, moving only one at a time and never placing a larger disk on a smaller one.

    Constraint satisfaction problems seek solutions meeting all constraints.

    • \\(n\\)-queens: Place \\(n\\) queens on an \\(n \\times n\\) chessboard so that no two attack each other.
    • Sudoku: Fill a \\(9 \\times 9\\) grid with digits \\(1\\) ~ \\(9\\) so that no digit repeats in any row, column, or \\(3 \\times 3\\) subgrid.
    • Graph coloring: Color an undirected graph's vertices with as few colors as possible, ensuring adjacent vertices have different colors.

    Combinatorial optimization problems seek an optimal solution satisfying conditions within a space of combinations.

    • 0-1 knapsack: Given items with values and weights and a knapsack, choose items to maximize total value within the capacity limit.
    • Traveling salesperson: Starting at a vertex in a graph, visit every other vertex exactly once and return to the start along the shortest possible route.
    • Maximum clique: Given an undirected graph, find its largest complete subgraph, in which every pair of vertices is connected by an edge.

    For many combinatorial optimization problems, backtracking is not the best approach.

    • 0-1 knapsack is usually solved more efficiently with dynamic programming.
    • The traveling salesperson problem is a well-known NP-hard problem. Common approaches include genetic algorithms and ant colony algorithms.
    • Maximum clique is a classic graph theory problem that can be approached with heuristics such as greedy algorithms.
    ","path":["Chapter 13.   Backtracking","13.1   Backtracking algorithms"],"tags":[]},{"location":"chapter_backtracking/exercises/","level":1,"title":"13.6   Exercises","text":"","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#1361-check-your-understanding","level":2,"title":"13.6.1   Check your understanding","text":"","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#1-will-this-permutation-algorithm-miss-results","level":3,"title":"1.   Will this permutation algorithm miss results?","text":"

    A backtracking algorithm tries the numbers 1, 2, 3 in order to generate all permutations. Whenever it chooses a number x, it:

    1. Appends x to the current path.
    2. Marks x as used.
    3. Recursively fills the next position.

    After the recursive call returns, the student removes only x from the end of the path, then continues trying the next number.

    1. Which permutation does the algorithm produce first? Can it still produce all six permutations?
    2. Before returning to the previous recursion level, is removing the last number from the path enough? If not, what else is needed? Explain.
    Reference answer
    1. It first produces [1, 2, 3], but cannot generate every permutation. Although the path becomes shorter as calls return, the numbers 1, 2, and 3 remain marked as used, leaving later branches with no available numbers.

    2. No. After removing x from the end of the path, we must also mark x as unused again. The current path and the used markers together describe the search state. Choosing a number changes both, so backtracking must restore both to allow other branches to choose x again.

    ","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#2-does-the-order-of-choosing-numbers-matter","level":3,"title":"2.   Does the order of choosing numbers matter?","text":"

    Given the sorted array [2, 3, 5] and target value 5, each number may be chosen repeatedly. The algorithm requires the numbers along each search path to appear in nondecreasing order.

    1. Which distinct combinations can be obtained?
    2. Why is it unnecessary to search the same set of numbers in different orders? What does the nondecreasing-order restriction achieve?
    3. The current path is [3], the remaining sum is 2, and the next candidate is 3. Why can we stop checking all further candidates at this level?
    Reference answer
    1. The distinct combinations are [2, 3] and [5].

    2. This problem treats [2, 3] and [3, 2] as the same combination: selection order does not distinguish answers. Requiring the path's numbers to appear in nondecreasing order lets the search skip duplicate combinations such as [3, 2] directly.

    3. The remaining sum is 2, and candidate 3 already exceeds it. Since the array is sorted, all candidates after 3 are larger still and cannot join the current combination. We can therefore stop checking this level.

    ","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#3-where-can-the-next-queen-be-placed","level":3,"title":"3.   Where can the next queen be placed?","text":"

    Place queens row by row on a 4 × 4 board, with both row and column indices starting at 0. Queens are already at (0, 1) and (1, 3). The next queen must be placed in row 2.

    1. Which columns are ruled out because they already contain a queen?
    2. Among the remaining columns, which positions are ruled out by sharing a diagonal with a queen?
    3. Which positions in row 2 remain available to try?
    Reference answer
    1. Columns 1 and 3 already contain queens, ruling out positions (2, 1) and (2, 3).

    2. Of the remaining positions, (2, 2) shares a diagonal with (1, 3), so it is also ruled out. Position (2, 0) shares neither a column nor a diagonal with either existing queen.

    3. The only position to try in row 2 is (2, 0).

      This step establishes only that the current placement is valid. If the board cannot be completed later, we must still backtrack and try other earlier choices.

    ","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#1362-programming-exercises","level":2,"title":"13.6.2   Programming exercises","text":"","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/exercises/#1-all-permutations-of-distinct-elements","level":3,"title":"1.   All permutations of distinct elements","text":"

    The integer array nums contains at least one element, and all its elements are distinct. List every possible ordering that uses each element exactly once, returning each ordering as an array. The permutations may appear in any order in the result. Use backtracking and a Boolean array to record whether the element at each position is already included in the current permutation.

    Hints
    1. The recursion depth indicates which position in the permutation is being filled.
    2. At each level, try only unused elements.
    3. When the path length reaches the length of nums, add a copy of the path to the answer.

    LeetCode

    ","path":["Chapter 13.   Backtracking","13.6   Exercises"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/","level":1,"title":"13.4   N-queens problem","text":"

    Question

    In chess, a queen can attack any piece in the same row, column, or diagonal. Given \\(n\\) queens and an \\(n \\times n\\) chessboard, find all arrangements in which no two queens can attack each other.

    As shown in Figure 13-15, there are two solutions when \\(n = 4\\). From a backtracking perspective, the \\(n \\times n\\) board has \\(n^2\\) squares, which provide all possible choices. As we place queens one by one, the board changes; its arrangement at any moment is the current state.

    Figure 13-15   Solutions to the 4-queens problem

    Figure 13-16 shows the three constraints: no two queens may share a row, column, or diagonal. Diagonals run in two directions: the main-diagonal direction \\ and the antidiagonal direction /.

    Figure 13-16   Constraints of the n-queens problem

    ","path":["Chapter 13.   Backtracking","13.4   N-queens problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#1-placing-queens-row-by-row","level":3,"title":"1.   Placing queens row by row","text":"

    There are as many queens as rows, namely \\(n\\), so we can immediately conclude that each row must contain exactly one queen.

    We can therefore place queens row by row: start at the first row and place one queen in each row through the last.

    Figure 13-17 illustrates this process for 4 queens. To fit the illustration, only one branch from the first row is expanded, and all arrangements violating the column or diagonal constraints are pruned.

    Figure 13-17   Placing queens row by row

    In essence, placing queens row by row is itself a form of pruning: it eliminates every search branch that places multiple queens in the same row.

    ","path":["Chapter 13.   Backtracking","13.4   N-queens problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#2-pruning-by-columns-and-diagonals","level":3,"title":"2.   Pruning by columns and diagonals","text":"

    To enforce the column constraint, use a Boolean array cols of length \\(n\\) to record whether each column contains a queen. Before placing a queen, consult cols to prune columns already occupied, and update cols as the search proceeds and backtracks.

    Tip

    The matrix starts at its top-left corner. Row indices increase downward, and column indices increase to the right.

    How do we enforce the diagonal constraints? Let \\((row, col)\\) be the row and column indices of a square. Along any diagonal in the main-diagonal direction, the row index minus the column index is the same: \\(row - col\\) is constant along that diagonal.

    Thus, if two squares satisfy \\(row_1 - col_1 = row_2 - col_2\\), they lie on the same main-direction diagonal. Using this property, the array diags1 in Figure 13-18 records whether each such diagonal contains a queen.

    Similarly, \\(row + col\\) is constant along each antidiagonal. We can use an array diags2 to enforce the antidiagonal constraint.

    Figure 13-18   Enforcing column and diagonal constraints

    ","path":["Chapter 13.   Backtracking","13.4   N-queens problem"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#3-code-implementation","level":3,"title":"3.   Code implementation","text":"

    In a square matrix of order \\(n\\), \\(row - col\\) ranges over \\([-n + 1, n - 1]\\), and \\(row + col\\) ranges over \\([0, 2n - 2]\\). There are therefore \\(2n - 1\\) diagonals in each direction, so both diags1 and diags2 have length \\(2n - 1\\).

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby n_queens.py
    def backtrack(\n    row: int,\n    n: int,\n    state: list[list[str]],\n    res: list[list[list[str]]],\n    cols: list[bool],\n    diags1: list[bool],\n    diags2: list[bool],\n):\n    \"\"\"Backtracking: n-queens\"\"\"\n    # Record a solution when a queen has been placed in every row\n    if row == n:\n        res.append([list(row) for row in state])\n        return\n    # Iterate over all columns\n    for col in range(n):\n        # Calculate the main-diagonal and anti-diagonal indices for this square\n        diag1 = row - col + n - 1\n        diag2 = row + col\n        # Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if not cols[col] and not diags1[diag1] and not diags2[diag2]:\n            # Try: place a queen on this square\n            state[row][col] = \"Q\"\n            cols[col] = diags1[diag1] = diags2[diag2] = True\n            # Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2)\n            # Backtrack: restore this square to empty\n            state[row][col] = \"#\"\n            cols[col] = diags1[diag1] = diags2[diag2] = False\n\ndef n_queens(n: int) -> list[list[list[str]]]:\n    \"\"\"Solve the n-queens problem\"\"\"\n    # Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    state = [[\"#\" for _ in range(n)] for _ in range(n)]\n    cols = [False] * n  # Track whether a column contains a queen\n    diags1 = [False] * (2 * n - 1)  # Track whether a main diagonal contains a queen\n    diags2 = [False] * (2 * n - 1)  # Track whether an anti-diagonal contains a queen\n    res = []\n    backtrack(0, n, state, res, cols, diags1, diags2)\n\n    return res\n
    n_queens.cpp
    /* Backtracking: n-queens */\nvoid backtrack(int row, int n, vector<vector<string>> &state, vector<vector<vector<string>>> &res, vector<bool> &cols,\n               vector<bool> &diags1, vector<bool> &diags2) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\";\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\";\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nvector<vector<vector<string>>> nQueens(int n) {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    vector<vector<string>> state(n, vector<string>(n, \"#\"));\n    vector<bool> cols(n, false);           // Track whether a column contains a queen\n    vector<bool> diags1(2 * n - 1, false); // Track whether a main diagonal contains a queen\n    vector<bool> diags2(2 * n - 1, false); // Track whether an anti-diagonal contains a queen\n    vector<vector<vector<string>>> res;\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
    n_queens.java
    /* Backtracking: n-queens */\nvoid backtrack(int row, int n, List<List<String>> state, List<List<List<String>>> res,\n        boolean[] cols, boolean[] diags1, boolean[] diags2) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        List<List<String>> copyState = new ArrayList<>();\n        for (List<String> sRow : state) {\n            copyState.add(new ArrayList<>(sRow));\n        }\n        res.add(copyState);\n        return;\n    }\n    // Iterate over all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state.get(row).set(col, \"Q\");\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state.get(row).set(col, \"#\");\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nList<List<List<String>>> nQueens(int n) {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    List<List<String>> state = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        List<String> row = new ArrayList<>();\n        for (int j = 0; j < n; j++) {\n            row.add(\"#\");\n        }\n        state.add(row);\n    }\n    boolean[] cols = new boolean[n]; // Track whether a column contains a queen\n    boolean[] diags1 = new boolean[2 * n - 1]; // Track whether a main diagonal contains a queen\n    boolean[] diags2 = new boolean[2 * n - 1]; // Track whether an anti-diagonal contains a queen\n    List<List<List<String>>> res = new ArrayList<>();\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
    n_queens.cs
    /* Backtracking: n-queens */\nvoid Backtrack(int row, int n, List<List<string>> state, List<List<List<string>>> res,\n        bool[] cols, bool[] diags1, bool[] diags2) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        List<List<string>> copyState = [];\n        foreach (List<string> sRow in state) {\n            copyState.Add(new List<string>(sRow));\n        }\n        res.Add(copyState);\n        return;\n    }\n    // Iterate over all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\";\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            Backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\";\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nList<List<List<string>>> NQueens(int n) {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    List<List<string>> state = [];\n    for (int i = 0; i < n; i++) {\n        List<string> row = [];\n        for (int j = 0; j < n; j++) {\n            row.Add(\"#\");\n        }\n        state.Add(row);\n    }\n    bool[] cols = new bool[n]; // Track whether a column contains a queen\n    bool[] diags1 = new bool[2 * n - 1]; // Track whether a main diagonal contains a queen\n    bool[] diags2 = new bool[2 * n - 1]; // Track whether an anti-diagonal contains a queen\n    List<List<List<string>>> res = [];\n\n    Backtrack(0, n, state, res, cols, diags1, diags2);\n\n    return res;\n}\n
    n_queens.go
    /* Backtracking: n-queens */\nfunc backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) {\n    // Record a solution when a queen has been placed in every row\n    if row == n {\n        newState := make([][]string, len(*state))\n        for i, _ := range newState {\n            newState[i] = make([]string, len((*state)[0]))\n            copy(newState[i], (*state)[i])\n\n        }\n        *res = append(*res, newState)\n        return\n    }\n    // Iterate over all columns\n    for col := 0; col < n; col++ {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        diag1 := row - col + n - 1\n        diag2 := row + col\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] {\n            // Try: place a queen on this square\n            (*state)[row][col] = \"Q\"\n            (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true\n            // Place a queen in the next row\n            backtrack(row+1, n, state, res, cols, diags1, diags2)\n            // Backtrack: restore this square to empty\n            (*state)[row][col] = \"#\"\n            (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfunc nQueens(n int) [][][]string {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    state := make([][]string, n)\n    for i := 0; i < n; i++ {\n        row := make([]string, n)\n        for i := 0; i < n; i++ {\n            row[i] = \"#\"\n        }\n        state[i] = row\n    }\n    // Track whether a column contains a queen\n    cols := make([]bool, n)\n    diags1 := make([]bool, 2*n-1)\n    diags2 := make([]bool, 2*n-1)\n    res := make([][][]string, 0)\n    backtrack(0, n, &state, &res, &cols, &diags1, &diags2)\n    return res\n}\n
    n_queens.swift
    /* Backtracking: n-queens */\nfunc backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) {\n    // Record a solution when a queen has been placed in every row\n    if row == n {\n        res.append(state)\n        return\n    }\n    // Iterate over all columns\n    for col in 0 ..< n {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        let diag1 = row - col + n - 1\n        let diag2 = row + col\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if !cols[col] && !diags1[diag1] && !diags2[diag2] {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\"\n            cols[col] = true\n            diags1[diag1] = true\n            diags2[diag2] = true\n            // Place a queen in the next row\n            backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\"\n            cols[col] = false\n            diags1[diag1] = false\n            diags2[diag2] = false\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfunc nQueens(n: Int) -> [[[String]]] {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    var state = Array(repeating: Array(repeating: \"#\", count: n), count: n)\n    var cols = Array(repeating: false, count: n) // Track whether a column contains a queen\n    var diags1 = Array(repeating: false, count: 2 * n - 1) // Track whether a main diagonal contains a queen\n    var diags2 = Array(repeating: false, count: 2 * n - 1) // Track whether an anti-diagonal contains a queen\n    var res: [[[String]]] = []\n\n    backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2)\n\n    return res\n}\n
    n_queens.js
    /* Backtracking: n-queens */\nfunction backtrack(row, n, state, res, cols, diags1, diags2) {\n    // Record a solution when a queen has been placed in every row\n    if (row === n) {\n        res.push(state.map((row) => row.slice()));\n        return;\n    }\n    // Iterate over all columns\n    for (let col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        const diag1 = row - col + n - 1;\n        const diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfunction nQueens(n) {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    const state = Array.from({ length: n }, () => Array(n).fill('#'));\n    const cols = Array(n).fill(false); // Track whether a column contains a queen\n    const diags1 = Array(2 * n - 1).fill(false); // Track whether a main diagonal contains a queen\n    const diags2 = Array(2 * n - 1).fill(false); // Track whether an anti-diagonal contains a queen\n    const res = [];\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n    return res;\n}\n
    n_queens.ts
    /* Backtracking: n-queens */\nfunction backtrack(\n    row: number,\n    n: number,\n    state: string[][],\n    res: string[][][],\n    cols: boolean[],\n    diags1: boolean[],\n    diags2: boolean[]\n): void {\n    // Record a solution when a queen has been placed in every row\n    if (row === n) {\n        res.push(state.map((row) => row.slice()));\n        return;\n    }\n    // Iterate over all columns\n    for (let col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        const diag1 = row - col + n - 1;\n        const diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfunction nQueens(n: number): string[][][] {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    const state = Array.from({ length: n }, () => Array(n).fill('#'));\n    const cols = Array(n).fill(false); // Track whether a column contains a queen\n    const diags1 = Array(2 * n - 1).fill(false); // Track whether a main diagonal contains a queen\n    const diags2 = Array(2 * n - 1).fill(false); // Track whether an anti-diagonal contains a queen\n    const res: string[][][] = [];\n\n    backtrack(0, n, state, res, cols, diags1, diags2);\n    return res;\n}\n
    n_queens.dart
    /* Backtracking: n-queens */\nvoid backtrack(\n  int row,\n  int n,\n  List<List<String>> state,\n  List<List<List<String>>> res,\n  List<bool> cols,\n  List<bool> diags1,\n  List<bool> diags2,\n) {\n  // Record a solution when a queen has been placed in every row\n  if (row == n) {\n    List<List<String>> copyState = [];\n    for (List<String> sRow in state) {\n      copyState.add(List.from(sRow));\n    }\n    res.add(copyState);\n    return;\n  }\n  // Iterate over all columns\n  for (int col = 0; col < n; col++) {\n    // Calculate the main-diagonal and anti-diagonal indices for this square\n    int diag1 = row - col + n - 1;\n    int diag2 = row + col;\n    // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n    if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n      // Try: place a queen on this square\n      state[row][col] = \"Q\";\n      cols[col] = true;\n      diags1[diag1] = true;\n      diags2[diag2] = true;\n      // Place a queen in the next row\n      backtrack(row + 1, n, state, res, cols, diags1, diags2);\n      // Backtrack: restore this square to empty\n      state[row][col] = \"#\";\n      cols[col] = false;\n      diags1[diag1] = false;\n      diags2[diag2] = false;\n    }\n  }\n}\n\n/* Solve the n-queens problem */\nList<List<List<String>>> nQueens(int n) {\n  // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n  List<List<String>> state = List.generate(n, (index) => List.filled(n, \"#\"));\n  List<bool> cols = List.filled(n, false); // Track whether a column contains a queen\n  List<bool> diags1 = List.filled(2 * n - 1, false); // Track whether a main diagonal contains a queen\n  List<bool> diags2 = List.filled(2 * n - 1, false); // Track whether an anti-diagonal contains a queen\n  List<List<List<String>>> res = [];\n\n  backtrack(0, n, state, res, cols, diags1, diags2);\n\n  return res;\n}\n
    n_queens.rs
    /* Backtracking: n-queens */\nfn backtrack(\n    row: usize,\n    n: usize,\n    state: &mut Vec<Vec<String>>,\n    res: &mut Vec<Vec<Vec<String>>>,\n    cols: &mut [bool],\n    diags1: &mut [bool],\n    diags2: &mut [bool],\n) {\n    // Record a solution when a queen has been placed in every row\n    if row == n {\n        res.push(state.clone());\n        return;\n    }\n    // Iterate over all columns\n    for col in 0..n {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        let diag1 = row + n - 1 - col;\n        let diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if !cols[col] && !diags1[diag1] && !diags2[diag2] {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\".into();\n            (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true);\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\".into();\n            (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false);\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfn n_queens(n: usize) -> Vec<Vec<Vec<String>>> {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    let mut state: Vec<Vec<String>> = vec![vec![\"#\".to_string(); n]; n];\n    let mut cols = vec![false; n]; // Track whether a column contains a queen\n    let mut diags1 = vec![false; 2 * n - 1]; // Track whether a main diagonal contains a queen\n    let mut diags2 = vec![false; 2 * n - 1]; // Track whether an anti-diagonal contains a queen\n    let mut res: Vec<Vec<Vec<String>>> = Vec::new();\n\n    backtrack(\n        0,\n        n,\n        &mut state,\n        &mut res,\n        &mut cols,\n        &mut diags1,\n        &mut diags2,\n    );\n\n    res\n}\n
    n_queens.c
    /* Backtracking: n-queens */\nvoid backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE],\n               bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        res[*resSize] = (char **)malloc(sizeof(char *) * n);\n        for (int i = 0; i < n; ++i) {\n            res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1));\n            strcpy(res[*resSize][i], state[i]);\n        }\n        (*resSize)++;\n        return;\n    }\n    // Iterate over all columns\n    for (int col = 0; col < n; col++) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        int diag1 = row - col + n - 1;\n        int diag2 = row + col;\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = 'Q';\n            cols[col] = diags1[diag1] = diags2[diag2] = true;\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2);\n            // Backtrack: restore this square to empty\n            state[row][col] = '#';\n            cols[col] = diags1[diag1] = diags2[diag2] = false;\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nchar ***nQueens(int n, int *returnSize) {\n    char state[MAX_SIZE][MAX_SIZE];\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    for (int i = 0; i < n; ++i) {\n        for (int j = 0; j < n; ++j) {\n            state[i][j] = '#';\n        }\n        state[i][n] = '\\0';\n    }\n    bool cols[MAX_SIZE] = {false};           // Track whether a column contains a queen\n    bool diags1[2 * MAX_SIZE - 1] = {false}; // Track whether a main diagonal contains a queen\n    bool diags2[2 * MAX_SIZE - 1] = {false}; // Track whether an anti-diagonal contains a queen\n\n    char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE);\n    *returnSize = 0;\n    backtrack(0, n, state, res, returnSize, cols, diags1, diags2);\n    return res;\n}\n
    n_queens.kt
    /* Backtracking: n-queens */\nfun backtrack(\n    row: Int,\n    n: Int,\n    state: MutableList<MutableList<String>>,\n    res: MutableList<MutableList<MutableList<String>>?>,\n    cols: BooleanArray,\n    diags1: BooleanArray,\n    diags2: BooleanArray\n) {\n    // Record a solution when a queen has been placed in every row\n    if (row == n) {\n        val copyState = mutableListOf<MutableList<String>>()\n        for (sRow in state) {\n            copyState.add(sRow.toMutableList())\n        }\n        res.add(copyState)\n        return\n    }\n    // Iterate over all columns\n    for (col in 0..<n) {\n        // Calculate the main-diagonal and anti-diagonal indices for this square\n        val diag1 = row - col + n - 1\n        val diag2 = row + col\n        // Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n        if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {\n            // Try: place a queen on this square\n            state[row][col] = \"Q\"\n            diags2[diag2] = true\n            diags1[diag1] = diags2[diag2]\n            cols[col] = diags1[diag1]\n            // Place a queen in the next row\n            backtrack(row + 1, n, state, res, cols, diags1, diags2)\n            // Backtrack: restore this square to empty\n            state[row][col] = \"#\"\n            diags2[diag2] = false\n            diags1[diag1] = diags2[diag2]\n            cols[col] = diags1[diag1]\n        }\n    }\n}\n\n/* Solve the n-queens problem */\nfun nQueens(n: Int): MutableList<MutableList<MutableList<String>>?> {\n    // Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n    val state = mutableListOf<MutableList<String>>()\n    for (i in 0..<n) {\n        val row = mutableListOf<String>()\n        for (j in 0..<n) {\n            row.add(\"#\")\n        }\n        state.add(row)\n    }\n    val cols = BooleanArray(n) // Track whether a column contains a queen\n    val diags1 = BooleanArray(2 * n - 1) // Track whether a main diagonal contains a queen\n    val diags2 = BooleanArray(2 * n - 1) // Track whether an anti-diagonal contains a queen\n    val res = mutableListOf<MutableList<MutableList<String>>?>()\n\n    backtrack(0, n, state, res, cols, diags1, diags2)\n\n    return res\n}\n
    n_queens.rb
    ### Backtracking: n-queens ###\ndef backtrack(row, n, state, res, cols, diags1, diags2)\n  # Record a solution when a queen has been placed in every row\n  if row == n\n    res << state.map { |row| row.dup }\n    return\n  end\n\n  # Iterate over all columns\n  for col in 0...n\n    # Calculate the main-diagonal and anti-diagonal indices for this square\n    diag1 = row - col + n - 1\n    diag2 = row + col\n    # Prune: the square must have no queen in its column, main diagonal, or anti-diagonal\n    if !cols[col] && !diags1[diag1] && !diags2[diag2]\n      # Try: place a queen on this square\n      state[row][col] = \"Q\"\n      cols[col] = diags1[diag1] = diags2[diag2] = true\n      # Place a queen in the next row\n      backtrack(row + 1, n, state, res, cols, diags1, diags2)\n      # Backtrack: restore this square to empty\n      state[row][col] = \"#\"\n      cols[col] = diags1[diag1] = diags2[diag2] = false\n    end\n  end\nend\n\n### Solve the n-queens problem ###\ndef n_queens(n)\n  # Initialize an n*n board, where 'Q' represents a queen and '#' represents an empty square\n  state = Array.new(n) { Array.new(n, \"#\") }\n  cols = Array.new(n, false) # Track whether a column contains a queen\n  diags1 = Array.new(2 * n - 1, false) # Track whether a main diagonal contains a queen\n  diags2 = Array.new(2 * n - 1, false) # Track whether an anti-diagonal contains a queen\n  res = []\n  backtrack(0, n, state, res, cols, diags1, diags2)\n\n  res\nend\n
    Visualize execution

    View full screen >

    We place a queen in each of \\(n\\) rows. Considering the column constraint, the successive rows have \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), and \\(1\\) choices, giving \\(O(n!)\\) time. Recording a solution requires copying the matrix state and appending the copy to res, which takes \\(O(n^2)\\) time. The overall time complexity is therefore \\(O(n! \\cdot n^2)\\). In practice, diagonal pruning greatly reduces the search space, so the search is often faster than this bound suggests.

    The array state uses \\(O(n^2)\\) space, while cols, diags1, and diags2 each use \\(O(n)\\) space. The maximum recursion depth is \\(n\\), requiring \\(O(n)\\) space for stack frames. The space complexity is therefore \\(O(n^2)\\).

    ","path":["Chapter 13.   Backtracking","13.4   N-queens problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/","level":1,"title":"13.2   Permutations problem","text":"

    Generating permutations is a classic application of backtracking. Given a collection, such as an array or a string, the task is to find every possible ordering of its elements.

    Table 13-2 gives several input arrays and all their permutations.

    Table 13-2   Examples of permutations

    Input array All permutations \\([1]\\) \\([1]\\) \\([1, 2]\\) \\([1, 2], [2, 1]\\) \\([1, 2, 3]\\) \\([1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]\\)","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1321-distinct-elements","level":2,"title":"13.2.1   Distinct elements","text":"

    Question

    Given an integer array with no duplicate elements, return all possible permutations.

    From a backtracking perspective, we can view a permutation as the result of a sequence of choices. For input \\([1, 2, 3]\\), choosing \\(1\\), then \\(3\\), then \\(2\\) produces \\([1, 3, 2]\\). Backtracking undoes a choice so that other choices can be tried.

    In the code, choices contains all the input elements, and state contains the elements chosen so far. Each element may be chosen only once, so every element in state must be unique.

    As shown in Figure 13-5, we can expand the search into a recursion tree, where each node represents the current state. Starting from the root, three rounds of choices lead to a leaf, and each leaf corresponds to one permutation.

    Figure 13-5   Recursion tree for permutations

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1-pruning-repeated-selections","level":3,"title":"1.   Pruning repeated selections","text":"

    To ensure that each element is chosen only once, introduce a Boolean array selected, where selected[i] indicates whether choices[i] has been chosen. Use it for the following pruning steps.

    • After choosing choices[i], set selected[i] to \\(\\text{True}\\) to mark it as selected.
    • When iterating over choices, skip all selected elements, pruning their branches.

    As shown in Figure 13-6, if we choose 1 first, 3 second, and 2 third, we must prune the branch for 1 in the second round and the branches for 1 and 3 in the third round.

    Figure 13-6   Example of pruning permutations

    This pruning reduces the search space from \\(O(n^n)\\) to \\(O(n!)\\).

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    With these details established, we can fill in the backtracking template. To keep the code short, we inline the template's helper functions into backtrack():

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_i.py
    def backtrack(\n    state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n    \"\"\"Backtracking: permutations I\"\"\"\n    # Record a solution when the state length equals the number of elements\n    if len(state) == len(choices):\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    for i, choice in enumerate(choices):\n        # Prune: do not choose an element more than once\n        if not selected[i]:\n            # Try: make a choice and update the state\n            selected[i] = True\n            state.append(choice)\n            # Proceed to the next choice\n            backtrack(state, choices, selected, res)\n            # Backtrack: undo the choice and restore the previous state\n            selected[i] = False\n            state.pop()\n\ndef permutations_i(nums: list[int]) -> list[list[int]]:\n    \"\"\"Permutations I\"\"\"\n    res = []\n    backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n    return res\n
    permutations_i.cpp
    /* Backtracking: permutations I */\nvoid backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size() == choices.size()) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.size(); i++) {\n        int choice = choices[i];\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.push_back(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop_back();\n        }\n    }\n}\n\n/* Permutations I */\nvector<vector<int>> permutationsI(vector<int> nums) {\n    vector<int> state;\n    vector<bool> selected(nums.size(), false);\n    vector<vector<int>> res;\n    backtrack(state, nums, selected, res);\n    return res;\n}\n
    permutations_i.java
    /* Backtracking: permutations I */\nvoid backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size() == choices.length) {\n        res.add(new ArrayList<Integer>(state));\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.length; i++) {\n        int choice = choices[i];\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.add(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.remove(state.size() - 1);\n        }\n    }\n}\n\n/* Permutations I */\nList<List<Integer>> permutationsI(int[] nums) {\n    List<List<Integer>> res = new ArrayList<List<Integer>>();\n    backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);\n    return res;\n}\n
    permutations_i.cs
    /* Backtracking: permutations I */\nvoid Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.Count == choices.Length) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.Length; i++) {\n        int choice = choices[i];\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.Add(choice);\n            // Proceed to the next choice\n            Backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.RemoveAt(state.Count - 1);\n        }\n    }\n}\n\n/* Permutations I */\nList<List<int>> PermutationsI(int[] nums) {\n    List<List<int>> res = [];\n    Backtrack([], nums, new bool[nums.Length], res);\n    return res;\n}\n
    permutations_i.go
    /* Backtracking: permutations I */\nfunc backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n    // Record a solution when the state length equals the number of elements\n    if len(*state) == len(*choices) {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n    }\n    // Iterate over all choices\n    for i := 0; i < len(*choices); i++ {\n        choice := (*choices)[i]\n        // Prune: do not choose an element more than once\n        if !(*selected)[i] {\n            // Try: make a choice and update the state\n            (*selected)[i] = true\n            *state = append(*state, choice)\n            // Proceed to the next choice\n            backtrackI(state, choices, selected, res)\n            // Backtrack: undo the choice and restore the previous state\n            (*selected)[i] = false\n            *state = (*state)[:len(*state)-1]\n        }\n    }\n}\n\n/* Permutations I */\nfunc permutationsI(nums []int) [][]int {\n    res := make([][]int, 0)\n    state := make([]int, 0)\n    selected := make([]bool, len(nums))\n    backtrackI(&state, &nums, &selected, &res)\n    return res\n}\n
    permutations_i.swift
    /* Backtracking: permutations I */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n    // Record a solution when the state length equals the number of elements\n    if state.count == choices.count {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    for (i, choice) in choices.enumerated() {\n        // Prune: do not choose an element more than once\n        if !selected[i] {\n            // Try: make a choice and update the state\n            selected[i] = true\n            state.append(choice)\n            // Proceed to the next choice\n            backtrack(state: &state, choices: choices, selected: &selected, res: &res)\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false\n            state.removeLast()\n        }\n    }\n}\n\n/* Permutations I */\nfunc permutationsI(nums: [Int]) -> [[Int]] {\n    var state: [Int] = []\n    var selected = Array(repeating: false, count: nums.count)\n    var res: [[Int]] = []\n    backtrack(state: &state, choices: nums, selected: &selected, res: &res)\n    return res\n}\n
    permutations_i.js
    /* Backtracking: permutations I */\nfunction backtrack(state, choices, selected, res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    choices.forEach((choice, i) => {\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations I */\nfunction permutationsI(nums) {\n    const res = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
    permutations_i.ts
    /* Backtracking: permutations I */\nfunction backtrack(\n    state: number[],\n    choices: number[],\n    selected: boolean[],\n    res: number[][]\n): void {\n    // Record a solution when the state length equals the number of elements\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    choices.forEach((choice, i) => {\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations I */\nfunction permutationsI(nums: number[]): number[][] {\n    const res: number[][] = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
    permutations_i.dart
    /* Backtracking: permutations I */\nvoid backtrack(\n  List<int> state,\n  List<int> choices,\n  List<bool> selected,\n  List<List<int>> res,\n) {\n  // Record a solution when the state length equals the number of elements\n  if (state.length == choices.length) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  for (int i = 0; i < choices.length; i++) {\n    int choice = choices[i];\n    // Prune: do not choose an element more than once\n    if (!selected[i]) {\n      // Try: make a choice and update the state\n      selected[i] = true;\n      state.add(choice);\n      // Proceed to the next choice\n      backtrack(state, choices, selected, res);\n      // Backtrack: undo the choice and restore the previous state\n      selected[i] = false;\n      state.removeLast();\n    }\n  }\n}\n\n/* Permutations I */\nList<List<int>> permutationsI(List<int> nums) {\n  List<List<int>> res = [];\n  backtrack([], nums, List.filled(nums.length, false), res);\n  return res;\n}\n
    permutations_i.rs
    /* Backtracking: permutations I */\nfn backtrack(mut state: Vec<i32>, choices: &[i32], selected: &mut [bool], res: &mut Vec<Vec<i32>>) {\n    // Record a solution when the state length equals the number of elements\n    if state.len() == choices.len() {\n        res.push(state);\n        return;\n    }\n    // Iterate over all choices\n    for i in 0..choices.len() {\n        let choice = choices[i];\n        // Prune: do not choose an element more than once\n        if !selected[i] {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state.clone(), choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    }\n}\n\n/* Permutations I */\nfn permutations_i(nums: &mut [i32]) -> Vec<Vec<i32>> {\n    let mut res = Vec::new(); // Result list (permutations)\n    backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res);\n    res\n}\n
    permutations_i.c
    /* Backtracking: permutations I */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n    // Record a solution when the state length equals the number of elements\n    if (stateSize == choicesSize) {\n        res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n        for (int i = 0; i < choicesSize; i++) {\n            res[*resSize][i] = state[i];\n        }\n        (*resSize)++;\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choicesSize; i++) {\n        int choice = choices[i];\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true;\n            state[stateSize] = choice;\n            // Proceed to the next choice\n            backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n        }\n    }\n}\n\n/* Permutations I */\nint **permutationsI(int *nums, int numsSize, int *returnSize) {\n    int *state = (int *)malloc(numsSize * sizeof(int));\n    bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n    for (int i = 0; i < numsSize; i++) {\n        selected[i] = false;\n    }\n    int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n    *returnSize = 0;\n\n    backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n    free(state);\n    free(selected);\n\n    return res;\n}\n
    permutations_i.kt
    /* Backtracking: permutations I */\nfun backtrack(\n    state: MutableList<Int>,\n    choices: IntArray,\n    selected: BooleanArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size == choices.size) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    for (i in choices.indices) {\n        val choice = choices[i]\n        // Prune: do not choose an element more than once\n        if (!selected[i]) {\n            // Try: make a choice and update the state\n            selected[i] = true\n            state.add(choice)\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res)\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false\n            state.removeAt(state.size - 1)\n        }\n    }\n}\n\n/* Permutations I */\nfun permutationsI(nums: IntArray): MutableList<MutableList<Int>?> {\n    val res = mutableListOf<MutableList<Int>?>()\n    backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n    return res\n}\n
    permutations_i.rb
    ### Backtracking: permutations I ###\ndef backtrack(state, choices, selected, res)\n  # Record a solution when the state length equals the number of elements\n  if state.length == choices.length\n    res << state.dup\n    return\n  end\n\n  # Iterate over all choices\n  choices.each_with_index do |choice, i|\n    # Prune: do not choose an element more than once\n    unless selected[i]\n      # Try: make a choice and update the state\n      selected[i] = true\n      state << choice\n      # Proceed to the next choice\n      backtrack(state, choices, selected, res)\n      # Backtrack: undo the choice and restore the previous state\n      selected[i] = false\n      state.pop\n    end\n  end\nend\n\n### Permutations I ###\ndef permutations_i(nums)\n  res = []\n  backtrack([], nums, Array.new(nums.length, false), res)\n  res\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1322-duplicate-elements","level":2,"title":"13.2.2   Duplicate elements","text":"

    Question

    Given an integer array that may contain duplicate elements, return all distinct permutations.

    Suppose the input is \\([1, 1, 2]\\). To distinguish the two occurrences of \\(1\\), denote the second \\(1\\) by \\(\\hat{1}\\).

    As shown in Figure 13-7, half the permutations generated by the previous method are duplicates.

    Figure 13-7   Duplicate permutations

    How can we remove these duplicates? A straightforward approach is to use a hash set to deduplicate the results. However, this is inelegant: branches that generate duplicate permutations are unnecessary and should be identified and pruned in advance, improving efficiency.

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1-pruning-equal-elements","level":3,"title":"1.   Pruning equal elements","text":"

    In Figure 13-8, choosing \\(1\\) or \\(\\hat{1}\\) in the first round is equivalent: every permutation produced under one choice is duplicated under the other. We should therefore prune \\(\\hat{1}\\).

    Similarly, after choosing \\(2\\) in the first round, choosing either \\(1\\) or \\(\\hat{1}\\) in the second produces duplicate branches. Prune the second-round choice of \\(\\hat{1}\\) as well.

    In essence, within any one round of choices, we want to try only one of the elements with the same value.

    Figure 13-8   Pruning duplicate permutations

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2-code-implementation_1","level":3,"title":"2.   Code implementation","text":"

    Building on the previous solution, create a hash set duplicated in each round to record the values already tried in that round and prune repeated values:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_ii.py
    def backtrack(\n    state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n    \"\"\"Backtracking: permutations II\"\"\"\n    # Record a solution when the state length equals the number of elements\n    if len(state) == len(choices):\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    duplicated = set[int]()\n    for i, choice in enumerate(choices):\n        # Prune: do not reuse an element or choose the same value again at this depth\n        if not selected[i] and choice not in duplicated:\n            # Try: make a choice and update the state\n            duplicated.add(choice)  # Record the chosen value\n            selected[i] = True\n            state.append(choice)\n            # Proceed to the next choice\n            backtrack(state, choices, selected, res)\n            # Backtrack: undo the choice and restore the previous state\n            selected[i] = False\n            state.pop()\n\ndef permutations_ii(nums: list[int]) -> list[list[int]]:\n    \"\"\"Permutations II\"\"\"\n    res = []\n    backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n    return res\n
    permutations_ii.cpp
    /* Backtracking: permutations II */\nvoid backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size() == choices.size()) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    unordered_set<int> duplicated;\n    for (int i = 0; i < choices.size(); i++) {\n        int choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && duplicated.find(choice) == duplicated.end()) {\n            // Try: make a choice and update the state\n            duplicated.emplace(choice); // Record the chosen value\n            selected[i] = true;\n            state.push_back(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop_back();\n        }\n    }\n}\n\n/* Permutations II */\nvector<vector<int>> permutationsII(vector<int> nums) {\n    vector<int> state;\n    vector<bool> selected(nums.size(), false);\n    vector<vector<int>> res;\n    backtrack(state, nums, selected, res);\n    return res;\n}\n
    permutations_ii.java
    /* Backtracking: permutations II */\nvoid backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size() == choices.length) {\n        res.add(new ArrayList<Integer>(state));\n        return;\n    }\n    // Iterate over all choices\n    Set<Integer> duplicated = new HashSet<Integer>();\n    for (int i = 0; i < choices.length; i++) {\n        int choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.contains(choice)) {\n            // Try: make a choice and update the state\n            duplicated.add(choice); // Record the chosen value\n            selected[i] = true;\n            state.add(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.remove(state.size() - 1);\n        }\n    }\n}\n\n/* Permutations II */\nList<List<Integer>> permutationsII(int[] nums) {\n    List<List<Integer>> res = new ArrayList<List<Integer>>();\n    backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);\n    return res;\n}\n
    permutations_ii.cs
    /* Backtracking: permutations II */\nvoid Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.Count == choices.Length) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    HashSet<int> duplicated = [];\n    for (int i = 0; i < choices.Length; i++) {\n        int choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.Contains(choice)) {\n            // Try: make a choice and update the state\n            duplicated.Add(choice); // Record the chosen value\n            selected[i] = true;\n            state.Add(choice);\n            // Proceed to the next choice\n            Backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.RemoveAt(state.Count - 1);\n        }\n    }\n}\n\n/* Permutations II */\nList<List<int>> PermutationsII(int[] nums) {\n    List<List<int>> res = [];\n    Backtrack([], nums, new bool[nums.Length], res);\n    return res;\n}\n
    permutations_ii.go
    /* Backtracking: permutations II */\nfunc backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n    // Record a solution when the state length equals the number of elements\n    if len(*state) == len(*choices) {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n    }\n    // Iterate over all choices\n    duplicated := make(map[int]struct{}, 0)\n    for i := 0; i < len(*choices); i++ {\n        choice := (*choices)[i]\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if _, ok := duplicated[choice]; !ok && !(*selected)[i] {\n            // Try: make a choice and update the state\n            // Record the chosen value\n            duplicated[choice] = struct{}{}\n            (*selected)[i] = true\n            *state = append(*state, choice)\n            // Proceed to the next choice\n            backtrackII(state, choices, selected, res)\n            // Backtrack: undo the choice and restore the previous state\n            (*selected)[i] = false\n            *state = (*state)[:len(*state)-1]\n        }\n    }\n}\n\n/* Permutations II */\nfunc permutationsII(nums []int) [][]int {\n    res := make([][]int, 0)\n    state := make([]int, 0)\n    selected := make([]bool, len(nums))\n    backtrackII(&state, &nums, &selected, &res)\n    return res\n}\n
    permutations_ii.swift
    /* Backtracking: permutations II */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n    // Record a solution when the state length equals the number of elements\n    if state.count == choices.count {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    var duplicated: Set<Int> = []\n    for (i, choice) in choices.enumerated() {\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if !selected[i], !duplicated.contains(choice) {\n            // Try: make a choice and update the state\n            duplicated.insert(choice) // Record the chosen value\n            selected[i] = true\n            state.append(choice)\n            // Proceed to the next choice\n            backtrack(state: &state, choices: choices, selected: &selected, res: &res)\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false\n            state.removeLast()\n        }\n    }\n}\n\n/* Permutations II */\nfunc permutationsII(nums: [Int]) -> [[Int]] {\n    var state: [Int] = []\n    var selected = Array(repeating: false, count: nums.count)\n    var res: [[Int]] = []\n    backtrack(state: &state, choices: nums, selected: &selected, res: &res)\n    return res\n}\n
    permutations_ii.js
    /* Backtracking: permutations II */\nfunction backtrack(state, choices, selected, res) {\n    // Record a solution when the state length equals the number of elements\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    const duplicated = new Set();\n    choices.forEach((choice, i) => {\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.has(choice)) {\n            // Try: make a choice and update the state\n            duplicated.add(choice); // Record the chosen value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations II */\nfunction permutationsII(nums) {\n    const res = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
    permutations_ii.ts
    /* Backtracking: permutations II */\nfunction backtrack(\n    state: number[],\n    choices: number[],\n    selected: boolean[],\n    res: number[][]\n): void {\n    // Record a solution when the state length equals the number of elements\n    if (state.length === choices.length) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    const duplicated = new Set();\n    choices.forEach((choice, i) => {\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.has(choice)) {\n            // Try: make a choice and update the state\n            duplicated.add(choice); // Record the chosen value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    });\n}\n\n/* Permutations II */\nfunction permutationsII(nums: number[]): number[][] {\n    const res: number[][] = [];\n    backtrack([], nums, Array(nums.length).fill(false), res);\n    return res;\n}\n
    permutations_ii.dart
    /* Backtracking: permutations II */\nvoid backtrack(\n  List<int> state,\n  List<int> choices,\n  List<bool> selected,\n  List<List<int>> res,\n) {\n  // Record a solution when the state length equals the number of elements\n  if (state.length == choices.length) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  Set<int> duplicated = {};\n  for (int i = 0; i < choices.length; i++) {\n    int choice = choices[i];\n    // Prune: do not reuse an element or choose the same value again at this depth\n    if (!selected[i] && !duplicated.contains(choice)) {\n      // Try: make a choice and update the state\n      duplicated.add(choice); // Record the chosen value\n      selected[i] = true;\n      state.add(choice);\n      // Proceed to the next choice\n      backtrack(state, choices, selected, res);\n      // Backtrack: undo the choice and restore the previous state\n      selected[i] = false;\n      state.removeLast();\n    }\n  }\n}\n\n/* Permutations II */\nList<List<int>> permutationsII(List<int> nums) {\n  List<List<int>> res = [];\n  backtrack([], nums, List.filled(nums.length, false), res);\n  return res;\n}\n
    permutations_ii.rs
    /* Backtracking: permutations II */\nfn backtrack(mut state: Vec<i32>, choices: &[i32], selected: &mut [bool], res: &mut Vec<Vec<i32>>) {\n    // Record a solution when the state length equals the number of elements\n    if state.len() == choices.len() {\n        res.push(state);\n        return;\n    }\n    // Iterate over all choices\n    let mut duplicated = HashSet::<i32>::new();\n    for i in 0..choices.len() {\n        let choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if !selected[i] && !duplicated.contains(&choice) {\n            // Try: make a choice and update the state\n            duplicated.insert(choice); // Record the chosen value\n            selected[i] = true;\n            state.push(choice);\n            // Proceed to the next choice\n            backtrack(state.clone(), choices, selected, res);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n            state.pop();\n        }\n    }\n}\n\n/* Permutations II */\nfn permutations_ii(nums: &mut [i32]) -> Vec<Vec<i32>> {\n    let mut res = Vec::new();\n    backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res);\n    res\n}\n
    permutations_ii.c
    /* Backtracking: permutations II */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n    // Record a solution when the state length equals the number of elements\n    if (stateSize == choicesSize) {\n        res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n        for (int i = 0; i < choicesSize; i++) {\n            res[*resSize][i] = state[i];\n        }\n        (*resSize)++;\n        return;\n    }\n    // Iterate over all choices\n    bool duplicated[MAX_SIZE] = {false};\n    for (int i = 0; i < choicesSize; i++) {\n        int choice = choices[i];\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated[choice]) {\n            // Try: make a choice and update the state\n            duplicated[choice] = true; // Record the chosen value\n            selected[i] = true;\n            state[stateSize] = choice;\n            // Proceed to the next choice\n            backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false;\n        }\n    }\n}\n\n/* Permutations II */\nint **permutationsII(int *nums, int numsSize, int *returnSize) {\n    int *state = (int *)malloc(numsSize * sizeof(int));\n    bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n    for (int i = 0; i < numsSize; i++) {\n        selected[i] = false;\n    }\n    int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n    *returnSize = 0;\n\n    backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n    free(state);\n    free(selected);\n\n    return res;\n}\n
    permutations_ii.kt
    /* Backtracking: permutations II */\nfun backtrack(\n    state: MutableList<Int>,\n    choices: IntArray,\n    selected: BooleanArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the state length equals the number of elements\n    if (state.size == choices.size) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    val duplicated = HashSet<Int>()\n    for (i in choices.indices) {\n        val choice = choices[i]\n        // Prune: do not reuse an element or choose the same value again at this depth\n        if (!selected[i] && !duplicated.contains(choice)) {\n            // Try: make a choice and update the state\n            duplicated.add(choice) // Record the chosen value\n            selected[i] = true\n            state.add(choice)\n            // Proceed to the next choice\n            backtrack(state, choices, selected, res)\n            // Backtrack: undo the choice and restore the previous state\n            selected[i] = false\n            state.removeAt(state.size - 1)\n        }\n    }\n}\n\n/* Permutations II */\nfun permutationsII(nums: IntArray): MutableList<MutableList<Int>?> {\n    val res = mutableListOf<MutableList<Int>?>()\n    backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n    return res\n}\n
    permutations_ii.rb
    ### Backtracking: permutations II ###\ndef backtrack(state, choices, selected, res)\n  # Record a solution when the state length equals the number of elements\n  if state.length == choices.length\n    res << state.dup\n    return\n  end\n\n  # Iterate over all choices\n  duplicated = Set.new\n  choices.each_with_index do |choice, i|\n    # Prune: do not reuse an element or choose the same value again at this depth\n    if !selected[i] && !duplicated.include?(choice)\n      # Try: make a choice and update the state\n      duplicated.add(choice)\n      selected[i] = true\n      state << choice\n      # Proceed to the next choice\n      backtrack(state, choices, selected, res)\n      # Backtrack: undo the choice and restore the previous state\n      selected[i] = false\n      state.pop\n    end\n  end\nend\n\n### Permutations II ###\ndef permutations_ii(nums)\n  res = []\n  backtrack([], nums, Array.new(nums.length, false), res)\n  res\nend\n
    Visualize execution

    View full screen >

    If all elements are distinct, \\(n\\) elements have \\(n!\\) permutations, where the exclamation mark denotes factorial. Recording each result requires copying a list of length \\(n\\), taking \\(O(n)\\) time. The time complexity is therefore \\(O(n!n)\\).

    The maximum recursion depth is \\(n\\), requiring \\(O(n)\\) space for stack frames. The array selected uses \\(O(n)\\) space. At most \\(n\\) duplicated sets exist at once, using \\(O(n^2)\\) space. The space complexity is therefore \\(O(n^2)\\).

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#3-comparing-the-two-pruning-methods","level":3,"title":"3.   Comparing the two pruning methods","text":"

    Although both selected and duplicated are used for pruning, they serve different purposes.

    • Pruning repeated selections: There is only one selected array throughout the search. It records which elements are in the current state, preventing the same element from appearing twice in state.
    • Pruning equal elements: Each round of choices (each call to backtrack) has its own duplicated set. It records the values already chosen in that round's for loop, ensuring that equal elements are tried only once in that round.

    Figure 13-9 shows the scope of each pruning condition. Each node represents a choice, and the nodes along a path from root to leaf form a permutation.

    Figure 13-9   Scope of the two pruning conditions

    ","path":["Chapter 13.   Backtracking","13.2   Permutations problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/","level":1,"title":"13.3   Subset sum problem","text":"","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1331-distinct-elements","level":2,"title":"13.3.1   Distinct elements","text":"

    Question

    Given an array of positive integers nums and a positive target integer target, find all combinations whose elements sum to target. The array has no duplicate elements, and each element may be chosen multiple times. Return the combinations as a list containing no duplicates.

    For example, with input \\(\\{3, 4, 5\\}\\) and target \\(9\\), the solutions are \\(\\{3, 3, 3\\}, \\{4, 5\\}\\). Keep two points in mind.

    • Each input element may be chosen an unlimited number of times.
    • Element order does not matter: \\(\\{4, 5\\}\\) and \\(\\{5, 4\\}\\) represent the same subset.
    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1-starting-from-the-permutations-solution","level":3,"title":"1.   Starting from the permutations solution","text":"

    As with permutations, we can view generating a subset as a sequence of choices. Update the sum as we make choices, and record the subset when the sum equals target.

    Unlike permutations, each element may be chosen an unlimited number of times, so we do not need a Boolean selected list. A small modification to the permutations code gives an initial solution:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i_naive.py
    def backtrack(\n    state: list[int],\n    target: int,\n    total: int,\n    choices: list[int],\n    res: list[list[int]],\n):\n    \"\"\"Backtracking: subset sum I\"\"\"\n    # Record a solution when the subset sum equals target\n    if total == target:\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    for i in range(len(choices)):\n        # Prune: skip this choice if the subset sum would exceed target\n        if total + choices[i] > target:\n            continue\n        # Try: make a choice and update the sum total\n        state.append(choices[i])\n        # Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res)\n        # Backtrack: undo the choice and restore the previous state\n        state.pop()\n\ndef subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum I (with duplicate subsets)\"\"\"\n    state = []  # State (subset)\n    total = 0  # Subset sum\n    res = []  # Result list (list of subsets)\n    backtrack(state, target, total, nums, res)\n    return res\n
    subset_sum_i_naive.cpp
    /* Backtracking: subset sum I */\nvoid backtrack(vector<int> &state, int target, int total, vector<int> &choices, vector<vector<int>> &res) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    for (size_t i = 0; i < choices.size(); i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.push_back(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nvector<vector<int>> subsetSumINaive(vector<int> &nums, int target) {\n    vector<int> state;       // State (subset)\n    int total = 0;           // Subset sum\n    vector<vector<int>> res; // Result list (list of subsets)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.java
    /* Backtracking: subset sum I */\nvoid backtrack(List<Integer> state, int target, int total, int[] choices, List<List<Integer>> res) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.length; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.add(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nList<List<Integer>> subsetSumINaive(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    int total = 0; // Subset sum\n    List<List<Integer>> res = new ArrayList<>(); // Result list (list of subsets)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.cs
    /* Backtracking: subset sum I */\nvoid Backtrack(List<int> state, int target, int total, int[] choices, List<List<int>> res) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choices.Length; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.Add(choices[i]);\n        // Proceed to the next choice\n        Backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nList<List<int>> SubsetSumINaive(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    int total = 0; // Subset sum\n    List<List<int>> res = []; // Result list (list of subsets)\n    Backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.go
    /* Backtracking: subset sum I */\nfunc backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) {\n    // Record a solution when the subset sum equals target\n    if target == total {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Iterate over all choices\n    for i := 0; i < len(*choices); i++ {\n        // Prune: skip this choice if the subset sum would exceed target\n        if total+(*choices)[i] > target {\n            continue\n        }\n        // Try: make a choice and update the sum total\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next choice\n        backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res)\n        // Backtrack: undo the choice and restore the previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfunc subsetSumINaive(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    total := 0              // Subset sum\n    res := make([][]int, 0) // Result list (list of subsets)\n    backtrackSubsetSumINaive(total, target, &state, &nums, &res)\n    return res\n}\n
    subset_sum_i_naive.swift
    /* Backtracking: subset sum I */\nfunc backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) {\n    // Record a solution when the subset sum equals target\n    if total == target {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    for i in choices.indices {\n        // Prune: skip this choice if the subset sum would exceed target\n        if total + choices[i] > target {\n            continue\n        }\n        // Try: make a choice and update the sum total\n        state.append(choices[i])\n        // Proceed to the next choice\n        backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfunc subsetSumINaive(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let total = 0 // Subset sum\n    var res: [[Int]] = [] // Result list (list of subsets)\n    backtrack(state: &state, target: target, total: total, choices: nums, res: &res)\n    return res\n}\n
    subset_sum_i_naive.js
    /* Backtracking: subset sum I */\nfunction backtrack(state, target, total, choices, res) {\n    // Record a solution when the subset sum equals target\n    if (total === target) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    for (let i = 0; i < choices.length; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfunction subsetSumINaive(nums, target) {\n    const state = []; // State (subset)\n    const total = 0; // Subset sum\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.ts
    /* Backtracking: subset sum I */\nfunction backtrack(\n    state: number[],\n    target: number,\n    total: number,\n    choices: number[],\n    res: number[][]\n): void {\n    // Record a solution when the subset sum equals target\n    if (total === target) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    for (let i = 0; i < choices.length; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfunction subsetSumINaive(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    const total = 0; // Subset sum\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, total, nums, res);\n    return res;\n}\n
    subset_sum_i_naive.dart
    /* Backtracking: subset sum I */\nvoid backtrack(\n  List<int> state,\n  int target,\n  int total,\n  List<int> choices,\n  List<List<int>> res,\n) {\n  // Record a solution when the subset sum equals target\n  if (total == target) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  for (int i = 0; i < choices.length; i++) {\n    // Prune: skip this choice if the subset sum would exceed target\n    if (total + choices[i] > target) {\n      continue;\n    }\n    // Try: make a choice and update the sum total\n    state.add(choices[i]);\n    // Proceed to the next choice\n    backtrack(state, target, total + choices[i], choices, res);\n    // Backtrack: undo the choice and restore the previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nList<List<int>> subsetSumINaive(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  int total = 0; // Sum of the elements\n  List<List<int>> res = []; // Result list (list of subsets)\n  backtrack(state, target, total, nums, res);\n  return res;\n}\n
    subset_sum_i_naive.rs
    /* Backtracking: subset sum I */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    total: i32,\n    choices: &[i32],\n    res: &mut Vec<Vec<i32>>,\n) {\n    // Record a solution when the subset sum equals target\n    if total == target {\n        res.push(state.clone());\n        return;\n    }\n    // Iterate over all choices\n    for i in 0..choices.len() {\n        // Prune: skip this choice if the subset sum would exceed target\n        if total + choices[i] > target {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    let total = 0; // Subset sum\n    let mut res = Vec::new(); // Result list (list of subsets)\n    backtrack(&mut state, target, total, nums, &mut res);\n    res\n}\n
    subset_sum_i_naive.c
    /* Backtracking: subset sum I */\nvoid backtrack(int target, int total, int *choices, int choicesSize) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        for (int i = 0; i < stateSize; i++) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Iterate over all choices\n    for (int i = 0; i < choicesSize; i++) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue;\n        }\n        // Try: make a choice and update the sum total\n        state[stateSize++] = choices[i];\n        // Proceed to the next choice\n        backtrack(target, total + choices[i], choices, choicesSize);\n        // Backtrack: undo the choice and restore the previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nvoid subsetSumINaive(int *nums, int numsSize, int target) {\n    resSize = 0; // Initialize the solution count to 0\n    backtrack(target, 0, nums, numsSize);\n}\n
    subset_sum_i_naive.kt
    /* Backtracking: subset sum I */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    total: Int,\n    choices: IntArray,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the subset sum equals target\n    if (total == target) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    for (i in choices.indices) {\n        // Prune: skip this choice if the subset sum would exceed target\n        if (total + choices[i] > target) {\n            continue\n        }\n        // Try: make a choice and update the sum total\n        state.add(choices[i])\n        // Proceed to the next choice\n        backtrack(state, target, total + choices[i], choices, res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum I (with duplicate subsets) */\nfun subsetSumINaive(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    val total = 0 // Subset sum\n    val res = mutableListOf<MutableList<Int>?>() // Result list (list of subsets)\n    backtrack(state, target, total, nums, res)\n    return res\n}\n
    subset_sum_i_naive.rb
    ### Backtracking: subset sum I ###\ndef backtrack(state, target, total, choices, res)\n  # Record a solution when the subset sum equals target\n  if total == target\n    res << state.dup\n    return\n  end\n\n  # Iterate over all choices\n  for i in 0...choices.length\n    # Prune: skip this choice if the subset sum would exceed target\n    next if total + choices[i] > target\n    # Try: make a choice and update the sum total\n    state << choices[i]\n    # Proceed to the next choice\n    backtrack(state, target, total + choices[i], choices, res)\n    # Backtrack: undo the choice and restore the previous state\n    state.pop\n  end\nend\n\n### Solve subset sum I (with duplicate subsets)###\ndef subset_sum_i_naive(nums, target)\n  state = [] # State (subset)\n  total = 0 # Subset sum\n  res = [] # Result list (list of subsets)\n  backtrack(state, target, total, nums, res)\n  res\nend\n
    Visualize execution

    View full screen >

    For input array \\([3, 4, 5]\\) and target \\(9\\), this code returns \\([3, 3, 3], [4, 5], [5, 4]\\). It finds all subsets summing to \\(9\\), but includes the duplicate subsets \\([4, 5]\\) and \\([5, 4]\\).

    The search distinguishes the order of choices, whereas subsets do not. As shown in Figure 13-10, choosing \\(4\\) then \\(5\\) and choosing \\(5\\) then \\(4\\) follow different branches but produce the same subset.

    Figure 13-10   Subset search and pruning when the sum exceeds the target

    One straightforward way to remove duplicate subsets is to deduplicate the result list. However, this is inefficient for two reasons.

    • With many array elements, especially when target is large, the search generates many duplicate subsets.
    • Comparing subsets (arrays) is expensive: we must sort them first, then compare their elements one by one.
    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2-pruning-duplicate-subsets","level":3,"title":"2.   Pruning duplicate subsets","text":"

    Instead, we can eliminate duplicates by pruning during the search. As shown in Figure 13-11, duplicates arise when array elements are chosen in different orders, as in the following example.

    1. Choosing \\(3\\) and then \\(4\\) in the first two rounds generates all subsets beginning with these choices, denoted by \\([3, 4, \\dots]\\).
    2. Later, after choosing \\(4\\) first, we should skip \\(3\\) in the second round, because subsets \\([4, 3, \\dots]\\) duplicate those generated in step 1..

    At each level of the search, choices are tried from left to right, so branches farther to the right have more choices pruned.

    1. Choosing \\(3\\) and then \\(5\\) generates subsets \\([3, 5, \\dots]\\).
    2. Choosing \\(4\\) and then \\(5\\) generates subsets \\([4, 5, \\dots]\\).
    3. If \\(5\\) is chosen first, skip both \\(3\\) and \\(4\\) in the second round, because \\([5, 3, \\dots]\\) and \\([5, 4, \\dots]\\) duplicate the subsets described in steps 1. and 2..

    Figure 13-11   Duplicate subsets caused by different choice orders

    In general, for input \\([x_1, x_2, \\dots, x_n]\\), a sequence of choices \\([x_{i_1}, x_{i_2}, \\dots, x_{i_m}]\\) must satisfy \\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\). Any sequence violating this condition creates a duplicate and should be pruned.

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#3-code-implementation","level":3,"title":"3.   Code implementation","text":"

    To implement this pruning, introduce start to specify where iteration begins. After choosing \\(x_{i}\\), begin the next round at index \\(i\\). This enforces \\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\) and ensures that each subset is unique.

    We also make two further improvements.

    • Sort nums before starting the search. While iterating over choices, stop the loop as soon as the subset sum exceeds target, because all later elements are larger and would also exceed it.
    • Remove the sum variable total and track the sum by subtracting from target. Record a solution when target reaches \\(0\\).
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i.py
    def backtrack(\n    state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n    \"\"\"Backtracking: subset sum I\"\"\"\n    # Record a solution when the remaining target is zero\n    if target == 0:\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    # Pruning 2: iterate from start to avoid generating duplicate subsets\n    for i in range(start, len(choices)):\n        # Pruning 1: exit the loop if this choice exceeds the remaining target\n        # The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0:\n            break\n        # Try: make a choice and update target and start\n        state.append(choices[i])\n        # Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res)\n        # Backtrack: undo the choice and restore the previous state\n        state.pop()\n\ndef subset_sum_i(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum I\"\"\"\n    state = []  # State (subset)\n    nums.sort()  # Sort nums\n    start = 0  # Starting index for iteration\n    res = []  # Result list (list of subsets)\n    backtrack(state, target, nums, start, res)\n    return res\n
    subset_sum_i.cpp
    /* Backtracking: subset sum I */\nvoid backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {\n    // Record a solution when the remaining target reaches 0\n    if (target == 0) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.size(); i++) {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.push_back(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum I */\nvector<vector<int>> subsetSumI(vector<int> &nums, int target) {\n    vector<int> state;              // State (subset)\n    sort(nums.begin(), nums.end()); // Sort nums\n    int start = 0;                  // Starting index for iteration\n    vector<vector<int>> res;        // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.java
    /* Backtracking: subset sum I */\nvoid backtrack(List<Integer> state, int target, int[] choices, int start, List<List<Integer>> res) {\n    // Record a solution when the remaining target is zero, so the subset sums to the original target\n    if (target == 0) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if the next element exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.add(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum I */\nList<List<Integer>> subsetSumI(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    Arrays.sort(nums); // Sort nums\n    int start = 0; // Starting index for iteration\n    List<List<Integer>> res = new ArrayList<>(); // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.cs
    /* Backtracking: subset sum I */\nvoid Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target == 0) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (int i = start; i < choices.Length; i++) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.Add(choices[i]);\n        // Proceed to the next choice\n        Backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum I */\nList<List<int>> SubsetSumI(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    Array.Sort(nums); // Sort nums\n    int start = 0; // Starting index for iteration\n    List<List<int>> res = []; // Result list (list of subsets)\n    Backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.go
    /* Backtracking: subset sum I */\nfunc backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) {\n    // Record a solution when the subset reaches the original target sum (remaining target is zero)\n    if target == 0 {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for i := start; i < len(*choices); i++ {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target-(*choices)[i] < 0 {\n            break\n        }\n        // Try: make a choice and update target and start\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next choice\n        backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res)\n        // Backtrack: undo the choice and restore the previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum I */\nfunc subsetSumI(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    sort.Ints(nums)         // Sort nums\n    start := 0              // Starting index for iteration\n    res := make([][]int, 0) // Result list (list of subsets)\n    backtrackSubsetSumI(start, target, &state, &nums, &res)\n    return res\n}\n
    subset_sum_i.swift
    /* Backtracking: subset sum I */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n    // Record a solution when the remaining target reaches zero\n    if target == 0 {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for i in choices.indices.dropFirst(start) {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0 {\n            break\n        }\n        // Try: make a choice and update target and start\n        state.append(choices[i])\n        // Proceed to the next choice\n        backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum I */\nfunc subsetSumI(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let nums = nums.sorted() // Sort nums\n    let start = 0 // Starting index for iteration\n    var res: [[Int]] = [] // Result list (list of subsets)\n    backtrack(state: &state, target: target, choices: nums, start: start, res: &res)\n    return res\n}\n
    subset_sum_i.js
    /* Backtracking: subset sum I */\nfunction backtrack(state, target, choices, start, res) {\n    // Record a solution when the remaining target is zero, so the subset sum equals the original target\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if the next choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfunction subsetSumI(nums, target) {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Starting index for iteration\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.ts
    /* Backtracking: subset sum I */\nfunction backtrack(\n    state: number[],\n    target: number,\n    choices: number[],\n    start: number,\n    res: number[][]\n): void {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfunction subsetSumI(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Starting index for iteration\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_i.dart
    /* Backtracking: subset sum I */\nvoid backtrack(\n  List<int> state,\n  int target,\n  List<int> choices,\n  int start,\n  List<List<int>> res,\n) {\n  // Record a solution when the subset sum reaches the original target (the remaining target is 0)\n  if (target == 0) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  // Pruning 2: iterate from start to avoid generating duplicate subsets\n  for (int i = start; i < choices.length; i++) {\n    // Pruning 1: exit the loop if this choice exceeds the remaining target\n    // The array is sorted, so later elements are at least as large and also exceed the remaining target\n    if (target - choices[i] < 0) {\n      break;\n    }\n    // Try: make a choice and update target and start\n    state.add(choices[i]);\n    // Proceed to the next choice\n    backtrack(state, target - choices[i], choices, i, res);\n    // Backtrack: undo the choice and restore the previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum I */\nList<List<int>> subsetSumI(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  nums.sort(); // Sort nums\n  int start = 0; // Starting index for iteration\n  List<List<int>> res = []; // Result list (list of subsets)\n  backtrack(state, target, nums, start, res);\n  return res;\n}\n
    subset_sum_i.rs
    /* Backtracking: subset sum I */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    choices: &[i32],\n    start: usize,\n    res: &mut Vec<Vec<i32>>,\n) {\n    // Record a solution when the remaining target is zero\n    if target == 0 {\n        res.push(state.clone());\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for i in start..choices.len() {\n        // Pruning 1: exit the loop if the next element exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0 {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum I */\nfn subset_sum_i(nums: &mut [i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    nums.sort(); // Sort nums\n    let start = 0; // Starting index for iteration\n    let mut res = Vec::new(); // Result list (list of subsets)\n    backtrack(&mut state, target, nums, start, &mut res);\n    res\n}\n
    subset_sum_i.c
    /* Backtracking: subset sum I */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n    // Record a solution when the remaining target is 0\n    if (target == 0) {\n        for (int i = 0; i < stateSize; ++i) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (int i = start; i < choicesSize; i++) {\n        // Pruning 1: exit the loop if the choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Try: make a choice and update target and start\n        state[stateSize] = choices[i];\n        stateSize++;\n        // Proceed to the next choice\n        backtrack(target - choices[i], choices, choicesSize, i);\n        // Backtrack: undo the choice and restore the previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum I */\nvoid subsetSumI(int *nums, int numsSize, int target) {\n    qsort(nums, numsSize, sizeof(int), cmp); // Sort nums\n    int start = 0;                           // Starting index for iteration\n    backtrack(target, nums, numsSize, start);\n}\n
    subset_sum_i.kt
    /* Backtracking: subset sum I */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    choices: IntArray,\n    start: Int,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target == 0) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    for (i in start..<choices.size) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break\n        }\n        // Try: make a choice and update target and start\n        state.add(choices[i])\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i, res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum I */\nfun subsetSumI(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    nums.sort() // Sort nums\n    val start = 0 // Starting index for iteration\n    val res = mutableListOf<MutableList<Int>?>() // Result list (list of subsets)\n    backtrack(state, target, nums, start, res)\n    return res\n}\n
    subset_sum_i.rb
    ### Backtracking: subset sum I ###\ndef backtrack(state, target, choices, start, res)\n  # Record a solution when the remaining target reaches zero\n  if target.zero?\n    res << state.dup\n    return\n  end\n  # Iterate over all choices\n  # Pruning 2: iterate from start to avoid generating duplicate subsets\n  for i in start...choices.length\n    # Pruning 1: exit the loop if this choice would exceed the remaining target\n    # The array is sorted, so later elements are at least as large and would also exceed the remaining target\n    break if target - choices[i] < 0\n    # Try: make a choice and update target and start\n    state << choices[i]\n    # Proceed to the next choice\n    backtrack(state, target - choices[i], choices, i, res)\n    # Backtrack: undo the choice and restore the previous state\n    state.pop\n  end\nend\n\n### Solve subset sum I ###\ndef subset_sum_i(nums, target)\n  state = [] # State (subset)\n  nums.sort! # Sort nums\n  start = 0 # Starting index for iteration\n  res = [] # Result list (list of subsets)\n  backtrack(state, target, nums, start, res)\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 13-12 shows the complete backtracking process for input \\([3, 4, 5]\\) and target \\(9\\).

    Figure 13-12   Backtracking for subset sum I

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1332-duplicate-elements","level":2,"title":"13.3.2   Duplicate elements","text":"

    Question

    Given an array of positive integers nums and a positive target integer target, find all combinations whose elements sum to target. The array may contain duplicate elements, and each element may be chosen only once. Return the combinations as a list containing no duplicates.

    Unlike the previous problem, the input may contain duplicate elements, which introduces a new issue. For array \\([4, \\hat{4}, 5]\\) and target \\(9\\), for example, the existing code produces \\([4, 5], [\\hat{4}, 5]\\): duplicate subsets.

    These duplicates occur because equal elements are tried more than once in a single round. In Figure 13-13, the first round has three choices, two of which are \\(4\\). They generate duplicate branches and therefore duplicate subsets. The two occurrences of \\(4\\) in the second round cause the same problem.

    Figure 13-13   Duplicate subsets caused by equal elements

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1-pruning-equal-elements","level":3,"title":"1.   Pruning equal elements","text":"

    To solve this, try each value only once in each round. The implementation uses the fact that equal elements are adjacent in a sorted array. Within a round, if an element equals the preceding element, that value has already been tried, so skip the current element.

    In addition, each array element may be chosen only once. Fortunately, start can enforce this constraint too: after choosing \\(x_{i}\\), begin the next round at index \\(i + 1\\). This both eliminates duplicate subsets and prevents reusing an element.

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_ii.py
    def backtrack(\n    state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n    \"\"\"Backtracking: subset sum II\"\"\"\n    # Record a solution when the remaining target is zero\n    if target == 0:\n        res.append(list(state))\n        return\n    # Iterate over all choices\n    # Pruning 2: iterate from start to avoid generating duplicate subsets\n    # Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for i in range(start, len(choices)):\n        # Pruning 1: exit the loop if this choice exceeds the remaining target\n        # The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0:\n            break\n        # Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if i > start and choices[i] == choices[i - 1]:\n            continue\n        # Try: make a choice and update target and start\n        state.append(choices[i])\n        # Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res)\n        # Backtrack: undo the choice and restore the previous state\n        state.pop()\n\ndef subset_sum_ii(nums: list[int], target: int) -> list[list[int]]:\n    \"\"\"Solve subset sum II\"\"\"\n    state = []  # State (subset)\n    nums.sort()  # Sort nums\n    start = 0  # Starting index for iteration\n    res = []  # Result list (list of subsets)\n    backtrack(state, target, nums, start, res)\n    return res\n
    subset_sum_ii.cpp
    /* Backtracking: subset sum II */\nvoid backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {\n    // Record a solution when the remaining target reaches 0\n    if (target == 0) {\n        res.push_back(state);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: iterate from start to avoid choosing the same element again\n    for (int i = start; i < choices.size(); i++) {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.push_back(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop_back();\n    }\n}\n\n/* Solve subset sum II */\nvector<vector<int>> subsetSumII(vector<int> &nums, int target) {\n    vector<int> state;              // State (subset)\n    sort(nums.begin(), nums.end()); // Sort nums\n    int start = 0;                  // Starting index for iteration\n    vector<vector<int>> res;        // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.java
    /* Backtracking: subset sum II */\nvoid backtrack(List<Integer> state, int target, int[] choices, int start, List<List<Integer>> res) {\n    // Record a solution when the remaining target is zero, so the subset sums to the original target\n    if (target == 0) {\n        res.add(new ArrayList<>(state));\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: iterate from start to avoid choosing the same element again\n    for (int i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if the next element exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.add(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.remove(state.size() - 1);\n    }\n}\n\n/* Solve subset sum II */\nList<List<Integer>> subsetSumII(int[] nums, int target) {\n    List<Integer> state = new ArrayList<>(); // State (subset)\n    Arrays.sort(nums); // Sort nums\n    int start = 0; // Starting index for iteration\n    List<List<Integer>> res = new ArrayList<>(); // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.cs
    /* Backtracking: subset sum II */\nvoid Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target == 0) {\n        res.Add(new List<int>(state));\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for (int i = start; i < choices.Length; i++) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.Add(choices[i]);\n        // Proceed to the next choice\n        Backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.RemoveAt(state.Count - 1);\n    }\n}\n\n/* Solve subset sum II */\nList<List<int>> SubsetSumII(int[] nums, int target) {\n    List<int> state = []; // State (subset)\n    Array.Sort(nums); // Sort nums\n    int start = 0; // Starting index for iteration\n    List<List<int>> res = []; // Result list (list of subsets)\n    Backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.go
    /* Backtracking: subset sum II */\nfunc backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) {\n    // Record a solution when the subset reaches the original target sum (remaining target is zero)\n    if target == 0 {\n        newState := append([]int{}, *state...)\n        *res = append(*res, newState)\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for i := start; i < len(*choices); i++ {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target-(*choices)[i] < 0 {\n            break\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if i > start && (*choices)[i] == (*choices)[i-1] {\n            continue\n        }\n        // Try: make a choice and update target and start\n        *state = append(*state, (*choices)[i])\n        // Proceed to the next choice\n        backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res)\n        // Backtrack: undo the choice and restore the previous state\n        *state = (*state)[:len(*state)-1]\n    }\n}\n\n/* Solve subset sum II */\nfunc subsetSumII(nums []int, target int) [][]int {\n    state := make([]int, 0) // State (subset)\n    sort.Ints(nums)         // Sort nums\n    start := 0              // Starting index for iteration\n    res := make([][]int, 0) // Result list (list of subsets)\n    backtrackSubsetSumII(start, target, &state, &nums, &res)\n    return res\n}\n
    subset_sum_ii.swift
    /* Backtracking: subset sum II */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n    // Record a solution when the remaining target reaches zero\n    if target == 0 {\n        res.append(state)\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for i in choices.indices.dropFirst(start) {\n        // Pruning 1: exit the loop if this choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0 {\n            break\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if i > start, choices[i] == choices[i - 1] {\n            continue\n        }\n        // Try: make a choice and update target and start\n        state.append(choices[i])\n        // Proceed to the next choice\n        backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeLast()\n    }\n}\n\n/* Solve subset sum II */\nfunc subsetSumII(nums: [Int], target: Int) -> [[Int]] {\n    var state: [Int] = [] // State (subset)\n    let nums = nums.sorted() // Sort nums\n    let start = 0 // Starting index for iteration\n    var res: [[Int]] = [] // Result list (list of subsets)\n    backtrack(state: &state, target: target, choices: nums, start: start, res: &res)\n    return res\n}\n
    subset_sum_ii.js
    /* Backtracking: subset sum II */\nfunction backtrack(state, target, choices, start, res) {\n    // Record a solution when the remaining target is zero, so the subset sum equals the original target\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if the next choice exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] === choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfunction subsetSumII(nums, target) {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Starting index for iteration\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.ts
    /* Backtracking: subset sum II */\nfunction backtrack(\n    state: number[],\n    target: number,\n    choices: number[],\n    start: number,\n    res: number[][]\n): void {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target === 0) {\n        res.push([...state]);\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for (let i = start; i < choices.length; i++) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] === choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfunction subsetSumII(nums: number[], target: number): number[][] {\n    const state = []; // State (subset)\n    nums.sort((a, b) => a - b); // Sort nums\n    const start = 0; // Starting index for iteration\n    const res = []; // Result list (list of subsets)\n    backtrack(state, target, nums, start, res);\n    return res;\n}\n
    subset_sum_ii.dart
    /* Backtracking: subset sum II */\nvoid backtrack(\n  List<int> state,\n  int target,\n  List<int> choices,\n  int start,\n  List<List<int>> res,\n) {\n  // Record a solution when the subset sum reaches the original target (the remaining target is 0)\n  if (target == 0) {\n    res.add(List.from(state));\n    return;\n  }\n  // Iterate over all choices\n  // Pruning 2: iterate from start to avoid generating duplicate subsets\n  // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n  for (int i = start; i < choices.length; i++) {\n    // Pruning 1: exit the loop if this choice exceeds the remaining target\n    // The array is sorted, so later elements are at least as large and also exceed the remaining target\n    if (target - choices[i] < 0) {\n      break;\n    }\n    // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n    if (i > start && choices[i] == choices[i - 1]) {\n      continue;\n    }\n    // Try: make a choice and update target and start\n    state.add(choices[i]);\n    // Proceed to the next choice\n    backtrack(state, target - choices[i], choices, i + 1, res);\n    // Backtrack: undo the choice and restore the previous state\n    state.removeLast();\n  }\n}\n\n/* Solve subset sum II */\nList<List<int>> subsetSumII(List<int> nums, int target) {\n  List<int> state = []; // State (subset)\n  nums.sort(); // Sort nums\n  int start = 0; // Starting index for iteration\n  List<List<int>> res = []; // Result list (list of subsets)\n  backtrack(state, target, nums, start, res);\n  return res;\n}\n
    subset_sum_ii.rs
    /* Backtracking: subset sum II */\nfn backtrack(\n    state: &mut Vec<i32>,\n    target: i32,\n    choices: &[i32],\n    start: usize,\n    res: &mut Vec<Vec<i32>>,\n) {\n    // Record a solution when the remaining target is zero\n    if target == 0 {\n        res.push(state.clone());\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: iterate from start to avoid choosing the same element again\n    for i in start..choices.len() {\n        // Pruning 1: exit the loop if the next element exceeds the remaining target\n        // The array is sorted, so later elements are at least as large and also exceed the remaining target\n        if target - choices[i] < 0 {\n            break;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if i > start && choices[i] == choices[i - 1] {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state.push(choices[i]);\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res);\n        // Backtrack: undo the choice and restore the previous state\n        state.pop();\n    }\n}\n\n/* Solve subset sum II */\nfn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec<Vec<i32>> {\n    let mut state = Vec::new(); // State (subset)\n    nums.sort(); // Sort nums\n    let start = 0; // Starting index for iteration\n    let mut res = Vec::new(); // Result list (list of subsets)\n    backtrack(&mut state, target, nums, start, &mut res);\n    res\n}\n
    subset_sum_ii.c
    /* Backtracking: subset sum II */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n    // Record a solution when the remaining target is 0\n    if (target == 0) {\n        for (int i = 0; i < stateSize; i++) {\n            res[resSize][i] = state[i];\n        }\n        resColSizes[resSize++] = stateSize;\n        return;\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: iterate from start to avoid choosing the same element again\n    for (int i = start; i < choicesSize; i++) {\n        // Pruning 1: skip this choice if it exceeds the remaining target\n        if (target - choices[i] < 0) {\n            continue;\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue;\n        }\n        // Try: make a choice and update target and start\n        state[stateSize] = choices[i];\n        stateSize++;\n        // Proceed to the next choice\n        backtrack(target - choices[i], choices, choicesSize, i + 1);\n        // Backtrack: undo the choice and restore the previous state\n        stateSize--;\n    }\n}\n\n/* Solve subset sum II */\nvoid subsetSumII(int *nums, int numsSize, int target) {\n    // Sort nums\n    qsort(nums, numsSize, sizeof(int), cmp);\n    // Start backtracking\n    backtrack(target, nums, numsSize, 0);\n}\n
    subset_sum_ii.kt
    /* Backtracking: subset sum II */\nfun backtrack(\n    state: MutableList<Int>,\n    target: Int,\n    choices: IntArray,\n    start: Int,\n    res: MutableList<MutableList<Int>?>\n) {\n    // Record a solution when the subset reaches the original target (remaining target is 0)\n    if (target == 0) {\n        res.add(state.toMutableList())\n        return\n    }\n    // Iterate over all choices\n    // Pruning 2: iterate from start to avoid generating duplicate subsets\n    // Pruning 3: start after the previously chosen index to avoid choosing the same element again\n    for (i in start..<choices.size) {\n        // Pruning 1: exit the loop if this choice would exceed the remaining target\n        // The array is sorted, so later elements are at least as large and would also exceed the remaining target\n        if (target - choices[i] < 0) {\n            break\n        }\n        // Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n        if (i > start && choices[i] == choices[i - 1]) {\n            continue\n        }\n        // Try: make a choice and update target and start\n        state.add(choices[i])\n        // Proceed to the next choice\n        backtrack(state, target - choices[i], choices, i + 1, res)\n        // Backtrack: undo the choice and restore the previous state\n        state.removeAt(state.size - 1)\n    }\n}\n\n/* Solve subset sum II */\nfun subsetSumII(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {\n    val state = mutableListOf<Int>() // State (subset)\n    nums.sort() // Sort nums\n    val start = 0 // Starting index for iteration\n    val res = mutableListOf<MutableList<Int>?>() // Result list (list of subsets)\n    backtrack(state, target, nums, start, res)\n    return res\n}\n
    subset_sum_ii.rb
    ### Backtracking: subset sum II ###\ndef backtrack(state, target, choices, start, res)\n  # Record a solution when the remaining target reaches zero\n  if target.zero?\n    res << state.dup\n    return\n  end\n\n  # Iterate over all choices\n  # Pruning 2: iterate from start to avoid generating duplicate subsets\n  # Pruning 3: start after the previously chosen index to avoid choosing the same element again\n  for i in start...choices.length\n    # Pruning 1: exit the loop if this choice would exceed the remaining target\n    # The array is sorted, so later elements are at least as large and would also exceed the remaining target\n    break if target - choices[i] < 0\n    # Pruning 4: if this element equals its left neighbor at the same search depth, the branch is a duplicate; skip it\n    next if i > start && choices[i] == choices[i - 1]\n    # Try: make a choice and update target and start\n    state << choices[i]\n    # Proceed to the next choice\n    backtrack(state, target - choices[i], choices, i + 1, res)\n    # Backtrack: undo the choice and restore the previous state\n    state.pop\n  end\nend\n\n### Solve subset sum II ###\ndef subset_sum_ii(nums, target)\n  state = [] # State (subset)\n  nums.sort! # Sort nums\n  start = 0 # Starting index for iteration\n  res = [] # Result list (list of subsets)\n  backtrack(state, target, nums, start, res)\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 13-14 shows backtracking for array \\([4, 4, 5]\\) and target \\(9\\), including four types of pruning. Follow the illustration alongside the code comments to understand the full search and how each pruning rule works.

    Figure 13-14   Backtracking for subset sum II

    ","path":["Chapter 13.   Backtracking","13.3   Subset sum problem"],"tags":[]},{"location":"chapter_backtracking/summary/","level":1,"title":"13.5   Summary","text":"","path":["Chapter 13.   Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_backtracking/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Backtracking is fundamentally exhaustive search. It traverses the solution space depth first to find solutions meeting the requirements, records them when found, and continues until all solutions are found or traversal is complete.
    • Backtracking alternates between trying a choice and undoing it. It explores choices through depth-first search; when a constraint is violated, it undoes the last choice, restores the previous state, and tries another choice. Trying and undoing are opposite operations.
    • Backtracking problems usually have multiple constraints that enable pruning. Pruning stops unnecessary branches early and can greatly improve search efficiency.
    • Backtracking is mainly used for search and constraint satisfaction problems. It can also solve combinatorial optimization problems, though more efficient or more effective approaches often exist.
    • The permutations problem asks for every possible ordering of a collection's elements. An array tracks whether each element has been selected, pruning branches that select the same element again and ensuring each is used only once.
    • If the input contains equal elements, permutation generation produces duplicate results. We must try each value only once per round, usually by using a hash set.
    • The subset sum problem asks for all subsets of a collection whose sum equals a target. Subsets disregard order, but the search generates all orderings, producing duplicates. Sorting the data before backtracking and using a variable to mark each round's starting index allows us to prune duplicate-generating branches.
    • Equal elements also produce duplicate subsets. Because the array is sorted, we can prune by comparing adjacent elements, ensuring that each value is tried only once per round.
    • The \\(n\\)-queens problem asks for arrangements of \\(n\\) queens on an \\(n \\times n\\) chessboard such that no two queens can attack each other. It imposes row, column, and both diagonal constraints. Placing queens row by row ensures exactly one queen per row.
    • Column and diagonal constraints are handled similarly. An array records whether each column contains a queen, indicating whether a chosen square is valid. Two more arrays track queens on diagonals in each direction. The main challenge is identifying the relationship between row and column indices for squares on the same diagonal.
    ","path":["Chapter 13.   Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_backtracking/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: How are backtracking and recursion related?

    Broadly speaking, backtracking is an algorithmic strategy, while recursion is a tool.

    • Backtracking is usually implemented recursively. It is one application of recursion, specifically to search problems.
    • Recursion expresses the idea of decomposing a problem into subproblems. It is commonly used in divide and conquer, backtracking, and dynamic programming (memoized recursion).
    ","path":["Chapter 13.   Backtracking","13.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/","level":1,"title":"Chapter 2.   Complexity analysis","text":"

    Abstract

    Complexity analysis is our guide to time and space in the vast universe of algorithms.

    It leads us to explore these two dimensions in depth as we search for more elegant solutions.

    ","path":["Chapter 2.   Complexity analysis"],"tags":[]},{"location":"chapter_computational_complexity/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 2.1   Evaluating algorithm efficiency
    • 2.2   Iteration and recursion
    • 2.3   Time complexity
    • 2.4   Space complexity
    • 2.5   Summary
    • 2.6   Exercises
    ","path":["Chapter 2.   Complexity analysis"],"tags":[]},{"location":"chapter_computational_complexity/exercises/","level":1,"title":"2.6   Exercises","text":"","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#261-check-your-understanding","level":2,"title":"2.6.1   Check your understanding","text":"","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#1-time-and-space-in-iteration-and-recursion","level":3,"title":"1.   Time and space in iteration and recursion","text":"

    Both functions below compute \\(1 + 2 + \\dots + n\\) (assume \\(n \\ge 1\\)). Set n to 4, follow the program's execution order to answer the questions, and then compare the efficiency of the two approaches.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby complexity_exercises.py
    def sum_iter(n: int) -> int:\n    \"\"\"Sum using iteration\"\"\"\n    res = 0\n    for i in range(1, n + 1):\n        res += i\n    return res\n\ndef sum_recur(n: int) -> int:\n    \"\"\"Sum using recursion\"\"\"\n    if n == 1:\n        return 1\n    return n + sum_recur(n - 1)\n
    complexity_exercises.cpp
    /* Sum using iteration */\nint sumIter(int n) {\n    int res = 0;\n    for (int i = 1; i <= n; ++i) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nint sumRecur(int n) {\n    if (n == 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.java
    /* Sum using iteration */\nint sumIter(int n) {\n    int res = 0;\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nint sumRecur(int n) {\n    if (n == 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.cs
    /* Sum using iteration */\nint SumIter(int n) {\n    int res = 0;\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nint SumRecur(int n) {\n    if (n == 1) {\n        return 1;\n    }\n    return n + SumRecur(n - 1);\n}\n
    complexity_exercises.go
    /* Sum using iteration */\nfunc sumIter(n int) int {\n    res := 0\n    for i := 1; i <= n; i++ {\n        res += i\n    }\n    return res\n}\n\n/* Sum using recursion */\nfunc sumRecur(n int) int {\n    if n == 1 {\n        return 1\n    }\n    return n + sumRecur(n-1)\n}\n
    complexity_exercises.swift
    /* Sum using iteration */\nfunc sumIter(n: Int) -> Int {\n    var res = 0\n    for i in 1 ... n {\n        res += i\n    }\n    return res\n}\n\n/* Sum using recursion */\nfunc sumRecur(n: Int) -> Int {\n    if n == 1 {\n        return 1\n    }\n    return n + sumRecur(n: n - 1)\n}\n
    complexity_exercises.js
    /* Sum using iteration */\nfunction sumIter(n) {\n    let res = 0;\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nfunction sumRecur(n) {\n    if (n === 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.ts
    /* Sum using iteration */\nfunction sumIter(n: number): number {\n    let res = 0;\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nfunction sumRecur(n: number): number {\n    if (n === 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.dart
    /* Sum using iteration */\nint sumIter(int n) {\n  int res = 0;\n  for (int i = 1; i <= n; i++) {\n    res += i;\n  }\n  return res;\n}\n\n/* Sum using recursion */\nint sumRecur(int n) {\n  if (n == 1) {\n    return 1;\n  }\n  return n + sumRecur(n - 1);\n}\n
    complexity_exercises.rs
    /* Sum using iteration */\nfn sum_iter(n: i32) -> i32 {\n    let mut res = 0;\n    for i in 1..=n {\n        res += i;\n    }\n    res\n}\n\n/* Sum using recursion */\nfn sum_recur(n: i32) -> i32 {\n    if n == 1 {\n        return 1;\n    }\n    n + sum_recur(n - 1)\n}\n
    complexity_exercises.c
    /* Sum using iteration */\nint sumIter(int n) {\n    int res = 0;\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Sum using recursion */\nint sumRecur(int n) {\n    if (n == 1) {\n        return 1;\n    }\n    return n + sumRecur(n - 1);\n}\n
    complexity_exercises.kt
    /* Sum using iteration */\nfun sumIter(n: Int): Int {\n    var res = 0\n    for (i in 1..n) {\n        res += i\n    }\n    return res\n}\n\n/* Sum using recursion */\nfun sumRecur(n: Int): Int {\n    if (n == 1) {\n        return 1\n    }\n    return n + sumRecur(n - 1)\n}\n
    complexity_exercises.rb
    ### Sum using iteration ###\ndef sum_iter(n)\n  res = 0\n  for i in 1..n\n    res += i\n  end\n  res\nend\n\n### Sum using recursion ###\ndef sum_recur(n)\n  return 1 if n == 1\n\n  n + sum_recur(n - 1)\nend\n
    1. When the iterative function runs with n = 4, what is the value of the accumulator res at the end of each iteration?
    2. When the recursive function runs with n = 4, what values does the parameter n take, in order? How is the result computed as the calls return, starting from the deepest call?
    3. What are the time and space complexities of each approach? Explain using the execution steps from questions 1 and 2.
    Reference answer
    1. The loop variable i takes the values 1, 2, 3, 4 in order. At the end of each iteration, res becomes 1, 3, 6, 10, respectively, so the iterative function returns 10.

    2. The parameter n follows the sequence 4 → 3 → 2 → 1. The deepest call returns 1. The remaining calls then compute 2 + 1 = 3, 3 + 3 = 6, and 4 + 6 = 10, in order. At the deepest point, all four function calls are still active.

    3. Both functions perform a number of iterations or calls proportional to \\(n\\), so both have time complexity \\(O(n)\\). Their space complexities differ: the iterative version uses only a constant number of variables, giving \\(O(1)\\). In the recursive version, earlier calls must wait for their results until the base case is reached. The call stack therefore holds up to \\(n\\) calls at once, giving space complexity \\(O(n)\\).

      When analyzing space complexity, consider the space used by recursive calls as well as the variables in the code.

    ","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#2-time-complexity-of-three-code-snippets","level":3,"title":"2.   Time complexity of three code snippets","text":"

    Each of the following three code snippets takes a positive integer \\(n\\) as input. Rank them from lowest to highest time complexity, and state each complexity.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby complexity_exercises.py
    def linear_loop(n: int) -> int:\n    \"\"\"Linear-time loop\"\"\"\n    res = 0\n    for i in range(n):\n        res += i\n    return res\n\ndef quadratic_loop(n: int) -> int:\n    \"\"\"Quadratic-time loop\"\"\"\n    res = 0\n    for i in range(n):\n        for j in range(i, n):\n            res += j\n    return res\n\ndef logarithmic_loop(n: int) -> int:\n    \"\"\"Logarithmic-time loop\"\"\"\n    while n > 1:\n        n //= 2\n    return n\n
    complexity_exercises.cpp
    /* Linear-time loop */\nint linearLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; ++i) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nint quadraticLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; ++i) {\n        for (int j = i; j < n; ++j) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nint logarithmicLoop(int n) {\n    while (n > 1) {\n        n /= 2;\n    }\n    return n;\n}\n
    complexity_exercises.java
    /* Linear-time loop */\nint linearLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nint quadraticLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        for (int j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nint logarithmicLoop(int n) {\n    while (n > 1) {\n        n /= 2;\n    }\n    return n;\n}\n
    complexity_exercises.cs
    /* Linear-time loop */\nint LinearLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nint QuadraticLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        for (int j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nint LogarithmicLoop(int n) {\n    while (n > 1) {\n        n /= 2;\n    }\n    return n;\n}\n
    complexity_exercises.go
    /* Linear-time loop */\nfunc linearLoop(n int) int {\n    res := 0\n    for i := 0; i < n; i++ {\n        res += i\n    }\n    return res\n}\n\n/* Quadratic-time loop */\nfunc quadraticLoop(n int) int {\n    res := 0\n    for i := 0; i < n; i++ {\n        for j := i; j < n; j++ {\n            res += j\n        }\n    }\n    return res\n}\n\n/* Logarithmic-time loop */\nfunc logarithmicLoop(n int) int {\n    for n > 1 {\n        n /= 2\n    }\n    return n\n}\n
    complexity_exercises.swift
    /* Linear-time loop */\nfunc linearLoop(n: Int) -> Int {\n    var res = 0\n    for i in 0 ..< n {\n        res += i\n    }\n    return res\n}\n\n/* Quadratic-time loop */\nfunc quadraticLoop(n: Int) -> Int {\n    var res = 0\n    for i in 0 ..< n {\n        for j in i ..< n {\n            res += j\n        }\n    }\n    return res\n}\n\n/* Logarithmic-time loop */\nfunc logarithmicLoop(n: Int) -> Int {\n    var n = n\n    while n > 1 {\n        n /= 2\n    }\n    return n\n}\n
    complexity_exercises.js
    /* Linear-time loop */\nfunction linearLoop(n) {\n    let res = 0;\n    for (let i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nfunction quadraticLoop(n) {\n    let res = 0;\n    for (let i = 0; i < n; i++) {\n        for (let j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nfunction logarithmicLoop(n) {\n    while (n > 1) {\n        n = Math.floor(n / 2);\n    }\n    return n;\n}\n
    complexity_exercises.ts
    /* Linear-time loop */\nfunction linearLoop(n: number): number {\n    let res = 0;\n    for (let i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nfunction quadraticLoop(n: number): number {\n    let res = 0;\n    for (let i = 0; i < n; i++) {\n        for (let j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nfunction logarithmicLoop(n: number): number {\n    while (n > 1) {\n        n = Math.floor(n / 2);\n    }\n    return n;\n}\n
    complexity_exercises.dart
    /* Linear-time loop */\nint linearLoop(int n) {\n  int res = 0;\n  for (int i = 0; i < n; i++) {\n    res += i;\n  }\n  return res;\n}\n\n/* Quadratic-time loop */\nint quadraticLoop(int n) {\n  int res = 0;\n  for (int i = 0; i < n; i++) {\n    for (int j = i; j < n; j++) {\n      res += j;\n    }\n  }\n  return res;\n}\n\n/* Logarithmic-time loop */\nint logarithmicLoop(int n) {\n  while (n > 1) {\n    n ~/= 2;\n  }\n  return n;\n}\n
    complexity_exercises.rs
    /* Linear-time loop */\nfn linear_loop(n: i32) -> i32 {\n    let mut res = 0;\n    for i in 0..n {\n        res += i;\n    }\n    res\n}\n\n/* Quadratic-time loop */\nfn quadratic_loop(n: i32) -> i32 {\n    let mut res = 0;\n    for i in 0..n {\n        for j in i..n {\n            res += j;\n        }\n    }\n    res\n}\n\n/* Logarithmic-time loop */\nfn logarithmic_loop(mut n: i32) -> i32 {\n    while n > 1 {\n        n /= 2;\n    }\n    n\n}\n
    complexity_exercises.c
    /* Linear-time loop */\nint linearLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        res += i;\n    }\n    return res;\n}\n\n/* Quadratic-time loop */\nint quadraticLoop(int n) {\n    int res = 0;\n    for (int i = 0; i < n; i++) {\n        for (int j = i; j < n; j++) {\n            res += j;\n        }\n    }\n    return res;\n}\n\n/* Logarithmic-time loop */\nint logarithmicLoop(int n) {\n    while (n > 1) {\n        n /= 2;\n    }\n    return n;\n}\n
    complexity_exercises.kt
    /* Linear-time loop */\nfun linearLoop(n: Int): Int {\n    var res = 0\n    for (i in 0 until n) {\n        res += i\n    }\n    return res\n}\n\n/* Quadratic-time loop */\nfun quadraticLoop(n: Int): Int {\n    var res = 0\n    for (i in 0 until n) {\n        for (j in i until n) {\n            res += j\n        }\n    }\n    return res\n}\n\n/* Logarithmic-time loop */\nfun logarithmicLoop(n: Int): Int {\n    var value = n\n    while (value > 1) {\n        value /= 2\n    }\n    return value\n}\n
    complexity_exercises.rb
    ### Linear-time loop ###\ndef linear_loop(n)\n  res = 0\n  for i in 0...n\n    res += i\n  end\n  res\nend\n\n### Quadratic-time loop ###\ndef quadratic_loop(n)\n  res = 0\n  for i in 0...n\n    for j in i...n\n      res += j\n    end\n  end\n  res\nend\n\n### Logarithmic-time loop ###\ndef logarithmic_loop(n)\n  n /= 2 while n > 1\n  n\nend\n
    Reference answer

    From lowest to highest: snippet 3, \\(O(\\log n)\\); snippet 1, \\(O(n)\\); snippet 2, \\(O(n^2)\\). Snippet 3 halves \\(n\\) at each iteration, so it runs approximately \\(\\log_2 n\\) times. Snippet 1 runs its loop exactly \\(n\\) times. In snippet 2, the inner loop runs \\(n,n-1,\\dots,1\\) times in successive iterations, for a total of \\(n(n+1)/2\\), giving quadratic time complexity.

    ","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#3-which-reversal-method-uses-less-space","level":3,"title":"3.   Which reversal method uses less space?","text":"

    There are two ways to reverse all the elements in an array nums:

    1. Create an array res of the same length, copy the elements into it in reverse order, and return it.
    2. Move two indices, i and j, inward from the beginning and end, swapping nums[i] with nums[j] in pairs.

      What is the space complexity of each approach? Which operation is in-place?

    Reference answer
    1. An auxiliary array as long as the input is needed, giving space complexity \\(O(n)\\).

    2. Only two index variables are used, giving space complexity \\(O(1)\\). This is an in-place operation.

      Keep in mind that in-place reversal modifies the input array. Prefer it only when modifying the input is allowed. If the original array must be preserved, the copying cost of approach 1 is unavoidable.

    ","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#262-programming-exercises","level":2,"title":"2.6.2   Programming exercises","text":"","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#1-fibonacci-number","level":3,"title":"1.   Fibonacci number","text":"

    The Fibonacci sequence satisfies \\(F(0)=0\\), \\(F(1)=1\\), and, for \\(n\\ge2\\), \\(F(n)=F(n-1)+F(n-2)\\).

    Given a nonnegative integer n, use a loop to compute and return \\(F(n)\\) without recursion.

    Hints
    1. Handle n equal to 0 or 1 separately first.
    2. Computing the next term requires only the previous two terms; there is no need to store the entire sequence.
    3. When updating the two variables, take care not to overwrite an old value before you have finished using it.

    LeetCode

    ","path":["Chapter 2.   Complexity analysis","2.6   Exercises"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/","level":1,"title":"2.2   Iteration and recursion","text":"

    Algorithms often repeat tasks, and repetition is closely tied to complexity analysis. Before introducing time and space complexity, let's examine two basic ways to repeat work in a program: iteration and recursion.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#221-iteration","level":2,"title":"2.2.1   Iteration","text":"

    Iteration is a control structure for repeating a task. A program repeatedly executes a block of code while a condition holds, stopping when it no longer does.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-for-loops","level":3,"title":"1.   For loops","text":"

    A for loop is one of the most common forms of iteration, especially when the number of iterations is known in advance.

    The following function uses a for loop to compute \\(1 + 2 + \\dots + n\\), storing the result in res. In Python, range(a, b) includes the left endpoint but excludes the right, so it visits \\(a, a + 1, \\dots, b-1\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
    def for_loop(n: int) -> int:\n    \"\"\"for loop\"\"\"\n    res = 0\n    # Sum 1, 2, ..., n-1, n in a loop\n    for i in range(1, n + 1):\n        res += i\n    return res\n
    iteration.cpp
    /* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (int i = 1; i <= n; ++i) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.java
    /* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.cs
    /* for loop */\nint ForLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.go
    /* for loop */\nfunc forLoop(n int) int {\n    res := 0\n    // Sum 1, 2, ..., n-1, n in a loop\n    for i := 1; i <= n; i++ {\n        res += i\n    }\n    return res\n}\n
    iteration.swift
    /* for loop */\nfunc forLoop(n: Int) -> Int {\n    var res = 0\n    // Sum 1, 2, ..., n-1, n in a loop\n    for i in 1 ... n {\n        res += i\n    }\n    return res\n}\n
    iteration.js
    /* for loop */\nfunction forLoop(n) {\n    let res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.ts
    /* for loop */\nfunction forLoop(n: number): number {\n    let res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (let i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.dart
    /* for loop */\nint forLoop(int n) {\n  int res = 0;\n  // Sum 1, 2, ..., n-1, n in a loop\n  for (int i = 1; i <= n; i++) {\n    res += i;\n  }\n  return res;\n}\n
    iteration.rs
    /* for loop */\nfn for_loop(n: i32) -> i32 {\n    let mut res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for i in 1..=n {\n        res += i;\n    }\n    res\n}\n
    iteration.c
    /* for loop */\nint forLoop(int n) {\n    int res = 0;\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (int i = 1; i <= n; i++) {\n        res += i;\n    }\n    return res;\n}\n
    iteration.kt
    /* for loop */\nfun forLoop(n: Int): Int {\n    var res = 0\n    // Sum 1, 2, ..., n-1, n in a loop\n    for (i in 1..n) {\n        res += i\n    }\n    return res\n}\n
    iteration.rb
    ### for loop ###\ndef for_loop(n)\n  res = 0\n\n  # Sum 1, 2, ..., n-1, n in a loop\n  for i in 1..n\n    res += i\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 2-1 is a flowchart of this summation function.

    Figure 2-1   Flowchart of the summation function

    Its operation count is proportional to the input size \\(n\\): the relationship is linear. Time complexity describes this relationship, as the next section will explain.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-while-loops","level":3,"title":"2.   While loops","text":"

    Like for, a while loop implements iteration. Before each iteration, it checks a condition: if true, execution continues; otherwise, the loop ends.

    Here is a while loop computing \\(1 + 2 + \\dots + n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
    def while_loop(n: int) -> int:\n    \"\"\"while loop\"\"\"\n    res = 0\n    i = 1  # Initialize the loop control variable\n    # Sum 1, 2, ..., n-1, n in a loop\n    while i <= n:\n        res += i\n        i += 1  # Update the loop control variable\n    return res\n
    iteration.cpp
    /* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.java
    /* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.cs
    /* while loop */\nint WhileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i += 1; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.go
    /* while loop */\nfunc whileLoop(n int) int {\n    res := 0\n    // Initialize the loop control variable\n    i := 1\n    // Sum 1, 2, ..., n-1, n in a loop\n    for i <= n {\n        res += i\n        // Update the loop control variable\n        i++\n    }\n    return res\n}\n
    iteration.swift
    /* while loop */\nfunc whileLoop(n: Int) -> Int {\n    var res = 0\n    var i = 1 // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while i <= n {\n        res += i\n        i += 1 // Update the loop control variable\n    }\n    return res\n}\n
    iteration.js
    /* while loop */\nfunction whileLoop(n) {\n    let res = 0;\n    let i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.ts
    /* while loop */\nfunction whileLoop(n: number): number {\n    let res = 0;\n    let i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.dart
    /* while loop */\nint whileLoop(int n) {\n  int res = 0;\n  int i = 1; // Initialize the loop control variable\n  // Sum 1, 2, ..., n-1, n in a loop\n  while (i <= n) {\n    res += i;\n    i++; // Update the loop control variable\n  }\n  return res;\n}\n
    iteration.rs
    /* while loop */\nfn while_loop(n: i32) -> i32 {\n    let mut res = 0;\n    let mut i = 1; // Initialize the loop control variable\n\n    // Sum 1, 2, ..., n-1, n in a loop\n    while i <= n {\n        res += i;\n        i += 1; // Update the loop control variable\n    }\n    res\n}\n
    iteration.c
    /* while loop */\nint whileLoop(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i;\n        i++; // Update the loop control variable\n    }\n    return res;\n}\n
    iteration.kt
    /* while loop */\nfun whileLoop(n: Int): Int {\n    var res = 0\n    var i = 1 // Initialize the loop control variable\n    // Sum 1, 2, ..., n-1, n in a loop\n    while (i <= n) {\n        res += i\n        i++ // Update the loop control variable\n    }\n    return res\n}\n
    iteration.rb
    ### while loop ###\ndef while_loop(n)\n  res = 0\n  i = 1 # Initialize the loop control variable\n\n  # Sum 1, 2, ..., n-1, n in a loop\n  while i <= n\n    res += i\n    i += 1 # Update the loop control variable\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    A while loop offers more flexibility than a for loop in how its condition variables are initialized and updated.

    For example, the following code updates the condition variable \\(i\\) twice per iteration, which is less convenient to express with a for loop:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
    def while_loop_ii(n: int) -> int:\n    \"\"\"while loop (two updates)\"\"\"\n    res = 0\n    i = 1  # Initialize the loop control variable\n    # Sum 1, 4, 10, ... in a loop\n    while i <= n:\n        res += i\n        # Update the loop control variable\n        i += 1\n        i *= 2\n    return res\n
    iteration.cpp
    /* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.java
    /* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.cs
    /* while loop (two updates) */\nint WhileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i += 1; \n        i *= 2;\n    }\n    return res;\n}\n
    iteration.go
    /* while loop (two updates) */\nfunc whileLoopII(n int) int {\n    res := 0\n    // Initialize the loop control variable\n    i := 1\n    // Sum 1, 4, 10, ... in a loop\n    for i <= n {\n        res += i\n        // Update the loop control variable\n        i++\n        i *= 2\n    }\n    return res\n}\n
    iteration.swift
    /* while loop (two updates) */\nfunc whileLoopII(n: Int) -> Int {\n    var res = 0\n    var i = 1 // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while i <= n {\n        res += i\n        // Update the loop control variable\n        i += 1\n        i *= 2\n    }\n    return res\n}\n
    iteration.js
    /* while loop (two updates) */\nfunction whileLoopII(n) {\n    let res = 0;\n    let i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.ts
    /* while loop (two updates) */\nfunction whileLoopII(n: number): number {\n    let res = 0;\n    let i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.dart
    /* while loop (two updates) */\nint whileLoopII(int n) {\n  int res = 0;\n  int i = 1; // Initialize the loop control variable\n  // Sum 1, 4, 10, ... in a loop\n  while (i <= n) {\n    res += i;\n    // Update the loop control variable\n    i++;\n    i *= 2;\n  }\n  return res;\n}\n
    iteration.rs
    /* while loop (two updates) */\nfn while_loop_ii(n: i32) -> i32 {\n    let mut res = 0;\n    let mut i = 1; // Initialize the loop control variable\n\n    // Sum 1, 4, 10, ... in a loop\n    while i <= n {\n        res += i;\n        // Update the loop control variable\n        i += 1;\n        i *= 2;\n    }\n    res\n}\n
    iteration.c
    /* while loop (two updates) */\nint whileLoopII(int n) {\n    int res = 0;\n    int i = 1; // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i;\n        // Update the loop control variable\n        i++;\n        i *= 2;\n    }\n    return res;\n}\n
    iteration.kt
    /* while loop (two updates) */\nfun whileLoopII(n: Int): Int {\n    var res = 0\n    var i = 1 // Initialize the loop control variable\n    // Sum 1, 4, 10, ... in a loop\n    while (i <= n) {\n        res += i\n        // Update the loop control variable\n        i++\n        i *= 2\n    }\n    return res\n}\n
    iteration.rb
    ### while loop (two updates)###\ndef while_loop_ii(n)\n  res = 0\n  i = 1 # Initialize the loop control variable\n\n  # Sum 1, 4, 10, ... in a loop\n  while i <= n\n    res += i\n    # Update the loop control variable\n    i += 1\n    i *= 2\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    In general, for loops are more compact, while while loops are more flexible. Both implement iteration; choose according to the problem's needs.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-nested-loops","level":3,"title":"3.   Nested loops","text":"

    We can place one loop inside another, as in the following for-loop example:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py
    def nested_for_loop(n: int) -> str:\n    \"\"\"Nested for loops\"\"\"\n    res = \"\"\n    # Loop over i = 1, 2, ..., n-1, n\n    for i in range(1, n + 1):\n        # Loop over j = 1, 2, ..., n-1, n\n        for j in range(1, n + 1):\n            res += f\"({i}, {j}), \"\n    return res\n
    iteration.cpp
    /* Nested for loops */\nstring nestedForLoop(int n) {\n    ostringstream res;\n    // Loop over i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; ++i) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; ++j) {\n            res << \"(\" << i << \", \" << j << \"), \";\n        }\n    }\n    return res.str();\n}\n
    iteration.java
    /* Nested for loops */\nString nestedForLoop(int n) {\n    StringBuilder res = new StringBuilder();\n    // Loop over i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            res.append(\"(\" + i + \", \" + j + \"), \");\n        }\n    }\n    return res.toString();\n}\n
    iteration.cs
    /* Nested for loops */\nstring NestedForLoop(int n) {\n    StringBuilder res = new();\n    // Loop over i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            res.Append($\"({i}, {j}), \");\n        }\n    }\n    return res.ToString();\n}\n
    iteration.go
    /* Nested for loops */\nfunc nestedForLoop(n int) string {\n    res := \"\"\n    // Loop over i = 1, 2, ..., n-1, n\n    for i := 1; i <= n; i++ {\n        for j := 1; j <= n; j++ {\n            // Loop over j = 1, 2, ..., n-1, n\n            res += fmt.Sprintf(\"(%d, %d), \", i, j)\n        }\n    }\n    return res\n}\n
    iteration.swift
    /* Nested for loops */\nfunc nestedForLoop(n: Int) -> String {\n    var res = \"\"\n    // Loop over i = 1, 2, ..., n-1, n\n    for i in 1 ... n {\n        // Loop over j = 1, 2, ..., n-1, n\n        for j in 1 ... n {\n            res.append(\"(\\(i), \\(j)), \")\n        }\n    }\n    return res\n}\n
    iteration.js
    /* Nested for loops */\nfunction nestedForLoop(n) {\n    let res = '';\n    // Loop over i = 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (let j = 1; j <= n; j++) {\n            res += `(${i}, ${j}), `;\n        }\n    }\n    return res;\n}\n
    iteration.ts
    /* Nested for loops */\nfunction nestedForLoop(n: number): string {\n    let res = '';\n    // Loop over i = 1, 2, ..., n-1, n\n    for (let i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (let j = 1; j <= n; j++) {\n            res += `(${i}, ${j}), `;\n        }\n    }\n    return res;\n}\n
    iteration.dart
    /* Nested for loops */\nString nestedForLoop(int n) {\n  String res = \"\";\n  // Loop over i = 1, 2, ..., n-1, n\n  for (int i = 1; i <= n; i++) {\n    // Loop over j = 1, 2, ..., n-1, n\n    for (int j = 1; j <= n; j++) {\n      res += \"($i, $j), \";\n    }\n  }\n  return res;\n}\n
    iteration.rs
    /* Nested for loops */\nfn nested_for_loop(n: i32) -> String {\n    let mut res = vec![];\n    // Loop over i = 1, 2, ..., n-1, n\n    for i in 1..=n {\n        // Loop over j = 1, 2, ..., n-1, n\n        for j in 1..=n {\n            res.push(format!(\"({}, {}), \", i, j));\n        }\n    }\n    res.join(\"\")\n}\n
    iteration.c
    /* Nested for loops */\nchar *nestedForLoop(int n) {\n    // There are n * n points; each \"(i, j), \" takes at most 6+10*2 characters, plus space for the final null character \\0\n    int size = n * n * 26 + 1;\n    char *res = malloc(size * sizeof(char));\n    // Loop over i = 1, 2, ..., n-1, n\n    for (int i = 1; i <= n; i++) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (int j = 1; j <= n; j++) {\n            char tmp[26];\n            snprintf(tmp, sizeof(tmp), \"(%d, %d), \", i, j);\n            strncat(res, tmp, size - strlen(res) - 1);\n        }\n    }\n    return res;\n}\n
    iteration.kt
    /* Nested for loops */\nfun nestedForLoop(n: Int): String {\n    val res = StringBuilder()\n    // Loop over i = 1, 2, ..., n-1, n\n    for (i in 1..n) {\n        // Loop over j = 1, 2, ..., n-1, n\n        for (j in 1..n) {\n            res.append(\" ($i, $j), \")\n        }\n    }\n    return res.toString()\n}\n
    iteration.rb
    ### Nested for loops ###\ndef nested_for_loop(n)\n  res = \"\"\n\n  # Loop over i = 1, 2, ..., n-1, n\n  for i in 1..n\n    # Loop over j = 1, 2, ..., n-1, n\n    for j in 1..n\n      res += \"(#{i}, #{j}), \"\n    end\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 2-2 shows the nested loop's flowchart.

    Figure 2-2   Flowchart of nested loops

    Here, the operation count is proportional to \\(n^2\\): runtime has a quadratic relationship with input size \\(n\\).

    Adding further nesting increases the dimension each time, producing cubic, fourth-power, and higher-order time complexity.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#222-recursion","level":2,"title":"2.2.2   Recursion","text":"

    Recursion is an algorithmic strategy in which a function calls itself to solve a problem. It has two main phases.

    1. Descent: The function calls itself more deeply, usually with smaller or simpler arguments, until a base case is reached.
    2. Return: Once the base case is reached, calls return from the deepest level upward, combining results along the way.

    In implementation, recursive code has three main elements.

    1. Base case: Determines when descent stops and returning begins.
    2. Recursive call: The function calls itself, usually with smaller or simpler arguments.
    3. Returned result: The current level returns its result to the previous level.

    In the following code, calling recur(n) computes \\(1 + 2 + \\dots + n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
    def recur(n: int) -> int:\n    \"\"\"Recursion\"\"\"\n    # Base case\n    if n == 1:\n        return 1\n    # Recurse: make a recursive call\n    res = recur(n - 1)\n    # Unwind: return the result\n    return n + res\n
    recursion.cpp
    /* Recursion */\nint recur(int n) {\n    // Base case\n    if (n == 1)\n        return 1;\n    // Recurse: make a recursive call\n    int res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.java
    /* Recursion */\nint recur(int n) {\n    // Base case\n    if (n == 1)\n        return 1;\n    // Recurse: make a recursive call\n    int res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.cs
    /* Recursion */\nint Recur(int n) {\n    // Base case\n    if (n == 1)\n        return 1;\n    // Recurse: make a recursive call\n    int res = Recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.go
    /* Recursion */\nfunc recur(n int) int {\n    // Base case\n    if n == 1 {\n        return 1\n    }\n    // Recurse: make a recursive call\n    res := recur(n - 1)\n    // Unwind: return the result\n    return n + res\n}\n
    recursion.swift
    /* Recursion */\nfunc recur(n: Int) -> Int {\n    // Base case\n    if n == 1 {\n        return 1\n    }\n    // Recurse: make a recursive call\n    let res = recur(n: n - 1)\n    // Unwind: return the result\n    return n + res\n}\n
    recursion.js
    /* Recursion */\nfunction recur(n) {\n    // Base case\n    if (n === 1) return 1;\n    // Recurse: make a recursive call\n    const res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.ts
    /* Recursion */\nfunction recur(n: number): number {\n    // Base case\n    if (n === 1) return 1;\n    // Recurse: make a recursive call\n    const res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.dart
    /* Recursion */\nint recur(int n) {\n  // Base case\n  if (n == 1) return 1;\n  // Recurse: make a recursive call\n  int res = recur(n - 1);\n  // Unwind: return the result\n  return n + res;\n}\n
    recursion.rs
    /* Recursion */\nfn recur(n: i32) -> i32 {\n    // Base case\n    if n == 1 {\n        return 1;\n    }\n    // Recurse: make a recursive call\n    let res = recur(n - 1);\n    // Unwind: return the result\n    n + res\n}\n
    recursion.c
    /* Recursion */\nint recur(int n) {\n    // Base case\n    if (n == 1)\n        return 1;\n    // Recurse: make a recursive call\n    int res = recur(n - 1);\n    // Unwind: return the result\n    return n + res;\n}\n
    recursion.kt
    /* Recursion */\nfun recur(n: Int): Int {\n    // Base case\n    if (n == 1)\n        return 1\n    // Descending: recursive calls\n    val res = recur(n - 1)\n    // Unwinding: return the result\n    return n + res\n}\n
    recursion.rb
    ### Recursion ###\ndef recur(n)\n  # Base case\n  return 1 if n == 1\n  # Recurse: make a recursive call\n  res = recur(n - 1)\n  # Unwind: return the result\n  n + res\nend\n
    Visualize execution

    View full screen >

    Figure 2-3 illustrates the recursive process.

    Figure 2-3   Recursive process of the summation function

    Iteration and recursion can produce the same answer, but represent two different ways of thinking about and solving a problem.

    • Iteration works bottom up, starting with basic steps and repeatedly performing or accumulating them until the task is complete.
    • Recursion works top down, dividing the original problem into smaller subproblems of the same form, then repeating the division until reaching base cases with known solutions.

    For the summation example, let \\(f(n) = 1 + 2 + \\dots + n\\).

    • Iteration: Loop from \\(1\\) to \\(n\\), accumulating the sum at each step to obtain \\(f(n)\\).
    • Recursion: Decompose the problem as \\(f(n) = n + f(n-1)\\) and keep doing so recursively until the base case \\(f(1) = 1\\).
    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-the-call-stack","level":3,"title":"1.   The call stack","text":"

    Each time a recursive function calls itself, the system allocates memory for the new call's local variables, return address, and other information. This has two consequences.

    • The call's context is stored in a stack frame until it returns. Recursion therefore usually uses more memory than iteration.
    • Each recursive call adds overhead. Recursion is therefore usually slower than a loop.

    As shown in Figure 2-4, before the base case is reached, \\(n\\) recursive calls remain active at once: the recursion depth is \\(n\\).

    Figure 2-4   Depth of recursive calls

    Programming languages usually limit recursion depth, and excessively deep recursion may cause a stack overflow.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-tail-recursion","level":3,"title":"2.   Tail recursion","text":"

    Interestingly, if the recursive call is the last operation before a function returns, a compiler or interpreter can optimize it to use as little space as iteration. This is called tail recursion.

    • Ordinary recursion: After a call returns, its caller must continue executing code, so the caller's context must be preserved.
    • Tail recursion: Nothing remains to be done after the recursive call returns, so the caller's context need not be retained.

    For \\(1 + 2 + \\dots + n\\), passing the accumulated result res as a parameter gives a tail-recursive solution:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
    def tail_recur(n, res):\n    \"\"\"Tail recursion\"\"\"\n    # Base case\n    if n == 0:\n        return res\n    # Tail-recursive call\n    return tail_recur(n - 1, res + n)\n
    recursion.cpp
    /* Tail recursion */\nint tailRecur(int n, int res) {\n    // Base case\n    if (n == 0)\n        return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.java
    /* Tail recursion */\nint tailRecur(int n, int res) {\n    // Base case\n    if (n == 0)\n        return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.cs
    /* Tail recursion */\nint TailRecur(int n, int res) {\n    // Base case\n    if (n == 0)\n        return res;\n    // Tail-recursive call\n    return TailRecur(n - 1, res + n);\n}\n
    recursion.go
    /* Tail recursion */\nfunc tailRecur(n int, res int) int {\n    // Base case\n    if n == 0 {\n        return res\n    }\n    // Tail-recursive call\n    return tailRecur(n-1, res+n)\n}\n
    recursion.swift
    /* Tail recursion */\nfunc tailRecur(n: Int, res: Int) -> Int {\n    // Base case\n    if n == 0 {\n        return res\n    }\n    // Tail-recursive call\n    return tailRecur(n: n - 1, res: res + n)\n}\n
    recursion.js
    /* Tail recursion */\nfunction tailRecur(n, res) {\n    // Base case\n    if (n === 0) return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.ts
    /* Tail recursion */\nfunction tailRecur(n: number, res: number): number {\n    // Base case\n    if (n === 0) return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.dart
    /* Tail recursion */\nint tailRecur(int n, int res) {\n  // Base case\n  if (n == 0) return res;\n  // Tail-recursive call\n  return tailRecur(n - 1, res + n);\n}\n
    recursion.rs
    /* Tail recursion */\nfn tail_recur(n: i32, res: i32) -> i32 {\n    // Base case\n    if n == 0 {\n        return res;\n    }\n    // Tail-recursive call\n    tail_recur(n - 1, res + n)\n}\n
    recursion.c
    /* Tail recursion */\nint tailRecur(int n, int res) {\n    // Base case\n    if (n == 0)\n        return res;\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n);\n}\n
    recursion.kt
    /* Tail recursion */\ntailrec fun tailRecur(n: Int, res: Int): Int {\n    // Add the tailrec modifier to enable tail-call optimization\n    // Base case\n    if (n == 0)\n        return res\n    // Tail-recursive call\n    return tailRecur(n - 1, res + n)\n}\n
    recursion.rb
    ### Tail recursion ###\ndef tail_recur(n, res)\n  # Base case\n  return res if n == 0\n  # Tail-recursive call\n  tail_recur(n - 1, res + n)\nend\n
    Visualize execution

    View full screen >

    Figure 2-5 illustrates tail recursion. Ordinary and tail recursion perform their additions at different points.

    • Ordinary recursion: Addition occurs during the return phase, after each deeper call returns.
    • Tail recursion: Addition occurs during descent; the return phase simply returns through the levels.

    Figure 2-5   Tail-recursive summation

    Tip

    Many compilers and interpreters do not support tail-recursion optimization. Python, for example, does not optimize it by default, so even a tail-recursive function can run into a stack overflow.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3-recursion-trees","level":3,"title":"3.   Recursion trees","text":"

    For divide-and-conquer problems, recursion often provides a more intuitive approach and more readable code than iteration. Consider the Fibonacci sequence.

    Question

    Given the Fibonacci sequence \\(0, 1, 1, 2, 3, 5, 8, 13, \\dots\\), find its \\(n\\)-th number.

    Let \\(f(n)\\) be its \\(n\\)-th number. Two facts follow.

    • The first two numbers are \\(f(1) = 0\\) and \\(f(2) = 1\\).
    • Every later number is the sum of the two preceding numbers: \\(f(n) = f(n - 1) + f(n - 2)\\).

    Use the recurrence for recursive calls and the first two numbers as base cases. Calling fib(n) then returns the \\(n\\)-th Fibonacci number:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
    def fib(n: int) -> int:\n    \"\"\"Fibonacci sequence: recursion\"\"\"\n    # Base cases: f(1) = 0, f(2) = 1\n    if n == 1 or n == 2:\n        return n - 1\n    # Recursive calls: f(n) = f(n-1) + f(n-2)\n    res = fib(n - 1) + fib(n - 2)\n    # Return the result f(n)\n    return res\n
    recursion.cpp
    /* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.java
    /* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.cs
    /* Fibonacci sequence: recursion */\nint Fib(int n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    int res = Fib(n - 1) + Fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.go
    /* Fibonacci sequence: recursion */\nfunc fib(n int) int {\n    // Base cases: f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1\n    }\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    res := fib(n-1) + fib(n-2)\n    // Return the result f(n)\n    return res\n}\n
    recursion.swift
    /* Fibonacci sequence: recursion */\nfunc fib(n: Int) -> Int {\n    // Base cases: f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1\n    }\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    let res = fib(n: n - 1) + fib(n: n - 2)\n    // Return the result f(n)\n    return res\n}\n
    recursion.js
    /* Fibonacci sequence: recursion */\nfunction fib(n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n === 1 || n === 2) return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    const res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.ts
    /* Fibonacci sequence: recursion */\nfunction fib(n: number): number {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n === 1 || n === 2) return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    const res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.dart
    /* Fibonacci sequence: recursion */\nint fib(int n) {\n  // Base cases: f(1) = 0, f(2) = 1\n  if (n == 1 || n == 2) return n - 1;\n  // Recursive calls: f(n) = f(n-1) + f(n-2)\n  int res = fib(n - 1) + fib(n - 2);\n  // Return the result f(n)\n  return res;\n}\n
    recursion.rs
    /* Fibonacci sequence: recursion */\nfn fib(n: i32) -> i32 {\n    // Base cases: f(1) = 0, f(2) = 1\n    if n == 1 || n == 2 {\n        return n - 1;\n    }\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    let res = fib(n - 1) + fib(n - 2);\n    // Return the result\n    res\n}\n
    recursion.c
    /* Fibonacci sequence: recursion */\nint fib(int n) {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1;\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    int res = fib(n - 1) + fib(n - 2);\n    // Return the result f(n)\n    return res;\n}\n
    recursion.kt
    /* Fibonacci sequence: recursion */\nfun fib(n: Int): Int {\n    // Base cases: f(1) = 0, f(2) = 1\n    if (n == 1 || n == 2)\n        return n - 1\n    // Recursive calls: f(n) = f(n-1) + f(n-2)\n    val res = fib(n - 1) + fib(n - 2)\n    // Return the result f(n)\n    return res\n}\n
    recursion.rb
    ### Fibonacci sequence: recursion ###\ndef fib(n)\n  # Base cases: f(1) = 0, f(2) = 1\n  return n - 1 if n == 1 || n == 2\n  # Recursive calls: f(n) = f(n-1) + f(n-2)\n  res = fib(n - 1) + fib(n - 2)\n  # Return the result f(n)\n  res\nend\n
    Visualize execution

    View full screen >

    The function makes two recursive calls, so each call creates two branches. Continuing this process produces a recursion tree with \\(n\\) levels, as shown in Figure 2-6.

    Figure 2-6   Recursion tree for the Fibonacci sequence

    Recursion embodies the idea of breaking a problem into smaller subproblems, a crucial divide-and-conquer strategy.

    • In algorithms, searching, sorting, backtracking, divide and conquer, dynamic programming, and many other techniques use this idea directly or indirectly.
    • In data structures, recursion naturally handles linked lists, trees, and graphs because divide-and-conquer reasoning suits them well.
    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#223-comparing-iteration-and-recursion","level":2,"title":"2.2.3   Comparing iteration and recursion","text":"

    Table 2-1 summarizes their differences in implementation, performance, and suitability.

    Table 2-1   Comparing iteration and recursion

    Iteration Recursion Implementation Loops A function calls itself Time efficiency Usually high, with no function-call overhead Each call adds overhead Memory use Usually a fixed amount of memory Accumulated calls may require many stack frames Suitable problems Simple repetitive tasks, with straightforward, readable code Subproblem decomposition, including trees, graphs, divide and conquer, and backtracking; concise, clear structure

    Tip

    If the following discussion feels difficult, return to it after reading the section on stacks.

    How are iteration and recursion connected? In the summation example, additions occur during the return phase. The first function called is the last to finish its addition, matching a stack's last-in, first-out behavior.

    The terms call stack and stack frame already suggest this close relationship.

    1. Descent: A function call adds a new stack frame to the call stack, holding local variables, parameters, the return address, and other data.
    2. Return: When the function finishes, its frame is removed, restoring the caller's execution context.

    Thus, an explicit stack can simulate the call stack, converting recursion into iteration:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py
    def for_loop_recur(n: int) -> int:\n    \"\"\"Simulate recursion using iteration\"\"\"\n    # Use an explicit stack to simulate the system call stack\n    stack = []\n    res = 0\n    # Simulate the recursive-call phase\n    for i in range(n, 0, -1):\n        # Simulate recursive calls by pushing onto the stack\n        stack.append(i)\n    # Simulate unwinding and accumulating the result\n    while stack:\n        # Simulate unwinding by popping from the stack\n        res += stack.pop()\n    # res = 1+2+3+...+n\n    return res\n
    recursion.cpp
    /* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    stack<int> stack;\n    int res = 0;\n    // Recurse: make a recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while (!stack.empty()) {\n        // Simulate unwinding by popping from the stack\n        res += stack.top();\n        stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.java
    /* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    Stack<Integer> stack = new Stack<>();\n    int res = 0;\n    // Recurse: make a recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while (!stack.isEmpty()) {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.cs
    /* Simulate recursion using iteration */\nint ForLoopRecur(int n) {\n    // Use an explicit stack to simulate the system call stack\n    Stack<int> stack = new();\n    int res = 0;\n    // Recurse: make a recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.Push(i);\n    }\n    // Unwind: return the result\n    while (stack.Count > 0) {\n        // Simulate unwinding by popping from the stack\n        res += stack.Pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.go
    /* Simulate recursion using iteration */\nfunc forLoopRecur(n int) int {\n    // Use an explicit stack to simulate the system call stack\n    stack := list.New()\n    res := 0\n    // Descending: simulate recursive calls\n    for i := n; i > 0; i-- {\n        // Simulate recursive calls by pushing onto the stack\n        stack.PushBack(i)\n    }\n    // Unwinding: simulate returning results\n    for stack.Len() != 0 {\n        // Simulate unwinding by popping from the stack\n        res += stack.Back().Value.(int)\n        stack.Remove(stack.Back())\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
    recursion.swift
    /* Simulate recursion using iteration */\nfunc forLoopRecur(n: Int) -> Int {\n    // Use an explicit stack to simulate the system call stack\n    var stack: [Int] = []\n    var res = 0\n    // Descending: simulate recursive calls\n    for i in (1 ... n).reversed() {\n        // Simulate recursive calls by pushing onto the stack\n        stack.append(i)\n    }\n    // Unwinding: simulate returning results\n    while !stack.isEmpty {\n        // Simulate unwinding by popping from the stack\n        res += stack.removeLast()\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
    recursion.js
    /* Simulate recursion using iteration */\nfunction forLoopRecur(n) {\n    // Use an explicit stack to simulate the system call stack\n    const stack = [];\n    let res = 0;\n    // Recurse: make a recursive call\n    for (let i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while (stack.length) {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.ts
    /* Simulate recursion using iteration */\nfunction forLoopRecur(n: number): number {\n    // Use an explicit stack to simulate the system call stack \n    const stack: number[] = [];\n    let res: number = 0;\n    // Recurse: make a recursive call\n    for (let i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while (stack.length) {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop();\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.dart
    /* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n  // Use an explicit stack to simulate the system call stack\n  List<int> stack = [];\n  int res = 0;\n  // Recurse: make a recursive call\n  for (int i = n; i > 0; i--) {\n    // Simulate recursive calls by pushing onto the stack\n    stack.add(i);\n  }\n  // Unwind: return the result\n  while (!stack.isEmpty) {\n    // Simulate unwinding by popping from the stack\n    res += stack.removeLast();\n  }\n  // res = 1+2+3+...+n\n  return res;\n}\n
    recursion.rs
    /* Simulate recursion using iteration */\nfn for_loop_recur(n: i32) -> i32 {\n    // Use an explicit stack to simulate the system call stack\n    let mut stack = Vec::new();\n    let mut res = 0;\n    // Recurse: make a recursive call\n    for i in (1..=n).rev() {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i);\n    }\n    // Unwind: return the result\n    while !stack.is_empty() {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop().unwrap();\n    }\n    // res = 1+2+3+...+n\n    res\n}\n
    recursion.c
    /* Simulate recursion using iteration */\nint forLoopRecur(int n) {\n    int stack[1000]; // Use a large array to simulate a stack\n    int top = -1;    // Top-of-stack index\n    int res = 0;\n    // Recurse: make a recursive call\n    for (int i = n; i > 0; i--) {\n        // Simulate recursive calls by pushing onto the stack\n        stack[1 + top++] = i;\n    }\n    // Unwind: return the result\n    while (top >= 0) {\n        // Simulate unwinding by popping from the stack\n        res += stack[top--];\n    }\n    // res = 1+2+3+...+n\n    return res;\n}\n
    recursion.kt
    /* Simulate recursion using iteration */\nfun forLoopRecur(n: Int): Int {\n    // Use an explicit stack to simulate the system call stack\n    val stack = Stack<Int>()\n    var res = 0\n    // Descending: recursive calls\n    for (i in n downTo 0) {\n        // Simulate recursive calls by pushing onto the stack\n        stack.push(i)\n    }\n    // Unwinding: return the result\n    while (stack.isNotEmpty()) {\n        // Simulate unwinding by popping from the stack\n        res += stack.pop()\n    }\n    // res = 1+2+3+...+n\n    return res\n}\n
    recursion.rb
    ### Simulate recursion using iteration ###\ndef for_loop_recur(n)\n  # Use an explicit stack to simulate the system call stack\n  stack = []\n  res = 0\n\n  # Recurse: make a recursive call\n  for i in n.downto(0)\n    # Simulate recursive calls by pushing onto the stack\n    stack << i\n  end\n  # Unwind: return the result\n  while !stack.empty?\n    res += stack.pop\n  end\n\n  # res = 1+2+3+...+n\n  res\nend\n
    Visualize execution

    View full screen >

    The resulting iterative code is more complicated. Although iteration and recursion can often be converted into each other, doing so is not always worthwhile.

    • The converted code may be harder to understand and less readable.
    • Simulating the call stack can be very difficult for complex problems.

    The choice between iteration and recursion depends on the problem. In practice, weigh their strengths and weaknesses and choose the approach that fits the situation.

    ","path":["Chapter 2.   Complexity analysis","2.2   Iteration and recursion"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/","level":1,"title":"2.1   Evaluating algorithm efficiency","text":"

    Algorithm design pursues two goals in sequence.

    1. Find a solution: The algorithm must reliably produce correct results across the specified input range.
    2. Find the best solution: When several approaches exist, we want the most efficient algorithm possible.

    Once correctness is established, efficiency becomes a primary measure of an algorithm's quality. It has two dimensions.

    • Time efficiency: How long the algorithm takes to run.
    • Space efficiency: How much memory it uses.

    Our goal is to design data structures and algorithms that are both fast and economical with memory. Evaluating efficiency effectively lets us compare algorithms and guide their design and optimization.

    There are two main evaluation methods: practical testing and theoretical estimation.

    ","path":["Chapter 2.   Complexity analysis","2.1   Evaluating algorithm efficiency"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#211-practical-testing","level":2,"title":"2.1.1   Practical testing","text":"

    Suppose algorithms A and B solve the same problem. The most direct comparison is to run both on a computer and measure runtime and memory use. This reflects actual behavior, but has substantial limitations.

    First, it is difficult to remove the influence of the test environment. Hardware affects performance: a highly parallel algorithm benefits from a multicore CPU, while a memory-intensive algorithm benefits from faster RAM. Results may differ across machines, so a fair average would require testing on many kinds of hardware, which is impractical.

    Second, comprehensive testing consumes substantial resources. Efficiency changes with input size. Algorithm A may be faster than B for small inputs, while the reverse holds for large inputs. A convincing conclusion requires testing many input sizes, consuming considerable computing resources.

    ","path":["Chapter 2.   Complexity analysis","2.1   Evaluating algorithm efficiency"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#212-theoretical-estimation","level":2,"title":"2.1.2   Theoretical estimation","text":"

    Given these limitations, we can estimate efficiency through calculation alone. This is called asymptotic complexity analysis, or simply complexity analysis.

    Complexity analysis relates an algorithm's time and space requirements to its input size. It describes how runtime and memory use grow as the input gets larger. We can unpack this definition into three points.

    • Time and space resources correspond to time complexity and space complexity.
    • Growth in input size means complexity relates efficiency to the amount of input data.
    • Growth trends mean we study how quickly time or space increases, rather than its exact value.

    Complexity analysis overcomes limitations of practical testing in several ways.

    • It does not require running code, saving energy and computing resources.
    • It is independent of the test environment, so its conclusions apply across platforms.
    • It reveals efficiency at different input sizes, especially for large inputs.

    Tip

    If complexity still feels unclear, do not worry. The following sections will explain it in detail.

    Complexity analysis gives us a yardstick for evaluating the time and space an algorithm needs and comparing different algorithms.

    As a mathematical concept, complexity can be abstract and challenging for beginners. This makes it a difficult topic to introduce first. Yet discussing a data structure or algorithm's properties inevitably involves runtime and memory use.

    Before studying data structures and algorithms in depth, build an initial understanding of complexity analysis so that you can analyze simple algorithms.

    ","path":["Chapter 2.   Complexity analysis","2.1   Evaluating algorithm efficiency"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/","level":1,"title":"2.4   Space complexity","text":"

    Space complexity measures how an algorithm's memory use grows as the input gets larger. It is much like time complexity, with memory usage replacing runtime.

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#241-space-used-by-an-algorithm","level":2,"title":"2.4.1   Space used by an algorithm","text":"

    The memory an algorithm uses during execution falls into these main categories.

    • Input space: Stores the input data.
    • Temporary space: Stores variables, objects, function contexts, and other working data.
    • Output space: Stores the output data.

    Space complexity generally counts temporary space plus output space.

    Temporary space can be divided further into three parts.

    • Temporary data: Constants, variables, objects, and other data used during execution.
    • Stack frames: Context data for function calls. Each call creates a frame at the top of the stack, which is released when the function returns.
    • Instruction space: Compiled program instructions, usually ignored in the analysis.

    We usually count temporary data, stack frames, and output data when analyzing space complexity, as shown in Figure 2-15.

    Figure 2-15   Space used by an algorithm

    The corresponding code is:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class Node:\n    \"\"\"Class\"\"\"\n    def __init__(self, x: int):\n        self.val: int = x              # Node value\n        self.next: Node | None = None  # Reference to the next node\n\ndef function() -> int:\n    \"\"\"Function\"\"\"\n    # Perform some operations...\n    return 0\n\ndef algorithm(n) -> int:  # Input data\n    A = 0                 # Temporary data (constant, conventionally named in uppercase)\n    b = 0                 # Temporary data (variable)\n    node = Node(0)        # Temporary data (object)\n    c = function()        # Stack frame (function call)\n    return A + b + c      # Output data\n
    /* Structure */\nstruct Node {\n    int val;\n    Node *next;\n    Node(int x) : val(x), next(nullptr) {}\n};\n\n/* Function */\nint func() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) {        // Input data\n    const int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node* node = new Node(0); // Temporary data (object)\n    int c = func();           // Stack frame (function call)\n    return a + b + c;         // Output data\n}\n
    /* Class */\nclass Node {\n    int val;\n    Node next;\n    Node(int x) { val = x; }\n}\n\n/* Function */\nint function() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) {        // Input data\n    final int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node node = new Node(0);  // Temporary data (object)\n    int c = function();       // Stack frame (function call)\n    return a + b + c;         // Output data\n}\n
    /* Class */\nclass Node(int x) {\n    int val = x;\n    Node next;\n}\n\n/* Function */\nint Function() {\n    // Perform some operations...\n    return 0;\n}\n\nint Algorithm(int n) {        // Input data\n    const int a = 0;          // Temporary data (constant)\n    int b = 0;                // Temporary data (variable)\n    Node node = new(0);       // Temporary data (object)\n    int c = Function();       // Stack frame (function call)\n    return a + b + c;         // Output data\n}\n
    /* Structure */\ntype node struct {\n    val  int\n    next *node\n}\n\n/* Create a node structure  */\nfunc newNode(val int) *node {\n    return &node{val: val}\n}\n\n/* Function */\nfunc function() int {\n    // Perform some operations...\n    return 0\n}\n\nfunc algorithm(n int) int { // Input data\n    const a = 0             // Temporary data (constant)\n    b := 0                  // Temporary data (variable)\n    newNode(0)              // Temporary data (object)\n    c := function()         // Stack frame (function call)\n    return a + b + c        // Output data\n}\n
    /* Class */\nclass Node {\n    var val: Int\n    var next: Node?\n\n    init(x: Int) {\n        val = x\n    }\n}\n\n/* Function */\nfunc function() -> Int {\n    // Perform some operations...\n    return 0\n}\n\nfunc algorithm(n: Int) -> Int { // Input data\n    let a = 0             // Temporary data (constant)\n    var b = 0             // Temporary data (variable)\n    let node = Node(x: 0) // Temporary data (object)\n    let c = function()    // Stack frame (function call)\n    return a + b + c      // Output data\n}\n
    /* Class */\nclass Node {\n    val;\n    next;\n    constructor(val) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.next = null;                       // Reference to the next node\n    }\n}\n\n/* Function */\nfunction constFunc() {\n    // Perform some operations\n    return 0;\n}\n\nfunction algorithm(n) {       // Input data\n    const a = 0;              // Temporary data (constant)\n    let b = 0;                // Temporary data (variable)\n    const node = new Node(0); // Temporary data (object)\n    const c = constFunc();    // Stack frame (function call)\n    return a + b + c;         // Output data\n}\n
    /* Class */\nclass Node {\n    val: number;\n    next: Node | null;\n    constructor(val?: number) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.next = null;                       // Reference to the next node\n    }\n}\n\n/* Function */\nfunction constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n\nfunction algorithm(n: number): number { // Input data\n    const a = 0;                        // Temporary data (constant)\n    let b = 0;                          // Temporary data (variable)\n    const node = new Node(0);           // Temporary data (object)\n    const c = constFunc();              // Stack frame (function call)\n    return a + b + c;                   // Output data\n}\n
    /* Class */\nclass Node {\n  int val;\n  Node next;\n  Node(this.val, [this.next]);\n}\n\n/* Function */\nint function() {\n  // Perform some operations...\n  return 0;\n}\n\nint algorithm(int n) {  // Input data\n  const int a = 0;      // Temporary data (constant)\n  int b = 0;            // Temporary data (variable)\n  Node node = Node(0);  // Temporary data (object)\n  int c = function();   // Stack frame (function call)\n  return a + b + c;     // Output data\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Structure */\nstruct Node {\n    val: i32,\n    next: Option<Rc<RefCell<Node>>>,\n}\n\n/* Create a Node structure */\nimpl Node {\n    fn new(val: i32) -> Self {\n        Self { val: val, next: None }\n    }\n}\n\n/* Function */\nfn function() -> i32 {      \n    // Perform some operations...\n    return 0;\n}\n\nfn algorithm(n: i32) -> i32 {       // Input data\n    const a: i32 = 0;               // Temporary data (constant)\n    let mut b = 0;                  // Temporary data (variable)\n    let node = Node::new(0);        // Temporary data (object)\n    let c = function();             // Stack frame (function call)\n    return a + b + c;               // Output data\n}\n
    /* Function */\nint func() {\n    // Perform some operations...\n    return 0;\n}\n\nint algorithm(int n) { // Input data\n    const int a = 0;   // Temporary data (constant)\n    int b = 0;         // Temporary data (variable)\n    int c = func();    // Stack frame (function call)\n    return a + b + c;  // Output data\n}\n
    /* Class */\nclass Node(var _val: Int) {\n    var next: Node? = null\n}\n\n/* Function */\nfun function(): Int {\n    // Perform some operations...\n    return 0\n}\n\nfun algorithm(n: Int): Int { // Input data\n    val a = 0                // Temporary data (constant)\n    var b = 0                // Temporary data (variable)\n    val node = Node(0)       // Temporary data (object)\n    val c = function()       // Stack frame (function call)\n    return a + b + c         // Output data\n}\n
    ### Class ###\nclass Node\n    attr_accessor :val      # Node value\n    attr_accessor :next     # Reference to the next node\n\n    def initialize(x)\n        @val = x\n    end\nend\n\n### Function ###\ndef function\n    # Perform some operations...\n    0\nend\n\n### Algorithm ###\ndef algorithm(n)        # Input data\n    a = 0               # Temporary data (constant)\n    b = 0               # Temporary data (variable)\n    node = Node.new(0)  # Temporary data (object)\n    c = function        # Stack frame (function call)\n    a + b + c           # Output data\nend\n
    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#242-deriving-space-complexity","level":2,"title":"2.4.2   Deriving space complexity","text":"

    The approach is much like time complexity analysis, except that we count memory used rather than operations performed.

    Unlike time complexity, we usually focus only on worst-case space complexity. Memory is a hard requirement: enough must be available for every valid input.

    In the following code, \"worst case\" has two meanings.

    1. The worst input: When \\(n < 10\\), space complexity is \\(O(1)\\). When \\(n > 10\\), initializing nums requires \\(O(n)\\) space, so worst-case space complexity is \\(O(n)\\).
    2. Peak memory during execution: Before the final line, the program uses \\(O(1)\\) space. Initializing nums raises usage to \\(O(n)\\), so worst-case space complexity is \\(O(n)\\).
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def algorithm(n: int):\n    a = 0               # O(1)\n    b = [0] * 10000     # O(1)\n    if n > 10:\n        nums = [0] * n  # O(n)\n
    void algorithm(int n) {\n    int a = 0;               // O(1)\n    vector<int> b(10000);    // O(1)\n    if (n > 10)\n        vector<int> nums(n); // O(n)\n}\n
    void algorithm(int n) {\n    int a = 0;                   // O(1)\n    int[] b = new int[10000];    // O(1)\n    if (n > 10)\n        int[] nums = new int[n]; // O(n)\n}\n
    void Algorithm(int n) {\n    int a = 0;                   // O(1)\n    int[] b = new int[10000];    // O(1)\n    if (n > 10) {\n        int[] nums = new int[n]; // O(n)\n    }\n}\n
    func algorithm(n int) {\n    a := 0                      // O(1)\n    b := make([]int, 10000)     // O(1)\n    var nums []int\n    if n > 10 {\n        nums := make([]int, n)  // O(n)\n    }\n    fmt.Println(a, b, nums)\n}\n
    func algorithm(n: Int) {\n    let a = 0 // O(1)\n    let b = Array(repeating: 0, count: 10000) // O(1)\n    if n > 10 {\n        let nums = Array(repeating: 0, count: n) // O(n)\n    }\n}\n
    function algorithm(n) {\n    const a = 0;                   // O(1)\n    const b = new Array(10000);    // O(1)\n    if (n > 10) {\n        const nums = new Array(n); // O(n)\n    }\n}\n
    function algorithm(n: number): void {\n    const a = 0;                   // O(1)\n    const b = new Array(10000);    // O(1)\n    if (n > 10) {\n        const nums = new Array(n); // O(n)\n    }\n}\n
    void algorithm(int n) {\n  int a = 0;                            // O(1)\n  List<int> b = List.filled(10000, 0);  // O(1)\n  if (n > 10) {\n    List<int> nums = List.filled(n, 0); // O(n)\n  }\n}\n
    fn algorithm(n: i32) {\n    let a = 0;                              // O(1)\n    let b = [0; 10000];                     // O(1)\n    if n > 10 {\n        let nums = vec![0; n as usize];     // O(n)\n    }\n}\n
    void algorithm(int n) {\n    int a = 0;               // O(1)\n    int b[10000];            // O(1)\n    if (n > 10)\n        int nums[n] = {0};   // O(n)\n}\n
    fun algorithm(n: Int) {\n    val a = 0                    // O(1)\n    val b = IntArray(10000)      // O(1)\n    if (n > 10) {\n        val nums = IntArray(n)   // O(n)\n    }\n}\n
    def algorithm(n)\n    a = 0                           # O(1)\n    b = Array.new(10000)            # O(1)\n    nums = Array.new(n) if n > 10   # O(n)\nend\n

    For recursive functions, remember to count stack frames. Consider the following code:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def function() -> int:\n    # Perform some operations\n    return 0\n\ndef loop(n: int):\n    \"\"\"The loop uses O(1) space\"\"\"\n    for _ in range(n):\n        function()\n\ndef recur(n: int):\n    \"\"\"The recursion uses O(n) space\"\"\"\n    if n == 1:\n        return\n    return recur(n - 1)\n
    int func() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n/* The recursion uses O(n) space */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
    int function() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        function();\n    }\n}\n/* The recursion uses O(n) space */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
    int Function() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nvoid Loop(int n) {\n    for (int i = 0; i < n; i++) {\n        Function();\n    }\n}\n/* The recursion uses O(n) space */\nint Recur(int n) {\n    if (n == 1) return 1;\n    return Recur(n - 1);\n}\n
    func function() int {\n    // Perform some operations\n    return 0\n}\n\n/* The loop uses O(1) space */\nfunc loop(n int) {\n    for i := 0; i < n; i++ {\n        function()\n    }\n}\n\n/* The recursion uses O(n) space */\nfunc recur(n int) {\n    if n == 1 {\n        return\n    }\n    recur(n - 1)\n}\n
    @discardableResult\nfunc function() -> Int {\n    // Perform some operations\n    return 0\n}\n\n/* The loop uses O(1) space */\nfunc loop(n: Int) {\n    for _ in 0 ..< n {\n        function()\n    }\n}\n\n/* The recursion uses O(n) space */\nfunc recur(n: Int) {\n    if n == 1 {\n        return\n    }\n    recur(n: n - 1)\n}\n
    function constFunc() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nfunction loop(n) {\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n/* The recursion uses O(n) space */\nfunction recur(n) {\n    if (n === 1) return;\n    return recur(n - 1);\n}\n
    function constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nfunction loop(n: number): void {\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n/* The recursion uses O(n) space */\nfunction recur(n: number): void {\n    if (n === 1) return;\n    return recur(n - 1);\n}\n
    int function() {\n  // Perform some operations\n  return 0;\n}\n/* The loop uses O(1) space */\nvoid loop(int n) {\n  for (int i = 0; i < n; i++) {\n    function();\n  }\n}\n/* The recursion uses O(n) space */\nvoid recur(int n) {\n  if (n == 1) return;\n  recur(n - 1);\n}\n
    fn function() -> i32 {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nfn loop(n: i32) {\n    for i in 0..n {\n        function();\n    }\n}\n/* The recursion uses O(n) space */\nfn recur(n: i32) {\n    if n == 1 {\n        return;\n    }\n    recur(n - 1);\n}\n
    int func() {\n    // Perform some operations\n    return 0;\n}\n/* The loop uses O(1) space */\nvoid loop(int n) {\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n/* The recursion uses O(n) space */\nvoid recur(int n) {\n    if (n == 1) return;\n    recur(n - 1);\n}\n
    fun function(): Int {\n    // Perform some operations\n    return 0\n}\n/* The loop uses O(1) space */\nfun loop(n: Int) {\n    for (i in 0..<n) {\n        function()\n    }\n}\n/* The recursion uses O(n) space */\nfun recur(n: Int) {\n    if (n == 1) return\n    return recur(n - 1)\n}\n
    def function\n    # Perform some operations\n    0\nend\n\n### The loop uses O(1) space ###\ndef loop(n)\n    (0...n).each { function }\nend\n\n### The recursion uses O(n) space ###\ndef recur(n)\n    return if n == 1\n    recur(n - 1)\nend\n

    Both loop() and recur() take \\(O(n)\\) time, but their space complexities differ.

    • loop() calls function() \\(n\\) times. Each call returns and releases its stack frame before the next iteration, so space complexity remains \\(O(1)\\).
    • During recur(), \\(n\\) calls can remain active at once, using \\(O(n)\\) space for stack frames.
    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#243-common-complexity-classes","level":2,"title":"2.4.3   Common complexity classes","text":"

    For input size \\(n\\), Figure 2-16 shows common space complexities in increasing order of growth.

    \\[ \\begin{aligned} & O(1) < O(\\log n) < O(n) < O(n^2) < O(2^n) \\newline & \\text{Constant} < \\text{Logarithmic} < \\text{Linear} < \\text{Quadratic} < \\text{Exponential} \\end{aligned} \\]

    Figure 2-16   Common space complexity classes

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#1-constant-space-o1","level":3,"title":"1.   Constant space \\(O(1)\\)","text":"

    Constant space commonly occurs when the number of constants, variables, and objects is independent of input size \\(n\\).

    Memory for variables initialized or functions called within a loop is released by the next iteration, so it does not accumulate. Space complexity remains \\(O(1)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def function() -> int:\n    \"\"\"Function\"\"\"\n    # Perform some operations\n    return 0\n\ndef constant(n: int):\n    \"\"\"Constant complexity\"\"\"\n    # Constants, variables, and objects occupy O(1) space\n    a = 0\n    nums = [0] * 10000\n    node = ListNode(0)\n    # Variables in the loop occupy O(1) space\n    for _ in range(n):\n        c = 0\n    # Function calls in the loop occupy O(1) space\n    for _ in range(n):\n        function()\n
    space_complexity.cpp
    /* Function */\nint func() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nvoid constant(int n) {\n    // Constants, variables, and objects occupy O(1) space\n    const int a = 0;\n    int b = 0;\n    vector<int> nums(10000);\n    ListNode node(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n
    space_complexity.java
    /* Function */\nint function() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nvoid constant(int n) {\n    // Constants, variables, and objects occupy O(1) space\n    final int a = 0;\n    int b = 0;\n    int[] nums = new int[10000];\n    ListNode node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        function();\n    }\n}\n
    space_complexity.cs
    /* Function */\nint Function() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nvoid Constant(int n) {\n    // Constants, variables, and objects occupy O(1) space\n    int a = 0;\n    int b = 0;\n    int[] nums = new int[10000];\n    ListNode node = new(0);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        Function();\n    }\n}\n
    space_complexity.go
    /* Function */\nfunc function() int {\n    // Perform some operations...\n    return 0\n}\n\n/* Constant complexity */\nfunc spaceConstant(n int) {\n    // Constants, variables, and objects occupy O(1) space\n    const a = 0\n    b := 0\n    nums := make([]int, 10000)\n    node := newNode(0)\n    // Variables in the loop occupy O(1) space\n    var c int\n    for i := 0; i < n; i++ {\n        c = 0\n    }\n    // Function calls in the loop occupy O(1) space\n    for i := 0; i < n; i++ {\n        function()\n    }\n    b += 0\n    c += 0\n    nums[0] = 0\n    node.val = 0\n}\n
    space_complexity.swift
    /* Function */\n@discardableResult\nfunc function() -> Int {\n    // Perform some operations\n    return 0\n}\n\n/* Constant complexity */\nfunc constant(n: Int) {\n    // Constants, variables, and objects occupy O(1) space\n    let a = 0\n    var b = 0\n    let nums = Array(repeating: 0, count: 10000)\n    let node = ListNode(x: 0)\n    // Variables in the loop occupy O(1) space\n    for _ in 0 ..< n {\n        let c = 0\n    }\n    // Function calls in the loop occupy O(1) space\n    for _ in 0 ..< n {\n        function()\n    }\n}\n
    space_complexity.js
    /* Function */\nfunction constFunc() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nfunction constant(n) {\n    // Constants, variables, and objects occupy O(1) space\n    const a = 0;\n    const b = 0;\n    const nums = new Array(10000);\n    const node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        const c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n
    space_complexity.ts
    /* Function */\nfunction constFunc(): number {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nfunction constant(n: number): void {\n    // Constants, variables, and objects occupy O(1) space\n    const a = 0;\n    const b = 0;\n    const nums = new Array(10000);\n    const node = new ListNode(0);\n    // Variables in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        const c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (let i = 0; i < n; i++) {\n        constFunc();\n    }\n}\n
    space_complexity.dart
    /* Function */\nint function() {\n  // Perform some operations\n  return 0;\n}\n\n/* Constant complexity */\nvoid constant(int n) {\n  // Constants, variables, and objects occupy O(1) space\n  final int a = 0;\n  int b = 0;\n  List<int> nums = List.filled(10000, 0);\n  ListNode node = ListNode(0);\n  // Variables in the loop occupy O(1) space\n  for (var i = 0; i < n; i++) {\n    int c = 0;\n  }\n  // Function calls in the loop occupy O(1) space\n  for (var i = 0; i < n; i++) {\n    function();\n  }\n}\n
    space_complexity.rs
    /* Function */\nfn function() -> i32 {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\n#[allow(unused)]\nfn constant(n: i32) {\n    // Constants, variables, and objects occupy O(1) space\n    const A: i32 = 0;\n    let b = 0;\n    let nums = vec![0; 10000];\n    let node = ListNode::new(0);\n    // Variables in the loop occupy O(1) space\n    for i in 0..n {\n        let c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for i in 0..n {\n        function();\n    }\n}\n
    space_complexity.c
    /* Function */\nint func() {\n    // Perform some operations\n    return 0;\n}\n\n/* Constant complexity */\nvoid constant(int n) {\n    // Constants, variables, and objects occupy O(1) space\n    const int a = 0;\n    int b = 0;\n    int nums[1000];\n    ListNode *node = newListNode(0);\n    free(node);\n    // Variables in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        int c = 0;\n    }\n    // Function calls in the loop occupy O(1) space\n    for (int i = 0; i < n; i++) {\n        func();\n    }\n}\n
    space_complexity.kt
    /* Function */\nfun function(): Int {\n    // Perform some operations\n    return 0\n}\n\n/* Constant complexity */\nfun constant(n: Int) {\n    // Constants, variables, and objects occupy O(1) space\n    val a = 0\n    var b = 0\n    val nums = Array(10000) { 0 }\n    val node = ListNode(0)\n    // Variables in the loop occupy O(1) space\n    for (i in 0..<n) {\n        val c = 0\n    }\n    // Function calls in the loop occupy O(1) space\n    for (i in 0..<n) {\n        function()\n    }\n}\n
    space_complexity.rb
    ### Function ###\ndef function\n  # Perform some operations\n  0\nend\n\n### Constant complexity ###\ndef constant(n)\n  # Constants, variables, and objects occupy O(1) space\n  a = 0\n  nums = [0] * 10000\n  node = ListNode.new\n\n  # Variables in the loop occupy O(1) space\n  (0...n).each { c = 0 }\n  # Function calls in the loop occupy O(1) space\n  (0...n).each { function }\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#2-linear-space-on","level":3,"title":"2.   Linear space \\(O(n)\\)","text":"

    Linear space commonly occurs in arrays, linked lists, stacks, and queues whose element counts are proportional to \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def linear(n: int):\n    \"\"\"Linear complexity\"\"\"\n    # A list of length n occupies O(n) space\n    nums = [0] * n\n    # A hash table with n entries occupies O(n) space\n    hmap = dict[int, str]()\n    for i in range(n):\n        hmap[i] = str(i)\n
    space_complexity.cpp
    /* Linear complexity */\nvoid linear(int n) {\n    // An array of length n uses O(n) space\n    vector<int> nums(n);\n    // A list of length n occupies O(n) space\n    vector<ListNode> nodes;\n    for (int i = 0; i < n; i++) {\n        nodes.push_back(ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    unordered_map<int, string> map;\n    for (int i = 0; i < n; i++) {\n        map[i] = to_string(i);\n    }\n}\n
    space_complexity.java
    /* Linear complexity */\nvoid linear(int n) {\n    // An array of length n uses O(n) space\n    int[] nums = new int[n];\n    // A list of length n occupies O(n) space\n    List<ListNode> nodes = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        nodes.add(new ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    Map<Integer, String> map = new HashMap<>();\n    for (int i = 0; i < n; i++) {\n        map.put(i, String.valueOf(i));\n    }\n}\n
    space_complexity.cs
    /* Linear complexity */\nvoid Linear(int n) {\n    // An array of length n uses O(n) space\n    int[] nums = new int[n];\n    // A list of length n occupies O(n) space\n    List<ListNode> nodes = [];\n    for (int i = 0; i < n; i++) {\n        nodes.Add(new ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    Dictionary<int, string> map = [];\n    for (int i = 0; i < n; i++) {\n        map.Add(i, i.ToString());\n    }\n}\n
    space_complexity.go
    /* Linear complexity */\nfunc spaceLinear(n int) {\n    // An array of length n uses O(n) space\n    _ = make([]int, n)\n    // A list of length n occupies O(n) space\n    var nodes []*node\n    for i := 0; i < n; i++ {\n        nodes = append(nodes, newNode(i))\n    }\n    // A hash table with n entries occupies O(n) space\n    m := make(map[int]string, n)\n    for i := 0; i < n; i++ {\n        m[i] = strconv.Itoa(i)\n    }\n}\n
    space_complexity.swift
    /* Linear complexity */\nfunc linear(n: Int) {\n    // An array of length n uses O(n) space\n    let nums = Array(repeating: 0, count: n)\n    // A list of length n occupies O(n) space\n    let nodes = (0 ..< n).map { ListNode(x: $0) }\n    // A hash table with n entries occupies O(n) space\n    let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, \"\\($0)\") })\n}\n
    space_complexity.js
    /* Linear complexity */\nfunction linear(n) {\n    // An array of length n uses O(n) space\n    const nums = new Array(n);\n    // A list of length n occupies O(n) space\n    const nodes = [];\n    for (let i = 0; i < n; i++) {\n        nodes.push(new ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    const map = new Map();\n    for (let i = 0; i < n; i++) {\n        map.set(i, i.toString());\n    }\n}\n
    space_complexity.ts
    /* Linear complexity */\nfunction linear(n: number): void {\n    // An array of length n uses O(n) space\n    const nums = new Array(n);\n    // A list of length n occupies O(n) space\n    const nodes: ListNode[] = [];\n    for (let i = 0; i < n; i++) {\n        nodes.push(new ListNode(i));\n    }\n    // A hash table with n entries occupies O(n) space\n    const map = new Map();\n    for (let i = 0; i < n; i++) {\n        map.set(i, i.toString());\n    }\n}\n
    space_complexity.dart
    /* Linear complexity */\nvoid linear(int n) {\n  // An array of length n uses O(n) space\n  List<int> nums = List.filled(n, 0);\n  // A list of length n occupies O(n) space\n  List<ListNode> nodes = [];\n  for (var i = 0; i < n; i++) {\n    nodes.add(ListNode(i));\n  }\n  // A hash table with n entries occupies O(n) space\n  Map<int, String> map = HashMap();\n  for (var i = 0; i < n; i++) {\n    map.putIfAbsent(i, () => i.toString());\n  }\n}\n
    space_complexity.rs
    /* Linear complexity */\n#[allow(unused)]\nfn linear(n: i32) {\n    // An array of length n uses O(n) space\n    let mut nums = vec![0; n as usize];\n    // A list of length n occupies O(n) space\n    let mut nodes = Vec::new();\n    for i in 0..n {\n        nodes.push(ListNode::new(i))\n    }\n    // A hash table with n entries occupies O(n) space\n    let mut map = HashMap::new();\n    for i in 0..n {\n        map.insert(i, i.to_string());\n    }\n}\n
    space_complexity.c
    /* Hash table */\ntypedef struct {\n    int key;\n    int val;\n    UT_hash_handle hh; // Implemented using uthash.h\n} HashTable;\n\n/* Linear complexity */\nvoid linear(int n) {\n    // An array of length n uses O(n) space\n    int *nums = malloc(sizeof(int) * n);\n    free(nums);\n\n    // A list of length n occupies O(n) space\n    ListNode **nodes = malloc(sizeof(ListNode *) * n);\n    for (int i = 0; i < n; i++) {\n        nodes[i] = newListNode(i);\n    }\n    // Free memory\n    for (int i = 0; i < n; i++) {\n        free(nodes[i]);\n    }\n    free(nodes);\n\n    // A hash table with n entries occupies O(n) space\n    HashTable *h = NULL;\n    for (int i = 0; i < n; i++) {\n        HashTable *tmp = malloc(sizeof(HashTable));\n        tmp->key = i;\n        tmp->val = i;\n        HASH_ADD_INT(h, key, tmp);\n    }\n\n    // Free memory\n    HashTable *curr, *tmp;\n    HASH_ITER(hh, h, curr, tmp) {\n        HASH_DEL(h, curr);\n        free(curr);\n    }\n}\n
    space_complexity.kt
    /* Linear complexity */\nfun linear(n: Int) {\n    // An array of length n uses O(n) space\n    val nums = Array(n) { 0 }\n    // A list of length n occupies O(n) space\n    val nodes = mutableListOf<ListNode>()\n    for (i in 0..<n) {\n        nodes.add(ListNode(i))\n    }\n    // A hash table with n entries occupies O(n) space\n    val map = mutableMapOf<Int, String>()\n    for (i in 0..<n) {\n        map[i] = i.toString()\n    }\n}\n
    space_complexity.rb
    ### Linear complexity ###\ndef linear(n)\n  # A list of length n occupies O(n) space\n  nums = Array.new(n, 0)\n\n  # A hash table with n entries occupies O(n) space\n  hmap = {}\n  for i in 0...n\n    hmap[i] = i.to_s\n  end\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 2-17, this function has recursion depth \\(n\\), with \\(n\\) active calls to linear_recur() at once, using \\(O(n)\\) space for stack frames:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def linear_recur(n: int):\n    \"\"\"Linear complexity (recursive implementation)\"\"\"\n    print(\"Recursion: n =\", n)\n    if n == 1:\n        return\n    linear_recur(n - 1)\n
    space_complexity.cpp
    /* Linear complexity (recursive implementation) */\nvoid linearRecur(int n) {\n    cout << \"Recursion: n = \" << n << endl;\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
    space_complexity.java
    /* Linear complexity (recursive implementation) */\nvoid linearRecur(int n) {\n    System.out.println(\"Recursive call: n = \" + n);\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
    space_complexity.cs
    /* Linear complexity (recursive implementation) */\nvoid LinearRecur(int n) {\n    Console.WriteLine(\"Recursive call: n = \" + n);\n    if (n == 1) return;\n    LinearRecur(n - 1);\n}\n
    space_complexity.go
    /* Linear complexity (recursive implementation) */\nfunc spaceLinearRecur(n int) {\n    fmt.Println(\"Recursive call: n =\", n)\n    if n == 1 {\n        return\n    }\n    spaceLinearRecur(n - 1)\n}\n
    space_complexity.swift
    /* Linear complexity (recursive implementation) */\nfunc linearRecur(n: Int) {\n    print(\"Recursion: n = \\(n)\")\n    if n == 1 {\n        return\n    }\n    linearRecur(n: n - 1)\n}\n
    space_complexity.js
    /* Linear complexity (recursive implementation) */\nfunction linearRecur(n) {\n    console.log(`Recursive call with n = ${n}`);\n    if (n === 1) return;\n    linearRecur(n - 1);\n}\n
    space_complexity.ts
    /* Linear complexity (recursive implementation) */\nfunction linearRecur(n: number): void {\n    console.log(`Recursive call with n = ${n}`);\n    if (n === 1) return;\n    linearRecur(n - 1);\n}\n
    space_complexity.dart
    /* Linear complexity (recursive implementation) */\nvoid linearRecur(int n) {\n  print('Recursive call: n = $n');\n  if (n == 1) return;\n  linearRecur(n - 1);\n}\n
    space_complexity.rs
    /* Linear complexity (recursive implementation) */\nfn linear_recur(n: i32) {\n    println!(\"Recursive call n = {}\", n);\n    if n == 1 {\n        return;\n    };\n    linear_recur(n - 1);\n}\n
    space_complexity.c
    /* Linear complexity (recursive implementation) */\nvoid linearRecur(int n) {\n    printf(\"Recursive call n = %d\\r\\n\", n);\n    if (n == 1)\n        return;\n    linearRecur(n - 1);\n}\n
    space_complexity.kt
    /* Linear complexity (recursive implementation) */\nfun linearRecur(n: Int) {\n    println(\"Recursive call: n = $n\")\n    if (n == 1)\n        return\n    linearRecur(n - 1)\n}\n
    space_complexity.rb
    ### Linear complexity (recursive implementation)###\ndef linear_recur(n)\n  puts \"Recursion: n = #{n}\"\n  return if n == 1\n  linear_recur(n - 1)\nend\n
    Visualize execution

    View full screen >

    Figure 2-17   Linear space complexity from recursion

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#3-quadratic-space-on2","level":3,"title":"3.   Quadratic space \\(O(n^2)\\)","text":"

    Quadratic space commonly occurs in matrices and graphs whose element counts grow with the square of \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def quadratic(n: int):\n    \"\"\"Quadratic complexity\"\"\"\n    # The two-dimensional list occupies O(n^2) space\n    num_matrix = [[0] * n for _ in range(n)]\n
    space_complexity.cpp
    /* Quadratic complexity */\nvoid quadratic(int n) {\n    // The two-dimensional list occupies O(n^2) space\n    vector<vector<int>> numMatrix;\n    for (int i = 0; i < n; i++) {\n        vector<int> tmp;\n        for (int j = 0; j < n; j++) {\n            tmp.push_back(0);\n        }\n        numMatrix.push_back(tmp);\n    }\n}\n
    space_complexity.java
    /* Quadratic complexity */\nvoid quadratic(int n) {\n    // The matrix uses O(n^2) space\n    int[][] numMatrix = new int[n][n];\n    // The two-dimensional list occupies O(n^2) space\n    List<List<Integer>> numList = new ArrayList<>();\n    for (int i = 0; i < n; i++) {\n        List<Integer> tmp = new ArrayList<>();\n        for (int j = 0; j < n; j++) {\n            tmp.add(0);\n        }\n        numList.add(tmp);\n    }\n}\n
    space_complexity.cs
    /* Quadratic complexity */\nvoid Quadratic(int n) {\n    // The matrix uses O(n^2) space\n    int[,] numMatrix = new int[n, n];\n    // The two-dimensional list occupies O(n^2) space\n    List<List<int>> numList = [];\n    for (int i = 0; i < n; i++) {\n        List<int> tmp = [];\n        for (int j = 0; j < n; j++) {\n            tmp.Add(0);\n        }\n        numList.Add(tmp);\n    }\n}\n
    space_complexity.go
    /* Quadratic complexity */\nfunc spaceQuadratic(n int) {\n    // The matrix uses O(n^2) space\n    numMatrix := make([][]int, n)\n    for i := 0; i < n; i++ {\n        numMatrix[i] = make([]int, n)\n    }\n}\n
    space_complexity.swift
    /* Quadratic complexity */\nfunc quadratic(n: Int) {\n    // A fully materialized two-dimensional list uses O(n^2) space; Swift may share repeated row storage\n    let numList = Array(repeating: Array(repeating: 0, count: n), count: n)\n}\n
    space_complexity.js
    /* Quadratic complexity */\nfunction quadratic(n) {\n    // The matrix uses O(n^2) space\n    const numMatrix = Array(n)\n        .fill(null)\n        .map(() => Array(n).fill(null));\n    // The two-dimensional list occupies O(n^2) space\n    const numList = [];\n    for (let i = 0; i < n; i++) {\n        const tmp = [];\n        for (let j = 0; j < n; j++) {\n            tmp.push(0);\n        }\n        numList.push(tmp);\n    }\n}\n
    space_complexity.ts
    /* Quadratic complexity */\nfunction quadratic(n: number): void {\n    // The matrix uses O(n^2) space\n    const numMatrix = Array(n)\n        .fill(null)\n        .map(() => Array(n).fill(null));\n    // The two-dimensional list occupies O(n^2) space\n    const numList = [];\n    for (let i = 0; i < n; i++) {\n        const tmp = [];\n        for (let j = 0; j < n; j++) {\n            tmp.push(0);\n        }\n        numList.push(tmp);\n    }\n}\n
    space_complexity.dart
    /* Quadratic complexity */\nvoid quadratic(int n) {\n  // The matrix uses O(n^2) space\n  List<List<int>> numMatrix = List.generate(n, (_) => List.filled(n, 0));\n  // The two-dimensional list occupies O(n^2) space\n  List<List<int>> numList = [];\n  for (var i = 0; i < n; i++) {\n    List<int> tmp = [];\n    for (int j = 0; j < n; j++) {\n      tmp.add(0);\n    }\n    numList.add(tmp);\n  }\n}\n
    space_complexity.rs
    /* Quadratic complexity */\n#[allow(unused)]\nfn quadratic(n: i32) {\n    // The matrix uses O(n^2) space\n    let num_matrix = vec![vec![0; n as usize]; n as usize];\n    // The two-dimensional list occupies O(n^2) space\n    let mut num_list = Vec::new();\n    for i in 0..n {\n        let mut tmp = Vec::new();\n        for j in 0..n {\n            tmp.push(0);\n        }\n        num_list.push(tmp);\n    }\n}\n
    space_complexity.c
    /* Quadratic complexity */\nvoid quadratic(int n) {\n    // The two-dimensional list occupies O(n^2) space\n    int **numMatrix = malloc(sizeof(int *) * n);\n    for (int i = 0; i < n; i++) {\n        int *tmp = malloc(sizeof(int) * n);\n        for (int j = 0; j < n; j++) {\n            tmp[j] = 0;\n        }\n        numMatrix[i] = tmp;\n    }\n\n    // Free memory\n    for (int i = 0; i < n; i++) {\n        free(numMatrix[i]);\n    }\n    free(numMatrix);\n}\n
    space_complexity.kt
    /* Quadratic complexity */\nfun quadratic(n: Int) {\n    // A full n*n matrix uses O(n^2) space; only its outer array is allocated here\n    val numMatrix = arrayOfNulls<Array<Int>?>(n)\n    // The two-dimensional list occupies O(n^2) space\n    val numList = mutableListOf<MutableList<Int>>()\n    for (i in 0..<n) {\n        val tmp = mutableListOf<Int>()\n        for (j in 0..<n) {\n            tmp.add(0)\n        }\n        numList.add(tmp)\n    }\n}\n
    space_complexity.rb
    ### Quadratic complexity ###\ndef quadratic(n)\n  # The two-dimensional list occupies O(n^2) space\n  Array.new(n) { Array.new(n, 0) }\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 2-18, the recursion depth is \\(n\\), and each call initializes an array. Their lengths are \\(n\\), \\(n-1\\), \\(\\dots\\), \\(2\\), and \\(1\\), averaging \\(n / 2\\), so total space is \\(O(n^2)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def quadratic_recur(n: int) -> int:\n    \"\"\"Quadratic complexity (recursive implementation)\"\"\"\n    if n <= 0:\n        return 0\n    # The lengths of array nums are n, n-1, ..., 2, 1\n    nums = [0] * n\n    return quadratic_recur(n - 1)\n
    space_complexity.cpp
    /* Quadratic complexity (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    vector<int> nums(n);\n    cout << \"Recursion: n = \" << n << \" , length of nums = \" << nums.size() << endl;\n    return quadraticRecur(n - 1);\n}\n
    space_complexity.java
    /* Quadratic complexity (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    // The lengths of array nums are n, n-1, ..., 2, 1\n    int[] nums = new int[n];\n    System.out.println(\"Recursive call: n = \" + n + \" with nums length = \" + nums.length);\n    return quadraticRecur(n - 1);\n}\n
    space_complexity.cs
    /* Quadratic complexity (recursive implementation) */\nint QuadraticRecur(int n) {\n    if (n <= 0) return 0;\n    int[] nums = new int[n];\n    Console.WriteLine(\"Recursive call: n = \" + n + \" with nums length = \" + nums.Length);\n    return QuadraticRecur(n - 1);\n}\n
    space_complexity.go
    /* Quadratic complexity (recursive implementation) */\nfunc spaceQuadraticRecur(n int) int {\n    if n <= 0 {\n        return 0\n    }\n    nums := make([]int, n)\n    fmt.Printf(\"Recursive call n = %d: length of nums = %d \\n\", n, len(nums))\n    return spaceQuadraticRecur(n - 1)\n}\n
    space_complexity.swift
    /* Quadratic complexity (recursive implementation) */\n@discardableResult\nfunc quadraticRecur(n: Int) -> Int {\n    if n <= 0 {\n        return 0\n    }\n    // The lengths of array nums are n, n-1, ..., 2, 1\n    let nums = Array(repeating: 0, count: n)\n    print(\"At recursion n = \\(n), nums has length \\(nums.count)\")\n    return quadraticRecur(n: n - 1)\n}\n
    space_complexity.js
    /* Quadratic complexity (recursive implementation) */\nfunction quadraticRecur(n) {\n    if (n <= 0) return 0;\n    const nums = new Array(n);\n    console.log(`In the recursive call with n = ${n}, nums has length ${nums.length}`);\n    return quadraticRecur(n - 1);\n}\n
    space_complexity.ts
    /* Quadratic complexity (recursive implementation) */\nfunction quadraticRecur(n: number): number {\n    if (n <= 0) return 0;\n    const nums = new Array(n);\n    console.log(`In the recursive call with n = ${n}, nums has length ${nums.length}`);\n    return quadraticRecur(n - 1);\n}\n
    space_complexity.dart
    /* Quadratic complexity (recursive implementation) */\nint quadraticRecur(int n) {\n  if (n <= 0) return 0;\n  List<int> nums = List.filled(n, 0);\n  print('In the recursive call with n = $n, length of nums = ${nums.length}');\n  return quadraticRecur(n - 1);\n}\n
    space_complexity.rs
    /* Quadratic complexity (recursive implementation) */\nfn quadratic_recur(n: i32) -> i32 {\n    if n <= 0 {\n        return 0;\n    };\n    // The lengths of array nums are n, n-1, ..., 2, 1\n    let nums = vec![0; n as usize];\n    println!(\"In recursive call n = {}, nums has length {}\", n, nums.len());\n    return quadratic_recur(n - 1);\n}\n
    space_complexity.c
    /* Quadratic complexity (recursive implementation) */\nint quadraticRecur(int n) {\n    if (n <= 0)\n        return 0;\n    int *nums = malloc(sizeof(int) * n);\n    printf(\"In recursive call n = %d, nums has length = %d\\r\\n\", n, n);\n    int res = quadraticRecur(n - 1);\n    free(nums);\n    return res;\n}\n
    space_complexity.kt
    /* Quadratic allocation total (recursive example; tailrec removes recursive stack growth) */\ntailrec fun quadraticRecur(n: Int): Int {\n    if (n <= 0)\n        return 0\n    // The lengths of array nums are n, n-1, ..., 2, 1\n    val nums = Array(n) { 0 }\n    println(\"In the recursive call with n = $n, nums has length ${nums.size}\")\n    return quadraticRecur(n - 1)\n}\n
    space_complexity.rb
    ### Quadratic complexity (recursive implementation)###\ndef quadratic_recur(n)\n  return 0 unless n > 0\n\n  # The lengths of array nums are n, n-1, ..., 2, 1\n  nums = Array.new(n, 0)\n  quadratic_recur(n - 1)\nend\n
    Visualize execution

    View full screen >

    Figure 2-18   Quadratic space complexity from recursion

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#4-exponential-space-o2n","level":3,"title":"4.   Exponential space \\(O(2^n)\\)","text":"

    Exponential space commonly occurs in binary trees. In Figure 2-19, a perfect binary tree with \\(n\\) levels contains \\(2^n - 1\\) nodes and uses \\(O(2^n)\\) space:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py
    def build_tree(n: int) -> TreeNode | None:\n    \"\"\"Exponential complexity (building a perfect binary tree)\"\"\"\n    if n == 0:\n        return None\n    root = TreeNode(0)\n    root.left = build_tree(n - 1)\n    root.right = build_tree(n - 1)\n    return root\n
    space_complexity.cpp
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode *buildTree(int n) {\n    if (n == 0)\n        return nullptr;\n    TreeNode *root = new TreeNode(0);\n    root->left = buildTree(n - 1);\n    root->right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.java
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode buildTree(int n) {\n    if (n == 0)\n        return null;\n    TreeNode root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.cs
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode? BuildTree(int n) {\n    if (n == 0) return null;\n    TreeNode root = new(0) {\n        left = BuildTree(n - 1),\n        right = BuildTree(n - 1)\n    };\n    return root;\n}\n
    space_complexity.go
    /* Exponential complexity (building a perfect binary tree) */\nfunc buildTree(n int) *TreeNode {\n    if n == 0 {\n        return nil\n    }\n    root := NewTreeNode(0)\n    root.Left = buildTree(n - 1)\n    root.Right = buildTree(n - 1)\n    return root\n}\n
    space_complexity.swift
    /* Exponential complexity (building a perfect binary tree) */\nfunc buildTree(n: Int) -> TreeNode? {\n    if n == 0 {\n        return nil\n    }\n    let root = TreeNode(x: 0)\n    root.left = buildTree(n: n - 1)\n    root.right = buildTree(n: n - 1)\n    return root\n}\n
    space_complexity.js
    /* Exponential complexity (building a perfect binary tree) */\nfunction buildTree(n) {\n    if (n === 0) return null;\n    const root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.ts
    /* Exponential complexity (building a perfect binary tree) */\nfunction buildTree(n: number): TreeNode | null {\n    if (n === 0) return null;\n    const root = new TreeNode(0);\n    root.left = buildTree(n - 1);\n    root.right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.dart
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode? buildTree(int n) {\n  if (n == 0) return null;\n  TreeNode root = TreeNode(0);\n  root.left = buildTree(n - 1);\n  root.right = buildTree(n - 1);\n  return root;\n}\n
    space_complexity.rs
    /* Exponential complexity (building a perfect binary tree) */\nfn build_tree(n: i32) -> Option<Rc<RefCell<TreeNode>>> {\n    if n == 0 {\n        return None;\n    };\n    let root = TreeNode::new(0);\n    root.borrow_mut().left = build_tree(n - 1);\n    root.borrow_mut().right = build_tree(n - 1);\n    return Some(root);\n}\n
    space_complexity.c
    /* Exponential complexity (building a perfect binary tree) */\nTreeNode *buildTree(int n) {\n    if (n == 0)\n        return NULL;\n    TreeNode *root = newTreeNode(0);\n    root->left = buildTree(n - 1);\n    root->right = buildTree(n - 1);\n    return root;\n}\n
    space_complexity.kt
    /* Exponential complexity (building a perfect binary tree) */\nfun buildTree(n: Int): TreeNode? {\n    if (n == 0)\n        return null\n    val root = TreeNode(0)\n    root.left = buildTree(n - 1)\n    root.right = buildTree(n - 1)\n    return root\n}\n
    space_complexity.rb
    ### Exponential complexity (building a perfect binary tree)###\ndef build_tree(n)\n  return if n == 0\n\n  TreeNode.new.tap do |root|\n    root.left = build_tree(n - 1)\n    root.right = build_tree(n - 1)\n  end\nend\n
    Visualize execution

    View full screen >

    Figure 2-19   Exponential space used by a perfect binary tree

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#5-logarithmic-space-olog-n","level":3,"title":"5.   Logarithmic space \\(O(\\log n)\\)","text":"

    Logarithmic space often appears in divide-and-conquer algorithms. For example, merge sort splits an array of length \\(n\\) in half at each recursive step, producing a recursion tree of height \\(\\log n\\) and using \\(O(\\log n)\\) space for stack frames.

    Another example is converting a number to a string. A positive integer \\(n\\) has \\(\\lfloor \\log_{10} n \\rfloor + 1\\) digits, giving a string of length \\(\\lfloor \\log_{10} n \\rfloor + 1\\). The space complexity is therefore \\(O(\\log_{10} n + 1) = O(\\log n)\\).

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#244-balancing-time-and-space","level":2,"title":"2.4.4   Balancing time and space","text":"

    Ideally, we would minimize both time and space complexity. In practice, optimizing both simultaneously is usually difficult.

    Reducing time complexity often increases space complexity, and vice versa. Using more memory to run faster is called trading space for time; accepting slower execution to use less memory is trading time for space.

    The choice depends on which resource matters more. Time is usually more valuable than space, so trading space for time is common. For very large datasets, however, keeping space complexity under control is also crucial.

    ","path":["Chapter 2.   Complexity analysis","2.4   Space complexity"],"tags":[]},{"location":"chapter_computational_complexity/summary/","level":1,"title":"2.5   Summary","text":"","path":["Chapter 2.   Complexity analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"

    Evaluating algorithm efficiency

    • Time and space efficiency are the two main measures of algorithm quality.
    • Practical testing measures efficiency, but cannot easily remove environmental effects and consumes substantial computing resources.
    • Complexity analysis avoids those limitations, applies across platforms, and reveals efficiency at different input sizes.

    Time complexity

    • Time complexity describes how runtime grows with input size. It is useful for evaluating efficiency, but may not distinguish algorithms accurately for small inputs or equal complexity classes.
    • Worst-case time complexity is expressed with big-\\(O\\) notation, an asymptotic upper bound describing the growth of the operation count \\(T(n)\\) as \\(n\\) tends to positive infinity.
    • Deriving time complexity takes two steps: count operations, then determine the asymptotic upper bound.
    • Common complexities, from slower to faster growth, include \\(O(1)\\), \\(O(\\log n)\\), \\(O(n)\\), \\(O(n \\log n)\\), \\(O(n^2)\\), \\(O(2^n)\\), and \\(O(n!)\\).
    • Some algorithms' complexity depends on the input's arrangement. We distinguish worst-case, best-case, and average-case complexity. Best-case complexity is rarely used because reaching it generally requires strict input conditions.
    • Average-case complexity describes performance on random inputs and is closest to typical practical behavior. Calculating it requires an input distribution and its mathematical expectation.

    Space complexity

    • Like time complexity, space complexity describes a growth trend: how memory usage increases with input size.
    • Memory used during execution includes input, temporary, and output space. Input space is usually excluded from space complexity. Temporary space includes temporary data, stack frames, and instructions; stack frames usually affect complexity only in recursive functions.
    • We usually consider worst-case space complexity, measuring memory under the worst input and at the moment of maximum usage.
    • Common space complexities, from slower to faster growth, include \\(O(1)\\), \\(O(\\log n)\\), \\(O(n)\\), \\(O(n^2)\\), and \\(O(2^n)\\).
    ","path":["Chapter 2.   Complexity analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Does tail recursion have \\(O(1)\\) space complexity?

    In theory, tail recursion can be optimized to \\(O(1)\\) space. However, most languages, including Java, Python, C++, Go, and C#, do not automatically optimize tail recursion, so its space complexity is usually treated as \\(O(n)\\).

    Q: What is the difference between a function and a method?

    A function can run independently, with all arguments passed explicitly. A method is associated with an object, receives that object implicitly when called, and can operate on the instance's data.

    Here are examples from several common languages.

    • C is procedural and has no built-in object-oriented model, so it has functions only. We can simulate object-oriented programming with structures (struct); functions associated with a structure then act like methods in other languages.
    • Java and C# are object-oriented, and executable blocks (methods) are usually part of a class. Static methods behave like functions: they belong to the class and cannot access a particular instance's variables directly.
    • C++ and Python support both procedural programming with functions and object-oriented programming with methods.

    Q: Does the common space complexity chart show absolute memory usage?

    No. It illustrates growth trends rather than absolute amounts of memory.

    For \\(n = 8\\), the plotted values may not match the corresponding functions exactly. Each curve includes a constant term that adjusts it to a visually convenient range.

    In practice, these constant terms are usually unknown, so complexity alone cannot generally identify the best solution for \\(n = 8\\). For \\(n = 8^5\\), the choice is easier because growth rates dominate.

    Q: Do algorithms sometimes trade time for space, or space for time, depending on the application?

    Most practical applications trade space for time. Database indexes, for example, often use B+ trees or hash indexes, consuming substantial memory to achieve \\(O(\\log n)\\) or even \\(O(1)\\) query time.

    When memory is scarce, the reverse tradeoff may be useful. In embedded development, an engineer might replace a hash table with sequential array search to save memory, accepting slower lookups.

    ","path":["Chapter 2.   Complexity analysis","2.5   Summary"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/","level":1,"title":"2.3   Time complexity","text":"

    Runtime gives a direct, accurate picture of an algorithm's efficiency. How might we predict the runtime of a piece of code precisely?

    1. Specify the execution platform, including hardware, programming language, and system environment, all of which affect performance.
    2. Determine the time required by each operation: for example, addition + might take 1 ns, multiplication * 10 ns, and print() 5 ns.
    3. Count every operation in the code and sum their execution times to obtain the total runtime.

    For example, consider the following code with input size \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    # On a particular execution platform\ndef algorithm(n: int):\n    a = 2      # 1 ns\n    a = a + 1  # 1 ns\n    a = a * 2  # 10 ns\n    # Repeat n times\n    for _ in range(n):  # 1 ns\n        print(0)        # 5 ns\n
    // On a particular execution platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Repeat n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        cout << 0 << endl;         // 5 ns\n    }\n}\n
    // On a particular execution platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Repeat n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        System.out.println(0);     // 5 ns\n    }\n}\n
    // On a particular execution platform\nvoid Algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Repeat n times\n    for (int i = 0; i < n; i++) {  // 1 ns\n        Console.WriteLine(0);      // 5 ns\n    }\n}\n
    // On a particular execution platform\nfunc algorithm(n int) {\n    a := 2     // 1 ns\n    a = a + 1  // 1 ns\n    a = a * 2  // 10 ns\n    // Repeat n times\n    for i := 0; i < n; i++ {  // 1 ns\n        fmt.Println(a)        // 5 ns\n    }\n}\n
    // On a particular execution platform\nfunc algorithm(n: Int) {\n    var a = 2 // 1 ns\n    a = a + 1 // 1 ns\n    a = a * 2 // 10 ns\n    // Repeat n times\n    for _ in 0 ..< n { // 1 ns\n        print(0) // 5 ns\n    }\n}\n
    // On a particular execution platform\nfunction algorithm(n) {\n    var a = 2; // 1 ns\n    a = a + 1; // 1 ns\n    a = a * 2; // 10 ns\n    // Repeat n times\n    for(let i = 0; i < n; i++) { // 1 ns\n        console.log(0); // 5 ns\n    }\n}\n
    // On a particular execution platform\nfunction algorithm(n: number): void {\n    var a: number = 2; // 1 ns\n    a = a + 1; // 1 ns\n    a = a * 2; // 10 ns\n    // Repeat n times\n    for(let i = 0; i < n; i++) { // 1 ns\n        console.log(0); // 5 ns\n    }\n}\n
    // On a particular execution platform\nvoid algorithm(int n) {\n  int a = 2; // 1 ns\n  a = a + 1; // 1 ns\n  a = a * 2; // 10 ns\n  // Repeat n times\n  for (int i = 0; i < n; i++) { // 1 ns\n    print(0); // 5 ns\n  }\n}\n
    // On a particular execution platform\nfn algorithm(n: i32) {\n    let mut a = 2;      // 1 ns\n    a = a + 1;          // 1 ns\n    a = a * 2;          // 10 ns\n    // Repeat n times\n    for _ in 0..n {     // 1 ns\n        println!(\"{}\", 0);  // 5 ns\n    }\n}\n
    // On a particular execution platform\nvoid algorithm(int n) {\n    int a = 2;  // 1 ns\n    a = a + 1;  // 1 ns\n    a = a * 2;  // 10 ns\n    // Repeat n times\n    for (int i = 0; i < n; i++) {   // 1 ns\n        printf(\"%d\", 0);            // 5 ns\n    }\n}\n
    // On a particular execution platform\nfun algorithm(n: Int) {\n    var a = 2 // 1 ns\n    a = a + 1 // 1 ns\n    a = a * 2 // 10 ns\n    // Repeat n times\n    for (i in 0..<n) {  // 1 ns\n        println(0)      // 5 ns\n    }\n}\n
    # On a particular execution platform\ndef algorithm(n)\n    a = 2       # 1 ns\n    a = a + 1   # 1 ns\n    a = a * 2   # 10 ns\n    # Repeat n times\n    (0...n).each do # 1 ns\n        puts 0      # 5 ns\n    end\nend\n

    This gives a runtime of \\((6n + 12)\\) ns:

    \\[ 1 + 1 + 10 + (1 + 5) \\times n = 6n + 12 \\]

    In practice, calculating runtime this way is neither suitable nor realistic. We do not want estimates tied to one platform, since algorithms run on many platforms. Also, the time taken by each operation is difficult to determine, making accurate prediction very hard.

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#231-measuring-growth-in-runtime","level":2,"title":"2.3.1   Measuring growth in runtime","text":"

    Time complexity analysis measures how runtime grows as the input gets larger, rather than the runtime itself.

    To make this abstract idea concrete, consider three algorithms, A, B, and C, with input size \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    # Algorithm A: constant time complexity\ndef algorithm_A(n: int):\n    print(0)\n# Algorithm B: linear time complexity\ndef algorithm_B(n: int):\n    for _ in range(n):\n        print(0)\n# Algorithm C: constant time complexity\ndef algorithm_C(n: int):\n    for _ in range(1000000):\n        print(0)\n
    // Algorithm A: constant time complexity\nvoid algorithm_A(int n) {\n    cout << 0 << endl;\n}\n// Algorithm B: linear time complexity\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        cout << 0 << endl;\n    }\n}\n// Algorithm C: constant time complexity\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        cout << 0 << endl;\n    }\n}\n
    // Algorithm A: constant time complexity\nvoid algorithm_A(int n) {\n    System.out.println(0);\n}\n// Algorithm B: linear time complexity\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        System.out.println(0);\n    }\n}\n// Algorithm C: constant time complexity\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        System.out.println(0);\n    }\n}\n
    // Algorithm A: constant time complexity\nvoid AlgorithmA(int n) {\n    Console.WriteLine(0);\n}\n// Algorithm B: linear time complexity\nvoid AlgorithmB(int n) {\n    for (int i = 0; i < n; i++) {\n        Console.WriteLine(0);\n    }\n}\n// Algorithm C: constant time complexity\nvoid AlgorithmC(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        Console.WriteLine(0);\n    }\n}\n
    // Algorithm A: constant time complexity\nfunc algorithm_A(n int) {\n    fmt.Println(0)\n}\n// Algorithm B: linear time complexity\nfunc algorithm_B(n int) {\n    for i := 0; i < n; i++ {\n        fmt.Println(0)\n    }\n}\n// Algorithm C: constant time complexity\nfunc algorithm_C(n int) {\n    for i := 0; i < 1000000; i++ {\n        fmt.Println(0)\n    }\n}\n
    // Algorithm A: constant time complexity\nfunc algorithmA(n: Int) {\n    print(0)\n}\n\n// Algorithm B: linear time complexity\nfunc algorithmB(n: Int) {\n    for _ in 0 ..< n {\n        print(0)\n    }\n}\n\n// Algorithm C: constant time complexity\nfunc algorithmC(n: Int) {\n    for _ in 0 ..< 1_000_000 {\n        print(0)\n    }\n}\n
    // Algorithm A: constant time complexity\nfunction algorithm_A(n) {\n    console.log(0);\n}\n// Algorithm B: linear time complexity\nfunction algorithm_B(n) {\n    for (let i = 0; i < n; i++) {\n        console.log(0);\n    }\n}\n// Algorithm C: constant time complexity\nfunction algorithm_C(n) {\n    for (let i = 0; i < 1000000; i++) {\n        console.log(0);\n    }\n}\n
    // Algorithm A: constant time complexity\nfunction algorithm_A(n: number): void {\n    console.log(0);\n}\n// Algorithm B: linear time complexity\nfunction algorithm_B(n: number): void {\n    for (let i = 0; i < n; i++) {\n        console.log(0);\n    }\n}\n// Algorithm C: constant time complexity\nfunction algorithm_C(n: number): void {\n    for (let i = 0; i < 1000000; i++) {\n        console.log(0);\n    }\n}\n
    // Algorithm A: constant time complexity\nvoid algorithmA(int n) {\n  print(0);\n}\n// Algorithm B: linear time complexity\nvoid algorithmB(int n) {\n  for (int i = 0; i < n; i++) {\n    print(0);\n  }\n}\n// Algorithm C: constant time complexity\nvoid algorithmC(int n) {\n  for (int i = 0; i < 1000000; i++) {\n    print(0);\n  }\n}\n
    // Algorithm A: constant time complexity\nfn algorithm_A(n: i32) {\n    println!(\"{}\", 0);\n}\n// Algorithm B: linear time complexity\nfn algorithm_B(n: i32) {\n    for _ in 0..n {\n        println!(\"{}\", 0);\n    }\n}\n// Algorithm C: constant time complexity\nfn algorithm_C(n: i32) {\n    for _ in 0..1000000 {\n        println!(\"{}\", 0);\n    }\n}\n
    // Algorithm A: constant time complexity\nvoid algorithm_A(int n) {\n    printf(\"%d\", 0);\n}\n// Algorithm B: linear time complexity\nvoid algorithm_B(int n) {\n    for (int i = 0; i < n; i++) {\n        printf(\"%d\", 0);\n    }\n}\n// Algorithm C: constant time complexity\nvoid algorithm_C(int n) {\n    for (int i = 0; i < 1000000; i++) {\n        printf(\"%d\", 0);\n    }\n}\n
    // Algorithm A: constant time complexity\nfun algoritm_A(n: Int) {\n    println(0)\n}\n// Algorithm B: linear time complexity\nfun algorithm_B(n: Int) {\n    for (i in 0..<n){\n        println(0)\n    }\n}\n// Algorithm C: constant time complexity\nfun algorithm_C(n: Int) {\n    for (i in 0..<1000000) {\n        println(0)\n    }\n}\n
    # Algorithm A: constant time complexity\ndef algorithm_A(n)\n    puts 0\nend\n\n# Algorithm B: linear time complexity\ndef algorithm_B(n)\n    (0...n).each { puts 0 }\nend\n\n# Algorithm C: constant time complexity\ndef algorithm_C(n)\n    (0...1_000_000).each { puts 0 }\nend\n

    Figure 2-7 shows their time complexities.

    • Algorithm A performs \\(1\\) print operation. Its runtime does not grow with \\(n\\), so it has constant time complexity.
    • Algorithm B prints \\(n\\) times. Its runtime grows linearly with \\(n\\), so it has linear time complexity.
    • Algorithm C prints \\(1000000\\) times. It may take a long time, but that time is independent of input size \\(n\\). Its complexity is therefore constant, like A.

    Figure 2-7   Runtime growth of algorithms A, B, and C

    How does time complexity analysis differ from calculating runtime directly?

    • Time complexity evaluates efficiency effectively. Algorithm B grows linearly, becoming slower than A when \\(n > 1\\) and slower than C when \\(n > 1000000\\). With sufficiently large input size \\(n\\), a constant-time algorithm will outperform a linear-time one. This is what a growth trend tells us.
    • Time complexity is easier to derive. The execution platform and kinds of elementary operations do not determine the growth trend. We can treat each elementary operation as taking the same unit of time, reducing runtime estimation to operation counting and greatly simplifying analysis.
    • Time complexity has limitations. Algorithms A and C have the same complexity but very different runtimes. Although B has higher complexity than C, it is much faster for small \\(n\\). Complexity alone may not settle such comparisons. Even so, it remains the most effective and widely used way to evaluate algorithm efficiency.
    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#232-asymptotic-upper-bounds","level":2,"title":"2.3.2   Asymptotic upper bounds","text":"

    Consider this function with input size \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def algorithm(n: int):\n    a = 1      # +1\n    a = a + 1  # +1\n    a = a * 2  # +1\n    # Repeat n times\n    for i in range(n):  # +1\n        print(0)        # +1\n
    void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Repeat n times\n    for (int i = 0; i < n; i++) { // +1 (i ++ executes each iteration)\n        cout << 0 << endl;    // +1\n    }\n}\n
    void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Repeat n times\n    for (int i = 0; i < n; i++) { // +1 (i ++ executes each iteration)\n        System.out.println(0);    // +1\n    }\n}\n
    void Algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Repeat n times\n    for (int i = 0; i < n; i++) {   // +1 (i ++ executes each iteration)\n        Console.WriteLine(0);   // +1\n    }\n}\n
    func algorithm(n int) {\n    a := 1      // +1\n    a = a + 1   // +1\n    a = a * 2   // +1\n    // Repeat n times\n    for i := 0; i < n; i++ {   // +1\n        fmt.Println(a)         // +1\n    }\n}\n
    func algorithm(n: Int) {\n    var a = 1 // +1\n    a = a + 1 // +1\n    a = a * 2 // +1\n    // Repeat n times\n    for _ in 0 ..< n { // +1\n        print(0) // +1\n    }\n}\n
    function algorithm(n) {\n    var a = 1; // +1\n    a += 1; // +1\n    a *= 2; // +1\n    // Repeat n times\n    for(let i = 0; i < n; i++){ // +1 (i ++ executes each iteration)\n        console.log(0); // +1\n    }\n}\n
    function algorithm(n: number): void{\n    var a: number = 1; // +1\n    a += 1; // +1\n    a *= 2; // +1\n    // Repeat n times\n    for(let i = 0; i < n; i++){ // +1 (i ++ executes each iteration)\n        console.log(0); // +1\n    }\n}\n
    void algorithm(int n) {\n  int a = 1; // +1\n  a = a + 1; // +1\n  a = a * 2; // +1\n  // Repeat n times\n  for (int i = 0; i < n; i++) { // +1 (i ++ executes each iteration)\n    print(0); // +1\n  }\n}\n
    fn algorithm(n: i32) {\n    let mut a = 1;   // +1\n    a = a + 1;      // +1\n    a = a * 2;      // +1\n\n    // Repeat n times\n    for _ in 0..n { // +1 (i ++ executes each iteration)\n        println!(\"{}\", 0); // +1\n    }\n}\n
    void algorithm(int n) {\n    int a = 1;  // +1\n    a = a + 1;  // +1\n    a = a * 2;  // +1\n    // Repeat n times\n    for (int i = 0; i < n; i++) {   // +1 (i ++ executes each iteration)\n        printf(\"%d\", 0);            // +1\n    }\n}\n
    fun algorithm(n: Int) {\n    var a = 1 // +1\n    a = a + 1 // +1\n    a = a * 2 // +1\n    // Repeat n times\n    for (i in 0..<n) { // +1 (i ++ executes each iteration)\n        println(0) // +1\n    }\n}\n
    def algorithm(n)\n    a = 1       # +1\n    a = a + 1   # +1\n    a = a * 2   # +1\n    # Repeat n times\n    (0...n).each do # +1\n        puts 0      # +1\n    end\nend\n

    Let \\(T(n)\\) be the operation count as a function of input size \\(n\\). For the function above:

    \\[ T(n) = 3 + 2n \\]

    Since \\(T(n)\\) is a first-degree function, runtime grows linearly, giving linear time complexity.

    We write linear time complexity as \\(O(n)\\). This is called big-\\(O\\) notation, and it gives an asymptotic upper bound on \\(T(n)\\).

    Time complexity analysis essentially finds an asymptotic upper bound on the operation count \\(T(n)\\). It has a precise mathematical definition.

    Asymptotic upper bound

    If a positive real constant \\(c\\) and a real number \\(n_0\\) exist such that \\(T(n) \\leq c \\cdot f(n)\\) for all \\(n > n_0\\), then \\(f(n)\\) is an asymptotic upper bound on \\(T(n)\\), written \\(T(n) = O(f(n))\\).

    As shown in Figure 2-8, we seek a function \\(f(n)\\) such that, as \\(n\\) tends to infinity, \\(T(n)\\) and \\(f(n)\\) have the same order of growth, differing only by a constant factor \\(c\\).

    Figure 2-8   Asymptotic upper bound of a function

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#233-how-to-derive-time-complexity","level":2,"title":"2.3.3   How to derive time complexity","text":"

    The mathematics of asymptotic bounds may feel unfamiliar. Do not worry if it is not fully clear yet: learn the practical method first, and its meaning will become clearer with practice.

    Once we identify \\(f(n)\\), the time complexity is \\(O(f(n))\\). Finding the asymptotic upper bound \\(f(n)\\) takes two steps: count operations, then determine the bound.

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-step-1-count-operations","level":3,"title":"1.   Step 1: Count operations","text":"

    We can count code operations line by line. Since the constant factor \\(c\\) in \\(c \\cdot f(n)\\) may be arbitrarily large, coefficients and constant terms in \\(T(n)\\) can be ignored. This gives three useful shortcuts.

    1. Ignore constant terms in \\(T(n)\\). They are independent of \\(n\\) and do not affect complexity.
    2. Drop coefficients. For example, \\(2n\\) or \\(5n + 1\\) iterations can be counted simply as \\(n\\), because the coefficient of \\(n\\) does not affect complexity.
    3. Multiply for nested loops. Multiply the outer and inner iteration counts, applying shortcuts 1. and 2. to each level.

    We can apply these shortcuts to the following function:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    def algorithm(n: int):\n    a = 1      # +0 (shortcut 1)\n    a = a + n  # +0 (shortcut 1)\n    # +n (shortcut 2)\n    for i in range(5 * n + 1):\n        print(0)\n    # +n*n (shortcut 3)\n    for i in range(2 * n):\n        for j in range(n + 1):\n            print(0)\n
    void algorithm(int n) {\n    int a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        cout << 0 << endl;\n    }\n    // +n*n (shortcut 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            cout << 0 << endl;\n        }\n    }\n}\n
    void algorithm(int n) {\n    int a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        System.out.println(0);\n    }\n    // +n*n (shortcut 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            System.out.println(0);\n        }\n    }\n}\n
    void Algorithm(int n) {\n    int a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        Console.WriteLine(0);\n    }\n    // +n*n (shortcut 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            Console.WriteLine(0);\n        }\n    }\n}\n
    func algorithm(n int) {\n    a := 1     // +0 (shortcut 1)\n    a = a + n  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for i := 0; i < 5 * n + 1; i++ {\n        fmt.Println(0)\n    }\n    // +n*n (shortcut 3)\n    for i := 0; i < 2 * n; i++ {\n        for j := 0; j < n + 1; j++ {\n            fmt.Println(0)\n        }\n    }\n}\n
    func algorithm(n: Int) {\n    var a = 1 // +0 (shortcut 1)\n    a = a + n // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for _ in 0 ..< (5 * n + 1) {\n        print(0)\n    }\n    // +n*n (shortcut 3)\n    for _ in 0 ..< (2 * n) {\n        for _ in 0 ..< (n + 1) {\n            print(0)\n        }\n    }\n}\n
    function algorithm(n) {\n    let a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (let i = 0; i < 5 * n + 1; i++) {\n        console.log(0);\n    }\n    // +n*n (shortcut 3)\n    for (let i = 0; i < 2 * n; i++) {\n        for (let j = 0; j < n + 1; j++) {\n            console.log(0);\n        }\n    }\n}\n
    function algorithm(n: number): void {\n    let a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (let i = 0; i < 5 * n + 1; i++) {\n        console.log(0);\n    }\n    // +n*n (shortcut 3)\n    for (let i = 0; i < 2 * n; i++) {\n        for (let j = 0; j < n + 1; j++) {\n            console.log(0);\n        }\n    }\n}\n
    void algorithm(int n) {\n  int a = 1; // +0 (shortcut 1)\n  a = a + n; // +0 (shortcut 1)\n  // +n (shortcut 2)\n  for (int i = 0; i < 5 * n + 1; i++) {\n    print(0);\n  }\n  // +n*n (shortcut 3)\n  for (int i = 0; i < 2 * n; i++) {\n    for (int j = 0; j < n + 1; j++) {\n      print(0);\n    }\n  }\n}\n
    fn algorithm(n: i32) {\n    let mut a = 1;     // +0 (shortcut 1)\n    a = a + n;        // +0 (shortcut 1)\n\n    // +n (shortcut 2)\n    for i in 0..(5 * n + 1) {\n        println!(\"{}\", 0);\n    }\n\n    // +n*n (shortcut 3)\n    for i in 0..(2 * n) {\n        for j in 0..(n + 1) {\n            println!(\"{}\", 0);\n        }\n    }\n}\n
    void algorithm(int n) {\n    int a = 1;  // +0 (shortcut 1)\n    a = a + n;  // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (int i = 0; i < 5 * n + 1; i++) {\n        printf(\"%d\", 0);\n    }\n    // +n*n (shortcut 3)\n    for (int i = 0; i < 2 * n; i++) {\n        for (int j = 0; j < n + 1; j++) {\n            printf(\"%d\", 0);\n        }\n    }\n}\n
    fun algorithm(n: Int) {\n    var a = 1   // +0 (shortcut 1)\n    a = a + n   // +0 (shortcut 1)\n    // +n (shortcut 2)\n    for (i in 0..<5 * n + 1) {\n        println(0)\n    }\n    // +n*n (shortcut 3)\n    for (i in 0..<2 * n) {\n        for (j in 0..<n + 1) {\n            println(0)\n        }\n    }\n}\n
    def algorithm(n)\n    a = 1       # +0 (shortcut 1)\n    a = a + n   # +0 (shortcut 1)\n    # +n (shortcut 2)\n    (0...(5 * n + 1)).each do { puts 0 }\n    # +n*n (shortcut 3)\n    (0...(2 * n)).each do\n        (0...(n + 1)).each do { puts 0 }\n    end\nend\n

    The formulas below compare full and simplified counts. Both give time complexity \\(O(n^2)\\).

    \\[ \\begin{aligned} T(n) & = 2n(n + 1) + (5n + 1) + 2 & \\text{Full count (-.-|||)} \\newline & = 2n^2 + 7n + 3 \\newline T(n) & = n^2 + n & \\text{Shortcut count (o.O)} \\end{aligned} \\]","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-step-2-determine-the-asymptotic-upper-bound","level":3,"title":"2.   Step 2: Determine the asymptotic upper bound","text":"

    The highest-order term in \\(T(n)\\) determines time complexity. As \\(n\\) tends to infinity, that term dominates, and the others become negligible.

    Table 2-2 includes deliberately extreme coefficients to emphasize that coefficients cannot overcome growth order. As \\(n\\) tends to infinity, those constants become insignificant.

    Table 2-2   Operation counts and their time complexities

    Operation count \\(T(n)\\) Time complexity \\(O(f(n))\\) \\(100000\\) \\(O(1)\\) \\(3n + 2\\) \\(O(n)\\) \\(2n^2 + 3n + 2\\) \\(O(n^2)\\) \\(n^3 + 10000n^2\\) \\(O(n^3)\\) \\(2^n + 10000n^{10000}\\) \\(O(2^n)\\)","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#234-common-complexity-classes","level":2,"title":"2.3.4   Common complexity classes","text":"

    For input size \\(n\\), common time complexities are shown below in increasing order of growth.

    \\[ \\begin{aligned} & O(1) < O(\\log n) < O(n) < O(n \\log n) < O(n^2) < O(2^n) < O(n!) \\newline & \\text{Constant} < \\text{Logarithmic} < \\text{Linear} < \\text{Linearithmic} < \\text{Quadratic} < \\text{Exponential} < \\text{Factorial} \\end{aligned} \\]

    Figure 2-9   Common time complexity classes

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-constant-time-o1","level":3,"title":"1.   Constant time \\(O(1)\\)","text":"

    A constant operation count is independent of input size \\(n\\): changing \\(n\\) does not change the count.

    In the following function, size may be large, but it is independent of input size \\(n\\), so the time complexity remains \\(O(1)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def constant(n: int) -> int:\n    \"\"\"Constant complexity\"\"\"\n    count = 0\n    size = 100000\n    for _ in range(size):\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Constant complexity */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
    time_complexity.java
    /* Constant complexity */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
    time_complexity.cs
    /* Constant complexity */\nint Constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++)\n        count++;\n    return count;\n}\n
    time_complexity.go
    /* Constant complexity */\nfunc constant(n int) int {\n    count := 0\n    size := 100000\n    for i := 0; i < size; i++ {\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Constant complexity */\nfunc constant(n: Int) -> Int {\n    var count = 0\n    let size = 100_000\n    for _ in 0 ..< size {\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Constant complexity */\nfunction constant(n) {\n    let count = 0;\n    const size = 100000;\n    for (let i = 0; i < size; i++) count++;\n    return count;\n}\n
    time_complexity.ts
    /* Constant complexity */\nfunction constant(n: number): number {\n    let count = 0;\n    const size = 100000;\n    for (let i = 0; i < size; i++) count++;\n    return count;\n}\n
    time_complexity.dart
    /* Constant complexity */\nint constant(int n) {\n  int count = 0;\n  int size = 100000;\n  for (var i = 0; i < size; i++) {\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Constant complexity */\nfn constant(n: i32) -> i32 {\n    _ = n;\n    let mut count = 0;\n    let size = 100_000;\n    for _ in 0..size {\n        count += 1;\n    }\n    count\n}\n
    time_complexity.c
    /* Constant complexity */\nint constant(int n) {\n    int count = 0;\n    int size = 100000;\n    for (int i = 0; i < size; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Constant complexity */\nfun constant(n: Int): Int {\n    var count = 0\n    val size = 100000\n    for (i in 0..<size)\n        count++\n    return count\n}\n
    time_complexity.rb
    ### Constant complexity ###\ndef constant(n)\n  count = 0\n  size = 100000\n\n  (0...size).each { count += 1 }\n\n  count\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-linear-time-on","level":3,"title":"2.   Linear time \\(O(n)\\)","text":"

    A linear operation count grows proportionally to input size \\(n\\). It commonly appears in a single loop:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def linear(n: int) -> int:\n    \"\"\"Linear complexity\"\"\"\n    count = 0\n    for _ in range(n):\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Linear complexity */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
    time_complexity.java
    /* Linear complexity */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
    time_complexity.cs
    /* Linear complexity */\nint Linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++)\n        count++;\n    return count;\n}\n
    time_complexity.go
    /* Linear complexity */\nfunc linear(n int) int {\n    count := 0\n    for i := 0; i < n; i++ {\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Linear complexity */\nfunc linear(n: Int) -> Int {\n    var count = 0\n    for _ in 0 ..< n {\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Linear complexity */\nfunction linear(n) {\n    let count = 0;\n    for (let i = 0; i < n; i++) count++;\n    return count;\n}\n
    time_complexity.ts
    /* Linear complexity */\nfunction linear(n: number): number {\n    let count = 0;\n    for (let i = 0; i < n; i++) count++;\n    return count;\n}\n
    time_complexity.dart
    /* Linear complexity */\nint linear(int n) {\n  int count = 0;\n  for (var i = 0; i < n; i++) {\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Linear complexity */\nfn linear(n: i32) -> i32 {\n    let mut count = 0;\n    for _ in 0..n {\n        count += 1;\n    }\n    count\n}\n
    time_complexity.c
    /* Linear complexity */\nint linear(int n) {\n    int count = 0;\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Linear complexity */\nfun linear(n: Int): Int {\n    var count = 0\n    for (i in 0..<n)\n        count++\n    return count\n}\n
    time_complexity.rb
    ### Linear complexity ###\ndef linear(n)\n  count = 0\n  (0...n).each { count += 1 }\n  count\nend\n
    Visualize execution

    View full screen >

    Traversing an array or linked list takes \\(O(n)\\) time, where \\(n\\) is its length:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def array_traversal(nums: list[int]) -> int:\n    \"\"\"Linear complexity (array traversal)\"\"\"\n    count = 0\n    # The number of iterations is proportional to the array length\n    for num in nums:\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Linear complexity (array traversal) */\nint arrayTraversal(vector<int> &nums) {\n    int count = 0;\n    // The number of iterations is proportional to the array length\n    for (int num : nums) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.java
    /* Linear complexity (array traversal) */\nint arrayTraversal(int[] nums) {\n    int count = 0;\n    // The number of iterations is proportional to the array length\n    for (int num : nums) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Linear complexity (array traversal) */\nint ArrayTraversal(int[] nums) {\n    int count = 0;\n    // The number of iterations is proportional to the array length\n    foreach (int num in nums) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.go
    /* Linear complexity (array traversal) */\nfunc arrayTraversal(nums []int) int {\n    count := 0\n    // The number of iterations is proportional to the array length\n    for range nums {\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Linear complexity (array traversal) */\nfunc arrayTraversal(nums: [Int]) -> Int {\n    var count = 0\n    // The number of iterations is proportional to the array length\n    for _ in nums {\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Linear complexity (array traversal) */\nfunction arrayTraversal(nums) {\n    let count = 0;\n    // The number of iterations is proportional to the array length\n    for (let i = 0; i < nums.length; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Linear complexity (array traversal) */\nfunction arrayTraversal(nums: number[]): number {\n    let count = 0;\n    // The number of iterations is proportional to the array length\n    for (let i = 0; i < nums.length; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Linear complexity (array traversal) */\nint arrayTraversal(List<int> nums) {\n  int count = 0;\n  // The number of iterations is proportional to the array length\n  for (var _num in nums) {\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Linear complexity (array traversal) */\nfn array_traversal(nums: &[i32]) -> i32 {\n    let mut count = 0;\n    // The number of iterations is proportional to the array length\n    for _ in nums {\n        count += 1;\n    }\n    count\n}\n
    time_complexity.c
    /* Linear complexity (array traversal) */\nint arrayTraversal(int *nums, int n) {\n    int count = 0;\n    // The number of iterations is proportional to the array length\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Linear complexity (array traversal) */\nfun arrayTraversal(nums: IntArray): Int {\n    var count = 0\n    // The number of iterations is proportional to the array length\n    for (num in nums) {\n        count++\n    }\n    return count\n}\n
    time_complexity.rb
    ### Linear complexity (array traversal)###\ndef array_traversal(nums)\n  count = 0\n\n  # The number of iterations is proportional to the array length\n  for num in nums\n    count += 1\n  end\n\n  count\nend\n
    Visualize execution

    View full screen >

    The meaning of input size \\(n\\) depends on the input type. In the first example, it is the variable \\(n\\); in the second, it is the array length \\(n\\).

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#3-quadratic-time-on2","level":3,"title":"3.   Quadratic time \\(O(n^2)\\)","text":"

    A quadratic operation count grows with the square of input size \\(n\\). It commonly appears in nested loops where both levels take \\(O(n)\\) time, giving \\(O(n^2)\\) overall:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def quadratic(n: int) -> int:\n    \"\"\"Quadratic complexity\"\"\"\n    count = 0\n    # The number of iterations is proportional to the square of the input size n\n    for i in range(n):\n        for j in range(n):\n            count += 1\n    return count\n
    time_complexity.cpp
    /* Quadratic complexity */\nint quadratic(int n) {\n    int count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.java
    /* Quadratic complexity */\nint quadratic(int n) {\n    int count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Quadratic complexity */\nint Quadratic(int n) {\n    int count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.go
    /* Quadratic complexity */\nfunc quadratic(n int) int {\n    count := 0\n    // The number of iterations is proportional to the square of the input size n\n    for i := 0; i < n; i++ {\n        for j := 0; j < n; j++ {\n            count++\n        }\n    }\n    return count\n}\n
    time_complexity.swift
    /* Quadratic complexity */\nfunc quadratic(n: Int) -> Int {\n    var count = 0\n    // The number of iterations is proportional to the square of the input size n\n    for _ in 0 ..< n {\n        for _ in 0 ..< n {\n            count += 1\n        }\n    }\n    return count\n}\n
    time_complexity.js
    /* Quadratic complexity */\nfunction quadratic(n) {\n    let count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Quadratic complexity */\nfunction quadratic(n: number): number {\n    let count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Quadratic complexity */\nint quadratic(int n) {\n  int count = 0;\n  // The number of iterations is proportional to the square of the input size n\n  for (int i = 0; i < n; i++) {\n    for (int j = 0; j < n; j++) {\n      count++;\n    }\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Quadratic complexity */\nfn quadratic(n: i32) -> i32 {\n    let mut count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for _ in 0..n {\n        for _ in 0..n {\n            count += 1;\n        }\n    }\n    count\n}\n
    time_complexity.c
    /* Quadratic complexity */\nint quadratic(int n) {\n    int count = 0;\n    // The number of iterations is proportional to the square of the input size n\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < n; j++) {\n            count++;\n        }\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Quadratic complexity */\nfun quadratic(n: Int): Int {\n    var count = 0\n    // The number of iterations is proportional to the square of the input size n\n    for (i in 0..<n) {\n        for (j in 0..<n) {\n            count++\n        }\n    }\n    return count\n}\n
    time_complexity.rb
    ### Quadratic complexity ###\ndef quadratic(n)\n  count = 0\n\n  # The number of iterations is proportional to the square of the input size n\n  for i in 0...n\n    for j in 0...n\n      count += 1\n    end\n  end\n\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-10 compares constant, linear, and quadratic time complexity.

    Figure 2-10   Constant, linear, and quadratic time complexity

    In bubble sort, for example, the outer loop runs \\(n - 1\\) times, while the inner loop runs \\(n-1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), and \\(1\\) times, averaging \\(n / 2\\). The resulting complexity is \\(O((n - 1) n / 2) = O(n^2)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def bubble_sort(nums: list[int]) -> int:\n    \"\"\"Quadratic complexity (bubble sort)\"\"\"\n    count = 0  # Counter\n    # Outer loop: the unsorted range is [0, i]\n    for i in range(len(nums) - 1, 0, -1):\n        # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                tmp: int = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = tmp\n                count += 3  # Swapping two elements takes 3 elementary operations\n    return count\n
    time_complexity.cpp
    /* Quadratic complexity (bubble sort) */\nint bubbleSort(vector<int> &nums) {\n    int count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.java
    /* Quadratic complexity (bubble sort) */\nint bubbleSort(int[] nums) {\n    int count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Quadratic complexity (bubble sort) */\nint BubbleSort(int[] nums) {\n    int count = 0;  // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n                count += 3;  // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.go
    /* Quadratic complexity (bubble sort) */\nfunc bubbleSort(nums []int) int {\n    count := 0 // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                tmp := nums[j]\n                nums[j] = nums[j+1]\n                nums[j+1] = tmp\n                count += 3 // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count\n}\n
    time_complexity.swift
    /* Quadratic complexity (bubble sort) */\nfunc bubbleSort(nums: inout [Int]) -> Int {\n    var count = 0 // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = tmp\n                count += 3 // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count\n}\n
    time_complexity.js
    /* Quadratic complexity (bubble sort) */\nfunction bubbleSort(nums) {\n    let count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Quadratic complexity (bubble sort) */\nfunction bubbleSort(nums: number[]): number {\n    let count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Quadratic complexity (bubble sort) */\nint bubbleSort(List<int> nums) {\n  int count = 0; // Counter\n  // Outer loop: the unsorted range is [0, i]\n  for (var i = nums.length - 1; i > 0; i--) {\n    // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for (var j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n        count += 3; // Swapping two elements takes 3 elementary operations\n      }\n    }\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Quadratic complexity (bubble sort) */\nfn bubble_sort(nums: &mut [i32]) -> i32 {\n    let mut count = 0; // Counter\n\n    // Outer loop: the unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    count\n}\n
    time_complexity.c
    /* Quadratic complexity (bubble sort) */\nint bubbleSort(int *nums, int n) {\n    int count = 0; // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = n - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                count += 3; // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Quadratic complexity (bubble sort) */\nfun bubbleSort(nums: IntArray): Int {\n    var count = 0 // Counter\n    // Outer loop: the unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n                count += 3 // Swapping two elements takes 3 elementary operations\n            }\n        }\n    }\n    return count\n}\n
    time_complexity.rb
    ### Quadratic complexity (bubble sort)###\ndef bubble_sort(nums)\n  count = 0  # Counter\n\n  # Outer loop: the unsorted range is [0, i]\n  for i in (nums.length - 1).downto(0)\n    # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        tmp = nums[j]\n        nums[j] = nums[j + 1]\n        nums[j + 1] = tmp\n        count += 3 # Swapping two elements takes 3 elementary operations\n      end\n    end\n  end\n\n  count\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#4-exponential-time-o2n","level":3,"title":"4.   Exponential time \\(O(2^n)\\)","text":"

    Cell division is a classic example of exponential growth. Starting with \\(1\\) cell, one round produces \\(2\\), two rounds produce \\(4\\), and after \\(n\\) rounds there are \\(2^n\\) cells.

    The figure and code below simulate cell division in \\(O(2^n)\\) time. The input \\(n\\) is the number of rounds, while the returned count is the total number of individual divisions.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def exponential(n: int) -> int:\n    \"\"\"Exponential complexity (iterative implementation)\"\"\"\n    count = 0\n    base = 1\n    # Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in range(n):\n        for _ in range(base):\n            count += 1\n        base *= 2\n    # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n
    time_complexity.cpp
    /* Exponential complexity (iterative implementation) */\nint exponential(int n) {\n    int count = 0, base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.java
    /* Exponential complexity (iterative implementation) */\nint exponential(int n) {\n    int count = 0, base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.cs
    /* Exponential complexity (iterative implementation) */\nint Exponential(int n) {\n    int count = 0, bas = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < bas; j++) {\n            count++;\n        }\n        bas *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.go
    /* Exponential complexity (iterative implementation)*/\nfunc exponential(n int) int {\n    count, base := 0, 1\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for i := 0; i < n; i++ {\n        for j := 0; j < base; j++ {\n            count++\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
    time_complexity.swift
    /* Exponential complexity (iterative implementation) */\nfunc exponential(n: Int) -> Int {\n    var count = 0\n    var base = 1\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in 0 ..< n {\n        for _ in 0 ..< base {\n            count += 1\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
    time_complexity.js
    /* Exponential complexity (iterative implementation) */\nfunction exponential(n) {\n    let count = 0,\n        base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.ts
    /* Exponential complexity (iterative implementation) */\nfunction exponential(n: number): number {\n    let count = 0,\n        base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (let i = 0; i < n; i++) {\n        for (let j = 0; j < base; j++) {\n            count++;\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.dart
    /* Exponential complexity (iterative implementation) */\nint exponential(int n) {\n  int count = 0, base = 1;\n  // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n  for (var i = 0; i < n; i++) {\n    for (var j = 0; j < base; j++) {\n      count++;\n    }\n    base *= 2;\n  }\n  // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n  return count;\n}\n
    time_complexity.rs
    /* Exponential complexity (iterative implementation) */\nfn exponential(n: i32) -> i32 {\n    let mut count = 0;\n    let mut base = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for _ in 0..n {\n        for _ in 0..base {\n            count += 1\n        }\n        base *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    count\n}\n
    time_complexity.c
    /* Exponential complexity (iterative implementation) */\nint exponential(int n) {\n    int count = 0;\n    int bas = 1;\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (int i = 0; i < n; i++) {\n        for (int j = 0; j < bas; j++) {\n            count++;\n        }\n        bas *= 2;\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count;\n}\n
    time_complexity.kt
    /* Exponential complexity (iterative implementation) */\nfun exponential(n: Int): Int {\n    var count = 0\n    var base = 1\n    // Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n    for (i in 0..<n) {\n        for (j in 0..<base) {\n            count++\n        }\n        base *= 2\n    }\n    // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n    return count\n}\n
    time_complexity.rb
    ### Exponential complexity (iterative implementation)###\ndef exponential(n)\n  count, base = 0, 1\n\n  # Each cell divides into two per round, giving the sequence 1, 2, 4, 8, ..., 2^(n-1)\n  (0...n).each do\n    (0...base).each { count += 1 }\n    base *= 2\n  end\n\n  # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-11   Exponential time complexity

    Exponential time often appears in recursive algorithms. The following function branches into two calls at each level, stopping after \\(n\\) levels:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def exp_recur(n: int) -> int:\n    \"\"\"Exponential complexity (recursive implementation)\"\"\"\n    if n == 1:\n        return 1\n    return exp_recur(n - 1) + exp_recur(n - 1) + 1\n
    time_complexity.cpp
    /* Exponential complexity (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.java
    /* Exponential complexity (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.cs
    /* Exponential complexity (recursive implementation) */\nint ExpRecur(int n) {\n    if (n == 1) return 1;\n    return ExpRecur(n - 1) + ExpRecur(n - 1) + 1;\n}\n
    time_complexity.go
    /* Exponential complexity (recursive implementation)*/\nfunc expRecur(n int) int {\n    if n == 1 {\n        return 1\n    }\n    return expRecur(n-1) + expRecur(n-1) + 1\n}\n
    time_complexity.swift
    /* Exponential complexity (recursive implementation) */\nfunc expRecur(n: Int) -> Int {\n    if n == 1 {\n        return 1\n    }\n    return expRecur(n: n - 1) + expRecur(n: n - 1) + 1\n}\n
    time_complexity.js
    /* Exponential complexity (recursive implementation) */\nfunction expRecur(n) {\n    if (n === 1) return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.ts
    /* Exponential complexity (recursive implementation) */\nfunction expRecur(n: number): number {\n    if (n === 1) return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.dart
    /* Exponential complexity (recursive implementation) */\nint expRecur(int n) {\n  if (n == 1) return 1;\n  return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.rs
    /* Exponential complexity (recursive implementation) */\nfn exp_recur(n: i32) -> i32 {\n    if n == 1 {\n        return 1;\n    }\n    exp_recur(n - 1) + exp_recur(n - 1) + 1\n}\n
    time_complexity.c
    /* Exponential complexity (recursive implementation) */\nint expRecur(int n) {\n    if (n == 1)\n        return 1;\n    return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n
    time_complexity.kt
    /* Exponential complexity (recursive implementation) */\nfun expRecur(n: Int): Int {\n    if (n == 1) {\n        return 1\n    }\n    return expRecur(n - 1) + expRecur(n - 1) + 1\n}\n
    time_complexity.rb
    ### Exponential complexity (recursive implementation)###\ndef exp_recur(n)\n  return 1 if n == 1\n  exp_recur(n - 1) + exp_recur(n - 1) + 1\nend\n
    Visualize execution

    View full screen >

    Exponential growth is very rapid and common in exhaustive approaches such as brute-force search and backtracking. It is unacceptable for large inputs, which usually require approaches such as dynamic programming or greedy algorithms.

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#5-logarithmic-time-olog-n","level":3,"title":"5.   Logarithmic time \\(O(\\log n)\\)","text":"

    Logarithmic growth corresponds to repeatedly halving a quantity, the reverse of exponential growth. Starting with input size \\(n\\), halving each round gives \\(\\log_2 n\\) iterations, the inverse of \\(2^n\\).

    The figure and code below simulate repeated halving, taking \\(O(\\log_2 n)\\) time, usually written \\(O(\\log n)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def logarithmic(n: int) -> int:\n    \"\"\"Logarithmic complexity (iterative implementation)\"\"\"\n    count = 0\n    while n > 1:\n        n = n / 2\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Logarithmic complexity (iterative implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.java
    /* Logarithmic complexity (iterative implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Logarithmic complexity (iterative implementation) */\nint Logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n /= 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.go
    /* Logarithmic complexity (iterative implementation)*/\nfunc logarithmic(n int) int {\n    count := 0\n    for n > 1 {\n        n = n / 2\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Logarithmic complexity (iterative implementation) */\nfunc logarithmic(n: Int) -> Int {\n    var count = 0\n    var n = n\n    while n > 1 {\n        n = n / 2\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Logarithmic complexity (iterative implementation) */\nfunction logarithmic(n) {\n    let count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Logarithmic complexity (iterative implementation) */\nfunction logarithmic(n: number): number {\n    let count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Logarithmic complexity (iterative implementation) */\nint logarithmic(int n) {\n  int count = 0;\n  while (n > 1) {\n    n = n ~/ 2;\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Logarithmic complexity (iterative implementation) */\nfn logarithmic(mut n: i32) -> i32 {\n    let mut count = 0;\n    while n > 1 {\n        n = n / 2;\n        count += 1;\n    }\n    count\n}\n
    time_complexity.c
    /* Logarithmic complexity (iterative implementation) */\nint logarithmic(int n) {\n    int count = 0;\n    while (n > 1) {\n        n = n / 2;\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Logarithmic complexity (iterative implementation) */\nfun logarithmic(n: Int): Int {\n    var n1 = n\n    var count = 0\n    while (n1 > 1) {\n        n1 /= 2\n        count++\n    }\n    return count\n}\n
    time_complexity.rb
    ### Logarithmic complexity (iterative implementation)###\ndef logarithmic(n)\n  count = 0\n\n  while n > 1\n    n /= 2\n    count += 1\n  end\n\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-12   Logarithmic time complexity

    Like exponential growth, logarithmic growth often occurs in recursion. The following code produces a recursion tree of height \\(\\log_2 n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def log_recur(n: int) -> int:\n    \"\"\"Logarithmic complexity (recursive implementation)\"\"\"\n    if n <= 1:\n        return 0\n    return log_recur(n / 2) + 1\n
    time_complexity.cpp
    /* Logarithmic complexity (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.java
    /* Logarithmic complexity (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.cs
    /* Logarithmic complexity (recursive implementation) */\nint LogRecur(int n) {\n    if (n <= 1) return 0;\n    return LogRecur(n / 2) + 1;\n}\n
    time_complexity.go
    /* Logarithmic complexity (recursive implementation)*/\nfunc logRecur(n int) int {\n    if n <= 1 {\n        return 0\n    }\n    return logRecur(n/2) + 1\n}\n
    time_complexity.swift
    /* Logarithmic complexity (recursive implementation) */\nfunc logRecur(n: Int) -> Int {\n    if n <= 1 {\n        return 0\n    }\n    return logRecur(n: n / 2) + 1\n}\n
    time_complexity.js
    /* Logarithmic complexity (recursive implementation) */\nfunction logRecur(n) {\n    if (n <= 1) return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.ts
    /* Logarithmic complexity (recursive implementation) */\nfunction logRecur(n: number): number {\n    if (n <= 1) return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.dart
    /* Logarithmic complexity (recursive implementation) */\nint logRecur(int n) {\n  if (n <= 1) return 0;\n  return logRecur(n ~/ 2) + 1;\n}\n
    time_complexity.rs
    /* Logarithmic complexity (recursive implementation) */\nfn log_recur(n: i32) -> i32 {\n    if n <= 1 {\n        return 0;\n    }\n    log_recur(n / 2) + 1\n}\n
    time_complexity.c
    /* Logarithmic complexity (recursive implementation) */\nint logRecur(int n) {\n    if (n <= 1)\n        return 0;\n    return logRecur(n / 2) + 1;\n}\n
    time_complexity.kt
    /* Logarithmic complexity (recursive implementation) */\nfun logRecur(n: Int): Int {\n    if (n <= 1)\n        return 0\n    return logRecur(n / 2) + 1\n}\n
    time_complexity.rb
    ### Logarithmic complexity (recursive implementation)###\ndef log_recur(n)\n  return 0 unless n > 1\n  log_recur(n / 2) + 1\nend\n
    Visualize execution

    View full screen >

    Logarithmic time often arises in divide-and-conquer algorithms, reflecting the idea of dividing a problem into smaller, simpler parts. It grows slowly and is highly desirable, second only to constant time among these classes.

    What is the base of \\(O(\\log n)\\)?

    More precisely, reducing the size by a factor of \\(m\\) gives \\(O(\\log_m n)\\). The change-of-base formula shows that different fixed bases yield the same complexity:

    \\[ O(\\log_m n) = O(\\log_k n / \\log_k m) = O(\\log_k n) \\]

    Thus, changing the base \\(m\\) does not affect the complexity. We usually omit \\(m\\) and simply write \\(O(\\log n)\\).

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#6-linearithmic-time-on-log-n","level":3,"title":"6.   Linearithmic time \\(O(n \\log n)\\)","text":"

    Linearithmic time often appears when one level of a nested process takes \\(O(\\log n)\\) and another takes \\(O(n)\\). An example follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def linear_log_recur(n: int) -> int:\n    \"\"\"Linearithmic complexity\"\"\"\n    if n <= 1:\n        return 1\n    # Split into two subproblems, each half the original size\n    count = linear_log_recur(n // 2) + linear_log_recur(n // 2)\n    # The current subproblem includes n operations\n    for _ in range(n):\n        count += 1\n    return count\n
    time_complexity.cpp
    /* Linearithmic complexity */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.java
    /* Linearithmic complexity */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Linearithmic complexity */\nint LinearLogRecur(int n) {\n    if (n <= 1) return 1;\n    int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.go
    /* Linearithmic complexity */\nfunc linearLogRecur(n int) int {\n    if n <= 1 {\n        return 1\n    }\n    count := linearLogRecur(n/2) + linearLogRecur(n/2)\n    for i := 0; i < n; i++ {\n        count++\n    }\n    return count\n}\n
    time_complexity.swift
    /* Linearithmic complexity */\nfunc linearLogRecur(n: Int) -> Int {\n    if n <= 1 {\n        return 1\n    }\n    var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2)\n    for _ in stride(from: 0, to: n, by: 1) {\n        count += 1\n    }\n    return count\n}\n
    time_complexity.js
    /* Linearithmic complexity */\nfunction linearLogRecur(n) {\n    if (n <= 1) return 1;\n    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (let i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Linearithmic complexity */\nfunction linearLogRecur(n: number): number {\n    if (n <= 1) return 1;\n    let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (let i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Linearithmic complexity */\nint linearLogRecur(int n) {\n  if (n <= 1) return 1;\n  int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2);\n  for (var i = 0; i < n; i++) {\n    count++;\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Linearithmic complexity */\nfn linear_log_recur(n: i32) -> i32 {\n    if n <= 1 {\n        return 1;\n    }\n    let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2);\n    for _ in 0..n {\n        count += 1;\n    }\n    return count;\n}\n
    time_complexity.c
    /* Linearithmic complexity */\nint linearLogRecur(int n) {\n    if (n <= 1)\n        return 1;\n    int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n    for (int i = 0; i < n; i++) {\n        count++;\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Linearithmic complexity */\nfun linearLogRecur(n: Int): Int {\n    if (n <= 1)\n        return 1\n    var count = linearLogRecur(n / 2) + linearLogRecur(n / 2)\n    for (i in 0..<n) {\n        count++\n    }\n    return count\n}\n
    time_complexity.rb
    ### Linearithmic complexity ###\ndef linear_log_recur(n)\n  return 1 unless n > 1\n\n  count = linear_log_recur(n / 2) + linear_log_recur(n / 2)\n  (0...n).each { count += 1 }\n\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-13 shows how linearithmic growth arises. Each level of the binary tree performs \\(n\\) operations, and there are \\(\\log_2 n + 1\\) levels, giving \\(O(n \\log n)\\) time.

    Figure 2-13   Linearithmic time complexity

    Common sorting algorithms, including quick sort, merge sort, and heap sort, typically take \\(O(n \\log n)\\) time.

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#7-factorial-time-on","level":3,"title":"7.   Factorial time \\(O(n!)\\)","text":"

    Factorial growth arises when generating every permutation of \\(n\\) distinct elements. The number of permutations is:

    \\[ n! = n \\times (n - 1) \\times (n - 2) \\times \\dots \\times 2 \\times 1 \\]

    Algorithms with factorial time complexity are usually implemented recursively. As the figure and code below show, each call creates \\(n\\) branches at the first level, \\(n - 1\\) at the second, and so on until level \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py
    def factorial_recur(n: int) -> int:\n    \"\"\"Factorial complexity (recursive implementation)\"\"\"\n    if n == 0:\n        return 1\n    count = 0\n    # Split one problem into n subproblems\n    for _ in range(n):\n        count += factorial_recur(n - 1)\n    return count\n
    time_complexity.cpp
    /* Factorial complexity (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    // Split one problem into n subproblems\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.java
    /* Factorial complexity (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    // Split one problem into n subproblems\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.cs
    /* Factorial complexity (recursive implementation) */\nint FactorialRecur(int n) {\n    if (n == 0) return 1;\n    int count = 0;\n    // Split one problem into n subproblems\n    for (int i = 0; i < n; i++) {\n        count += FactorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.go
    /* Factorial complexity (recursive implementation) */\nfunc factorialRecur(n int) int {\n    if n == 0 {\n        return 1\n    }\n    count := 0\n    // Split one problem into n subproblems\n    for i := 0; i < n; i++ {\n        count += factorialRecur(n - 1)\n    }\n    return count\n}\n
    time_complexity.swift
    /* Factorial complexity (recursive implementation) */\nfunc factorialRecur(n: Int) -> Int {\n    if n == 0 {\n        return 1\n    }\n    var count = 0\n    // Split one problem into n subproblems\n    for _ in 0 ..< n {\n        count += factorialRecur(n: n - 1)\n    }\n    return count\n}\n
    time_complexity.js
    /* Factorial complexity (recursive implementation) */\nfunction factorialRecur(n) {\n    if (n === 0) return 1;\n    let count = 0;\n    // Split one problem into n subproblems\n    for (let i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.ts
    /* Factorial complexity (recursive implementation) */\nfunction factorialRecur(n: number): number {\n    if (n === 0) return 1;\n    let count = 0;\n    // Split one problem into n subproblems\n    for (let i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.dart
    /* Factorial complexity (recursive implementation) */\nint factorialRecur(int n) {\n  if (n == 0) return 1;\n  int count = 0;\n  // Split one problem into n subproblems\n  for (var i = 0; i < n; i++) {\n    count += factorialRecur(n - 1);\n  }\n  return count;\n}\n
    time_complexity.rs
    /* Factorial complexity (recursive implementation) */\nfn factorial_recur(n: i32) -> i32 {\n    if n == 0 {\n        return 1;\n    }\n    let mut count = 0;\n    // Split one problem into n subproblems\n    for _ in 0..n {\n        count += factorial_recur(n - 1);\n    }\n    count\n}\n
    time_complexity.c
    /* Factorial complexity (recursive implementation) */\nint factorialRecur(int n) {\n    if (n == 0)\n        return 1;\n    int count = 0;\n    for (int i = 0; i < n; i++) {\n        count += factorialRecur(n - 1);\n    }\n    return count;\n}\n
    time_complexity.kt
    /* Factorial complexity (recursive implementation) */\nfun factorialRecur(n: Int): Int {\n    if (n == 0)\n        return 1\n    var count = 0\n    // Split one problem into n subproblems\n    for (i in 0..<n) {\n        count += factorialRecur(n - 1)\n    }\n    return count\n}\n
    time_complexity.rb
    ### Factorial complexity (recursive implementation)###\ndef factorial_recur(n)\n  return 1 if n == 0\n\n  count = 0\n  # Split one problem into n subproblems\n  (0...n).each { count += factorial_recur(n - 1) }\n\n  count\nend\n
    Visualize execution

    View full screen >

    Figure 2-14   Factorial time complexity

    Since \\(n! > 2^n\\) whenever \\(n \\geq 4\\), factorial growth is faster than exponential growth and is also unacceptable for large \\(n\\).

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#235-worst-case-best-case-and-average-case-time-complexity","level":2,"title":"2.3.5   Worst-case, best-case, and average-case time complexity","text":"

    An algorithm's efficiency often depends on how its input data is arranged. Suppose nums is an array of length \\(n\\) containing every integer from \\(1\\) through \\(n\\) exactly once in a random order. The task is to return the index of \\(1\\). We can conclude the following.

    • If nums = [?, ?, ..., 1], with \\(1\\) last, we must traverse the whole array, giving worst-case time complexity \\(O(n)\\).
    • If nums = [1, ?, ?, ...], with \\(1\\) first, no further traversal is needed regardless of array length, giving best-case time complexity \\(\\Omega(1)\\).

    Worst-case time complexity corresponds to an asymptotic upper bound, expressed with big-\\(O\\) notation. Best-case time complexity corresponds to an asymptotic lower bound, expressed with \\(\\Omega\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby worst_best_time_complexity.py
    def random_numbers(n: int) -> list[int]:\n    \"\"\"Generate an array containing 1, 2, ..., n in shuffled order\"\"\"\n    # Generate the array nums = 1, 2, 3, ..., n\n    nums = [i for i in range(1, n + 1)]\n    # Randomly shuffle the array elements\n    random.shuffle(nums)\n    return nums\n\ndef find_one(nums: list[int]) -> int:\n    \"\"\"Find the index of the number 1 in array nums\"\"\"\n    for i in range(len(nums)):\n        # When 1 is at the start of the array, the best-case time complexity is O(1)\n        # When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if nums[i] == 1:\n            return i\n    return -1\n
    worst_best_time_complexity.cpp
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nvector<int> randomNumbers(int n) {\n    vector<int> nums(n);\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Use the system time to generate a random seed\n    unsigned seed = chrono::system_clock::now().time_since_epoch().count();\n    // Randomly shuffle the array elements\n    shuffle(nums.begin(), nums.end(), default_random_engine(seed));\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nint findOne(vector<int> &nums) {\n    for (int i = 0; i < nums.size(); i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
    worst_best_time_complexity.java
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nint[] randomNumbers(int n) {\n    Integer[] nums = new Integer[n];\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle the array elements\n    Collections.shuffle(Arrays.asList(nums));\n    // Integer[] -> int[]\n    int[] res = new int[n];\n    for (int i = 0; i < n; i++) {\n        res[i] = nums[i];\n    }\n    return res;\n}\n\n/* Find the index of the number 1 in array nums */\nint findOne(int[] nums) {\n    for (int i = 0; i < nums.length; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
    worst_best_time_complexity.cs
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nint[] RandomNumbers(int n) {\n    int[] nums = new int[n];\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n\n    // Randomly shuffle the array elements\n    for (int i = 0; i < nums.Length; i++) {\n        int index = new Random().Next(i, nums.Length);\n        (nums[i], nums[index]) = (nums[index], nums[i]);\n    }\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nint FindOne(int[] nums) {\n    for (int i = 0; i < nums.Length; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
    worst_best_time_complexity.go
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfunc randomNumbers(n int) []int {\n    nums := make([]int, n)\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for i := 0; i < n; i++ {\n        nums[i] = i + 1\n    }\n    // Randomly shuffle the array elements\n    rand.Shuffle(len(nums), func(i, j int) {\n        nums[i], nums[j] = nums[j], nums[i]\n    })\n    return nums\n}\n\n/* Find the index of the number 1 in array nums */\nfunc findOne(nums []int) int {\n    for i := 0; i < len(nums); i++ {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if nums[i] == 1 {\n            return i\n        }\n    }\n    return -1\n}\n
    worst_best_time_complexity.swift
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfunc randomNumbers(n: Int) -> [Int] {\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    var nums = Array(1 ... n)\n    // Randomly shuffle the array elements\n    nums.shuffle()\n    return nums\n}\n\n/* Find the index of the number 1 in array nums */\nfunc findOne(nums: [Int]) -> Int {\n    for i in nums.indices {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if nums[i] == 1 {\n            return i\n        }\n    }\n    return -1\n}\n
    worst_best_time_complexity.js
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfunction randomNumbers(n) {\n    const nums = Array(n);\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (let i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle the array elements\n    for (let i = 0; i < n; i++) {\n        const r = Math.floor(Math.random() * (i + 1));\n        const temp = nums[i];\n        nums[i] = nums[r];\n        nums[r] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nfunction findOne(nums) {\n    for (let i = 0; i < nums.length; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] === 1) {\n            return i;\n        }\n    }\n    return -1;\n}\n
    worst_best_time_complexity.ts
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfunction randomNumbers(n: number): number[] {\n    const nums = Array(n);\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (let i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle the array elements\n    for (let i = 0; i < n; i++) {\n        const r = Math.floor(Math.random() * (i + 1));\n        const temp = nums[i];\n        nums[i] = nums[r];\n        nums[r] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nfunction findOne(nums: number[]): number {\n    for (let i = 0; i < nums.length; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] === 1) {\n            return i;\n        }\n    }\n    return -1;\n}\n
    worst_best_time_complexity.dart
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nList<int> randomNumbers(int n) {\n  final nums = List.filled(n, 0);\n  // Generate the array nums = { 1, 2, 3, ..., n }\n  for (var i = 0; i < n; i++) {\n    nums[i] = i + 1;\n  }\n  // Randomly shuffle the array elements\n  nums.shuffle();\n\n  return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nint findOne(List<int> nums) {\n  for (var i = 0; i < nums.length; i++) {\n    // When 1 is at the start of the array, the best-case time complexity is O(1)\n    // When 1 is at the end of the array, the worst-case time complexity is O(n)\n    if (nums[i] == 1) return i;\n  }\n\n  return -1;\n}\n
    worst_best_time_complexity.rs
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfn random_numbers(n: i32) -> Vec<i32> {\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    let mut nums = (1..=n).collect::<Vec<i32>>();\n    // Randomly shuffle the array elements\n    nums.shuffle(&mut thread_rng());\n    nums\n}\n\n/* Find the index of the number 1 in array nums */\nfn find_one(nums: &[i32]) -> Option<usize> {\n    for i in 0..nums.len() {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if nums[i] == 1 {\n            return Some(i);\n        }\n    }\n    None\n}\n
    worst_best_time_complexity.c
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nint *randomNumbers(int n) {\n    // Allocate heap memory for a one-dimensional array of n int elements\n    int *nums = (int *)malloc(n * sizeof(int));\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (int i = 0; i < n; i++) {\n        nums[i] = i + 1;\n    }\n    // Randomly shuffle the array elements\n    for (int i = n - 1; i > 0; i--) {\n        int j = rand() % (i + 1);\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n    return nums;\n}\n\n/* Find the index of the number 1 in array nums */\nint findOne(int *nums, int n) {\n    for (int i = 0; i < n; i++) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i;\n    }\n    return -1;\n}\n
    worst_best_time_complexity.kt
    /* Generate an array containing { 1, 2, ..., n } in shuffled order */\nfun randomNumbers(n: Int): Array<Int?> {\n    val nums = IntArray(n)\n    // Generate the array nums = { 1, 2, 3, ..., n }\n    for (i in 0..<n) {\n        nums[i] = i + 1\n    }\n    // Randomly shuffle the array elements\n    nums.shuffle()\n    val res = arrayOfNulls<Int>(n)\n    for (i in 0..<n) {\n        res[i] = nums[i]\n    }\n    return res\n}\n\n/* Find the index of the number 1 in array nums */\nfun findOne(nums: Array<Int?>): Int {\n    for (i in nums.indices) {\n        // When 1 is at the start of the array, the best-case time complexity is O(1)\n        // When 1 is at the end of the array, the worst-case time complexity is O(n)\n        if (nums[i] == 1)\n            return i\n    }\n    return -1\n}\n
    worst_best_time_complexity.rb
    ### Generate an array containing 1, 2, ..., n in shuffled order ###\ndef random_numbers(n)\n  # Generate the array nums = 1, 2, 3, ..., n\n  nums = Array.new(n) { |i| i + 1 }\n  # Randomly shuffle the array elements\n  nums.shuffle!\nend\n\n### Find the index of the number 1 in array nums ###\ndef find_one(nums)\n  for i in 0...nums.length\n    # When 1 is at the start of the array, the best-case time complexity is O(1)\n    # When 1 is at the end of the array, the worst-case time complexity is O(n)\n    return i if nums[i] == 1\n  end\n\n  -1\nend\n
    Visualize execution

    View full screen >

    Best-case complexity is rarely used in practice because it may occur only with low probability and can be misleading. Worst-case complexity is more useful because it gives a safe performance bound, allowing us to use the algorithm with confidence.

    In this example, both the worst and best cases occur only for particular arrangements, perhaps with low probability, and may not reflect ordinary performance. Average-case complexity describes performance on random inputs and is expressed with \\(\\Theta\\).

    For some algorithms, the average case is easy to derive. In the example above, \\(1\\) is equally likely to occur at any index in the shuffled array. The average number of iterations is half the array length, \\(n / 2\\), giving \\(\\Theta(n / 2) = \\Theta(n)\\).

    For more complex algorithms, average-case complexity can be difficult to calculate because the overall expectation under the input distribution is hard to derive. In such cases, we usually judge efficiency by worst-case complexity.

    Why is \\(\\Theta\\) rarely seen?

    Perhaps because \\(O\\) is so familiar, it is often used for average-case complexity too. Strictly speaking, this usage is not standard. In this book and other resources, read a statement such as \"average-case time complexity \\(O(n)\\)\" as \\(\\Theta(n)\\).

    ","path":["Chapter 2.   Complexity analysis","2.3   Time complexity"],"tags":[]},{"location":"chapter_data_structure/","level":1,"title":"Chapter 3.   Data structures","text":"

    Abstract

    Data structures provide a framework that is both sturdy and versatile.

    They offer a blueprint for organizing data, a foundation on which algorithms come to life.

    ","path":["Chapter 3.   Data structures"],"tags":[]},{"location":"chapter_data_structure/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 3.1   Classifying data structures
    • 3.2   Basic data types
    • 3.3   Number encoding *
    • 3.4   Character encoding *
    • 3.5   Summary
    • 3.6   Exercises
    ","path":["Chapter 3.   Data structures"],"tags":[]},{"location":"chapter_data_structure/basic_data_types/","level":1,"title":"3.2   Basic data types","text":"

    Computer data comes in many forms: text, images, video, audio, 3D models, and more. Although organized differently, all these forms are built from basic data types.

    Basic data types are types on which the CPU can operate directly and which algorithms use directly. The main types include the following.

    • Integer types: byte, short, int, and long.
    • Floating-point types: float and double, used to represent fractional numbers.
    • The character type char, used for letters in different languages, punctuation, and even emoji.
    • The Boolean type bool, used for yes-or-no values.

    Basic data types are stored in binary form. One binary digit is \\(1\\) bit. In the vast majority of modern operating systems, \\(1\\) byte consists of \\(8\\) bits.

    A basic data type's value range depends on the space it occupies. Taking Java as an example:

    • An integer byte occupies \\(1\\) byte = \\(8\\) bits and can represent \\(2^{8}\\) distinct values.
    • An integer int occupies \\(4\\) bytes = \\(32\\) bits and can represent \\(2^{32}\\) distinct values.

    Table 3-1 lists storage sizes, value ranges, and default values for Java's basic data types. There is no need to memorize it; understand the general picture and refer back when needed.

    Table 3-1   Storage sizes and value ranges of basic data types

    Type Name Storage size Minimum value Maximum value Default value Integer byte 1 byte \\(-2^7\\) (\\(-128\\)) \\(2^7 - 1\\) (\\(127\\)) \\(0\\) short 2 bytes \\(-2^{15}\\) \\(2^{15} - 1\\) \\(0\\) int 4 bytes \\(-2^{31}\\) \\(2^{31} - 1\\) \\(0\\) long 8 bytes \\(-2^{63}\\) \\(2^{63} - 1\\) \\(0\\) Floating-point float 4 bytes \\(1.175 \\times 10^{-38}\\) \\(3.403 \\times 10^{38}\\) \\(0.0\\text{f}\\) double 8 bytes \\(2.225 \\times 10^{-308}\\) \\(1.798 \\times 10^{308}\\) \\(0.0\\) Character char 2 bytes \\(0\\) \\(2^{16} - 1\\) \\(0\\) Boolean bool 1 byte \\(\\text{false}\\) \\(\\text{true}\\) \\(\\text{false}\\)

    Table 3-1 describes Java's basic data types. Each programming language defines its own types, whose sizes, value ranges, and default values may differ.

    • In Python, an int can be arbitrarily large, limited only by available memory. A float is a 64-bit double-precision number. There is no char type: a single character is a str of length 1.
    • C and C++ do not fix the sizes of basic data types; they vary by implementation and platform. Table 3-1 follows the LP64 data model, used by 64-bit Unix operating systems including Linux and macOS.
    • A char occupies 1 byte in C and C++. In most programming languages, character size depends on the encoding used; see the character encoding section.
    • Although a Boolean needs only 1 bit (\\(0\\) or \\(1\\)), it usually occupies 1 byte in memory because modern CPUs typically use a byte as the smallest addressable memory unit.

    How do basic data types relate to data structures? A data structure is a way to organize and store data in a computer. The emphasis is on structure, rather than the data itself.

    To represent a row of numbers, an array comes naturally to mind. Its linear structure captures adjacency and ordering, regardless of whether the stored values are integers (int), fractional numbers (float), or characters (char).

    In other words, basic data types specify what kind of data is stored, while data structures specify how it is organized. The following code uses the same data structure, an array, for different basic types, including int, float, char, and bool.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    # Initialize arrays with different basic data types\nnumbers: list[int] = [0] * 5\ndecimals: list[float] = [0.0] * 5\n# A Python character is a string of length 1\ncharacters: list[str] = ['0'] * 5\nbools: list[bool] = [False] * 5\n# Python lists can hold different basic data types and object references\ndata = [0, 0.0, 'a', False, ListNode(0)]\n
    // Initialize arrays with different basic data types\nint numbers[5];\nfloat decimals[5];\nchar characters[5];\nbool bools[5];\n
    // Initialize arrays with different basic data types\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nboolean[] bools = new boolean[5];\n
    // Initialize arrays with different basic data types\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nbool[] bools = new bool[5];\n
    // Initialize arrays with different basic data types\nvar numbers = [5]int{}\nvar decimals = [5]float64{}\nvar characters = [5]byte{}\nvar bools = [5]bool{}\n
    // Initialize arrays with different basic data types\nlet numbers = Array(repeating: 0, count: 5)\nlet decimals = Array(repeating: 0.0, count: 5)\nlet characters: [Character] = Array(repeating: \"a\", count: 5)\nlet bools = Array(repeating: false, count: 5)\n
    // JavaScript arrays can hold different basic data types and objects\nconst array = [0, 0.0, 'a', false];\n
    // Initialize arrays with different basic data types\nconst numbers: number[] = [];\nconst characters: string[] = [];\nconst bools: boolean[] = [];\n
    // Initialize arrays with different basic data types\nList<int> numbers = List.filled(5, 0);\nList<double> decimals = List.filled(5, 0.0);\nList<String> characters = List.filled(5, 'a');\nList<bool> bools = List.filled(5, false);\n
    // Initialize arrays with different basic data types\nlet numbers: Vec<i32> = vec![0; 5];\nlet decimals: Vec<f32> = vec![0.0; 5];\nlet characters: Vec<char> = vec!['0'; 5];\nlet bools: Vec<bool> = vec![false; 5];\n
    // Initialize arrays with different basic data types\nint numbers[10];\nfloat decimals[10];\nchar characters[10];\nbool bools[10];\n
    // Initialize arrays with different basic data types\nval numbers = IntArray(5)\nval decinals = FloatArray(5)\nval characters = CharArray(5)\nval bools = BooleanArray(5)\n
    # Ruby arrays can hold different basic data types and object references\ndata = [0, 0.0, 'a', false, ListNode(0)]\n
    Visualize execution

    View full screen >

    ","path":["Chapter 3.   Data structures","3.2   Basic data types"],"tags":[]},{"location":"chapter_data_structure/character_encoding/","level":1,"title":"3.4   Character encoding *","text":"

    All computer data is stored as binary numbers, including characters of type char. To represent characters, we need a character set that assigns each character a unique binary number. The computer can then convert numbers to characters by looking them up.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#341-ascii-character-set","level":2,"title":"3.4.1   ASCII character set","text":"

    ASCII, short for American Standard Code for Information Interchange, was the earliest character set. It uses 7 bits (the lower 7 bits of a byte) per character, allowing up to 128 characters. As shown in Figure 3-6, ASCII includes uppercase and lowercase English letters, digits 0 ~ 9, punctuation, and control characters such as newlines and tabs.

    Figure 3-6   ASCII codes

    However, ASCII can represent only English text. As computers spread worldwide, EASCII character sets emerged to support more languages. Extending ASCII from 7 bits to 8 allowed 256 distinct characters.

    Different regions developed their own EASCII character sets. The first 128 characters remained identical to ASCII, while the remaining 128 varied to meet different languages' needs.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#342-gbk-character-set","level":2,"title":"3.4.2   GBK character set","text":"

    Eventually, even EASCII proved too small for many languages. Chinese, for example, has nearly 100,000 characters, with thousands used daily. In 1980, China's National Bureau of Standards released GB2312, containing 6,763 Chinese characters and meeting most basic needs for processing Chinese text.

    GB2312 could not handle some rare and traditional characters. GBK extended it to 21,886 Chinese characters. In GBK encoding, ASCII characters use one byte, and Chinese characters use two.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#343-unicode-character-set","level":2,"title":"3.4.3   Unicode character set","text":"

    As computing grew, character sets and encoding standards proliferated, creating problems. Most covered only particular languages and did not work well in multilingual settings. Multiple standards also existed for the same language, so exchanging text between computers using different encodings could produce garbled text.

    Researchers asked: could a sufficiently comprehensive character set, containing all the world's languages and symbols, solve both multilingual compatibility and garbled-text problems? This idea led to Unicode.

    Unicode, whose Chinese name means \"unified code\", can theoretically accommodate over a million characters. It aims to bring the world's characters into one universal set for processing and displaying different languages, reducing garbled text caused by incompatible standards. Since its release in 1991, it has continued adding languages and characters. As of September 2022, Unicode contained 149,186 characters, including letters, symbols, and emoji.

    As a universal character set, Unicode assigns each character a unique code point, or character number, from U+0000 to U+10FFFF. It does not, by itself, specify how these code points are stored in a computer. How can a system parse text containing code points of different lengths? For example, how does it distinguish one two-byte character from two one-byte characters?

    One straightforward solution is to store every character in a fixed-width encoding. In Figure 3-7, each character in \"Hello\" occupies 1 byte, while each in \"算法\" occupies 2 bytes. Padding the high bits with zeros lets us encode every character in \"Hello 算法\" with 2 bytes. The system can then read one character every 2 bytes to recover the phrase.

    Figure 3-7   Example of Unicode encoding

    ASCII has already shown that English text needs only 1 byte per character. This scheme would double its storage compared with ASCII, wasting memory. We therefore need a more efficient Unicode encoding.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#344-utf-8-encoding","level":2,"title":"3.4.4   UTF-8 encoding","text":"

    UTF-8 is now the most widely used Unicode encoding worldwide. It is variable-length, using 1 to 4 bytes per character depending on the character. ASCII characters need 1 byte, Latin and Greek letters need 2, common Chinese characters need 3, and some less common characters need 4.

    UTF-8's rules have two cases.

    • For a one-byte character, set the highest bit to \\(0\\) and the remaining 7 bits to its Unicode code point. ASCII occupies the first 128 Unicode code points, so UTF-8 is backward-compatible with ASCII. UTF-8 can therefore decode old ASCII text.
    • For an \\(n\\)-byte character, where \\(n > 1\\), set the first byte's leading \\(n\\) bits to \\(1\\) and the next bit, \\(n + 1\\), to \\(0\\). Starting with the second byte, set the leading 2 bits of every byte to \\(10\\). Fill the remaining bits with the character's Unicode code point.

    Figure 3-8 shows UTF-8 for \"Hello算法\". Since the leading \\(n\\) bits are \\(1\\), the system can determine the character length \\(n\\) by counting the leading \\(1\\) bits.

    Why do all remaining bytes begin with \\(10\\)? This \\(10\\) acts as a check marker. If decoding starts at the wrong byte, the leading \\(10\\) helps the system quickly detect the problem.

    The prefix \\(10\\) works as a marker because no character's first byte can begin with \\(10\\) under UTF-8's rules. To see this by contradiction, suppose it did begin with \\(10\\). The single leading one would imply a length of \\(1\\) byte, corresponding to ASCII, but an ASCII byte must begin with \\(0\\), a contradiction.

    Figure 3-8   Example of UTF-8 encoding

    Two other common encodings are:

    • UTF-16: Uses 2 or 4 bytes per character. ASCII and common non-English characters use 2 bytes; some characters require 4. For a two-byte character, its UTF-16 encoding equals its Unicode code point.
    • UTF-32: Uses 4 bytes for every character. It therefore takes more space than UTF-8 or UTF-16, especially for text containing mostly ASCII characters.

    In terms of storage, UTF-8 is efficient for English characters because each uses only 1 byte. UTF-16 is more efficient for some non-English characters, such as Chinese, using 2 bytes where UTF-8 may require 3.

    In terms of compatibility, UTF-8 is the most widely supported, and many tools and libraries prioritize it.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#345-character-encoding-in-programming-languages","level":2,"title":"3.4.5   Character encoding in programming languages","text":"

    Historically, most programming languages have stored strings at runtime using fixed-width encodings such as UTF-16 or UTF-32. Fixed-width encoding lets us treat a string as an array, with the following advantages.

    • Random access: UTF-16 strings support straightforward random access. With variable-length UTF-8, finding the \\(i\\)-th character requires traversing from the beginning to the \\(i\\)-th character, taking \\(O(n)\\) time.
    • Character counting: Similarly, finding the length of a UTF-16 string is an \\(O(1)\\) operation, while finding the character count of a UTF-8 string requires traversing the whole string.
    • String operations: Operations such as splitting, concatenation, insertion, and deletion are easier on UTF-16 strings. With UTF-8, they usually require extra work to avoid producing invalid encodings.

    The design of string representations is an interesting subject involving many factors.

    • Java's String uses UTF-16, with 2 bytes per character. When Java was designed, 16 bits were thought sufficient for every character. This turned out to be incorrect: Unicode later expanded beyond 16 bits, so a Java character may now require a pair of 16-bit values called a surrogate pair.
    • JavaScript and TypeScript use UTF-16 for similar historical reasons. When Netscape introduced JavaScript in 1995, Unicode was still young, and 16 bits could represent all its characters at the time.
    • C# uses UTF-16 largely because Microsoft designed .NET, and many Microsoft technologies, including Windows, use UTF-16 extensively.

    Having underestimated the number of characters, these languages had to adopt surrogate pairs for Unicode characters beyond 16 bits. This was a compromise. A character in such a string may occupy 2 or 4 bytes, losing the benefit of fixed width, and handling surrogate pairs requires additional code, making programming and debugging more complex.

    For these reasons, some languages use different representations.

    • Python's str uses Unicode with a flexible representation whose character width depends on the largest code point in the string. If all characters are ASCII, each uses 1 byte. If some exceed ASCII but all lie within the Basic Multilingual Plane (BMP), each uses 2 bytes. If any lie outside the BMP, each uses 4 bytes.
    • Go's string uses UTF-8 internally. Go also provides rune to represent a single Unicode code point.
    • Rust's str and String use UTF-8 internally, while char represents a single Unicode code point.

    These descriptions concern string storage within programming languages. How strings are stored in files or transmitted over a network is a separate question. For file storage and network transmission, strings are usually encoded as UTF-8 for compatibility and space efficiency.

    ","path":["Chapter 3.   Data structures","3.4   Character encoding *"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/","level":1,"title":"3.1   Classifying data structures","text":"

    Common data structures include arrays, linked lists, stacks, queues, hash tables, trees, heaps, and graphs. They can be classified by their logical structure and physical structure.

    ","path":["Chapter 3.   Data structures","3.1   Classifying data structures"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#311-logical-structure-linear-and-nonlinear","level":2,"title":"3.1.1   Logical structure: linear and nonlinear","text":"

    Logical structure describes the relationships between data elements. Arrays and linked lists arrange data in sequence, expressing linear relationships. Trees arrange data hierarchically from top to bottom, expressing ancestry and descent. Graphs consist of vertices and edges that express complex network relationships.

    As shown in Figure 3-1, logical structures fall into two broad categories: linear and nonlinear. A linear structure arranges data in a logical sequence; a nonlinear structure does not.

    • Linear data structures: Arrays, linked lists, stacks, queues, and hash tables. Elements have one-to-one sequential relationships.
    • Nonlinear data structures: Trees, heaps, graphs, and hash tables.

    Nonlinear data structures can be divided further into tree and network structures.

    • Tree structures: Trees, heaps, and hash tables, with one-to-many relationships.
    • Network structures: Graphs, with many-to-many relationships.

    Figure 3-1   Linear and nonlinear data structures

    ","path":["Chapter 3.   Data structures","3.1   Classifying data structures"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#312-physical-structure-contiguous-and-scattered-storage","level":2,"title":"3.1.2   Physical structure: contiguous and scattered storage","text":"

    While an algorithm runs, the data it processes is mainly stored in memory. Figure 3-2 shows a memory module, with each black chip containing storage space. We can imagine memory as a huge Excel spreadsheet, where each cell holds a certain amount of data.

    The system uses memory addresses to access data at particular locations. As shown in Figure 3-2, the computer assigns each cell a number according to specific rules, giving every memory location a unique address. Programs use these addresses to access data.

    Figure 3-2   Memory module, storage space, and memory addresses

    Tip

    The Excel spreadsheet analogy is a simplification. Real memory is more complex and involves address spaces, memory management, caching, virtual memory, and physical memory.

    Memory is a resource used by all programs. When a program occupies a region of memory, other programs usually cannot use that region at the same time. Memory resources are therefore an important consideration when designing data structures and algorithms. For example, an algorithm's peak memory usage should not exceed the system's available memory. If no large contiguous region is available, the chosen data structure must support scattered storage.

    As shown in Figure 3-3, physical structure describes how data is stored in computer memory: either contiguously (arrays) or in scattered locations (linked lists). It determines how data can be accessed, updated, inserted, and deleted. The two storage forms have complementary strengths in time and space efficiency.

    Figure 3-3   Contiguous and scattered storage

    All data structures are implemented using arrays, linked lists, or a combination of the two. For example, stacks and queues can use either arrays or linked lists, while hash tables may use both.

    • Array-based implementations: Stacks, queues, hash tables, trees, heaps, graphs, matrices, tensors (arrays with dimension \\(\\geq 3\\)), and more.
    • Linked-list-based implementations: Stacks, queues, hash tables, trees, heaps, graphs, and more.

    A linked list can change length during execution after initialization, so it is called a dynamic data structure. An array's length is fixed after initialization, so it is called a static data structure. However, reallocating memory can change an array's length, providing a degree of dynamic behavior.

    Tip

    If physical structure feels difficult to understand, read the next chapter first and then return to this section.

    ","path":["Chapter 3.   Data structures","3.1   Classifying data structures"],"tags":[]},{"location":"chapter_data_structure/exercises/","level":1,"title":"3.6   Exercises","text":"","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#361-check-your-understanding","level":2,"title":"3.6.1   Check your understanding","text":"","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#1-relationships-between-data-in-everyday-situations","level":3,"title":"1.   Relationships between data in everyday situations","text":"

    For each situation below, choose a linear, tree, or network structure based on the relationships between the data, and explain your choice:

    1. Students stand in a line, each concerned only with the people immediately in front of and behind them.
    2. A school is organized in a hierarchy: school → grade → class.
    3. City roads connect intersections. An intersection can lead to several others, and the roads may form cycles.
    Reference answer
    1. A linear structure. Apart from the first and last people, each person has exactly one neighbor in front and one behind, forming a line of relationships.

    2. A tree structure. Each class belongs to a grade, and each grade belongs to the school, forming a hierarchy from top to bottom.

    3. A network structure. An intersection can connect to several others, and routes can form cycles. These relationships cannot be arranged in a single sequence or a strict hierarchy.

      To identify a structure, first examine the relationships between its elements, rather than how much memory it occupies.

    ","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#2-storing-a-logical-sequence-in-memory","level":3,"title":"2.   Storing a logical sequence in memory","text":"

    Consider two simplified memory layouts for storing the logical sequence A → B → C:

    • Layout A: A, B, C are stored in memory cells numbered 20, 21, 22, respectively.
    • Layout B: A, B, C are stored in cells numbered 20, 7, 31, respectively. A records the location of B, and B records the location of C.
    1. Which layout uses contiguous storage, and which uses noncontiguous storage?
    2. Which layout is more like an array, and which is more like a linked list?
    3. The cell numbers in layout B are not in ascending order. Why can it still represent the logical sequence A → B → C?
    Reference answer
    1. Layout A uses consecutive memory cells, so its storage is contiguous. In layout B, nodes occupy separate locations, so its storage is noncontiguous.

    2. Layout A is more like an array; layout B is more like a linked list.

    3. The logical order is determined by the links recorded between nodes, not by the numerical order of their memory cell numbers. The location stored in A leads to B, and the location stored in B leads to C, so we can still visit A, B, C in sequence.

      This also shows that logical and physical structure are two ways of looking at the same data.

    ","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#3-data-types-and-structures-in-homework-records","level":3,"title":"3.   Data types and structures in homework records","text":"

    A study group records whether four students have submitted their homework, in seating order:

    [true, false, true, true]

    1. Which basic data type is suitable for each element?
    2. These four elements form a sequence in seating order. What logical structure does this use?
    3. If the group later records homework scores instead, as [90, 0, 85, 100], has the type of content changed, or the way it is organized? Explain.
    Reference answer
    1. Each element represents only yes or no, so the Boolean type bool is appropriate.

    2. The elements form a linear structure in seating order and can be stored in an array.

    3. The content type has changed: the elements are now integers instead of Boolean values. Their organization has not changed. The data still forms a sequence in seating order and can still use the linear structure of an array.

      A basic data type describes what is stored; a data structure describes how the data is organized.

    ","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#362-programming-exercises","level":2,"title":"3.6.2   Programming exercises","text":"","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/exercises/#1-counting-ones-in-a-binary-representation","level":3,"title":"1.   Counting ones in a binary representation","text":"

    Given a nonnegative integer n, count the ones in its binary representation.

    Use bitwise operations. Do not convert the binary representation to a string or use a built-in function that counts the ones directly.

    Hints
    1. n & 1 extracts the rightmost bit of n, allowing you to check whether it is 1.
    2. Shifting right by one bit discards the current rightmost bit; most languages use the >> operator.
    3. After implementing the method that checks each bit and shifts right, observe that n & (n - 1) changes the rightmost 1 in n to 0.

    LeetCode

    ","path":["Chapter 3.   Data structures","3.6   Exercises"],"tags":[]},{"location":"chapter_data_structure/number_encoding/","level":1,"title":"3.3   Number encoding *","text":"

    Tip

    Sections marked with * in their titles are optional. If you are short on time or find them difficult, you can skip them initially and return after completing the required sections.

    ","path":["Chapter 3.   Data structures","3.3   Number encoding *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#331-sign-magnitude-ones-complement-and-twos-complement","level":2,"title":"3.3.1   Sign-magnitude, one's complement, and two's complement","text":"

    The previous table showed that every integer type represents one more negative value than positive value. For example, byte ranges over \\([-128, 127]\\). Understanding this counterintuitive fact requires sign-magnitude, one's complement, and two's complement.

    First, integers are stored in two's complement form. Before explaining why, let's define the three representations.

    • Sign-magnitude: The highest bit is the sign: \\(0\\) for positive and \\(1\\) for negative. The remaining bits represent the magnitude.
    • One's complement: A positive number has the same representation as sign-magnitude. For a negative number, invert every bit except the sign bit of its sign-magnitude representation.
    • Two's complement: A positive number again has the same representation as sign-magnitude. For a negative number, add \\(1\\) to its one's complement.

    Figure 3-4 shows how to convert among these representations.

    Figure 3-4   Converting between sign-magnitude, one's complement, and two's complement

    Although sign-magnitude is intuitive, it has limitations. First, negative sign-magnitude values cannot be used directly in ordinary binary arithmetic. For example, directly adding \\(1 + (-2)\\) in this representation gives \\(-3\\), which is clearly wrong.

    \\[ \\begin{aligned} & 1 + (-2) \\newline & \\rightarrow 0000 \\; 0001 + 1000 \\; 0010 \\newline & = 1000 \\; 0011 \\newline & \\rightarrow -3 \\end{aligned} \\]

    To address this, computers introduced one's complement. Convert the operands to one's complement, calculate \\(1 + (-2)\\), and convert the result back to sign-magnitude to obtain the correct answer, \\(-1\\).

    \\[ \\begin{aligned} & 1 + (-2) \\newline & \\rightarrow 0000 \\; 0001 \\; \\text{(sign-magnitude)} + 1000 \\; 0010 \\; \\text{(sign-magnitude)} \\newline & = 0000 \\; 0001 \\; \\text{(one's complement)} + 1111 \\; 1101 \\; \\text{(one's complement)} \\newline & = 1111 \\; 1110 \\; \\text{(one's complement)} \\newline & = 1000 \\; 0001 \\; \\text{(sign-magnitude)} \\newline & \\rightarrow -1 \\end{aligned} \\]

    Second, sign-magnitude has two representations of zero, \\(+0\\) and \\(-0\\). Two binary encodings for zero can create ambiguity. Conditional checks may be wrong if they do not handle both forms, while special handling requires extra checks that can reduce efficiency.

    \\[ \\begin{aligned} +0 & \\rightarrow 0000 \\; 0000 \\newline -0 & \\rightarrow 1000 \\; 0000 \\end{aligned} \\]

    One's complement also has positive and negative zero, so computers introduced two's complement. Consider converting negative zero through all three representations:

    \\[ \\begin{aligned} -0 \\rightarrow \\; & 1000 \\; 0000 \\; \\text{(sign-magnitude)} \\newline = \\; & 1111 \\; 1111 \\; \\text{(one's complement)} \\newline = 1 \\; & 0000 \\; 0000 \\; \\text{(two's complement)} \\newline \\end{aligned} \\]

    Adding \\(1\\) to the one's complement of negative zero produces a carry. A byte has only 8 bits, so the \\(1\\) carried into the ninth bit is discarded. Negative zero's two's complement is therefore \\(0000 \\; 0000\\), the same as positive zero. Two's complement has only one zero, eliminating the ambiguity.

    One question remains: if byte ranges over \\([-128, 127]\\), where does the extra negative value \\(-128\\) come from? Every integer in \\([-127, +127]\\) has sign-magnitude, one's complement, and two's complement representations, and its sign-magnitude and two's complement forms can be converted into each other.

    However, the two's complement value \\(1000 \\; 0000\\) is an exception with no corresponding sign-magnitude form. Applying the conversion gives \\(0000 \\; 0000\\), but that represents \\(0\\), whose two's complement should be itself. The special pattern \\(1000 \\; 0000\\) is defined to represent \\(-128\\). Indeed, two's complement addition of \\((-1) + (-127)\\) gives \\(-128\\).

    \\[ \\begin{aligned} & (-127) + (-1) \\newline & \\rightarrow 1111 \\; 1111 \\; \\text{(sign-magnitude)} + 1000 \\; 0001 \\; \\text{(sign-magnitude)} \\newline & = 1000 \\; 0000 \\; \\text{(one's complement)} + 1111 \\; 1110 \\; \\text{(one's complement)} \\newline & = 1000 \\; 0001 \\; \\text{(two's complement)} + 1111 \\; 1111 \\; \\text{(two's complement)} \\newline & = 1000 \\; 0000 \\; \\text{(two's complement)} \\newline & \\rightarrow -128 \\end{aligned} \\]

    All the calculations above use addition. This points to an important fact: computer arithmetic circuits are largely built around addition. Compared with operations such as multiplication, division, and subtraction, addition is simpler to implement in hardware, easier to parallelize, and faster.

    This does not mean computers can only add. Combining addition with basic logical operations makes other arithmetic possible. For example, subtraction \\(a - b\\) becomes addition \\(a + (-b)\\), while multiplication and division can be expressed as repeated additions or subtractions.

    We can now summarize why computers use two's complement. It lets the same circuitry and operations handle both positive and negative addition, without dedicated subtraction circuitry or special handling for positive and negative zero. This simplifies hardware and improves efficiency.

    Two's complement is an elegant design. Space limits our discussion here, but interested readers are encouraged to explore it further.

    ","path":["Chapter 3.   Data structures","3.3   Number encoding *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#332-floating-point-encoding","level":2,"title":"3.3.2   Floating-point encoding","text":"

    Both int and float occupy 4 bytes, yet float has a much wider range. This seems surprising: since float also represents fractions, should its range not be smaller?

    The reason is that float uses a different representation. Write a 32-bit binary number as:

    \\[ b_{31} b_{30} b_{29} \\ldots b_2 b_1 b_0 \\]

    Under IEEE 754, a 32-bit float has three parts.

    • Sign \\(\\mathrm{S}\\): 1 bit, corresponding to \\(b_{31}\\).
    • Exponent \\(\\mathrm{E}\\): 8 bits, corresponding to \\(b_{30} b_{29} \\ldots b_{23}\\).
    • Fraction \\(\\mathrm{N}\\): 23 bits, corresponding to \\(b_{22} b_{21} \\ldots b_0\\).

    The value represented by the binary float is:

    \\[ \\text {val} = (-1)^{b_{31}} \\times 2^{\\left(b_{30} b_{29} \\ldots b_{23}\\right)_2-127} \\times\\left(1 . b_{22} b_{21} \\ldots b_0\\right)_2 \\]

    In decimal notation, this becomes:

    \\[ \\text {val}=(-1)^{\\mathrm{S}} \\times 2^{\\mathrm{E} -127} \\times (1 + \\mathrm{N}) \\]

    The components have these ranges:

    \\[ \\begin{aligned} \\mathrm{S} \\in & \\{ 0, 1\\}, \\quad \\mathrm{E} \\in \\{ 1, 2, \\dots, 254 \\} \\newline (1 + \\mathrm{N}) = & (1 + \\sum_{i=1}^{23} b_{23-i} 2^{-i}) \\subset [1, 2 - 2^{-23}] \\end{aligned} \\]

    Figure 3-5   Example of an IEEE 754 float calculation

    In Figure 3-5, \\(\\mathrm{S} = 0\\), \\(\\mathrm{E} = 124\\), and \\(\\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\\), giving:

    \\[ \\text { val } = (-1)^0 \\times 2^{124 - 127} \\times (1 + 0.375) = 0.171875 \\]

    We can now answer the original question: the exponent gives float a much wider range than int. The largest positive float is \\(2^{254 - 127} \\times (2 - 2^{-23}) \\approx 3.4 \\times 10^{38}\\); changing the sign bit gives the most negative value.

    The wider range of float comes at the cost of precision. An int uses all 32 bits to represent evenly spaced integer values. Because float includes an exponent, the gap between adjacent representable values tends to grow as their magnitude increases.

    As shown in Table 3-2, exponent values \\(\\mathrm{E} = 0\\) and \\(\\mathrm{E} = 255\\) have special meanings, representing zero, infinity, and \\(\\mathrm{NaN}\\), among other values.

    Table 3-2   Meanings of exponent values

    Exponent E Fraction \\(\\mathrm{N} = 0\\) Fraction \\(\\mathrm{N} \\ne 0\\) Formula \\(0\\) \\(\\pm 0\\) Subnormal number \\((-1)^{\\mathrm{S}} \\times 2^{-126} \\times (0.\\mathrm{N})\\) \\(1, 2, \\dots, 254\\) Normal number Normal number \\((-1)^{\\mathrm{S}} \\times 2^{(\\mathrm{E} -127)} \\times (1.\\mathrm{N})\\) \\(255\\) \\(\\pm \\infty\\) \\(\\mathrm{NaN}\\)

    Subnormal numbers substantially improve precision near zero. The smallest positive normal number is \\(2^{-126}\\), and the smallest positive subnormal number is \\(2^{-126} \\times 2^{-23}\\).

    Double-precision double uses a similar representation, which we will not detail here.

    ","path":["Chapter 3.   Data structures","3.3   Number encoding *"],"tags":[]},{"location":"chapter_data_structure/summary/","level":1,"title":"3.5   Summary","text":"","path":["Chapter 3.   Data structures","3.5   Summary"],"tags":[]},{"location":"chapter_data_structure/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Data structures can be classified by logical and physical structure. Logical structure describes relationships between data elements, while physical structure describes how data is stored in memory.
    • Common logical structures are linear, tree, and network structures. We usually classify data structures as linear (arrays, linked lists, stacks, queues) or nonlinear (trees, graphs, heaps). A hash table implementation may contain both linear and nonlinear structures.
    • During execution, data resides in computer memory. Each location has a memory address, which programs use to access its data.
    • The two main physical structures are contiguous storage (arrays) and scattered storage (linked lists). All data structures are implemented using arrays, linked lists, or both.
    • Basic data types include integers (byte, short, int, long), floating-point numbers (float, double), characters (char), and Booleans (bool). Their value ranges depend on storage size and representation.
    • Sign-magnitude, one's complement, and two's complement are three ways to encode numbers, and can be converted into one another. In sign-magnitude representation, the highest bit is the sign and the remaining bits give the magnitude.
    • Integers are stored in two's complement. This lets the computer handle positive and negative addition uniformly, avoids separate hardware circuitry for subtraction, and eliminates the ambiguity of positive and negative zero.
    • A floating-point encoding consists of 1 sign bit, 8 exponent bits, and 23 fraction bits. The exponent provides a much wider range than integers, at the cost of precision.
    • ASCII was the earliest English character set. It uses 1 byte and contains 128 characters. GBK is a common Chinese character set with over 20,000 Chinese characters. Unicode aims to provide a comprehensive character standard for the world's languages, avoiding garbled text caused by incompatible encodings.
    • UTF-8 is the most popular Unicode encoding and is widely compatible. Its variable length makes it extensible and space-efficient. UTF-16 and UTF-32 use fixed lengths. For Chinese text, UTF-16 uses less space than UTF-8. Languages such as Java and C# use UTF-16 by default.
    ","path":["Chapter 3.   Data structures","3.5   Summary"],"tags":[]},{"location":"chapter_data_structure/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Why can a hash table contain both linear and nonlinear data structures?

    A hash table is built on an array. To handle hash collisions, it may use separate chaining, discussed later in the Hash collisions section: each array bucket points to a linked list, which may be converted to a tree (usually a red-black tree) if it grows beyond a threshold.

    From a storage perspective, each bucket in the underlying array may contain a value, a linked list, or a tree. A hash table may therefore contain both linear structures (arrays and linked lists) and nonlinear structures (trees).

    Q: Does char occupy 1 byte?

    The size of char depends on the programming language's encoding. For example, Java, JavaScript, TypeScript, and C# use UTF-16 to store Unicode code points, so their char type occupies 2 bytes.

    Q: Is calling array-based structures static misleading? A stack supports dynamic operations such as pushing and popping.

    A stack does support dynamic data operations, but its underlying structure remains static in length. Although array-based structures can add and remove elements dynamically, their capacity is fixed. When the data exceeds the allocated capacity, a larger array must be created and the old contents copied into it.

    Q: When we create a stack or queue without specifying its size, why is it still called a static data structure?

    In high-level languages, the class automatically chooses an initial capacity for a stack or queue. For example, Java's ArrayList typically starts with a capacity of 10. Resizing is also automatic; see the later section on lists.

    Q: Converting sign-magnitude to two's complement uses \"invert, then add 1\". Its inverse should be \"subtract 1, then invert\", so why does \"invert, then add 1\" also work in the reverse direction?

    These conversions are really computations of a complement. If \\(a + b = c\\), we call \\(a\\) the complement of \\(b\\) with respect to \\(c\\), and conversely \\(b\\) the complement of \\(a\\) with respect to \\(c\\).

    Consider the \\(n = 4\\)-bit binary number \\(0010\\). Treating it as sign-magnitude and disregarding the sign bit, we obtain its two's complement by inverting and adding 1:

    \\[ 0010 \\rightarrow 1101 \\rightarrow 1110 \\]

    The two representations sum to \\(0010 + 1110 = 10000\\). Thus, \\(1110\\) is the complement of \\(0010\\) with respect to \\(10000\\). The \"invert, then add 1\" operation computes the complement with respect to \\(10000\\).

    What is the complement of \\(1110\\) with respect to \\(10000\\)? We can again invert and add 1:

    \\[ 1110 \\rightarrow 0001 \\rightarrow 0010 \\]

    The two representations are complements of each other with respect to \\(10000\\), so conversion in either direction can use the same operation: invert, then add 1.

    Of course, we can also recover the sign-magnitude form of \\(1110\\) by the inverse operation: subtract 1, then invert.

    \\[ 1110 \\rightarrow 1101 \\rightarrow 0010 \\]

    Both \"invert, then add 1\" and \"subtract 1, then invert\" compute the complement with respect to \\(10000\\), so they are equivalent.

    In essence, inverting computes the complement with respect to \\(1111\\), since sign-magnitude + one's complement = 1111. Adding 1 to the one's complement then gives the complement with respect to \\(10000\\).

    The example uses \\(n = 4\\), but the reasoning extends to binary numbers of any bit width.

    ","path":["Chapter 3.   Data structures","3.5   Summary"],"tags":[]},{"location":"chapter_divide_and_conquer/","level":1,"title":"Chapter 12.   Divide and conquer","text":"

    Abstract

    A difficult problem is broken down layer by layer, becoming simpler with each division.

    Divide and conquer reveals an important truth: start with the simple parts, and complexity becomes manageable.

    ","path":["Chapter 12.   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 12.1   Divide and conquer
    • 12.2   Divide-and-conquer search
    • 12.3   Building a binary tree
    • 12.4   Tower of Hanoi
    • 12.5   Summary
    • 12.6   Exercises
    ","path":["Chapter 12.   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/","level":1,"title":"12.2   Divide-and-conquer search","text":"

    We have learned that search algorithms fall into two broad categories.

    • Brute-force search: Traverses a data structure, taking \\(O(n)\\) time.
    • Adaptive search: Uses a particular organization of data or prior information to achieve \\(O(\\log n)\\) or even \\(O(1)\\) time.

    In fact, search algorithms that run in \\(O(\\log n)\\) time are usually based on divide and conquer, as seen in binary search and trees.

    • Each step of binary search reduces the problem of finding a target in an array to a smaller problem of finding it in half of the array. This continues until the array is empty or the target is found.
    • Trees exemplify divide and conquer. In data structures such as binary search trees, AVL trees, and heaps, various operations take \\(O(\\log n)\\) time.

    Binary search uses divide and conquer in the following way.

    • The problem can be divided: By comparing the middle element with the target, binary search recursively reduces the original problem of searching an array to the subproblem of searching half of that array.
    • The subproblems are independent: Binary search handles only one subproblem at each step, unaffected by other subproblems.
    • The subproblem solutions need not be combined: Binary search seeks a particular element, so there is no need to combine solutions. Solving the subproblem also solves the original problem.

    Divide and conquer makes searching faster because brute-force search eliminates only one option per step, whereas divide-and-conquer search can eliminate half the options.

    ","path":["Chapter 12.   Divide and conquer","12.2   Divide-and-conquer search"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/#1-implementing-binary-search-with-divide-and-conquer","level":3,"title":"1.   Implementing binary search with divide and conquer","text":"

    Earlier chapters implemented binary search iteratively. We will now implement it recursively using divide and conquer.

    Question

    Given a sorted array nums of length \\(n\\) with distinct elements, find the element target.

    From a divide-and-conquer perspective, let \\(f(i, j)\\) denote the subproblem for the search interval \\([i, j]\\).

    Starting with the original problem \\(f(0, n-1)\\), perform binary search as follows.

    1. Calculate the midpoint \\(m\\) of the search interval \\([i, j]\\) and use it to eliminate half the interval.
    2. Recursively solve the subproblem of half the size, either \\(f(i, m-1)\\) or \\(f(m+1, j)\\).
    3. Repeat steps 1. and 2., returning when target is found or the interval is empty.

    Figure 12-4 shows the divide-and-conquer process for finding \\(6\\) in an array using binary search.

    Figure 12-4   Divide-and-conquer process for binary search

    In the implementation, we define a recursive function dfs() to solve \\(f(i, j)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_recur.py
    def dfs(nums: list[int], target: int, i: int, j: int) -> int:\n    \"\"\"Binary search: problem f(i, j)\"\"\"\n    # If the interval is empty, the target is absent; return -1\n    if i > j:\n        return -1\n    # Calculate the midpoint index m\n    m = (i + j) // 2\n    if nums[m] < target:\n        # Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j)\n    elif nums[m] > target:\n        # Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1)\n    else:\n        # The target element was found; return its index\n        return m\n\ndef binary_search(nums: list[int], target: int) -> int:\n    \"\"\"Binary search\"\"\"\n    n = len(nums)\n    # Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1)\n
    binary_search_recur.cpp
    /* Binary search: problem f(i, j) */\nint dfs(vector<int> &nums, int target, int i, int j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(vector<int> &nums, int target) {\n    int n = nums.size();\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.java
    /* Binary search: problem f(i, j) */\nint dfs(int[] nums, int target, int i, int j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(int[] nums, int target) {\n    int n = nums.length;\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.cs
    /* Binary search: problem f(i, j) */\nint DFS(int[] nums, int target, int i, int j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return DFS(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return DFS(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint BinarySearch(int[] nums, int target) {\n    int n = nums.Length;\n    // Solve problem f(0, n-1)\n    return DFS(nums, target, 0, n - 1);\n}\n
    binary_search_recur.go
    /* Binary search: problem f(i, j) */\nfunc dfs(nums []int, target, i, j int) int {\n    // If the interval is empty, the target is absent; return -1\n    if i > j {\n        return -1\n    }\n    //    Calculate the midpoint index\n    m := i + ((j - i) >> 1)\n    //Compare the midpoint element with the target\n    if nums[m] < target {\n        // If it is smaller, recurse on the right half\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m+1, j)\n    } else if nums[m] > target {\n        // If it is larger, recurse on the left half\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m-1)\n    } else {\n        // The target element was found; return its index\n        return m\n    }\n}\n\n/* Binary search */\nfunc binarySearch(nums []int, target int) int {\n    n := len(nums)\n    return dfs(nums, target, 0, n-1)\n}\n
    binary_search_recur.swift
    /* Binary search: problem f(i, j) */\nfunc dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int {\n    // If the interval is empty, the target is absent; return -1\n    if i > j {\n        return -1\n    }\n    // Calculate the midpoint index m\n    let m = (i + j) / 2\n    if nums[m] < target {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums: nums, target: target, i: m + 1, j: j)\n    } else if nums[m] > target {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums: nums, target: target, i: i, j: m - 1)\n    } else {\n        // The target element was found; return its index\n        return m\n    }\n}\n\n/* Binary search */\nfunc binarySearch(nums: [Int], target: Int) -> Int {\n    // Solve problem f(0, n-1)\n    dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1)\n}\n
    binary_search_recur.js
    /* Binary search: problem f(i, j) */\nfunction dfs(nums, target, i, j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    const m = i + ((j - i) >> 1);\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfunction binarySearch(nums, target) {\n    const n = nums.length;\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.ts
    /* Binary search: problem f(i, j) */\nfunction dfs(nums: number[], target: number, i: number, j: number): number {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    const m = i + ((j - i) >> 1);\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfunction binarySearch(nums: number[], target: number): number {\n    const n = nums.length;\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.dart
    /* Binary search: problem f(i, j) */\nint dfs(List<int> nums, int target, int i, int j) {\n  // If the interval is empty, the target is absent; return -1\n  if (i > j) {\n    return -1;\n  }\n  // Calculate the midpoint index m\n  int m = (i + j) ~/ 2;\n  if (nums[m] < target) {\n    // Recurse on subproblem f(m+1, j)\n    return dfs(nums, target, m + 1, j);\n  } else if (nums[m] > target) {\n    // Recurse on subproblem f(i, m-1)\n    return dfs(nums, target, i, m - 1);\n  } else {\n    // The target element was found; return its index\n    return m;\n  }\n}\n\n/* Binary search */\nint binarySearch(List<int> nums, int target) {\n  int n = nums.length;\n  // Solve problem f(0, n-1)\n  return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.rs
    /* Binary search: problem f(i, j) */\nfn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 {\n    // If the interval is empty, the target is absent; return -1\n    if i > j {\n        return -1;\n    }\n    let m: i32 = i + (j - i) / 2;\n    if nums[m as usize] < target {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if nums[m as usize] > target {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nfn binary_search(nums: &[i32], target: i32) -> i32 {\n    let n = nums.len() as i32;\n    // Solve problem f(0, n-1)\n    dfs(nums, target, 0, n - 1)\n}\n
    binary_search_recur.c
    /* Binary search: problem f(i, j) */\nint dfs(int nums[], int target, int i, int j) {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1;\n    }\n    // Calculate the midpoint index m\n    int m = (i + j) / 2;\n    if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        return dfs(nums, target, m + 1, j);\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        return dfs(nums, target, i, m - 1);\n    } else {\n        // The target element was found; return its index\n        return m;\n    }\n}\n\n/* Binary search */\nint binarySearch(int nums[], int target, int numsSize) {\n    int n = numsSize;\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1);\n}\n
    binary_search_recur.kt
    /* Binary search: problem f(i, j) */\nfun dfs(\n    nums: IntArray,\n    target: Int,\n    i: Int,\n    j: Int\n): Int {\n    // If the interval is empty, the target is absent; return -1\n    if (i > j) {\n        return -1\n    }\n    // Calculate the midpoint index m\n    val m = (i + j) / 2\n    return if (nums[m] < target) {\n        // Recurse on subproblem f(m+1, j)\n        dfs(nums, target, m + 1, j)\n    } else if (nums[m] > target) {\n        // Recurse on subproblem f(i, m-1)\n        dfs(nums, target, i, m - 1)\n    } else {\n        // The target element was found; return its index\n        m\n    }\n}\n\n/* Binary search */\nfun binarySearch(nums: IntArray, target: Int): Int {\n    val n = nums.size\n    // Solve problem f(0, n-1)\n    return dfs(nums, target, 0, n - 1)\n}\n
    binary_search_recur.rb
    ### Binary search: problem f(i, j) ###\ndef dfs(nums, target, i, j)\n  # If the interval is empty, the target is absent; return -1\n  return -1 if i > j\n\n  # Calculate the midpoint index m\n  m = (i + j) / 2\n\n  if nums[m] < target\n    # Recurse on subproblem f(m+1, j)\n    return dfs(nums, target, m + 1, j)\n  elsif nums[m] > target\n    # Recurse on subproblem f(i, m-1)\n    return dfs(nums, target, i, m - 1)\n  else\n    # The target element was found; return its index\n    return m\n  end\nend\n\n### Binary search ###\ndef binary_search(nums, target)\n  n = nums.length\n  # Solve problem f(0, n-1)\n  dfs(nums, target, 0, n - 1)\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 12.   Divide and conquer","12.2   Divide-and-conquer search"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/","level":1,"title":"12.3   Building a binary tree","text":"

    Question

    Given a binary tree's preorder traversal preorder and inorder traversal inorder, reconstruct the tree and return its root node. Assume that all node values are distinct, as shown in Figure 12-5.

    Figure 12-5   Example input for reconstructing a binary tree

    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#1-identifying-a-divide-and-conquer-problem","level":3,"title":"1.   Identifying a divide-and-conquer problem","text":"

    Reconstructing a binary tree from preorder and inorder is a typical divide-and-conquer problem.

    • The problem can be divided: We can split the original problem into two subproblems, building the left subtree and building the right subtree, plus one operation: initializing the root node. Each subtree can be divided in the same way into smaller subtrees, until we reach the smallest subproblem, an empty subtree.
    • The subproblems are independent: The left and right subtrees are independent and do not overlap. To build the left subtree, we need only the portions of the preorder and inorder traversals corresponding to it. The same applies to the right subtree.
    • The subproblem solutions can be combined: Once we have built the left and right subtrees, we can attach them to the root to obtain the solution to the original problem.
    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#2-dividing-the-subtrees","level":3,"title":"2.   Dividing the subtrees","text":"

    The analysis above shows that divide and conquer applies, but how do we use preorder and inorder to separate the left and right subtrees?

    By definition, both preorder and inorder consist of three parts.

    • Preorder: [ root | left subtree | right subtree ]. For the tree above, this is [ 3 | 9 | 2 1 7 ].
    • Inorder: [ left subtree | root | right subtree ]. For the tree above, this is [ 9 | 3 | 1 2 7 ].

    Using the example above, we can divide the traversals with the steps shown in Figure 12-6.

    1. The first element in the preorder traversal, 3, is the root value.
    2. Find the index of root value 3 in inorder, then use it to divide inorder into [ 9 | 3 | 1 2 7 ].
    3. This division of inorder tells us that the left and right subtrees have 1 and 3 nodes, respectively, allowing us to divide preorder into [ 3 | 9 | 2 1 7 ].

    Figure 12-6   Separating subtrees in preorder and inorder traversals

    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#3-describing-subtree-intervals-with-variables","level":3,"title":"3.   Describing subtree intervals with variables","text":"

    Using this method, we have identified the index intervals for the root, left subtree, and right subtree in preorder and inorder. We need a few pointer variables to describe these intervals.

    • Let \\(i\\) be the index of the current tree's root in preorder.
    • Let \\(m\\) be the index of the current tree's root in inorder.
    • Let \\([l, r]\\) be the interval occupied by the current tree in inorder.

    As shown in Table 12-1, these variables describe the root index in preorder and each subtree's interval in inorder.

    Table 12-1   Root and subtree indices in preorder and inorder traversals

    Root index in preorder Subtree interval in inorder Current tree \\(i\\) \\([l, r]\\) Left subtree \\(i + 1\\) \\([l, m-1]\\) Right subtree \\(i + 1 + (m - l)\\) \\([m+1, r]\\)

    The term \\((m-l)\\) in the right subtree's root index is the number of nodes in the left subtree. Figure 12-7 helps illustrate this.

    Figure 12-7   Index intervals for the root and the left and right subtrees

    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#4-code-implementation","level":3,"title":"4.   Code implementation","text":"

    To find \\(m\\) efficiently, we use a hash table hmap mapping each element of inorder to its index:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby build_tree.py
    def dfs(\n    preorder: list[int],\n    inorder_map: dict[int, int],\n    i: int,\n    l: int,\n    r: int,\n) -> TreeNode | None:\n    \"\"\"Build a binary tree: divide and conquer\"\"\"\n    # Stop when the subtree interval is empty\n    if r - l < 0:\n        return None\n    # Initialize the root node\n    root = TreeNode(preorder[i])\n    # Look up m to separate the left and right subtrees\n    m = inorder_map[preorder[i]]\n    # Subproblem: build the left subtree\n    root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n    # Subproblem: build the right subtree\n    root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n    # Return the root node\n    return root\n\ndef build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None:\n    \"\"\"Build a binary tree\"\"\"\n    # Initialize a hash table mapping each element in inorder to its index\n    inorder_map = {val: i for i, val in enumerate(inorder)}\n    root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1)\n    return root\n
    build_tree.cpp
    /* Build a binary tree: divide and conquer */\nTreeNode *dfs(vector<int> &preorder, unordered_map<int, int> &inorderMap, int i, int l, int r) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0)\n        return NULL;\n    // Initialize the root node\n    TreeNode *root = new TreeNode(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root->left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nTreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) {\n    // Initialize a hash table mapping each element in inorder to its index\n    unordered_map<int, int> inorderMap;\n    for (int i = 0; i < inorder.size(); i++) {\n        inorderMap[inorder[i]] = i;\n    }\n    TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1);\n    return root;\n}\n
    build_tree.java
    /* Build a binary tree: divide and conquer */\nTreeNode dfs(int[] preorder, Map<Integer, Integer> inorderMap, int i, int l, int r) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0)\n        return null;\n    // Initialize the root node\n    TreeNode root = new TreeNode(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    int m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nTreeNode buildTree(int[] preorder, int[] inorder) {\n    // Initialize a hash table mapping each element in inorder to its index\n    Map<Integer, Integer> inorderMap = new HashMap<>();\n    for (int i = 0; i < inorder.length; i++) {\n        inorderMap.put(inorder[i], i);\n    }\n    TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
    build_tree.cs
    /* Build a binary tree: divide and conquer */\nTreeNode? DFS(int[] preorder, Dictionary<int, int> inorderMap, int i, int l, int r) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0)\n        return null;\n    // Initialize the root node\n    TreeNode root = new(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root.left = DFS(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nTreeNode? BuildTree(int[] preorder, int[] inorder) {\n    // Initialize a hash table mapping each element in inorder to its index\n    Dictionary<int, int> inorderMap = [];\n    for (int i = 0; i < inorder.Length; i++) {\n        inorderMap.TryAdd(inorder[i], i);\n    }\n    TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1);\n    return root;\n}\n
    build_tree.go
    /* Build a binary tree: divide and conquer */\nfunc dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode {\n    // Stop when the subtree interval is empty\n    if r-l < 0 {\n        return nil\n    }\n    // Initialize the root node\n    root := NewTreeNode(preorder[i])\n    // Look up m to separate the left and right subtrees\n    m := inorderMap[preorder[i]]\n    // Subproblem: build the left subtree\n    root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1)\n    // Subproblem: build the right subtree\n    root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r)\n    // Return the root node\n    return root\n}\n\n/* Build a binary tree */\nfunc buildTree(preorder, inorder []int) *TreeNode {\n    // Initialize a hash table mapping each element in inorder to its index\n    inorderMap := make(map[int]int, len(inorder))\n    for i := 0; i < len(inorder); i++ {\n        inorderMap[inorder[i]] = i\n    }\n\n    root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1)\n    return root\n}\n
    build_tree.swift
    /* Build a binary tree: divide and conquer */\nfunc dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? {\n    // Stop when the subtree interval is empty\n    if r - l < 0 {\n        return nil\n    }\n    // Initialize the root node\n    let root = TreeNode(x: preorder[i])\n    // Look up m to separate the left and right subtrees\n    let m = inorderMap[preorder[i]]!\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1)\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r)\n    // Return the root node\n    return root\n}\n\n/* Build a binary tree */\nfunc buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? {\n    // Initialize a hash table mapping each element in inorder to its index\n    let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }\n    return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1)\n}\n
    build_tree.js
    /* Build a binary tree: divide and conquer */\nfunction dfs(preorder, inorderMap, i, l, r) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0) return null;\n    // Initialize the root node\n    const root = new TreeNode(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    const m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nfunction buildTree(preorder, inorder) {\n    // Initialize a hash table mapping each element in inorder to its index\n    let inorderMap = new Map();\n    for (let i = 0; i < inorder.length; i++) {\n        inorderMap.set(inorder[i], i);\n    }\n    const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
    build_tree.ts
    /* Build a binary tree: divide and conquer */\nfunction dfs(\n    preorder: number[],\n    inorderMap: Map<number, number>,\n    i: number,\n    l: number,\n    r: number\n): TreeNode | null {\n    // Stop when the subtree interval is empty\n    if (r - l < 0) return null;\n    // Initialize the root node\n    const root: TreeNode = new TreeNode(preorder[i]);\n    // Look up m to separate the left and right subtrees\n    const m = inorderMap.get(preorder[i]);\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nfunction buildTree(preorder: number[], inorder: number[]): TreeNode | null {\n    // Initialize a hash table mapping each element in inorder to its index\n    let inorderMap = new Map<number, number>();\n    for (let i = 0; i < inorder.length; i++) {\n        inorderMap.set(inorder[i], i);\n    }\n    const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n    return root;\n}\n
    build_tree.dart
    /* Build a binary tree: divide and conquer */\nTreeNode? dfs(\n  List<int> preorder,\n  Map<int, int> inorderMap,\n  int i,\n  int l,\n  int r,\n) {\n  // Stop when the subtree interval is empty\n  if (r - l < 0) {\n    return null;\n  }\n  // Initialize the root node\n  TreeNode? root = TreeNode(preorder[i]);\n  // Look up m to separate the left and right subtrees\n  int m = inorderMap[preorder[i]]!;\n  // Subproblem: build the left subtree\n  root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n  // Subproblem: build the right subtree\n  root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n  // Return the root node\n  return root;\n}\n\n/* Build a binary tree */\nTreeNode? buildTree(List<int> preorder, List<int> inorder) {\n  // Initialize a hash table mapping each element in inorder to its index\n  Map<int, int> inorderMap = {};\n  for (int i = 0; i < inorder.length; i++) {\n    inorderMap[inorder[i]] = i;\n  }\n  TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n  return root;\n}\n
    build_tree.rs
    /* Build a binary tree: divide and conquer */\nfn dfs(\n    preorder: &[i32],\n    inorder_map: &HashMap<i32, i32>,\n    i: i32,\n    l: i32,\n    r: i32,\n) -> Option<Rc<RefCell<TreeNode>>> {\n    // Stop when the subtree interval is empty\n    if r - l < 0 {\n        return None;\n    }\n    // Initialize the root node\n    let root = TreeNode::new(preorder[i as usize]);\n    // Look up m to separate the left and right subtrees\n    let m = inorder_map.get(&preorder[i as usize]).unwrap();\n    // Subproblem: build the left subtree\n    root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1);\n    // Subproblem: build the right subtree\n    root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r);\n    // Return the root node\n    Some(root)\n}\n\n/* Build a binary tree */\nfn build_tree(preorder: &[i32], inorder: &[i32]) -> Option<Rc<RefCell<TreeNode>>> {\n    // Initialize a hash table mapping each element in inorder to its index\n    let mut inorder_map: HashMap<i32, i32> = HashMap::new();\n    for i in 0..inorder.len() {\n        inorder_map.insert(inorder[i], i as i32);\n    }\n    let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1);\n    root\n}\n
    build_tree.c
    /* Build a binary tree: divide and conquer */\nTreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) {\n    // Stop when the subtree interval is empty\n    if (r - l < 0)\n        return NULL;\n    // Initialize the root node\n    TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode));\n    root->val = preorder[i];\n    root->left = NULL;\n    root->right = NULL;\n    // Look up m to separate the left and right subtrees\n    int m = inorderMap[preorder[i]];\n    // Subproblem: build the left subtree\n    root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size);\n    // Subproblem: build the right subtree\n    root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size);\n    // Return the root node\n    return root;\n}\n\n/* Build a binary tree */\nTreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) {\n    // Initialize a lookup array mapping inorder values to their indices\n    int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE);\n    for (int i = 0; i < inorderSize; i++) {\n        inorderMap[inorder[i]] = i;\n    }\n    TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize);\n    free(inorderMap);\n    return root;\n}\n
    build_tree.kt
    /* Build a binary tree: divide and conquer */\nfun dfs(\n    preorder: IntArray,\n    inorderMap: Map<Int?, Int?>,\n    i: Int,\n    l: Int,\n    r: Int\n): TreeNode? {\n    // Stop when the subtree interval is empty\n    if (r - l < 0) return null\n    // Initialize the root node\n    val root = TreeNode(preorder[i])\n    // Look up m to separate the left and right subtrees\n    val m = inorderMap[preorder[i]]!!\n    // Subproblem: build the left subtree\n    root.left = dfs(preorder, inorderMap, i + 1, l, m - 1)\n    // Subproblem: build the right subtree\n    root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r)\n    // Return the root node\n    return root\n}\n\n/* Build a binary tree */\nfun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {\n    // Initialize a hash table mapping each element in inorder to its index\n    val inorderMap = HashMap<Int?, Int?>()\n    for (i in inorder.indices) {\n        inorderMap[inorder[i]] = i\n    }\n    val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1)\n    return root\n}\n
    build_tree.rb
    ### Build a binary tree: divide and conquer ###\ndef dfs(preorder, inorder_map, i, l, r)\n  # Stop when the subtree interval is empty\n  return if r - l < 0\n\n  # Initialize the root node\n  root = TreeNode.new(preorder[i])\n  # Look up m to separate the left and right subtrees\n  m = inorder_map[preorder[i]]\n  # Subproblem: build the left subtree\n  root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n  # Subproblem: build the right subtree\n  root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n\n  # Return the root node\n  root\nend\n\n### Build a binary tree ###\ndef build_tree(preorder, inorder)\n  # Initialize a hash table mapping each element in inorder to its index\n  inorder_map = {}\n  inorder.each_with_index { |val, i| inorder_map[val] = i }\n  dfs(preorder, inorder_map, 0, 0, inorder.length - 1)\nend\n
    Visualize execution

    View full screen >

    Figure 12-8 shows the recursive reconstruction process. Nodes are created during recursive descent, while edges (references) are established as the recursive calls return.

    <1><2><3><4><5><6><7><8><9>

    Figure 12-8   Recursive process for building a binary tree

    Figure 12-9 shows how preorder and inorder are divided in each recursive call.

    Figure 12-9   Division within each recursive call

    For a tree with \\(n\\) nodes, initializing each node (executing one call to dfs()) takes \\(O(1)\\) time. The overall time complexity is therefore \\(O(n)\\).

    The hash table maps inorder elements to indices, requiring \\(O(n)\\) space. In the worst case, the binary tree degenerates into a linked list, and the recursion depth reaches \\(n\\), using \\(O(n)\\) space for stack frames. The overall space complexity is therefore \\(O(n)\\).

    ","path":["Chapter 12.   Divide and conquer","12.3   Building a binary tree"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/","level":1,"title":"12.1   Divide and conquer","text":"

    Divide and conquer is a fundamental and widely used algorithmic strategy. Usually implemented recursively, it has two stages: dividing the problem and combining its solutions.

    1. Divide (the partitioning stage): Recursively split the original problem into two or more subproblems until the smallest subproblems are reached.
    2. Conquer (the combining stage): Starting with the known solutions to the smallest subproblems, combine solutions from the bottom up to construct the solution to the original problem.

    As shown in Figure 12-1, merge sort is a classic application of divide and conquer.

    1. Divide: Recursively split the original array (the original problem) into two subarrays (subproblems) until each subarray contains just one element (the smallest subproblem).
    2. Conquer: Merge the sorted subarrays (subproblem solutions) from the bottom up to obtain the sorted original array (the solution to the original problem).

    Figure 12-1   Divide-and-conquer strategy in merge sort

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1211-recognizing-divide-and-conquer-problems","level":2,"title":"12.1.1   Recognizing divide-and-conquer problems","text":"

    The following criteria usually help determine whether divide and conquer is suitable for a problem.

    1. The problem can be divided: The original problem can be split into smaller, similar subproblems, which can in turn be divided recursively in the same way.
    2. The subproblems are independent: They neither overlap nor depend on one another, so they can be solved independently.
    3. The subproblem solutions can be combined: Combining them yields the solution to the original problem.

    Merge sort clearly satisfies all three criteria.

    1. The problem can be divided: Recursively split an array (the original problem) into two subarrays (subproblems).
    2. The subproblems are independent: Each subarray can be sorted independently.
    3. The subproblem solutions can be combined: Merge the two sorted subarrays (subproblem solutions) into a single sorted array (the solution to the original problem).
    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1212-improving-efficiency-with-divide-and-conquer","level":2,"title":"12.1.2   Improving efficiency with divide and conquer","text":"

    Divide and conquer not only solves algorithmic problems effectively, but often improves efficiency as well. Quick sort, merge sort, and heap sort outperform selection sort, bubble sort, and insertion sort because they use this strategy.

    This raises a question: why does divide and conquer improve efficiency, and what is the underlying reason? Why is dividing a large problem, solving its subproblems, and combining their solutions faster than solving it directly? We can examine this from two perspectives: the number of operations and parallel computation.

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1-reducing-the-number-of-operations","level":3,"title":"1.   Reducing the number of operations","text":"

    Consider bubble sort, which takes \\(O(n^2)\\) time to sort an array of length \\(n\\). Suppose we split the array at its midpoint, as shown in Figure 12-2. Splitting takes \\(O(n)\\) time, sorting each half takes \\(O((n / 2)^2)\\) time, and merging the halves takes \\(O(n)\\) time. The total time complexity is:

    \\[ O(n + (\\frac{n}{2})^2 \\times 2 + n) = O(\\frac{n^2}{2} + 2n) \\]

    Figure 12-2   Bubble sort before and after dividing the array

    Now consider the following inequality, whose left and right sides represent the total operation counts before and after division:

    \\[ \\begin{aligned} n^2 & > \\frac{n^2}{2} + 2n \\newline n^2 - \\frac{n^2}{2} - 2n & > 0 \\newline n(n - 4) & > 0 \\end{aligned} \\]

    When \\(n > 4\\), dividing the array reduces the number of operations and should make sorting faster. The time complexity is still quadratic, \\(O(n^2)\\); only the constant factor has decreased.

    What if we keep splitting each subarray into two at its midpoint until every subarray contains just one element? This is precisely the idea behind merge sort, which takes \\(O(n \\log n)\\) time.

    What if we introduce more split points, dividing the original array evenly into \\(k\\) subarrays? This resembles bucket sort, which is well suited to sorting large amounts of data and can theoretically achieve \\(O(n + k)\\) time.

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#2-using-parallel-computation","level":3,"title":"2.   Using parallel computation","text":"

    The subproblems produced by divide and conquer are independent, so they can usually be solved in parallel. Thus, divide and conquer can both reduce time complexity and help the operating system parallelize the work.

    Parallelization is especially effective on multicore or multiprocessor systems. Multiple subproblems can be processed simultaneously, making fuller use of computing resources and substantially reducing total runtime.

    For example, in the bucket sort shown in Figure 12-3, a large dataset is evenly distributed among buckets. The sorting tasks for different buckets can then be assigned to different computing units, and their results combined afterward.

    Figure 12-3   Parallel computation in bucket sort

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1213-common-applications-of-divide-and-conquer","level":2,"title":"12.1.3   Common applications of divide and conquer","text":"

    Divide and conquer solves many classic algorithmic problems.

    • Closest pair of points: Divide a set of points into two parts, find the closest pair within each part, then find the closest pair spanning the two parts.
    • Large integer multiplication: Karatsuba's algorithm, for example, breaks multiplication of large integers into several smaller integer multiplications and additions.
    • Matrix multiplication: Strassen's algorithm, for example, breaks multiplication of large matrices into multiple smaller matrix multiplications and additions.
    • Tower of Hanoi: This problem can be solved recursively and is a classic application of divide and conquer.
    • Counting inversions: In a sequence, an earlier number and a later number form an inversion if the earlier one is larger. Inversions can be counted using divide and conquer with merge sort.

    Divide and conquer is also widely used in designing algorithms and data structures.

    • Binary search: Split a sorted array at its middle index, compare the middle element with the target to decide which half to discard, then repeat the same operation on the remaining interval.
    • Merge sort: This was described at the start of the section.
    • Quick sort: Choose a pivot and divide the array into two subarrays, one containing elements smaller than the pivot and the other containing larger elements. Repeat the division on both parts until each subarray contains just one element.
    • Bucket sort: Distribute data among multiple buckets, sort the elements in each bucket, and then take the elements from the buckets in order to obtain a sorted array.
    • Trees: Operations such as search, insertion, and deletion in binary search trees, AVL trees, red-black trees, B-trees, and B+ trees can be viewed as applications of divide and conquer.
    • Heaps: A heap is a special kind of complete binary tree. Its operations, such as insertion, deletion, and heapification, implicitly use divide and conquer.
    • Hash tables: Although hash tables do not directly use divide and conquer, some collision-handling strategies use it indirectly. For example, long linked lists in separate chaining may be converted into red-black trees to speed up lookups.

    As we can see, divide and conquer quietly underlies many algorithms and data structures, often without being explicitly named.

    ","path":["Chapter 12.   Divide and conquer","12.1   Divide and conquer"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/","level":1,"title":"12.6   Exercises","text":"","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1261-check-your-understanding","level":2,"title":"12.6.1   Check your understanding","text":"","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1-which-tasks-suit-divide-and-conquer","level":3,"title":"1.   Which tasks suit divide and conquer?","text":"

    A student wants to solve the following tasks by splitting each in half, solving the halves separately, and combining the results. For each task, decide whether divide and conquer is suitable, is possible but does not reduce the total work, or is unsuitable because the halves cannot be solved independently. Explain.

    1. Sort an unsorted array.
    2. Find the maximum value in an array.
    3. Execute a sequence of stack operations push(x) and pop() in order, outputting the element returned by each pop().
    Reference answer
    1. Suitable: split in half, sort the halves independently, and merge in \\(O(n)\\) time. This is exactly merge sort.
    2. Possible, but without reducing the total work: the two halves must still examine all \\(n\\) elements between them, taking \\(O(n)\\) time, just like a direct scan.
    3. The halves cannot be solved independently: the stack contents at the start of the second half depend on the results of the first half. The second half therefore cannot run independently of the first half's result.
    ","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#2-how-fast-exponentiation-reduces-computation","level":3,"title":"2.   How fast exponentiation reduces computation","text":"

    The following recursive function uses divide and conquer to compute \\(x^n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby fast_power.py
    def fast_pow(x: int, n: int) -> int:\n    \"\"\"Fast exponentiation\"\"\"\n    if n == 0:\n        return 1\n    half = fast_pow(x, n // 2)\n    if n % 2 == 0:\n        return half * half\n    return half * half * x\n
    fast_power.cpp
    /* Fast exponentiation */\nint fastPow(int x, int n) {\n    if (n == 0) {\n        return 1;\n    }\n    int half = fastPow(x, n / 2);\n    if (n % 2 == 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.java
    /* Fast exponentiation */\nint fastPow(int x, int n) {\n    if (n == 0) {\n        return 1;\n    }\n    int half = fastPow(x, n / 2);\n    if (n % 2 == 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.cs
    /* Fast exponentiation */\nint FastPow(int x, int n) {\n    if (n == 0) {\n        return 1;\n    }\n    int half = FastPow(x, n / 2);\n    if (n % 2 == 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.go
    /* Fast exponentiation */\nfunc fastPow(x, n int) int {\n    if n == 0 {\n        return 1\n    }\n    half := fastPow(x, n/2)\n    if n%2 == 0 {\n        return half * half\n    }\n    return half * half * x\n}\n
    fast_power.swift
    /* Fast exponentiation */\nfunc fastPow(x: Int, n: Int) -> Int {\n    if n == 0 {\n        return 1\n    }\n    let half = fastPow(x: x, n: n / 2)\n    if n % 2 == 0 {\n        return half * half\n    }\n    return half * half * x\n}\n
    fast_power.js
    /* Fast exponentiation */\nfunction fastPow(x, n) {\n    if (n === 0) {\n        return 1;\n    }\n    const half = fastPow(x, Math.floor(n / 2));\n    if (n % 2 === 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.ts
    /* Fast exponentiation */\nfunction fastPow(x: number, n: number): number {\n    if (n === 0) {\n        return 1;\n    }\n    const half = fastPow(x, Math.floor(n / 2));\n    if (n % 2 === 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.dart
    /* Fast exponentiation */\nint fastPow(int x, int n) {\n  if (n == 0) {\n    return 1;\n  }\n  int half = fastPow(x, n ~/ 2);\n  if (n % 2 == 0) {\n    return half * half;\n  }\n  return half * half * x;\n}\n
    fast_power.rs
    /* Fast exponentiation */\nfn fast_pow(x: i32, n: i32) -> i32 {\n    if n == 0 {\n        return 1;\n    }\n    let half = fast_pow(x, n / 2);\n    if n % 2 == 0 {\n        return half * half;\n    }\n    half * half * x\n}\n
    fast_power.c
    /* Fast exponentiation */\nint fastPow(int x, int n) {\n    if (n == 0) {\n        return 1;\n    }\n    int half = fastPow(x, n / 2);\n    if (n % 2 == 0) {\n        return half * half;\n    }\n    return half * half * x;\n}\n
    fast_power.kt
    /* Fast exponentiation */\nfun fastPow(x: Int, n: Int): Int {\n    if (n == 0) {\n        return 1\n    }\n    val half = fastPow(x, n / 2)\n    if (n % 2 == 0) {\n        return half * half\n    }\n    return half * half * x\n}\n
    fast_power.rb
    ### Fast exponentiation ###\ndef fast_pow(x, n)\n  return 1 if n == 0\n\n  half = fast_pow(x, n / 2)\n  return half * half if n.even?\n\n  half * half * x\nend\n

    Run it with x = 3 and n = 5:

    1. What values does n take in successive recursive calls?
    2. Starting with the deepest call, what value does each call return?
    3. Why should the recursive result be saved in half instead of making the same recursive call on each side of the multiplication?
    Reference answer
    1. The parameter follows 5 → 2 → 1 → 0, halving the exponent each time until the base case is reached.

    2. When n = 0, the return value is 1. When n = 1, it is \\(1×1×3=3\\). When n = 2, it is \\(3×3=9\\); when n = 5, it is \\(9×9×3=243\\).

    3. Calling the same subproblem on both sides of the multiplication repeats exactly the same computation. Saving the result in half means each level makes only one recursive call, with recursion depth approximately \\(\\log n\\). Making two calls would cause substantial repeated computation.

    ","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#3-splitting-traversal-sequences-into-left-and-right-subtrees","level":3,"title":"3.   Splitting traversal sequences into left and right subtrees","text":"

    A binary tree with distinct node values has these preorder and inorder traversals:

    • Preorder: [A, B, D, E, C]
    • Inorder: [D, B, E, A, C]

    Split only at the root level; there is no need to recurse further or draw the entire tree.

    1. What is the root node?
    2. Which parts of the inorder sequence belong to the left and right subtrees?
    3. Which parts of the preorder sequence belong to the left and right subtrees? What are the root's immediate children?
    Reference answer
    1. The first node in preorder is the root, so the root is A.

    2. A splits the inorder sequence into two parts: [D, B, E] for the left subtree and [C] for the right subtree.

    3. The left subtree has three nodes, so the three preorder elements after root A belong to the left subtree: [B, D, E]. The remaining [C] belongs to the right subtree. Thus, the root's left child is B, and its right child is C.

    ","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1262-programming-exercises","level":2,"title":"12.6.2   Programming exercises","text":"","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1-fast-exponentiation","level":3,"title":"1.   Fast exponentiation","text":"

    Given a real number x and an integer n, compute \\(x^n\\) without calling a built-in power function. Use recursive divide and conquer: halve the exponent at each step and reuse the computed subproblem result. For this problem, \\(x^0=1\\), including when x = 0. When n < 0, x != 0 is guaranteed, and the answer can be expressed as \\((1/x)^{-n}\\).

    Hints
    1. When n is 0, the answer is 1.
    2. After computing x to the power n // 2, save it in half instead of making the recursive call a second time.
    3. When n < 0, first replace x with 1 / x and n with -n. In C++ or Java, you can convert n to a 64-bit integer first to avoid overflow when negating the smallest 32-bit integer.

    LeetCode

    ","path":["Chapter 12.   Divide and conquer","12.6   Exercises"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/","level":1,"title":"12.4   Tower of Hanoi","text":"

    In merge sort and binary tree reconstruction, we divided the original problem into two subproblems, each half its size. The Tower of Hanoi uses a different division strategy.

    Question

    Given three rods called A, B, and C, initially rod A holds \\(n\\) disks arranged from smallest at the top to largest at the bottom. Move all \\(n\\) disks to rod C while preserving their order, as shown in Figure 12-10. The following rules apply.

    1. A disk can only be removed from the top of one rod and placed on top of another.
    2. Only one disk may be moved at a time.
    3. A smaller disk must always be above a larger disk.

    Figure 12-10   Example of the Tower of Hanoi

    Let \\(f(i)\\) denote a Tower of Hanoi problem of size \\(i\\). For example, \\(f(3)\\) means moving \\(3\\) disks from A to C.

    ","path":["Chapter 12.   Divide and conquer","12.4   Tower of Hanoi"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#1-considering-the-base-cases","level":3,"title":"1.   Considering the base cases","text":"

    For \\(f(1)\\), with just one disk, simply move it directly from A to C, as shown in Figure 12-11.

    <1><2>

    Figure 12-11   Solution for a problem of size 1

    For \\(f(2)\\), with two disks, we need to use B because the smaller disk must remain above the larger one, as shown in Figure 12-12.

    1. Move the smaller top disk from A to B.
    2. Move the larger disk from A to C.
    3. Move the smaller disk from B to C.
    <1><2><3><4>

    Figure 12-12   Solution for a problem of size 2

    We can summarize the solution to \\(f(2)\\) as moving two disks from A to C using B. Here, C is the destination rod and B is the auxiliary rod.

    ","path":["Chapter 12.   Divide and conquer","12.4   Tower of Hanoi"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#2-dividing-the-problem","level":3,"title":"2.   Dividing the problem","text":"

    For \\(f(3)\\), with three disks, the situation is a little more complicated.

    Since we know how to solve \\(f(1)\\) and \\(f(2)\\), we can use divide and conquer: treat the top two disks on A as a group and follow the steps in Figure 12-13. This moves all three disks from A to C.

    1. Use B as the destination and C as the auxiliary rod to move two disks from A to B.
    2. Move the remaining disk directly from A to C.
    3. Use C as the destination and A as the auxiliary rod to move two disks from B to C.
    <1><2><3><4>

    Figure 12-13   Solution for a problem of size 3

    In essence, we have divided \\(f(3)\\) into two \\(f(2)\\) subproblems and one \\(f(1)\\) subproblem. Solving these three subproblems in order solves the original problem. This shows that the subproblems are independent and their solutions can be combined.

    We can now state the divide-and-conquer strategy for the Tower of Hanoi, illustrated in Figure 12-14: divide \\(f(n)\\) into two subproblems \\(f(n-1)\\) and one subproblem \\(f(1)\\), then solve them in this order.

    1. Move \\(n-1\\) disks from A to B using C.
    2. Move the remaining \\(1\\) disk directly from A to C.
    3. Move \\(n-1\\) disks from B to C using A.

    Each of the two \\(f(n-1)\\) subproblems can be divided recursively in the same way until we reach the smallest subproblem, \\(f(1)\\). The solution to \\(f(1)\\) is a single move.

    Figure 12-14   Divide-and-conquer strategy for the Tower of Hanoi

    ","path":["Chapter 12.   Divide and conquer","12.4   Tower of Hanoi"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#3-code-implementation","level":3,"title":"3.   Code implementation","text":"

    We define a recursive function dfs(i, src, buf, tar) that moves the top \\(i\\) disks from rod src to destination rod tar using auxiliary rod buf:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hanota.py
    def move(src: list[int], tar: list[int]):\n    \"\"\"Move one disk\"\"\"\n    # Take a disk from the top of src\n    pan = src.pop()\n    # Place the disk on top of tar\n    tar.append(pan)\n\ndef dfs(i: int, src: list[int], buf: list[int], tar: list[int]):\n    \"\"\"Solve the Tower of Hanoi problem f(i)\"\"\"\n    # If this subproblem has only one disk to move, move it directly from src to tar\n    if i == 1:\n        move(src, tar)\n        return\n    # Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf)\n    # Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar)\n    # Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar)\n\ndef solve_hanota(A: list[int], B: list[int], C: list[int]):\n    \"\"\"Solve the Tower of Hanoi problem\"\"\"\n    n = len(A)\n    # Move the top n disks from A to C using B\n    dfs(n, A, B, C)\n
    hanota.cpp
    /* Move one disk */\nvoid move(vector<int> &src, vector<int> &tar) {\n    // Take a disk from the top of src\n    int pan = src.back();\n    src.pop_back();\n    // Place the disk on top of tar\n    tar.push_back(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, vector<int> &src, vector<int> &buf, vector<int> &tar) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i == 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(vector<int> &A, vector<int> &B, vector<int> &C) {\n    int n = A.size();\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.java
    /* Move one disk */\nvoid move(List<Integer> src, List<Integer> tar) {\n    // Take a disk from the top of src\n    Integer pan = src.remove(src.size() - 1);\n    // Place the disk on top of tar\n    tar.add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, List<Integer> src, List<Integer> buf, List<Integer> tar) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i == 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(List<Integer> A, List<Integer> B, List<Integer> C) {\n    int n = A.size();\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.cs
    /* Move one disk */\nvoid Move(List<int> src, List<int> tar) {\n    // Take a disk from the top of src\n    int pan = src[^1];\n    src.RemoveAt(src.Count - 1);\n    // Place the disk on top of tar\n    tar.Add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid DFS(int i, List<int> src, List<int> buf, List<int> tar) {\n    // If only one disk in this subproblem remains on src, move it directly to tar\n    if (i == 1) {\n        Move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    DFS(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    Move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    DFS(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid SolveHanota(List<int> A, List<int> B, List<int> C) {\n    int n = A.Count;\n    // Move the top n disks from A to C using B\n    DFS(n, A, B, C);\n}\n
    hanota.go
    /* Move one disk */\nfunc move(src, tar *list.List) {\n    // Take a disk from the top of src\n    pan := src.Back()\n    // Place the disk on top of tar\n    tar.PushBack(pan.Value)\n    // Remove the disk from the top of src\n    src.Remove(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunc dfsHanota(i int, src, buf, tar *list.List) {\n    // If only one disk in this subproblem remains on src, move it directly to tar\n    if i == 1 {\n        move(src, tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfsHanota(i-1, src, tar, buf)\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfsHanota(i-1, buf, src, tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfunc solveHanota(A, B, C *list.List) {\n    n := A.Len()\n    // Move the top n disks from A to C using B\n    dfsHanota(n, A, B, C)\n}\n
    hanota.swift
    /* Move one disk */\nfunc move(src: inout [Int], tar: inout [Int]) {\n    // Take a disk from the top of src\n    let pan = src.popLast()!\n    // Place the disk on top of tar\n    tar.append(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunc dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) {\n    // If only one disk in this subproblem remains on src, move it directly to tar\n    if i == 1 {\n        move(src: &src, tar: &tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i: i - 1, src: &src, buf: &tar, tar: &buf)\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src: &src, tar: &tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i: i - 1, src: &buf, buf: &src, tar: &tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfunc solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) {\n    let n = A.count\n    // The end of each list represents the top of the peg\n    // Move the top n disks from A to C using B\n    dfs(i: n, src: &A, buf: &B, tar: &C)\n}\n
    hanota.js
    /* Move one disk */\nfunction move(src, tar) {\n    // Take a disk from the top of src\n    const pan = src.pop();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunction dfs(i, src, buf, tar) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i === 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfunction solveHanota(A, B, C) {\n    const n = A.length;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.ts
    /* Move one disk */\nfunction move(src: number[], tar: number[]): void {\n    // Take a disk from the top of src\n    const pan = src.pop();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfunction dfs(i: number, src: number[], buf: number[], tar: number[]): void {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i === 1) {\n        move(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfunction solveHanota(A: number[], B: number[], C: number[]): void {\n    const n = A.length;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.dart
    /* Move one disk */\nvoid move(List<int> src, List<int> tar) {\n  // Take a disk from the top of src\n  int pan = src.removeLast();\n  // Place the disk on top of tar\n  tar.add(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, List<int> src, List<int> buf, List<int> tar) {\n  // If only one disk remains to be moved from src, move it directly to tar\n  if (i == 1) {\n    move(src, tar);\n    return;\n  }\n  // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n  dfs(i - 1, src, tar, buf);\n  // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n  move(src, tar);\n  // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n  dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(List<int> A, List<int> B, List<int> C) {\n  int n = A.length;\n  // Move the top n disks from A to C using B\n  dfs(n, A, B, C);\n}\n
    hanota.rs
    /* Move one disk */\nfn move_pan(src: &mut Vec<i32>, tar: &mut Vec<i32>) {\n    // Take a disk from the top of src\n    let pan = src.pop().unwrap();\n    // Place the disk on top of tar\n    tar.push(pan);\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfn dfs(i: i32, src: &mut Vec<i32>, buf: &mut Vec<i32>, tar: &mut Vec<i32>) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if i == 1 {\n        move_pan(src, tar);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move_pan(src, tar);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar);\n}\n\n/* Solve the Tower of Hanoi problem */\nfn solve_hanota(A: &mut Vec<i32>, B: &mut Vec<i32>, C: &mut Vec<i32>) {\n    let n = A.len() as i32;\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C);\n}\n
    hanota.c
    /* Move one disk */\nvoid move(int *src, int *srcSize, int *tar, int *tarSize) {\n    // Take a disk from the top of src\n    int pan = src[*srcSize - 1];\n    src[*srcSize - 1] = 0;\n    (*srcSize)--;\n    // Place the disk on top of tar\n    tar[*tarSize] = pan;\n    (*tarSize)++;\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nvoid dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) {\n    // If only one disk remains to be moved from src, move it directly to tar\n    if (i == 1) {\n        move(src, srcSize, tar, tarSize);\n        return;\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize);\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, srcSize, tar, tarSize);\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize);\n}\n\n/* Solve the Tower of Hanoi problem */\nvoid solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) {\n    // Move the top n disks from A to C using B\n    dfs(*ASize, A, ASize, B, BSize, C, CSize);\n}\n
    hanota.kt
    /* Move one disk */\nfun move(src: MutableList<Int>, tar: MutableList<Int>) {\n    // Take a disk from the top of src\n    val pan = src.removeAt(src.size - 1)\n    // Place the disk on top of tar\n    tar.add(pan)\n}\n\n/* Solve the Tower of Hanoi problem f(i) */\nfun dfs(i: Int, src: MutableList<Int>, buf: MutableList<Int>, tar: MutableList<Int>) {\n    // If only one disk in this subproblem remains on src, move it directly to tar\n    if (i == 1) {\n        move(src, tar)\n        return\n    }\n    // Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n    dfs(i - 1, src, tar, buf)\n    // Subproblem f(1): move the remaining disk of this subproblem from src to tar\n    move(src, tar)\n    // Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n    dfs(i - 1, buf, src, tar)\n}\n\n/* Solve the Tower of Hanoi problem */\nfun solveHanota(A: MutableList<Int>, B: MutableList<Int>, C: MutableList<Int>) {\n    val n = A.size\n    // Move the top n disks from A to C using B\n    dfs(n, A, B, C)\n}\n
    hanota.rb
    ### Move one disk ###\ndef move(src, tar)\n  # Take a disk from the top of src\n  pan = src.pop\n  # Place the disk on top of tar\n  tar << pan\nend\n\n### Solve the Tower of Hanoi problem f(i) ###\ndef dfs(i, src, buf, tar)\n  # If only one disk remains to be moved from src, move it directly to tar\n  if i == 1\n    move(src, tar)\n    return\n  end\n\n  # Subproblem f(i-1): move the top i-1 disks from src to buf using tar\n  dfs(i - 1, src, tar, buf)\n  # Subproblem f(1): move the remaining disk in the current subproblem from src to tar\n  move(src, tar)\n  # Subproblem f(i-1): move the top i-1 disks from buf to tar using src\n  dfs(i - 1, buf, src, tar)\nend\n\n### Solve the Tower of Hanoi problem ###\ndef solve_hanota(_A, _B, _C)\n  n = _A.length\n  # Move the top n disks from A to C using B\n  dfs(n, _A, _B, _C)\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 12-15, the Tower of Hanoi forms a recursion tree of height \\(n\\). Each node represents a subproblem and corresponds to a call to dfs(). The time complexity is therefore \\(O(2^n)\\), and the space complexity is \\(O(n)\\).

    Figure 12-15   Recursion tree for the Tower of Hanoi

    Quote

    The Tower of Hanoi comes from an ancient legend. In a temple in ancient India, monks had three tall diamond rods and \\(64\\) gold disks of different sizes. They continually moved the disks, believing that the world would end when the last disk was placed in its correct position.

    Even at one move per second, however, this would take about \\(2^{64} \\approx 1.84×10^{19}\\) seconds, or roughly \\(585\\) billion years, far longer than current estimates of the age of the universe. If the legend is true, we should have little reason to worry about the end of the world.

    ","path":["Chapter 12.   Divide and conquer","12.4   Tower of Hanoi"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/","level":1,"title":"12.5   Summary","text":"","path":["Chapter 12.   Divide and conquer","12.5   Summary"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Divide and conquer is a common algorithm design strategy with two stages: dividing the problem and combining solutions. It is usually implemented recursively.
    • To recognize a divide-and-conquer problem, ask whether it can be divided, whether the subproblems are independent, and whether their solutions can be combined.
    • Merge sort is a classic example. It recursively divides an array into two equally sized subarrays until each contains one element, then merges them level by level to complete the sort.
    • Divide and conquer often improves efficiency by reducing the number of operations and making it easier to parallelize the work.
    • Divide and conquer solves many algorithmic problems and appears widely in the design of data structures and algorithms.
    • Adaptive search is more efficient than brute-force search. Search algorithms with \\(O(\\log n)\\) time complexity are usually based on divide and conquer.
    • Binary search is another classic example, with no step for combining subproblem solutions. It can be implemented through recursive divide and conquer.
    • Reconstructing a binary tree can be divided into building its left and right subtrees. This is achieved by partitioning index intervals in the preorder and inorder traversals.
    • A Tower of Hanoi problem of size \\(n\\) can be divided into two subproblems of size \\(n-1\\) and one of size \\(1\\). Solving these three subproblems in order solves the original problem.
    ","path":["Chapter 12.   Divide and conquer","12.5   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/","level":1,"title":"Chapter 14.   Dynamic programming","text":"

    Abstract

    Streams flow into rivers, and rivers flow into the sea.

    Dynamic programming brings together solutions to small problems to answer a larger one, guiding us step by step to the other shore.

    ","path":["Chapter 14.   Dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 14.1   Introduction to dynamic programming
    • 14.2   Properties of dynamic programming problems
    • 14.3   Solving dynamic programming problems
    • 14.4   0-1 knapsack problem
    • 14.5   Unbounded knapsack problem
    • 14.6   Edit distance problem
    • 14.7   Summary
    • 14.8   Exercises
    ","path":["Chapter 14.   Dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/","level":1,"title":"14.2   Properties of dynamic programming problems","text":"

    The previous section showed how dynamic programming solves a problem by decomposing it into subproblems. Decomposition is a general algorithmic idea, but divide and conquer, dynamic programming, and backtracking use it differently.

    • Divide and conquer recursively splits a problem into independent subproblems until the smallest ones are reached, then combines their solutions as the recursion returns to solve the original problem.
    • Dynamic programming also decomposes problems recursively. Its main difference from divide and conquer is that its subproblems depend on one another, and many overlapping subproblems appear during decomposition.
    • Backtracking enumerates possible solutions by trying choices and undoing them, using pruning to avoid unnecessary branches. A solution consists of a sequence of decisions, and the prefix before each decision can be viewed as a subproblem.

    Dynamic programming is often used for optimization problems. Besides overlapping subproblems, these problems have two key properties: optimal substructure and the no-aftereffect property.

    ","path":["Chapter 14.   Dynamic programming","14.2   Properties of dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1421-optimal-substructure","level":2,"title":"14.2.1   Optimal substructure","text":"

    Let's modify the climbing stairs problem slightly to illustrate optimal substructure more clearly.

    Minimum cost to climb stairs

    Given a staircase, you can climb \\(1\\) or \\(2\\) steps at a time. Each step has a nonnegative integer indicating the cost paid on that step. A nonnegative integer array \\(cost\\) gives the costs: \\(cost[i]\\) is the cost of step \\(i\\), and \\(cost[0]\\) refers to the ground (the starting point). What is the minimum cost of reaching the top?

    As shown in Figure 14-6, if steps \\(1\\), \\(2\\), and \\(3\\) cost \\(1\\), \\(10\\), and \\(1\\), respectively, the minimum cost of reaching step \\(3\\) from the ground is \\(2\\).

    Figure 14-6   Minimum cost of reaching step 3

    Let \\(dp[i]\\) be the total cost of reaching step \\(i\\). Since step \\(i\\) can only be reached from step \\(i - 1\\) or \\(i - 2\\), \\(dp[i]\\) must be either \\(dp[i - 1] + cost[i]\\) or \\(dp[i - 2] + cost[i]\\). To minimize the cost, choose the smaller of the two:

    \\[ dp[i] = \\min(dp[i-1], dp[i-2]) + cost[i] \\]

    This introduces the meaning of optimal substructure: an optimal solution to the original problem is constructed from optimal solutions to its subproblems.

    This problem clearly has optimal substructure: choose the better of \\(dp[i-1]\\) and \\(dp[i-2]\\) and use it to construct the optimal solution \\(dp[i]\\).

    Does the climbing stairs problem from the previous section also have optimal substructure? Its goal is to count solutions, so it seems to be a counting problem. But rephrase the goal as finding the maximum number of solutions, and although the problem remains equivalent, optimal substructure becomes apparent: the maximum number of ways to reach step \\(n\\) is the sum of the maximum numbers for steps \\(n-1\\) and \\(n-2\\). The interpretation of optimal substructure is therefore flexible and can vary by problem.

    The state transition equation, together with the initial states \\(dp[1] = cost[1]\\) and \\(dp[2] = cost[2]\\), gives the following dynamic programming code:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py
    def min_cost_climbing_stairs_dp(cost: list[int]) -> int:\n    \"\"\"Minimum cost to climb stairs: dynamic programming\"\"\"\n    n = len(cost) - 1\n    if n == 1 or n == 2:\n        return cost[n]\n    # Initialize the dp table to store subproblem solutions\n    dp = [0] * (n + 1)\n    # Initial states: set the solutions to the smallest subproblems\n    dp[1], dp[2] = cost[1], cost[2]\n    # State transitions: solve progressively larger subproblems using smaller ones\n    for i in range(3, n + 1):\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    return dp[n]\n
    min_cost_climbing_stairs_dp.cpp
    /* Minimum cost to climb stairs: dynamic programming */\nint minCostClimbingStairsDP(vector<int> &cost) {\n    int n = cost.size() - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize the dp table to store subproblem solutions\n    vector<int> dp(n + 1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.java
    /* Minimum cost to climb stairs: dynamic programming */\nint minCostClimbingStairsDP(int[] cost) {\n    int n = cost.length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize the dp table to store subproblem solutions\n    int[] dp = new int[n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.cs
    /* Minimum cost to climb stairs: dynamic programming */\nint MinCostClimbingStairsDP(int[] cost) {\n    int n = cost.Length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize the dp table to store subproblem solutions\n    int[] dp = new int[n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.go
    /* Minimum cost to climb stairs: dynamic programming */\nfunc minCostClimbingStairsDP(cost []int) int {\n    n := len(cost) - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    min := func(a, b int) int {\n        if a < b {\n            return a\n        }\n        return b\n    }\n    // Initialize the dp table to store subproblem solutions\n    dp := make([]int, n+1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i] = min(dp[i-1], dp[i-2]) + cost[i]\n    }\n    return dp[n]\n}\n
    min_cost_climbing_stairs_dp.swift
    /* Minimum cost to climb stairs: dynamic programming */\nfunc minCostClimbingStairsDP(cost: [Int]) -> Int {\n    let n = cost.count - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    // Initialize the dp table to store subproblem solutions\n    var dp = Array(repeating: 0, count: n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3 ... n {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    }\n    return dp[n]\n}\n
    min_cost_climbing_stairs_dp.js
    /* Minimum cost to climb stairs: dynamic programming */\nfunction minCostClimbingStairsDP(cost) {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    // Initialize the dp table to store subproblem solutions\n    const dp = new Array(n + 1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.ts
    /* Minimum cost to climb stairs: dynamic programming */\nfunction minCostClimbingStairsDP(cost: Array<number>): number {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    // Initialize the dp table to store subproblem solutions\n    const dp = new Array(n + 1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    return dp[n];\n}\n
    min_cost_climbing_stairs_dp.dart
    /* Minimum cost to climb stairs: dynamic programming */\nint minCostClimbingStairsDP(List<int> cost) {\n  int n = cost.length - 1;\n  if (n == 1 || n == 2) return cost[n];\n  // Initialize the dp table to store subproblem solutions\n  List<int> dp = List.filled(n + 1, 0);\n  // Initial states: set the solutions to the smallest subproblems\n  dp[1] = cost[1];\n  dp[2] = cost[2];\n  // State transitions: solve progressively larger subproblems using smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n  }\n  return dp[n];\n}\n
    min_cost_climbing_stairs_dp.rs
    /* Minimum cost to climb stairs: dynamic programming */\nfn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 {\n    let n = cost.len() - 1;\n    if n == 1 || n == 2 {\n        return cost[n];\n    }\n    // Initialize the dp table to store subproblem solutions\n    let mut dp = vec![-1; n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3..=n {\n        dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    dp[n]\n}\n
    min_cost_climbing_stairs_dp.c
    /* Minimum cost to climb stairs: dynamic programming */\nint minCostClimbingStairsDP(int cost[], int costSize) {\n    int n = costSize - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    // Initialize the dp table to store subproblem solutions\n    int *dp = calloc(n + 1, sizeof(int));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1];\n    dp[2] = cost[2];\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i];\n    }\n    int res = dp[n];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    min_cost_climbing_stairs_dp.kt
    /* Minimum cost to climb stairs: dynamic programming */\nfun minCostClimbingStairsDP(cost: IntArray): Int {\n    val n = cost.size - 1\n    if (n == 1 || n == 2) return cost[n]\n    // Initialize the dp table to store subproblem solutions\n    val dp = IntArray(n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = cost[1]\n    dp[2] = cost[2]\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (i in 3..n) {\n        dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n    }\n    return dp[n]\n}\n
    min_cost_climbing_stairs_dp.rb
    ### Minimum cost to climb stairs: dynamic programming ###\ndef min_cost_climbing_stairs_dp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  # Initialize the dp table to store subproblem solutions\n  dp = Array.new(n + 1, 0)\n  # Initial states: set the solutions to the smallest subproblems\n  dp[1], dp[2] = cost[1], cost[2]\n  # State transitions: solve progressively larger subproblems using smaller ones\n  (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n  dp[n]\nend\n
    Visualize execution

    View full screen >

    Figure 14-7 illustrates this dynamic programming process.

    Figure 14-7   Dynamic programming for the minimum climbing cost

    We can also reduce the state storage from a one-dimensional array to scalar variables, lowering the space complexity from \\(O(n)\\) to \\(O(1)\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py
    def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int:\n    \"\"\"Minimum cost to climb stairs: space-optimized dynamic programming\"\"\"\n    n = len(cost) - 1\n    if n == 1 or n == 2:\n        return cost[n]\n    a, b = cost[1], cost[2]\n    for i in range(3, n + 1):\n        a, b = b, min(a, b) + cost[i]\n    return b\n
    min_cost_climbing_stairs_dp.cpp
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(vector<int> &cost) {\n    int n = cost.size() - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.java
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(int[] cost) {\n    int n = cost.length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.cs
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint MinCostClimbingStairsDPComp(int[] cost) {\n    int n = cost.Length - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = Math.Min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.go
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfunc minCostClimbingStairsDPComp(cost []int) int {\n    n := len(cost) - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    min := func(a, b int) int {\n        if a < b {\n            return a\n        }\n        return b\n    }\n    // Initial states: set the solutions to the smallest subproblems\n    a, b := cost[1], cost[2]\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        tmp := b\n        b = min(a, tmp) + cost[i]\n        a = tmp\n    }\n    return b\n}\n
    min_cost_climbing_stairs_dp.swift
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfunc minCostClimbingStairsDPComp(cost: [Int]) -> Int {\n    let n = cost.count - 1\n    if n == 1 || n == 2 {\n        return cost[n]\n    }\n    var (a, b) = (cost[1], cost[2])\n    for i in 3 ... n {\n        (a, b) = (b, min(a, b) + cost[i])\n    }\n    return b\n}\n
    min_cost_climbing_stairs_dp.js
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfunction minCostClimbingStairsDPComp(cost) {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    let a = cost[1],\n        b = cost[2];\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.ts
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfunction minCostClimbingStairsDPComp(cost: Array<number>): number {\n    const n = cost.length - 1;\n    if (n === 1 || n === 2) {\n        return cost[n];\n    }\n    let a = cost[1],\n        b = cost[2];\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = Math.min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.dart
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(List<int> cost) {\n  int n = cost.length - 1;\n  if (n == 1 || n == 2) return cost[n];\n  int a = cost[1], b = cost[2];\n  for (int i = 3; i <= n; i++) {\n    int tmp = b;\n    b = min(a, tmp) + cost[i];\n    a = tmp;\n  }\n  return b;\n}\n
    min_cost_climbing_stairs_dp.rs
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 {\n    let n = cost.len() - 1;\n    if n == 1 || n == 2 {\n        return cost[n];\n    };\n    let (mut a, mut b) = (cost[1], cost[2]);\n    for i in 3..=n {\n        let tmp = b;\n        b = cmp::min(a, tmp) + cost[i];\n        a = tmp;\n    }\n    b\n}\n
    min_cost_climbing_stairs_dp.c
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nint minCostClimbingStairsDPComp(int cost[], int costSize) {\n    int n = costSize - 1;\n    if (n == 1 || n == 2)\n        return cost[n];\n    int a = cost[1], b = cost[2];\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = myMin(a, tmp) + cost[i];\n        a = tmp;\n    }\n    return b;\n}\n
    min_cost_climbing_stairs_dp.kt
    /* Minimum cost to climb stairs: space-optimized dynamic programming */\nfun minCostClimbingStairsDPComp(cost: IntArray): Int {\n    val n = cost.size - 1\n    if (n == 1 || n == 2) return cost[n]\n    var a = cost[1]\n    var b = cost[2]\n    for (i in 3..n) {\n        val tmp = b\n        b = min(a, tmp) + cost[i]\n        a = tmp\n    }\n    return b\n}\n
    min_cost_climbing_stairs_dp.rb
    ### Minimum cost to climb stairs: dynamic programming ###\ndef min_cost_climbing_stairs_dp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  # Initialize the dp table to store subproblem solutions\n  dp = Array.new(n + 1, 0)\n  # Initial states: set the solutions to the smallest subproblems\n  dp[1], dp[2] = cost[1], cost[2]\n  # State transitions: solve progressively larger subproblems using smaller ones\n  (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n  dp[n]\nend\n\n# Minimum cost to climb stairs: space-optimized dynamic programming\ndef min_cost_climbing_stairs_dp_comp(cost)\n  n = cost.length - 1\n  return cost[n] if n == 1 || n == 2\n  a, b = cost[1], cost[2]\n  (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] }\n  b\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.2   Properties of dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1422-no-aftereffect-property","level":2,"title":"14.2.2   No-aftereffect property","text":"

    The no-aftereffect property is central to effective dynamic programming: given a fully specified state, its future depends only on that state, not on the sequence of states that led to it.

    In the climbing stairs problem, state \\(i\\) can lead to \\(i+1\\) or \\(i+2\\), corresponding to moves of \\(1\\) or \\(2\\) steps. Neither choice requires us to consider states before \\(i\\): they do not affect the future of state \\(i\\).

    Adding a constraint changes the situation.

    Climbing stairs with a constraint

    Given a staircase with \\(n\\) steps, you may climb \\(1\\) or \\(2\\) steps at a time, but may not make two consecutive moves of \\(1\\) step. How many ways are there to reach the top?

    As shown in Figure 14-8, only \\(2\\) ways remain to reach step \\(3\\): taking \\(1\\) step three times in a row violates the constraint and is discarded.

    Figure 14-8   Number of ways to reach step 3 with the constraint

    If the previous move climbed \\(1\\) step, the next must climb \\(2\\) steps. Thus, the next choice cannot be determined by the current state (the current step number) alone; it also depends on the preceding state (the previous step number).

    The no-aftereffect property no longer holds with this state definition, and \\(dp[i] = dp[i-1] + dp[i-2]\\) is no longer valid. Using \\(dp[i-1]\\) means taking \\(1\\) step now, but it includes many ways in which the preceding move also took \\(1\\) step. We cannot simply add all of \\(dp[i-1]\\) to \\(dp[i]\\) without violating the constraint.

    We therefore expand the state definition: state \\([i, j]\\) means we are on step \\(i\\) and the previous move climbed \\(j\\) steps, where \\(j \\in \\{1, 2\\}\\). This distinguishes whether the preceding move climbed \\(1\\) or \\(2\\) steps, allowing us to determine how the current state could have been reached.

    • If the previous move climbed \\(1\\) step, the move before it must have climbed \\(2\\) steps. Thus, \\(dp[i, 1]\\) can only come from \\(dp[i-1, 2]\\).
    • If the previous move climbed \\(2\\) steps, the move before it could have climbed \\(1\\) or \\(2\\) steps. Thus, \\(dp[i, 2]\\) can come from \\(dp[i-2, 1]\\) or \\(dp[i-2, 2]\\).

    As shown in Figure 14-9, \\(dp[i, j]\\) now counts the ways to reach state \\([i, j]\\). The state transition equations are:

    \\[ \\begin{cases} dp[i, 1] = dp[i-1, 2] \\\\ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \\end{cases} \\]

    Figure 14-9   Recurrence with the constraint

    Finally, return \\(dp[n, 1] + dp[n, 2]\\), the total number of ways to reach step \\(n\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_constraint_dp.py
    def climbing_stairs_constraint_dp(n: int) -> int:\n    \"\"\"Climbing stairs with constraints: dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return 1\n    # Initialize the dp table to store subproblem solutions\n    dp = [[0] * 3 for _ in range(n + 1)]\n    # Initial states: set the solutions to the smallest subproblems\n    dp[1][1], dp[1][2] = 1, 0\n    dp[2][1], dp[2][2] = 0, 1\n    # State transitions: solve progressively larger subproblems using smaller ones\n    for i in range(3, n + 1):\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    return dp[n][1] + dp[n][2]\n
    climbing_stairs_constraint_dp.cpp
    /* Climbing stairs with constraints: dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    vector<vector<int>> dp(n + 1, vector<int>(3, 0));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.java
    /* Climbing stairs with constraints: dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    int[][] dp = new int[n + 1][3];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.cs
    /* Climbing stairs with constraints: dynamic programming */\nint ClimbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    int[,] dp = new int[n + 1, 3];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1, 1] = 1;\n    dp[1, 2] = 0;\n    dp[2, 1] = 0;\n    dp[2, 2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i, 1] = dp[i - 1, 2];\n        dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2];\n    }\n    return dp[n, 1] + dp[n, 2];\n}\n
    climbing_stairs_constraint_dp.go
    /* Climbing stairs with constraints: dynamic programming */\nfunc climbingStairsConstraintDP(n int) int {\n    if n == 1 || n == 2 {\n        return 1\n    }\n    // Initialize the dp table to store subproblem solutions\n    dp := make([][3]int, n+1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i][1] = dp[i-1][2]\n        dp[i][2] = dp[i-2][1] + dp[i-2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
    climbing_stairs_constraint_dp.swift
    /* Climbing stairs with constraints: dynamic programming */\nfunc climbingStairsConstraintDP(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return 1\n    }\n    // Initialize the dp table to store subproblem solutions\n    var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3 ... n {\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
    climbing_stairs_constraint_dp.js
    /* Climbing stairs with constraints: dynamic programming */\nfunction climbingStairsConstraintDP(n) {\n    if (n === 1 || n === 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    const dp = Array.from(new Array(n + 1), () => new Array(3));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.ts
    /* Climbing stairs with constraints: dynamic programming */\nfunction climbingStairsConstraintDP(n: number): number {\n    if (n === 1 || n === 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    const dp = Array.from({ length: n + 1 }, () => new Array(3));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.dart
    /* Climbing stairs with constraints: dynamic programming */\nint climbingStairsConstraintDP(int n) {\n  if (n == 1 || n == 2) {\n    return 1;\n  }\n  // Initialize the dp table to store subproblem solutions\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(3, 0));\n  // Initial states: set the solutions to the smallest subproblems\n  dp[1][1] = 1;\n  dp[1][2] = 0;\n  dp[2][1] = 0;\n  dp[2][2] = 1;\n  // State transitions: solve progressively larger subproblems using smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i][1] = dp[i - 1][2];\n    dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n  }\n  return dp[n][1] + dp[n][2];\n}\n
    climbing_stairs_constraint_dp.rs
    /* Climbing stairs with constraints: dynamic programming */\nfn climbing_stairs_constraint_dp(n: usize) -> i32 {\n    if n == 1 || n == 2 {\n        return 1;\n    };\n    // Initialize the dp table to store subproblem solutions\n    let mut dp = vec![vec![-1; 3]; n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3..=n {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    dp[n][1] + dp[n][2]\n}\n
    climbing_stairs_constraint_dp.c
    /* Climbing stairs with constraints: dynamic programming */\nint climbingStairsConstraintDP(int n) {\n    if (n == 1 || n == 2) {\n        return 1;\n    }\n    // Initialize the dp table to store subproblem solutions\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(3, sizeof(int));\n    }\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1;\n    dp[1][2] = 0;\n    dp[2][1] = 0;\n    dp[2][2] = 1;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i][1] = dp[i - 1][2];\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n    }\n    int res = dp[n][1] + dp[n][2];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
    climbing_stairs_constraint_dp.kt
    /* Climbing stairs with constraints: dynamic programming */\nfun climbingStairsConstraintDP(n: Int): Int {\n    if (n == 1 || n == 2) {\n        return 1\n    }\n    // Initialize the dp table to store subproblem solutions\n    val dp = Array(n + 1) { IntArray(3) }\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1][1] = 1\n    dp[1][2] = 0\n    dp[2][1] = 0\n    dp[2][2] = 1\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (i in 3..n) {\n        dp[i][1] = dp[i - 1][2]\n        dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n    }\n    return dp[n][1] + dp[n][2]\n}\n
    climbing_stairs_constraint_dp.rb
    ### Climbing stairs with constraints: dynamic programming ###\ndef climbing_stairs_constraint_dp(n)\n  return 1 if n == 1 || n == 2\n\n  # Initialize the dp table to store subproblem solutions\n  dp = Array.new(n + 1) { Array.new(3, 0) }\n  # Initial states: set the solutions to the smallest subproblems\n  dp[1][1], dp[1][2] = 1, 0\n  dp[2][1], dp[2][2] = 0, 1\n  # State transitions: solve progressively larger subproblems using smaller ones\n  for i in 3...(n + 1)\n    dp[i][1] = dp[i - 1][2]\n    dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n  end\n\n  dp[n][1] + dp[n][2]\nend\n
    Visualize execution

    View full screen >

    In this example, only one preceding state mattered, so expanding the state definition restored the no-aftereffect property. Some problems, however, depend much more strongly on their history.

    Climbing stairs that generate obstacles

    Given a staircase with \\(n\\) steps, you may climb \\(1\\) or \\(2\\) steps at a time. Whenever you reach step \\(i\\), the system places an obstacle on step \\(2i\\), and you may never land on step \\(2i\\) afterward. For example, if your first two moves land on steps \\(2\\) and \\(3\\), then steps \\(4\\) and \\(6\\) are blocked for all later moves. How many ways are there to reach the top?

    Here, each next move depends on all previous states, because every move places an obstacle higher up and affects future moves. Such problems are often difficult to solve with dynamic programming.

    Many complex combinatorial optimization problems, such as the traveling salesperson problem, do not satisfy the no-aftereffect property. For such problems, we often choose other methods, such as heuristic search, genetic algorithms, or reinforcement learning, to obtain useful locally optimal solutions within a limited time.

    ","path":["Chapter 14.   Dynamic programming","14.2   Properties of dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/","level":1,"title":"14.3   Solving dynamic programming problems","text":"

    The previous two sections introduced the main properties of dynamic programming problems. We will now explore two practical questions.

    1. How can we recognize a dynamic programming problem?
    2. Where should we start, and what are the steps for solving one?
    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1431-recognizing-the-problem","level":2,"title":"14.3.1   Recognizing the problem","text":"

    In general, a problem is suitable for dynamic programming if it has overlapping subproblems, optimal substructure, and the no-aftereffect property. These can be difficult to identify directly from a problem statement, so we usually start with a broader test: could backtracking (exhaustive search) solve it?

    Problems suited to backtracking usually fit a decision tree model. They can be represented as a tree in which each node is a decision and each path is a sequence of decisions.

    In other words, if a problem has clear choices and a solution is produced through a sequence of them, it fits the decision tree model and can usually be solved by backtracking.

    Beyond this, some signs make dynamic programming more promising.

    • The problem asks for an optimum, such as a maximum, minimum, greatest number, or smallest number.
    • States can be represented by a list, a multidimensional matrix, or a tree, and a recurrence relates each state to nearby states.

    Other signs make it less promising.

    • The goal is to find all possible solutions rather than an optimal one.
    • The problem explicitly involves permutations or combinations and asks for multiple concrete arrangements.

    If a problem fits the decision tree model and has clear positive signs, we can tentatively treat it as a dynamic programming problem and verify that assumption while solving it.

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1432-steps-for-solving-the-problem","level":2,"title":"14.3.2   Steps for solving the problem","text":"

    The process varies with the problem's nature and difficulty, but usually includes describing the decisions, defining states, creating a \\(dp\\) table, deriving a state transition equation, and identifying boundary conditions.

    To make these steps concrete, we will use the classic minimum path sum problem.

    Question

    Given an \\(n \\times m\\) grid grid, each cell contains a nonnegative integer representing its cost. A robot starts at the top-left cell and can move one cell down or right at a time until it reaches the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right.

    In the example shown in Figure 14-10, the minimum path sum is \\(13\\).

    Figure 14-10   Example input for minimum path sum

    Step 1: Identify each decision, define the state, and establish the \\(dp\\) table

    Each decision moves one cell down or right. If the current cell has indices \\([i, j]\\), the next indices are \\([i+1, j]\\) or \\([i, j+1]\\). The state therefore contains two variables, the row and column indices, and is denoted by \\([i, j]\\).

    State \\([i, j]\\) corresponds to the subproblem of finding the minimum path sum from the starting point \\([0, 0]\\) to \\([i, j]\\). Its solution is \\(dp[i, j]\\).

    This gives the two-dimensional \\(dp\\) matrix shown in Figure 14-11, with the same dimensions as the input \\(grid\\).

    Figure 14-11   State definition and dp table

    Note

    Dynamic programming and backtracking can be described as sequences of decisions. A state consists of the decision variables and should contain every variable needed to describe progress, with enough information to derive the next state.

    Each state corresponds to a subproblem. A \\(dp\\) table stores the solutions to all subproblems, with one dimension of the \\(dp\\) table for each independent state variable. In essence, the \\(dp\\) table maps states to subproblem solutions.

    Step 2: Identify optimal substructure and derive the state transition equation

    State \\([i, j]\\) can only be reached from the cell above, \\([i-1, j]\\), or the cell to the left, \\([i, j-1]\\). The optimal substructure is therefore that the minimum path sum to \\([i, j]\\) is determined by the smaller of the minimum path sums to \\([i, j-1]\\) and \\([i-1, j]\\).

    This gives the state transition equation illustrated in Figure 14-12:

    \\[ dp[i, j] = \\min(dp[i-1, j], dp[i, j-1]) + grid[i, j] \\]

    Figure 14-12   Optimal substructure and state transition equation

    Note

    Using the defined \\(dp\\) table, examine the relationship between the original problem and its subproblems. Find how to construct an optimal solution from optimal subproblem solutions: this is the optimal substructure.

    Once we identify optimal substructure, we can use it to build the state transition equation.

    Step 3: Determine the boundary conditions and evaluation order

    A state in the first row can only come from its left, and a state in the first column can only come from above. Thus, the first row \\(i = 0\\) and first column \\(j = 0\\) define the boundary conditions.

    As shown in Figure 14-13, each cell depends on the cells to its left and above. We therefore traverse the matrix with an outer loop over rows and an inner loop over columns.

    Figure 14-13   Boundary conditions and evaluation order

    Note

    Boundary conditions initialize the \\(dp\\) table in dynamic programming and support pruning in search.

    The key to choosing an evaluation order is ensuring that all smaller subproblems needed by the current state have already been solved correctly.

    We could now write the dynamic programming solution directly. However, decomposing a problem is a top-down way of thinking, so implementing \"brute-force search \\(\\rightarrow\\) memoized search \\(\\rightarrow\\) dynamic programming\" often follows our reasoning more naturally.

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1-method-1-brute-force-search","level":3,"title":"1.   Method 1: Brute-force search","text":"

    Starting from \\([i, j]\\), recursively decompose the problem into smaller states \\([i-1, j]\\) and \\([i, j-1]\\). The recursive function has the following elements.

    • Parameters: The state \\([i, j]\\).
    • Return value: The minimum path sum \\(dp[i, j]\\) from \\([0, 0]\\) to \\([i, j]\\).
    • Base case: When \\(i = 0\\) and \\(j = 0\\), return \\(grid[0, 0]\\).
    • Pruning: If \\(i < 0\\) or \\(j < 0\\), the indices are out of bounds. Return cost \\(+\\infty\\) to mark the path as infeasible.

    The implementation is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
    def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int:\n    \"\"\"Minimum path sum: brute-force search\"\"\"\n    # Stop searching at the top-left cell\n    if i == 0 and j == 0:\n        return grid[0][0]\n    # If a row or column index is out of bounds, return a cost of +∞\n    if i < 0 or j < 0:\n        return inf\n    # Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    up = min_path_sum_dfs(grid, i - 1, j)\n    left = min_path_sum_dfs(grid, i, j - 1)\n    # Return the minimum path cost from the top-left corner to (i, j)\n    return min(left, up) + grid[i][j]\n
    min_path_sum.cpp
    /* Minimum path sum: brute-force search */\nint minPathSumDFS(vector<vector<int>> &grid, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n}\n
    min_path_sum.java
    /* Minimum path sum: brute-force search */\nint minPathSumDFS(int[][] grid, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Integer.MAX_VALUE;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
    min_path_sum.cs
    /* Minimum path sum: brute-force search */\nint MinPathSumDFS(int[][] grid, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return int.MaxValue;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    int up = MinPathSumDFS(grid, i - 1, j);\n    int left = MinPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return Math.Min(left, up) + grid[i][j];\n}\n
    min_path_sum.go
    /* Minimum path sum: brute-force search */\nfunc minPathSumDFS(grid [][]int, i, j int) int {\n    // Stop searching at the top-left cell\n    if i == 0 && j == 0 {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return math.MaxInt to represent an infinite cost\n    if i < 0 || j < 0 {\n        return math.MaxInt\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    up := minPathSumDFS(grid, i-1, j)\n    left := minPathSumDFS(grid, i, j-1)\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return int(math.Min(float64(left), float64(up))) + grid[i][j]\n}\n
    min_path_sum.swift
    /* Minimum path sum: brute-force search */\nfunc minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int {\n    // Stop searching at the top-left cell\n    if i == 0, j == 0 {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return Int.max to represent an infinite cost\n    if i < 0 || j < 0 {\n        return .max\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    let up = minPathSumDFS(grid: grid, i: i - 1, j: j)\n    let left = minPathSumDFS(grid: grid, i: i, j: j - 1)\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return min(left, up) + grid[i][j]\n}\n
    min_path_sum.js
    /* Minimum path sum: brute-force search */\nfunction minPathSumDFS(grid, i, j) {\n    // Stop searching at the top-left cell\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    const up = minPathSumDFS(grid, i - 1, j);\n    const left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
    min_path_sum.ts
    /* Minimum path sum: brute-force search */\nfunction minPathSumDFS(\n    grid: Array<Array<number>>,\n    i: number,\n    j: number\n): number {\n    // Stop searching at the top-left cell\n    if (i === 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    const up = minPathSumDFS(grid, i - 1, j);\n    const left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return Math.min(left, up) + grid[i][j];\n}\n
    min_path_sum.dart
    /* Minimum path sum: brute-force search */\nint minPathSumDFS(List<List<int>> grid, int i, int j) {\n  // Stop searching at the top-left cell\n  if (i == 0 && j == 0) {\n    return grid[0][0];\n  }\n  // If a row or column index is out of bounds, return a large cost representing +∞\n  if (i < 0 || j < 0) {\n    // Dart int has no infinity value; use 2^31 as a sentinel for this example\n    return BigInt.from(2).pow(31).toInt();\n  }\n  // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n  int up = minPathSumDFS(grid, i - 1, j);\n  int left = minPathSumDFS(grid, i, j - 1);\n  // Return the minimum path cost from the top-left corner to (i, j)\n  return min(left, up) + grid[i][j];\n}\n
    min_path_sum.rs
    /* Minimum path sum: brute-force search */\nfn min_path_sum_dfs(grid: &Vec<Vec<i32>>, i: i32, j: i32) -> i32 {\n    // Stop searching at the top-left cell\n    if i == 0 && j == 0 {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return i32::MAX to represent infinite cost\n    if i < 0 || j < 0 {\n        return i32::MAX;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    let up = min_path_sum_dfs(grid, i - 1, j);\n    let left = min_path_sum_dfs(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    std::cmp::min(left, up) + grid[i as usize][j as usize]\n}\n
    min_path_sum.c
    /* Minimum path sum: brute-force search */\nint minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    int up = minPathSumDFS(grid, i - 1, j);\n    int left = minPathSumDFS(grid, i, j - 1);\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n}\n
    min_path_sum.kt
    /* Minimum path sum: brute-force search */\nfun minPathSumDFS(grid: Array<IntArray>, i: Int, j: Int): Int {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return Int.MAX_VALUE as an infinite-cost sentinel\n    if (i < 0 || j < 0) {\n        return Int.MAX_VALUE\n    }\n    // Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n    val up = minPathSumDFS(grid, i - 1, j)\n    val left = minPathSumDFS(grid, i, j - 1)\n    // Return the minimum path cost from the top-left corner to (i, j)\n    return min(left, up) + grid[i][j]\n}\n
    min_path_sum.rb
    ### Minimum path sum: brute-force search ###\ndef min_path_sum_dfs(grid, i, j)\n  # Stop searching at the top-left cell\n  return grid[i][j] if i == 0 && j == 0\n  # If a row or column index is out of bounds, return a cost of +∞\n  return Float::INFINITY if i < 0 || j < 0\n  # Calculate the minimum path costs from the top-left corner to (i-1, j) and (i, j-1)\n  up = min_path_sum_dfs(grid, i - 1, j)\n  left = min_path_sum_dfs(grid, i, j - 1)\n  # Return the minimum path cost from the top-left corner to (i, j)\n  [left, up].min + grid[i][j]\nend\n
    Visualize execution

    View full screen >

    Figure 14-14 shows the recursion tree rooted at \\(dp[2, 1]\\). It includes overlapping subproblems, whose number grows rapidly with the size of grid.

    The underlying reason for overlapping subproblems is that multiple paths can reach the same cell from the top-left corner.

    Figure 14-14   Recursion tree for brute-force search

    Each state offers two choices, down and right, and reaching the bottom-right corner takes \\(m + n - 2\\) moves. The worst-case time complexity is therefore \\(O(2^{m + n})\\), where \\(n\\) and \\(m\\) are the numbers of rows and columns. This estimate ignores the grid boundaries, where only one choice remains, so the actual number of paths is smaller.

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#2-method-2-memoized-search","level":3,"title":"2.   Method 2: Memoized search","text":"

    Introduce a memoization table mem with the same dimensions as grid to store subproblem solutions and prune repeated subproblems:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
    def min_path_sum_dfs_mem(\n    grid: list[list[int]], mem: list[list[int]], i: int, j: int\n) -> int:\n    \"\"\"Minimum path sum: memoized search\"\"\"\n    # Stop searching at the top-left cell\n    if i == 0 and j == 0:\n        return grid[0][0]\n    # If a row or column index is out of bounds, return a cost of +∞\n    if i < 0 or j < 0:\n        return inf\n    # If a result has already been recorded, return it directly\n    if mem[i][j] != -1:\n        return mem[i][j]\n    # Minimum path costs to the cells on the left and above\n    up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n    left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n    # Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n
    min_path_sum.cpp
    /* Minimum path sum: memoized search */\nint minPathSumDFSMem(vector<vector<int>> &grid, vector<vector<int>> &mem, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n    return mem[i][j];\n}\n
    min_path_sum.java
    /* Minimum path sum: memoized search */\nint minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Integer.MAX_VALUE;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
    min_path_sum.cs
    /* Minimum path sum: memoized search */\nint MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return int.MaxValue;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    int up = MinPathSumDFSMem(grid, mem, i - 1, j);\n    int left = MinPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = Math.Min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
    min_path_sum.go
    /* Minimum path sum: memoized search */\nfunc minPathSumDFSMem(grid, mem [][]int, i, j int) int {\n    // Stop searching at the top-left cell\n    if i == 0 && j == 0 {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return math.MaxInt to represent an infinite cost\n    if i < 0 || j < 0 {\n        return math.MaxInt\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][j] != -1 {\n        return mem[i][j]\n    }\n    // Minimum path costs to the cells on the left and above\n    up := minPathSumDFSMem(grid, mem, i-1, j)\n    left := minPathSumDFSMem(grid, mem, i, j-1)\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j]\n    return mem[i][j]\n}\n
    min_path_sum.swift
    /* Minimum path sum: memoized search */\nfunc minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int {\n    // Stop searching at the top-left cell\n    if i == 0, j == 0 {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return Int.max to represent an infinite cost\n    if i < 0 || j < 0 {\n        return .max\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][j] != -1 {\n        return mem[i][j]\n    }\n    // Minimum path costs to the cells on the left and above\n    let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j)\n    let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1)\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n}\n
    min_path_sum.js
    /* Minimum path sum: memoized search */\nfunction minPathSumDFSMem(grid, mem, i, j) {\n    // Stop searching at the top-left cell\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] !== -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    const up = minPathSumDFSMem(grid, mem, i - 1, j);\n    const left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
    min_path_sum.ts
    /* Minimum path sum: memoized search */\nfunction minPathSumDFSMem(\n    grid: Array<Array<number>>,\n    mem: Array<Array<number>>,\n    i: number,\n    j: number\n): number {\n    // Stop searching at the top-left cell\n    if (i === 0 && j === 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return Infinity;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    const up = minPathSumDFSMem(grid, mem, i - 1, j);\n    const left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = Math.min(left, up) + grid[i][j];\n    return mem[i][j];\n}\n
    min_path_sum.dart
    /* Minimum path sum: memoized search */\nint minPathSumDFSMem(List<List<int>> grid, List<List<int>> mem, int i, int j) {\n  // Stop searching at the top-left cell\n  if (i == 0 && j == 0) {\n    return grid[0][0];\n  }\n  // If a row or column index is out of bounds, return a large cost representing +∞\n  if (i < 0 || j < 0) {\n    // Dart int has no infinity value; use 2^31 as a sentinel for this example\n    return BigInt.from(2).pow(31).toInt();\n  }\n  // If a result has already been recorded, return it directly\n  if (mem[i][j] != -1) {\n    return mem[i][j];\n  }\n  // Minimum path costs to the cells on the left and above\n  int up = minPathSumDFSMem(grid, mem, i - 1, j);\n  int left = minPathSumDFSMem(grid, mem, i, j - 1);\n  // Record and return the minimum path cost from the top-left corner to (i, j)\n  mem[i][j] = min(left, up) + grid[i][j];\n  return mem[i][j];\n}\n
    min_path_sum.rs
    /* Minimum path sum: memoized search */\nfn min_path_sum_dfs_mem(grid: &Vec<Vec<i32>>, mem: &mut Vec<Vec<i32>>, i: i32, j: i32) -> i32 {\n    // Stop searching at the top-left cell\n    if i == 0 && j == 0 {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return i32::MAX to represent infinite cost\n    if i < 0 || j < 0 {\n        return i32::MAX;\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i as usize][j as usize] != -1 {\n        return mem[i as usize][j as usize];\n    }\n    // Minimum path costs to the cells on the left and above\n    let up = min_path_sum_dfs_mem(grid, mem, i - 1, j);\n    let left = min_path_sum_dfs_mem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize];\n    mem[i as usize][j as usize]\n}\n
    min_path_sum.c
    /* Minimum path sum: memoized search */\nint minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0];\n    }\n    // If a row or column index is out of bounds, return a cost of +∞\n    if (i < 0 || j < 0) {\n        return INT_MAX;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j];\n    }\n    // Minimum path costs to the cells on the left and above\n    int up = minPathSumDFSMem(grid, mem, i - 1, j);\n    int left = minPathSumDFSMem(grid, mem, i, j - 1);\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n    return mem[i][j];\n}\n
    min_path_sum.kt
    /* Minimum path sum: memoized search */\nfun minPathSumDFSMem(\n    grid: Array<IntArray>,\n    mem: Array<IntArray>,\n    i: Int,\n    j: Int\n): Int {\n    // Stop searching at the top-left cell\n    if (i == 0 && j == 0) {\n        return grid[0][0]\n    }\n    // If a row or column index is out of bounds, return Int.MAX_VALUE as an infinite-cost sentinel\n    if (i < 0 || j < 0) {\n        return Int.MAX_VALUE\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][j] != -1) {\n        return mem[i][j]\n    }\n    // Minimum path costs to the cells on the left and above\n    val up = minPathSumDFSMem(grid, mem, i - 1, j)\n    val left = minPathSumDFSMem(grid, mem, i, j - 1)\n    // Record and return the minimum path cost from the top-left corner to (i, j)\n    mem[i][j] = min(left, up) + grid[i][j]\n    return mem[i][j]\n}\n
    min_path_sum.rb
    ### Minimum path sum: memoized search ###\ndef min_path_sum_dfs_mem(grid, mem, i, j)\n  # Stop searching at the top-left cell\n  return grid[0][0] if i == 0 && j == 0\n  # If a row or column index is out of bounds, return a cost of +∞\n  return Float::INFINITY if i < 0 || j < 0\n  # If a result has already been recorded, return it directly\n  return mem[i][j] if mem[i][j] != -1\n  # Minimum path costs to the cells on the left and above\n  up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n  left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n  # Record and return the minimum path cost from the top-left corner to (i, j)\n  mem[i][j] = [left, up].min + grid[i][j]\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-15, memoization ensures that every subproblem is solved only once. The time complexity is therefore determined by the number of states, the grid size \\(O(nm)\\).

    Figure 14-15   Recursion tree for memoized search

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#3-method-3-dynamic-programming","level":3,"title":"3.   Method 3: Dynamic programming","text":"

    The following code implements dynamic programming iteratively:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
    def min_path_sum_dp(grid: list[list[int]]) -> int:\n    \"\"\"Minimum path sum: dynamic programming\"\"\"\n    n, m = len(grid), len(grid[0])\n    # Initialize the dp table\n    dp = [[0] * m for _ in range(n)]\n    dp[0][0] = grid[0][0]\n    # State transitions: first row\n    for j in range(1, m):\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    # State transitions: first column\n    for i in range(1, n):\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    # State transitions: remaining rows and columns\n    for i in range(1, n):\n        for j in range(1, m):\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n    return dp[n - 1][m - 1]\n
    min_path_sum.cpp
    /* Minimum path sum: dynamic programming */\nint minPathSumDP(vector<vector<int>> &grid) {\n    int n = grid.size(), m = grid[0].size();\n    // Initialize the dp table\n    vector<vector<int>> dp(n, vector<int>(m));\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
    min_path_sum.java
    /* Minimum path sum: dynamic programming */\nint minPathSumDP(int[][] grid) {\n    int n = grid.length, m = grid[0].length;\n    // Initialize the dp table\n    int[][] dp = new int[n][m];\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
    min_path_sum.cs
    /* Minimum path sum: dynamic programming */\nint MinPathSumDP(int[][] grid) {\n    int n = grid.Length, m = grid[0].Length;\n    // Initialize the dp table\n    int[,] dp = new int[n, m];\n    dp[0, 0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[0, j] = dp[0, j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (int i = 1; i < n; i++) {\n        dp[i, 0] = dp[i - 1, 0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1, m - 1];\n}\n
    min_path_sum.go
    /* Minimum path sum: dynamic programming */\nfunc minPathSumDP(grid [][]int) int {\n    n, m := len(grid), len(grid[0])\n    // Initialize the dp table\n    dp := make([][]int, n)\n    for i := 0; i < n; i++ {\n        dp[i] = make([]int, m)\n    }\n    dp[0][0] = grid[0][0]\n    // State transitions: first row\n    for j := 1; j < m; j++ {\n        dp[0][j] = dp[0][j-1] + grid[0][j]\n    }\n    // State transitions: first column\n    for i := 1; i < n; i++ {\n        dp[i][0] = dp[i-1][0] + grid[i][0]\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i < n; i++ {\n        for j := 1; j < m; j++ {\n            dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j]\n        }\n    }\n    return dp[n-1][m-1]\n}\n
    min_path_sum.swift
    /* Minimum path sum: dynamic programming */\nfunc minPathSumDP(grid: [[Int]]) -> Int {\n    let n = grid.count\n    let m = grid[0].count\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: m), count: n)\n    dp[0][0] = grid[0][0]\n    // State transitions: first row\n    for j in 1 ..< m {\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    }\n    // State transitions: first column\n    for i in 1 ..< n {\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    }\n    // State transitions: remaining rows and columns\n    for i in 1 ..< n {\n        for j in 1 ..< m {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n        }\n    }\n    return dp[n - 1][m - 1]\n}\n
    min_path_sum.js
    /* Minimum path sum: dynamic programming */\nfunction minPathSumDP(grid) {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n }, () =>\n        Array.from({ length: m }, () => 0)\n    );\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (let j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (let i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i < n; i++) {\n        for (let j = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
    min_path_sum.ts
    /* Minimum path sum: dynamic programming */\nfunction minPathSumDP(grid: Array<Array<number>>): number {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n }, () =>\n        Array.from({ length: m }, () => 0)\n    );\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (let j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (let i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i < n; i++) {\n        for (let j: number = 1; j < m; j++) {\n            dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    return dp[n - 1][m - 1];\n}\n
    min_path_sum.dart
    /* Minimum path sum: dynamic programming */\nint minPathSumDP(List<List<int>> grid) {\n  int n = grid.length, m = grid[0].length;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n, (i) => List.filled(m, 0));\n  dp[0][0] = grid[0][0];\n  // State transitions: first row\n  for (int j = 1; j < m; j++) {\n    dp[0][j] = dp[0][j - 1] + grid[0][j];\n  }\n  // State transitions: first column\n  for (int i = 1; i < n; i++) {\n    dp[i][0] = dp[i - 1][0] + grid[i][0];\n  }\n  // State transitions: remaining rows and columns\n  for (int i = 1; i < n; i++) {\n    for (int j = 1; j < m; j++) {\n      dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n    }\n  }\n  return dp[n - 1][m - 1];\n}\n
    min_path_sum.rs
    /* Minimum path sum: dynamic programming */\nfn min_path_sum_dp(grid: &Vec<Vec<i32>>) -> i32 {\n    let (n, m) = (grid.len(), grid[0].len());\n    // Initialize the dp table\n    let mut dp = vec![vec![0; m]; n];\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for j in 1..m {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for i in 1..n {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for i in 1..n {\n        for j in 1..m {\n            dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    dp[n - 1][m - 1]\n}\n
    min_path_sum.c
    /* Minimum path sum: dynamic programming */\nint minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n    // Initialize the dp table\n    int **dp = malloc(n * sizeof(int *));\n    for (int i = 0; i < n; i++) {\n        dp[i] = calloc(m, sizeof(int));\n    }\n    dp[0][0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j];\n    }\n    // State transitions: first column\n    for (int i = 1; i < n; i++) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0];\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i < n; i++) {\n        for (int j = 1; j < m; j++) {\n            dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n        }\n    }\n    int res = dp[n - 1][m - 1];\n    // Free memory\n    for (int i = 0; i < n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
    min_path_sum.kt
    /* Minimum path sum: dynamic programming */\nfun minPathSumDP(grid: Array<IntArray>): Int {\n    val n = grid.size\n    val m = grid[0].size\n    // Initialize the dp table\n    val dp = Array(n) { IntArray(m) }\n    dp[0][0] = grid[0][0]\n    // State transitions: first row\n    for (j in 1..<m) {\n        dp[0][j] = dp[0][j - 1] + grid[0][j]\n    }\n    // State transitions: first column\n    for (i in 1..<n) {\n        dp[i][0] = dp[i - 1][0] + grid[i][0]\n    }\n    // State transitions: remaining rows and columns\n    for (i in 1..<n) {\n        for (j in 1..<m) {\n            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n        }\n    }\n    return dp[n - 1][m - 1]\n}\n
    min_path_sum.rb
    ### Minimum path sum: dynamic programming ###\ndef min_path_sum_dp(grid)\n  n, m = grid.length, grid.first.length\n  # Initialize the dp table\n  dp = Array.new(n) { Array.new(m, 0) }\n  dp[0][0] = grid[0][0]\n  # State transitions: first row\n  (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }\n  # State transitions: first column\n  (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }\n  # State transitions: remaining rows and columns\n  for i in 1...n\n    for j in 1...m\n      dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j]\n    end\n  end\n  dp[n -1][m -1]\nend\n
    Visualize execution

    View full screen >

    Figure 14-16 shows the state transitions for minimum path sum. They visit the entire grid, so the time complexity is \\(O(nm)\\).

    The array dp has dimensions \\(n \\times m\\), so the space complexity is \\(O(nm)\\).

    <1><2><3><4><5><6><7><8><9><10><11><12>

    Figure 14-16   Dynamic programming for minimum path sum

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#4-space-optimization","level":3,"title":"4.   Space optimization","text":"

    Each cell depends only on the cells to its left and above, so a single-row array is enough to represent the \\(dp\\) table.

    Because dp stores only one row of states, we cannot initialize the entire first column in advance. Instead, update its entry when processing each row:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py
    def min_path_sum_dp_comp(grid: list[list[int]]) -> int:\n    \"\"\"Minimum path sum: space-optimized dynamic programming\"\"\"\n    n, m = len(grid), len(grid[0])\n    # Initialize the dp table\n    dp = [0] * m\n    # State transitions: first row\n    dp[0] = grid[0][0]\n    for j in range(1, m):\n        dp[j] = dp[j - 1] + grid[0][j]\n    # State transitions: remaining rows\n    for i in range(1, n):\n        # State transitions: first column\n        dp[0] = dp[0] + grid[i][0]\n        # State transitions: remaining columns\n        for j in range(1, m):\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n    return dp[m - 1]\n
    min_path_sum.cpp
    /* Minimum path sum: space-optimized dynamic programming */\nint minPathSumDPComp(vector<vector<int>> &grid) {\n    int n = grid.size(), m = grid[0].size();\n    // Initialize the dp table\n    vector<int> dp(m);\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.java
    /* Minimum path sum: space-optimized dynamic programming */\nint minPathSumDPComp(int[][] grid) {\n    int n = grid.length, m = grid[0].length;\n    // Initialize the dp table\n    int[] dp = new int[m];\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.cs
    /* Minimum path sum: space-optimized dynamic programming */\nint MinPathSumDPComp(int[][] grid) {\n    int n = grid.Length, m = grid[0].Length;\n    // Initialize the dp table\n    int[] dp = new int[m];\n    dp[0] = grid[0][0];\n    // State transitions: first row\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.go
    /* Minimum path sum: space-optimized dynamic programming */\nfunc minPathSumDPComp(grid [][]int) int {\n    n, m := len(grid), len(grid[0])\n    // Initialize the dp table\n    dp := make([]int, m)\n    // State transitions: first row\n    dp[0] = grid[0][0]\n    for j := 1; j < m; j++ {\n        dp[j] = dp[j-1] + grid[0][j]\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i < n; i++ {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transitions: remaining columns\n        for j := 1; j < m; j++ {\n            dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j]\n        }\n    }\n    return dp[m-1]\n}\n
    min_path_sum.swift
    /* Minimum path sum: space-optimized dynamic programming */\nfunc minPathSumDPComp(grid: [[Int]]) -> Int {\n    let n = grid.count\n    let m = grid[0].count\n    // Initialize the dp table\n    var dp = Array(repeating: 0, count: m)\n    // State transitions: first row\n    dp[0] = grid[0][0]\n    for j in 1 ..< m {\n        dp[j] = dp[j - 1] + grid[0][j]\n    }\n    // State transitions: remaining rows\n    for i in 1 ..< n {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transitions: remaining columns\n        for j in 1 ..< m {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n        }\n    }\n    return dp[m - 1]\n}\n
    min_path_sum.js
    /* Minimum path sum: space-optimized dynamic programming */\nfunction minPathSumDPComp(grid) {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize the dp table\n    const dp = new Array(m);\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (let j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (let i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (let j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.ts
    /* Minimum path sum: space-optimized dynamic programming */\nfunction minPathSumDPComp(grid: Array<Array<number>>): number {\n    const n = grid.length,\n        m = grid[0].length;\n    // Initialize the dp table\n    const dp = new Array(m);\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (let j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (let i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (let j = 1; j < m; j++) {\n            dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    return dp[m - 1];\n}\n
    min_path_sum.dart
    /* Minimum path sum: space-optimized dynamic programming */\nint minPathSumDPComp(List<List<int>> grid) {\n  int n = grid.length, m = grid[0].length;\n  // Initialize the dp table\n  List<int> dp = List.filled(m, 0);\n  dp[0] = grid[0][0];\n  for (int j = 1; j < m; j++) {\n    dp[j] = dp[j - 1] + grid[0][j];\n  }\n  // State transitions: remaining rows\n  for (int i = 1; i < n; i++) {\n    // State transitions: first column\n    dp[0] = dp[0] + grid[i][0];\n    // State transitions: remaining columns\n    for (int j = 1; j < m; j++) {\n      dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n    }\n  }\n  return dp[m - 1];\n}\n
    min_path_sum.rs
    /* Minimum path sum: space-optimized dynamic programming */\nfn min_path_sum_dp_comp(grid: &Vec<Vec<i32>>) -> i32 {\n    let (n, m) = (grid.len(), grid[0].len());\n    // Initialize the dp table\n    let mut dp = vec![0; m];\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for j in 1..m {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for i in 1..n {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for j in 1..m {\n            dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    dp[m - 1]\n}\n
    min_path_sum.c
    /* Minimum path sum: space-optimized dynamic programming */\nint minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n    // Initialize the dp table\n    int *dp = calloc(m, sizeof(int));\n    // State transitions: first row\n    dp[0] = grid[0][0];\n    for (int j = 1; j < m; j++) {\n        dp[j] = dp[j - 1] + grid[0][j];\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i < n; i++) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0];\n        // State transitions: remaining columns\n        for (int j = 1; j < m; j++) {\n            dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j];\n        }\n    }\n    int res = dp[m - 1];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    min_path_sum.kt
    /* Minimum path sum: space-optimized dynamic programming */\nfun minPathSumDPComp(grid: Array<IntArray>): Int {\n    val n = grid.size\n    val m = grid[0].size\n    // Initialize the dp table\n    val dp = IntArray(m)\n    // State transitions: first row\n    dp[0] = grid[0][0]\n    for (j in 1..<m) {\n        dp[j] = dp[j - 1] + grid[0][j]\n    }\n    // State transitions: remaining rows\n    for (i in 1..<n) {\n        // State transitions: first column\n        dp[0] = dp[0] + grid[i][0]\n        // State transitions: remaining columns\n        for (j in 1..<m) {\n            dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n        }\n    }\n    return dp[m - 1]\n}\n
    min_path_sum.rb
    ### Minimum path sum: space-optimized dynamic programming ###\ndef min_path_sum_dp_comp(grid)\n  n, m = grid.length, grid.first.length\n  # Initialize the dp table\n  dp = Array.new(m, 0)\n  # State transitions: first row\n  dp[0] = grid[0][0]\n  (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }\n  # State transitions: remaining rows\n  for i in 1...n\n    # State transitions: first column\n    dp[0] = dp[0] + grid[i][0]\n    # State transitions: remaining columns\n    (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] }\n  end\n  dp[m - 1]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.3   Solving dynamic programming problems"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/","level":1,"title":"14.6   Edit distance problem","text":"

    Edit distance, also called Levenshtein distance, is the minimum number of edits needed to transform one string into another. It is commonly used in information retrieval and natural language processing to measure similarity between sequences.

    Question

    Given two strings \\(s\\) and \\(t\\), return the minimum number of edits needed to transform \\(s\\) into \\(t\\).

    Three edits are allowed: insert a character, delete a character, or replace a character with any other character.

    As shown in Figure 14-27, transforming kitten into sitting takes 3 edits: 2 replacements and 1 insertion. Transforming hello into algo also takes 3 edits: 2 replacements and 1 deletion.

    Figure 14-27   Examples of edit distance

    Edit distance fits naturally into a decision tree model. Strings are nodes, and each decision (an edit) is an edge.

    As shown in Figure 14-28, without restrictions on the edits, each node can have many outgoing edges, one for each possible operation. Thus, many paths can transform hello into algo.

    From the decision tree perspective, the goal is to find the shortest path between nodes hello and algo.

    Figure 14-28   Edit distance as a decision tree

    ","path":["Chapter 14.   Dynamic programming","14.6   Edit distance problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#1-dynamic-programming-approach","level":3,"title":"1.   Dynamic programming approach","text":"

    Step 1: Identify each decision, define the state, and establish the \\(dp\\) table

    Each decision applies one edit to string \\(s\\).

    We want each edit to reduce the problem's size so that we can form subproblems. Let the lengths of \\(s\\) and \\(t\\) be \\(n\\) and \\(m\\). First consider their last characters, \\(s[n-1]\\) and \\(t[m-1]\\).

    • If \\(s[n-1]\\) and \\(t[m-1]\\) match, skip them and consider \\(s[n-2]\\) and \\(t[m-2]\\).
    • If \\(s[n-1]\\) and \\(t[m-1]\\) differ, apply an edit to \\(s\\) (insertion, deletion, or replacement) to match the endings, allowing us to move on to a smaller problem.

    Each decision (edit) in \\(s\\) changes the characters still to be matched in \\(s\\) and \\(t\\). The state therefore specifies the \\(i\\)-th and \\(j\\)-th characters currently under consideration in \\(s\\) and \\(t\\), denoted by \\([i, j]\\).

    State \\([i, j]\\) represents the subproblem of finding the minimum number of edits needed to transform the first \\(i\\) characters of \\(s\\) into the first \\(j\\) characters of \\(t\\).

    This gives a two-dimensional \\(dp\\) table of size \\((n+1) \\times (m+1)\\).

    Step 2: Identify optimal substructure and derive the state transition equation

    For subproblem \\(dp[i, j]\\), the final characters are \\(s[i-1]\\) and \\(t[j-1]\\). The three possible edits give the cases shown in Figure 14-29.

    1. Insert \\(t[j-1]\\) after \\(s[i-1]\\), leaving subproblem \\(dp[i, j-1]\\).
    2. Delete \\(s[i-1]\\), leaving subproblem \\(dp[i-1, j]\\).
    3. Replace \\(s[i-1]\\) with \\(t[j-1]\\), leaving subproblem \\(dp[i-1, j-1]\\).

    Figure 14-29   State transitions for edit distance

    The optimal substructure follows: the minimum edit count \\(dp[i, j]\\) is the smallest of \\(dp[i, j-1]\\), \\(dp[i-1, j]\\), and \\(dp[i-1, j-1]\\), plus \\(1\\) for the current edit. The state transition equation is:

    \\[ dp[i, j] = \\min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 \\]

    When \\(s[i-1]\\) and \\(t[j-1]\\) match, no edit is needed for the current characters, and the state transition equation becomes:

    \\[ dp[i, j] = dp[i-1, j-1] \\]

    Step 3: Determine the boundary conditions and evaluation order

    If both strings are empty, \\(0\\) edits are needed, so \\(dp[0, 0] = 0\\). If \\(s\\) is empty but \\(t\\) is not, the minimum edit count is the length of \\(t\\), giving the first row \\(dp[0, j] = j\\). If \\(s\\) is nonempty but \\(t\\) is empty, the count is the length of \\(s\\), giving the first column \\(dp[i, 0] = i\\).

    The solution \\(dp[i, j]\\) depends on entries to its left, above, and upper left, so two nested loops can traverse the \\(dp\\) table in increasing index order.

    ","path":["Chapter 14.   Dynamic programming","14.6   Edit distance problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py
    def edit_distance_dp(s: str, t: str) -> int:\n    \"\"\"Edit distance: dynamic programming\"\"\"\n    n, m = len(s), len(t)\n    dp = [[0] * (m + 1) for _ in range(n + 1)]\n    # State transitions: first row and first column\n    for i in range(1, n + 1):\n        dp[i][0] = i\n    for j in range(1, m + 1):\n        dp[0][j] = j\n    # State transitions: remaining rows and columns\n    for i in range(1, n + 1):\n        for j in range(1, m + 1):\n            if s[i - 1] == t[j - 1]:\n                # If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1]\n            else:\n                # Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1\n    return dp[n][m]\n
    edit_distance.cpp
    /* Edit distance: dynamic programming */\nint editDistanceDP(string s, string t) {\n    int n = s.length(), m = t.length();\n    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));\n    // State transitions: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
    edit_distance.java
    /* Edit distance: dynamic programming */\nint editDistanceDP(String s, String t) {\n    int n = s.length(), m = t.length();\n    int[][] dp = new int[n + 1][m + 1];\n    // State transitions: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) == t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
    edit_distance.cs
    /* Edit distance: dynamic programming */\nint EditDistanceDP(string s, string t) {\n    int n = s.Length, m = t.Length;\n    int[,] dp = new int[n + 1, m + 1];\n    // State transitions: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i, 0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0, j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[i, j] = dp[i - 1, j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n, m];\n}\n
    edit_distance.go
    /* Edit distance: dynamic programming */\nfunc editDistanceDP(s string, t string) int {\n    n := len(s)\n    m := len(t)\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, m+1)\n    }\n    // State transitions: first row and first column\n    for i := 1; i <= n; i++ {\n        dp[i][0] = i\n    }\n    for j := 1; j <= m; j++ {\n        dp[0][j] = j\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i <= n; i++ {\n        for j := 1; j <= m; j++ {\n            if s[i-1] == t[j-1] {\n                // If the two bytes are equal, skip both (each byte is one character for ASCII input)\n                dp[i][j] = dp[i-1][j-1]\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
    edit_distance.swift
    /* Edit distance: dynamic programming */\nfunc editDistanceDP(s: String, t: String) -> Int {\n    let n = s.utf8CString.count\n    let m = t.utf8CString.count\n    var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1)\n    // State transitions: first row and first column\n    for i in 1 ... n {\n        dp[i][0] = i\n    }\n    for j in 1 ... m {\n        dp[0][j] = j\n    }\n    // State transitions: remaining rows and columns\n    for i in 1 ... n {\n        for j in 1 ... m {\n            if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n                // If the two current UTF-8 bytes match, skip both\n                dp[i][j] = dp[i - 1][j - 1]\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
    edit_distance.js
    /* Edit distance: dynamic programming */\nfunction editDistanceDP(s, t) {\n    const n = s.length,\n        m = t.length;\n    const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));\n    // State transitions: first row and first column\n    for (let i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (let j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] =\n                    Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
    edit_distance.ts
    /* Edit distance: dynamic programming */\nfunction editDistanceDP(s: string, t: string): number {\n    const n = s.length,\n        m = t.length;\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: m + 1 }, () => 0)\n    );\n    // State transitions: first row and first column\n    for (let i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (let j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let j = 1; j <= m; j++) {\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] =\n                    Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    return dp[n][m];\n}\n
    edit_distance.dart
    /* Edit distance: dynamic programming */\nint editDistanceDP(String s, String t) {\n  int n = s.length, m = t.length;\n  List<List<int>> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0));\n  // State transitions: first row and first column\n  for (int i = 1; i <= n; i++) {\n    dp[i][0] = i;\n  }\n  for (int j = 1; j <= m; j++) {\n    dp[0][j] = j;\n  }\n  // State transitions: remaining rows and columns\n  for (int i = 1; i <= n; i++) {\n    for (int j = 1; j <= m; j++) {\n      if (s[i - 1] == t[j - 1]) {\n        // If the two characters are equal, skip both of them\n        dp[i][j] = dp[i - 1][j - 1];\n      } else {\n        // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n        dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n      }\n    }\n  }\n  return dp[n][m];\n}\n
    edit_distance.rs
    /* Edit distance: dynamic programming */\nfn edit_distance_dp(s: &str, t: &str) -> i32 {\n    let (n, m) = (s.len(), t.len());\n    let mut dp = vec![vec![0; m + 1]; n + 1];\n    // State transitions: first row and first column\n    for i in 1..=n {\n        dp[i][0] = i as i32;\n    }\n    for j in 1..m {\n        dp[0][j] = j as i32;\n    }\n    // State transitions: remaining rows and columns\n    for i in 1..=n {\n        for j in 1..=m {\n            if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] =\n                    std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    dp[n][m]\n}\n
    edit_distance.c
    /* Edit distance: dynamic programming */\nint editDistanceDP(char *s, char *t, int n, int m) {\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(m + 1, sizeof(int));\n    }\n    // State transitions: first row and first column\n    for (int i = 1; i <= n; i++) {\n        dp[i][0] = i;\n    }\n    for (int j = 1; j <= m; j++) {\n        dp[0][j] = j;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int j = 1; j <= m; j++) {\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1];\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n            }\n        }\n    }\n    int res = dp[n][m];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
    edit_distance.kt
    /* Edit distance: dynamic programming */\nfun editDistanceDP(s: String, t: String): Int {\n    val n = s.length\n    val m = t.length\n    val dp = Array(n + 1) { IntArray(m + 1) }\n    // State transitions: first row and first column\n    for (i in 1..n) {\n        dp[i][0] = i\n    }\n    for (j in 1..m) {\n        dp[0][j] = j\n    }\n    // State transitions: remaining rows and columns\n    for (i in 1..n) {\n        for (j in 1..m) {\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[i][j] = dp[i - 1][j - 1]\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n            }\n        }\n    }\n    return dp[n][m]\n}\n
    edit_distance.rb
    ### Edit distance: dynamic programming ###\ndef edit_distance_dp(s, t)\n  n, m = s.length, t.length\n  dp = Array.new(n + 1) { Array.new(m + 1, 0) }\n  # State transitions: first row and first column\n  (1...(n + 1)).each { |i| dp[i][0] = i }\n  (1...(m + 1)).each { |j| dp[0][j] = j }\n  # State transitions: remaining rows and columns\n  for i in 1...(n + 1)\n    for j in 1...(m +1)\n      if s[i - 1] == t[j - 1]\n        # If the two characters are equal, skip both of them\n        dp[i][j] = dp[i - 1][j - 1]\n      else\n        # Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n        dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1\n      end\n    end\n  end\n  dp[n][m]\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-30, the state transition process resembles that of the knapsack problem: both fill a two-dimensional grid.

    <1><2><3><4><5><6><7><8><9><10><11><12><13><14><15>

    Figure 14-30   Dynamic programming for edit distance

    ","path":["Chapter 14.   Dynamic programming","14.6   Edit distance problem"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#3-space-optimization","level":3,"title":"3.   Space optimization","text":"

    The state \\(dp[i,j]\\) depends on \\(dp[i-1, j]\\) above, \\(dp[i, j-1]\\) to the left, and \\(dp[i-1, j-1]\\) at the upper left. Forward iteration loses \\(dp[i-1, j-1]\\), while backward iteration cannot compute \\(dp[i, j-1]\\) in advance. Neither order alone is sufficient.

    We can use a variable leftup to preserve the upper-left solution \\(dp[i-1, j-1]\\), leaving only the left and upper entries to consider. The situation then matches unbounded knapsack, and forward iteration works. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py
    def edit_distance_dp_comp(s: str, t: str) -> int:\n    \"\"\"Edit distance: space-optimized dynamic programming\"\"\"\n    n, m = len(s), len(t)\n    dp = [0] * (m + 1)\n    # State transitions: first row\n    for j in range(1, m + 1):\n        dp[j] = j\n    # State transitions: remaining rows\n    for i in range(1, n + 1):\n        # State transitions: first column\n        leftup = dp[0]  # Temporarily store dp[i-1, j-1]\n        dp[0] += 1\n        # State transitions: remaining columns\n        for j in range(1, m + 1):\n            temp = dp[j]\n            if s[i - 1] == t[j - 1]:\n                # If the two characters are equal, skip both of them\n                dp[j] = leftup\n            else:\n                # Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = min(dp[j - 1], dp[j], leftup) + 1\n            leftup = temp  # Update to dp[i-1, j-1] for the next iteration\n    return dp[m]\n
    edit_distance.cpp
    /* Edit distance: space-optimized dynamic programming */\nint editDistanceDPComp(string s, string t) {\n    int n = s.length(), m = t.length();\n    vector<int> dp(m + 1, 0);\n    // State transitions: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i <= n; i++) {\n        // State transitions: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.java
    /* Edit distance: space-optimized dynamic programming */\nint editDistanceDPComp(String s, String t) {\n    int n = s.length(), m = t.length();\n    int[] dp = new int[m + 1];\n    // State transitions: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i <= n; i++) {\n        // State transitions: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s.charAt(i - 1) == t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.cs
    /* Edit distance: space-optimized dynamic programming */\nint EditDistanceDPComp(string s, string t) {\n    int n = s.Length, m = t.Length;\n    int[] dp = new int[m + 1];\n    // State transitions: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i <= n; i++) {\n        // State transitions: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.go
    /* Edit distance: space-optimized dynamic programming */\nfunc editDistanceDPComp(s string, t string) int {\n    n := len(s)\n    m := len(t)\n    dp := make([]int, m+1)\n    // State transitions: first row\n    for j := 1; j <= m; j++ {\n        dp[j] = j\n    }\n    // State transitions: remaining rows\n    for i := 1; i <= n; i++ {\n        // State transitions: first column\n        leftUp := dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transitions: remaining columns\n        for j := 1; j <= m; j++ {\n            temp := dp[j]\n            if s[i-1] == t[j-1] {\n                // If the two bytes are equal, skip both (each byte is one character for ASCII input)\n                dp[j] = leftUp\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1\n            }\n            leftUp = temp // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m]\n}\n
    edit_distance.swift
    /* Edit distance: space-optimized dynamic programming */\nfunc editDistanceDPComp(s: String, t: String) -> Int {\n    let n = s.utf8CString.count\n    let m = t.utf8CString.count\n    var dp = Array(repeating: 0, count: m + 1)\n    // State transitions: first row\n    for j in 1 ... m {\n        dp[j] = j\n    }\n    // State transitions: remaining rows\n    for i in 1 ... n {\n        // State transitions: first column\n        var leftup = dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transitions: remaining columns\n        for j in 1 ... m {\n            let temp = dp[j]\n            if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n                // If the two current UTF-8 bytes match, skip both\n                dp[j] = leftup\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n            }\n            leftup = temp // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m]\n}\n
    edit_distance.js
    /* Edit distance: space-optimized dynamic programming */\nfunction editDistanceDPComp(s, t) {\n    const n = s.length,\n        m = t.length;\n    const dp = new Array(m + 1).fill(0);\n    // State transitions: first row\n    for (let j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (let i = 1; i <= n; i++) {\n        // State transitions: first column\n        let leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (let j = 1; j <= m; j++) {\n            const temp = dp[j];\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.ts
    /* Edit distance: space-optimized dynamic programming */\nfunction editDistanceDPComp(s: string, t: string): number {\n    const n = s.length,\n        m = t.length;\n    const dp = new Array(m + 1).fill(0);\n    // State transitions: first row\n    for (let j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (let i = 1; i <= n; i++) {\n        // State transitions: first column\n        let leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (let j = 1; j <= m; j++) {\n            const temp = dp[j];\n            if (s.charAt(i - 1) === t.charAt(j - 1)) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m];\n}\n
    edit_distance.dart
    /* Edit distance: space-optimized dynamic programming */\nint editDistanceDPComp(String s, String t) {\n  int n = s.length, m = t.length;\n  List<int> dp = List.filled(m + 1, 0);\n  // State transitions: first row\n  for (int j = 1; j <= m; j++) {\n    dp[j] = j;\n  }\n  // State transitions: remaining rows\n  for (int i = 1; i <= n; i++) {\n    // State transitions: first column\n    int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n    dp[0] = i;\n    // State transitions: remaining columns\n    for (int j = 1; j <= m; j++) {\n      int temp = dp[j];\n      if (s[i - 1] == t[j - 1]) {\n        // If the two characters are equal, skip both of them\n        dp[j] = leftup;\n      } else {\n        // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n        dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n      }\n      leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n    }\n  }\n  return dp[m];\n}\n
    edit_distance.rs
    /* Edit distance: space-optimized dynamic programming */\nfn edit_distance_dp_comp(s: &str, t: &str) -> i32 {\n    let (n, m) = (s.len(), t.len());\n    let mut dp = vec![0; m + 1];\n    // State transitions: first row\n    for j in 1..m {\n        dp[j] = j as i32;\n    }\n    // State transitions: remaining rows\n    for i in 1..=n {\n        // State transitions: first column\n        let mut leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i as i32;\n        // State transitions: remaining columns\n        for j in 1..=m {\n            let temp = dp[j];\n            if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    dp[m]\n}\n
    edit_distance.c
    /* Edit distance: space-optimized dynamic programming */\nint editDistanceDPComp(char *s, char *t, int n, int m) {\n    int *dp = calloc(m + 1, sizeof(int));\n    // State transitions: first row\n    for (int j = 1; j <= m; j++) {\n        dp[j] = j;\n    }\n    // State transitions: remaining rows\n    for (int i = 1; i <= n; i++) {\n        // State transitions: first column\n        int leftup = dp[0]; // Temporarily store dp[i-1, j-1]\n        dp[0] = i;\n        // State transitions: remaining columns\n        for (int j = 1; j <= m; j++) {\n            int temp = dp[j];\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup;\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1;\n            }\n            leftup = temp; // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    int res = dp[m];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    edit_distance.kt
    /* Edit distance: space-optimized dynamic programming */\nfun editDistanceDPComp(s: String, t: String): Int {\n    val n = s.length\n    val m = t.length\n    val dp = IntArray(m + 1)\n    // State transitions: first row\n    for (j in 1..m) {\n        dp[j] = j\n    }\n    // State transitions: remaining rows\n    for (i in 1..n) {\n        // State transitions: first column\n        var leftup = dp[0] // Temporarily store dp[i-1, j-1]\n        dp[0] = i\n        // State transitions: remaining columns\n        for (j in 1..m) {\n            val temp = dp[j]\n            if (s[i - 1] == t[j - 1]) {\n                // If the two characters are equal, skip both of them\n                dp[j] = leftup\n            } else {\n                // Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n                dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n            }\n            leftup = temp // Update to dp[i-1, j-1] for the next iteration\n        }\n    }\n    return dp[m]\n}\n
    edit_distance.rb
    ### Edit distance: space-optimized dynamic programming ###\ndef edit_distance_dp_comp(s, t)\n  n, m = s.length, t.length\n  dp = Array.new(m + 1, 0)\n  # State transitions: first row\n  (1...(m + 1)).each { |j| dp[j] = j }\n  # State transitions: remaining rows\n  for i in 1...(n + 1)\n    # State transitions: first column\n    leftup = dp.first # Temporarily store dp[i-1, j-1]\n    dp[0] += 1\n    # State transitions: remaining columns\n    for j in 1...(m + 1)\n      temp = dp[j]\n      if s[i - 1] == t[j - 1]\n        # If the two characters are equal, skip both of them\n        dp[j] = leftup\n      else\n        # Minimum edits = the minimum among the insertion, deletion, and replacement subproblems + 1\n        dp[j] = [dp[j - 1], dp[j], leftup].min + 1\n      end\n      leftup = temp # Update to dp[i-1, j-1] for the next iteration\n    end\n  end\n  dp[m]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.6   Edit distance problem"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/","level":1,"title":"14.8   Exercises","text":"","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1481-check-your-understanding","level":2,"title":"14.8.1   Check your understanding","text":"","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1-when-is-dynamic-programming-a-good-fit","level":3,"title":"1.   When is dynamic programming a good fit?","text":"

    A student says, “Whenever you can write a recurrence relation, you should use dynamic programming.” For each task below, decide whether dynamic programming, backtracking, or a loop or mathematical formula without a dp table is the best fit. Give one key reason for each choice.

    1. Find the minimum number of coins needed to make an amount of 6 using denominations [1, 3, 4]. Each denomination can be used any number of times.
    2. Output all permutations of [1, 2, 3].
    3. Calculate \\(1 + 2 + \\dots + n\\).

      For the task you consider suitable for dynamic programming, also explain what dp[i] represents.

    Reference answer
    1. Dynamic programming is a good fit. Let dp[i] be the minimum number of coins needed to make an amount of i. For each coin c whose denomination does not exceed i, consider dp[i-c] + 1 as a candidate answer, then take the minimum of these candidates. Different choices repeatedly lead to the same amounts, and an optimal solution for a larger amount can be built from optimal solutions for smaller amounts. The answer for an amount of 6 is 2: 3 + 3.

    2. Use backtracking. The task requires generating all 6 permutations individually. Backtracking systematically makes a choice, continues the search, then undoes the choice and explores other branches. Whatever method you use, outputting all the permutations requires enumerating them.

    3. A loop or the formula for an arithmetic series is sufficient. Although you can write S(i) = S(i-1) + i, calculating S(i) depends on just one smaller value, S(i-1). Each partial sum needs to be calculated only once, so there are no overlapping subproblems and no need for a dp table. Being able to write a recurrence does not mean you need dynamic programming.

    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#2-how-do-you-calculate-a-cell-in-the-knapsack-table","level":3,"title":"2.   How do you calculate a cell in the knapsack table?","text":"

    Consider a 0-1 knapsack problem with item weights wgt = [1, 2, 3], values val = [5, 11, 15], and a knapsack capacity of 4. Here, dp[i][c] is the maximum value obtainable using only the first \\(i\\) items with a capacity limit of \\(c\\). The knapsack does not have to be filled exactly.

    Calculate just the state dp[3][4]. You are given dp[2][4] = 16 and dp[2][1] = 5.

    1. What is the candidate value if you do not take item 3?
    2. If you take item 3, how much capacity remains, and what is the candidate value?
    3. What should dp[3][4] be? Which items does this solution take?
    Reference answer
    1. If you do not take item 3, use the result for the first two items. The candidate value is dp[2][4] = 16.

    2. Item 3 weighs 3, so taking it leaves a capacity of \\(4-3=1\\). The candidate value is dp[2][1] + 15 = 5 + 15 = 20.

    3. Comparing 16 and 20 gives dp[3][4] = 20. This solution takes items 1 and 3, with a total weight of \\(1+3=4\\) and a total value of \\(5+15=20\\).

      Calculating this state illustrates a single “take it or leave it” comparison in the 0-1 knapsack problem.

    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#3-in-what-order-should-knapsack-capacities-be-updated","level":3,"title":"3.   In what order should knapsack capacities be updated?","text":"

    A 0-1 knapsack problem has just one item, with weight 2 and value 5. The knapsack capacity is 4. Each item can be taken at most once, and the initial one-dimensional array is dp = [0, 0, 0, 0, 0].

    A student processes this item by updating capacities from 2 to 4:

    • Updating dp[2] gives 5.
    • Updating dp[3] also gives 5.
    • When updating dp[4], the student uses the newly computed dp[2], obtaining dp[4] = 10.
    1. Is dp[4] = 10 correct? Why or why not?
    2. Given that each item can be taken at most once, what should dp[4] be?
    3. When processing each item, should capacities be updated from largest to smallest or from smallest to largest? What problem does this prevent?
    Reference answer
    1. The result is incorrect. A value of 10 means taking the item worth 5 twice, which violates the condition that each item can be taken at most once.

    2. The knapsack can contain only this one item, so the correct value of dp[4] is 5.

    3. Update capacities from largest to smallest: 4, then 3, then 2. This ensures that when calculating dp[c], the value read from dp[c-2] is still the result from before the current item was processed. The current item therefore cannot be used more than once in the same round of updates.

    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1482-programming-exercises","level":2,"title":"14.8.2   Programming exercises","text":"","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1-counting-ways-to-climb-stairs","level":3,"title":"1.   Counting ways to climb stairs","text":"

    A staircase has n steps. You can climb either 1 or 2 steps at a time and must land exactly on step n. Calculate the number of different ways to climb the staircase. Assume n >= 1; two ways differ only in their sequence of 1-step and 2-step moves. Use a one-dimensional dynamic programming array. For now, do not use the space optimization that retains only two states.

    Hints
    1. The final move to step i must be either a 1-step or a 2-step move.
    2. This gives dp[i] = dp[i-1] + dp[i-2].
    3. Handle n = 1 and n = 2 first, then fill the table starting at step 3.

    LeetCode

    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#2-0-1-knapsack","level":3,"title":"2.   0-1 knapsack","text":"

    You are given arrays wgt and val of equal length. Item i has a positive integer weight wgt[i] and a nonnegative integer value val[i]. The knapsack capacity cap is a nonnegative integer. Each item can be taken at most once. Find the maximum total value that fits in the knapsack without exceeding cap in total weight. Use one-dimensional dynamic programming.

    Hints
    1. Initialize an array dp of length cap + 1, where dp[c] is the maximum value obtainable with a capacity limit of c.
    2. When processing item i, compare dp[c] for leaving it out with dp[c-wgt[i]] + val[i] for taking it.
    3. Update capacities from largest to smallest to avoid taking the current item more than once in the same round.
    ","path":["Chapter 14.   Dynamic programming","14.8   Exercises"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/","level":1,"title":"14.1   Introduction to dynamic programming","text":"

    Dynamic programming is an important algorithmic paradigm. It breaks a problem into smaller subproblems and stores their solutions to avoid repeated computation, greatly improving time efficiency.

    We will start with a classic example, give a brute-force backtracking solution, identify its overlapping subproblems, and gradually derive a more efficient dynamic programming solution.

    Climbing stairs

    Given a staircase with \\(n\\) steps, you may climb \\(1\\) or \\(2\\) steps at a time. How many ways are there to reach the top?

    As shown in Figure 14-1, a staircase with \\(3\\) steps has \\(3\\) possible ways to reach the top.

    Figure 14-1   Number of ways to reach step 3

    The goal is to count solutions, so we can use backtracking to enumerate every possibility. View climbing as a sequence of choices: starting from the ground, choose to climb \\(1\\) or \\(2\\) steps each time. Add \\(1\\) to the count whenever we reach the top, and prune any branch that goes beyond it. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_backtrack.py
    def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int:\n    \"\"\"Backtracking\"\"\"\n    # When step n is reached, increase the number of ways by 1\n    if state == n:\n        res[0] += 1\n    # Iterate over all choices\n    for choice in choices:\n        # Prune: do not go beyond step n\n        if state + choice > n:\n            continue\n        # Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res)\n        # Backtrack\n\ndef climbing_stairs_backtrack(n: int) -> int:\n    \"\"\"Climbing stairs: backtracking\"\"\"\n    choices = [1, 2]  # Choose to climb 1 or 2 steps\n    state = 0  # Start climbing from step 0\n    res = [0]  # Use res[0] to record the number of ways\n    backtrack(choices, state, n, res)\n    return res[0]\n
    climbing_stairs_backtrack.cpp
    /* Backtracking */\nvoid backtrack(vector<int> &choices, int state, int n, vector<int> &res) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res[0]++;\n    // Iterate over all choices\n    for (auto &choice : choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n)\n            continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nint climbingStairsBacktrack(int n) {\n    vector<int> choices = {1, 2}; // Choose to climb 1 or 2 steps\n    int state = 0;                // Start climbing from step 0\n    vector<int> res = {0};        // Use res[0] to record the number of ways\n    backtrack(choices, state, n, res);\n    return res[0];\n}\n
    climbing_stairs_backtrack.java
    /* Backtracking */\nvoid backtrack(List<Integer> choices, int state, int n, List<Integer> res) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res.set(0, res.get(0) + 1);\n    // Iterate over all choices\n    for (Integer choice : choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n)\n            continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nint climbingStairsBacktrack(int n) {\n    List<Integer> choices = Arrays.asList(1, 2); // Choose to climb 1 or 2 steps\n    int state = 0; // Start climbing from step 0\n    List<Integer> res = new ArrayList<>();\n    res.add(0); // Use res[0] to record the number of ways\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
    climbing_stairs_backtrack.cs
    /* Backtracking */\nvoid Backtrack(List<int> choices, int state, int n, List<int> res) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res[0]++;\n    // Iterate over all choices\n    foreach (int choice in choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n)\n            continue;\n        // Try: make a choice and update the state\n        Backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nint ClimbingStairsBacktrack(int n) {\n    List<int> choices = [1, 2]; // Choose to climb 1 or 2 steps\n    int state = 0; // Start climbing from step 0\n    List<int> res = [0]; // Use res[0] to record the number of ways\n    Backtrack(choices, state, n, res);\n    return res[0];\n}\n
    climbing_stairs_backtrack.go
    /* Backtracking */\nfunc backtrack(choices []int, state, n int, res []int) {\n    // When step n is reached, increase the number of ways by 1\n    if state == n {\n        res[0] = res[0] + 1\n    }\n    // Iterate over all choices\n    for _, choice := range choices {\n        // Prune: do not go beyond step n\n        if state+choice > n {\n            continue\n        }\n        // Try: make a choice and update the state\n        backtrack(choices, state+choice, n, res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfunc climbingStairsBacktrack(n int) int {\n    // Choose to climb 1 or 2 steps\n    choices := []int{1, 2}\n    // Start climbing from step 0\n    state := 0\n    res := make([]int, 1)\n    // Use res[0] to record the number of ways\n    res[0] = 0\n    backtrack(choices, state, n, res)\n    return res[0]\n}\n
    climbing_stairs_backtrack.swift
    /* Backtracking */\nfunc backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) {\n    // When step n is reached, increase the number of ways by 1\n    if state == n {\n        res[0] += 1\n    }\n    // Iterate over all choices\n    for choice in choices {\n        // Prune: do not go beyond step n\n        if state + choice > n {\n            continue\n        }\n        // Try: make a choice and update the state\n        backtrack(choices: choices, state: state + choice, n: n, res: &res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfunc climbingStairsBacktrack(n: Int) -> Int {\n    let choices = [1, 2] // Choose to climb 1 or 2 steps\n    let state = 0 // Start climbing from step 0\n    var res: [Int] = []\n    res.append(0) // Use res[0] to record the number of ways\n    backtrack(choices: choices, state: state, n: n, res: &res)\n    return res[0]\n}\n
    climbing_stairs_backtrack.js
    /* Backtracking */\nfunction backtrack(choices, state, n, res) {\n    // When step n is reached, increase the number of ways by 1\n    if (state === n) res.set(0, res.get(0) + 1);\n    // Iterate over all choices\n    for (const choice of choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n) continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfunction climbingStairsBacktrack(n) {\n    const choices = [1, 2]; // Choose to climb 1 or 2 steps\n    const state = 0; // Start climbing from step 0\n    const res = new Map();\n    res.set(0, 0); // Store the number of ways at key 0 in res\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
    climbing_stairs_backtrack.ts
    /* Backtracking */\nfunction backtrack(\n    choices: number[],\n    state: number,\n    n: number,\n    res: Map<0, any>\n): void {\n    // When step n is reached, increase the number of ways by 1\n    if (state === n) res.set(0, res.get(0) + 1);\n    // Iterate over all choices\n    for (const choice of choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n) continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfunction climbingStairsBacktrack(n: number): number {\n    const choices = [1, 2]; // Choose to climb 1 or 2 steps\n    const state = 0; // Start climbing from step 0\n    const res = new Map();\n    res.set(0, 0); // Store the number of ways at key 0 in res\n    backtrack(choices, state, n, res);\n    return res.get(0);\n}\n
    climbing_stairs_backtrack.dart
    /* Backtracking */\nvoid backtrack(List<int> choices, int state, int n, List<int> res) {\n  // When step n is reached, increase the number of ways by 1\n  if (state == n) {\n    res[0]++;\n  }\n  // Iterate over all choices\n  for (int choice in choices) {\n    // Prune: do not go beyond step n\n    if (state + choice > n) continue;\n    // Try: make a choice and update the state\n    backtrack(choices, state + choice, n, res);\n    // Backtrack\n  }\n}\n\n/* Climbing stairs: backtracking */\nint climbingStairsBacktrack(int n) {\n  List<int> choices = [1, 2]; // Choose to climb 1 or 2 steps\n  int state = 0; // Start climbing from step 0\n  List<int> res = [];\n  res.add(0); // Use res[0] to record the number of ways\n  backtrack(choices, state, n, res);\n  return res[0];\n}\n
    climbing_stairs_backtrack.rs
    /* Backtracking */\nfn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) {\n    // When step n is reached, increase the number of ways by 1\n    if state == n {\n        res[0] = res[0] + 1;\n    }\n    // Iterate over all choices\n    for &choice in choices {\n        // Prune: do not go beyond step n\n        if state + choice > n {\n            continue;\n        }\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfn climbing_stairs_backtrack(n: usize) -> i32 {\n    let choices = vec![1, 2]; // Choose to climb 1 or 2 steps\n    let state = 0; // Start climbing from step 0\n    let mut res = Vec::new();\n    res.push(0); // Use res[0] to record the number of ways\n    backtrack(&choices, state, n as i32, &mut res);\n    res[0]\n}\n
    climbing_stairs_backtrack.c
    /* Backtracking */\nvoid backtrack(int *choices, int state, int n, int *res, int len) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res[0]++;\n    // Iterate over all choices\n    for (int i = 0; i < len; i++) {\n        int choice = choices[i];\n        // Prune: do not go beyond step n\n        if (state + choice > n)\n            continue;\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res, len);\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nint climbingStairsBacktrack(int n) {\n    int choices[2] = {1, 2}; // Choose to climb 1 or 2 steps\n    int state = 0;           // Start climbing from step 0\n    int *res = (int *)malloc(sizeof(int));\n    *res = 0; // Use res[0] to record the number of ways\n    int len = sizeof(choices) / sizeof(int);\n    backtrack(choices, state, n, res, len);\n    int result = *res;\n    free(res);\n    return result;\n}\n
    climbing_stairs_backtrack.kt
    /* Backtracking */\nfun backtrack(\n    choices: MutableList<Int>,\n    state: Int,\n    n: Int,\n    res: MutableList<Int>\n) {\n    // When step n is reached, increase the number of ways by 1\n    if (state == n)\n        res[0] = res[0] + 1\n    // Iterate over all choices\n    for (choice in choices) {\n        // Prune: do not go beyond step n\n        if (state + choice > n) continue\n        // Try: make a choice and update the state\n        backtrack(choices, state + choice, n, res)\n        // Backtrack\n    }\n}\n\n/* Climbing stairs: backtracking */\nfun climbingStairsBacktrack(n: Int): Int {\n    val choices = mutableListOf(1, 2) // Choose to climb 1 or 2 steps\n    val state = 0 // Start climbing from step 0\n    val res = mutableListOf<Int>()\n    res.add(0) // Use res[0] to record the number of ways\n    backtrack(choices, state, n, res)\n    return res[0]\n}\n
    climbing_stairs_backtrack.rb
    ### Backtracking ###\ndef backtrack(choices, state, n, res)\n  # When step n is reached, increase the number of ways by 1\n  res[0] += 1 if state == n\n  # Iterate over all choices\n  for choice in choices\n    # Prune: do not go beyond step n\n    next if state + choice > n\n\n    # Try: make a choice and update the state\n    backtrack(choices, state + choice, n, res)\n  end\n  # Backtrack\nend\n\n### Climbing stairs: backtracking ###\ndef climbing_stairs_backtrack(n)\n  choices = [1, 2] # Choose to climb 1 or 2 steps\n  state = 0 # Start climbing from step 0\n  res = [0] # Use res[0] to record the number of ways\n  backtrack(choices, state, n, res)\n  res.first\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1411-method-1-brute-force-search","level":2,"title":"14.1.1   Method 1: Brute-force search","text":"

    Backtracking usually does not explicitly decompose the problem. Instead, it treats a solution as a sequence of decisions and searches all possible solutions through trial and pruning.

    Let's analyze this problem through decomposition. Let \\(dp[i]\\) be the number of ways to reach step \\(i\\). The original problem is \\(dp[i]\\), and its subproblems include:

    \\[ dp[i-1], dp[i-2], \\dots, dp[2], dp[1] \\]

    Since each move climbs \\(1\\) or \\(2\\) steps, when we stand on step \\(i\\), we must previously have been on step \\(i - 1\\) or \\(i - 2\\). In other words, step \\(i\\) can only be reached from step \\(i -1\\) or \\(i - 2\\).

    This gives an important result: the number of ways to reach step \\(i - 1\\) plus the number of ways to reach step \\(i - 2\\) equals the number of ways to reach step \\(i\\). In symbols:

    \\[ dp[i] = dp[i-1] + dp[i-2] \\]

    Thus, the subproblems are linked by a recurrence: the solution to the original problem can be constructed from subproblem solutions. Figure 14-2 illustrates this relationship.

    Figure 14-2   Recurrence for the number of ways

    The recurrence gives a brute-force search solution. Starting at \\(dp[n]\\), recursively express a larger problem as the sum of two smaller ones until reaching \\(dp[1]\\) or \\(dp[2]\\). Their solutions are known: \\(dp[1] = 1\\) and \\(dp[2] = 2\\), meaning steps \\(1\\) and \\(2\\) can be reached in \\(1\\) and \\(2\\) ways, respectively.

    Like the standard backtracking code, the following code performs depth-first search, but is more concise:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs.py
    def dfs(i: int) -> int:\n    \"\"\"Search\"\"\"\n    # The values of dp[1] and dp[2] are known; return the corresponding value\n    if i == 1 or i == 2:\n        return i\n    # dp[i] = dp[i-1] + dp[i-2]\n    count = dfs(i - 1) + dfs(i - 2)\n    return count\n\ndef climbing_stairs_dfs(n: int) -> int:\n    \"\"\"Climbing stairs: search\"\"\"\n    return dfs(n)\n
    climbing_stairs_dfs.cpp
    /* Search */\nint dfs(int i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.java
    /* Search */\nint dfs(int i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.cs
    /* Search */\nint DFS(int i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = DFS(i - 1) + DFS(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nint ClimbingStairsDFS(int n) {\n    return DFS(n);\n}\n
    climbing_stairs_dfs.go
    /* Search */\nfunc dfs(i int) int {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    count := dfs(i-1) + dfs(i-2)\n    return count\n}\n\n/* Climbing stairs: search */\nfunc climbingStairsDFS(n int) int {\n    return dfs(n)\n}\n
    climbing_stairs_dfs.swift
    /* Search */\nfunc dfs(i: Int) -> Int {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i: i - 1) + dfs(i: i - 2)\n    return count\n}\n\n/* Climbing stairs: search */\nfunc climbingStairsDFS(n: Int) -> Int {\n    dfs(i: n)\n}\n
    climbing_stairs_dfs.js
    /* Search */\nfunction dfs(i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i === 1 || i === 2) return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nfunction climbingStairsDFS(n) {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.ts
    /* Search */\nfunction dfs(i: number): number {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i === 1 || i === 2) return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nfunction climbingStairsDFS(n: number): number {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.dart
    /* Search */\nint dfs(int i) {\n  // The values of dp[1] and dp[2] are known; return them\n  if (i == 1 || i == 2) return i;\n  // dp[i] = dp[i-1] + dp[i-2]\n  int count = dfs(i - 1) + dfs(i - 2);\n  return count;\n}\n\n/* Climbing stairs: search */\nint climbingStairsDFS(int n) {\n  return dfs(n);\n}\n
    climbing_stairs_dfs.rs
    /* Search */\nfn dfs(i: usize) -> i32 {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i as i32;\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i - 1) + dfs(i - 2);\n    count\n}\n\n/* Climbing stairs: search */\nfn climbing_stairs_dfs(n: usize) -> i32 {\n    dfs(n)\n}\n
    climbing_stairs_dfs.c
    /* Search */\nint dfs(int i) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1) + dfs(i - 2);\n    return count;\n}\n\n/* Climbing stairs: search */\nint climbingStairsDFS(int n) {\n    return dfs(n);\n}\n
    climbing_stairs_dfs.kt
    /* Search */\nfun dfs(i: Int): Int {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2) return i\n    // dp[i] = dp[i-1] + dp[i-2]\n    val count = dfs(i - 1) + dfs(i - 2)\n    return count\n}\n\n/* Climbing stairs: search */\nfun climbingStairsDFS(n: Int): Int {\n    return dfs(n)\n}\n
    climbing_stairs_dfs.rb
    ### Search ###\ndef dfs(i)\n  # The values of dp[1] and dp[2] are known; return them\n  return i if i == 1 || i == 2\n  # dp[i] = dp[i-1] + dp[i-2]\n  dfs(i - 1) + dfs(i - 2)\nend\n\n### Climbing stairs: search ###\ndef climbing_stairs_dfs(n)\n  dfs(n)\nend\n
    Visualize execution

    View full screen >

    Figure 14-3 shows the recursion tree for brute-force search. For \\(dp[n]\\), its depth is \\(n\\), and the time complexity is \\(O(2^n)\\). Exponential growth is explosive, so a relatively large \\(n\\) leads to a very long wait.

    Figure 14-3   Recursion tree for climbing stairs

    As Figure 14-3 shows, overlapping subproblems cause the exponential time complexity. For example, \\(dp[9]\\) splits into \\(dp[8]\\) and \\(dp[7]\\), while \\(dp[8]\\) splits into \\(dp[7]\\) and \\(dp[6]\\). Both contain \\(dp[7]\\).

    These subproblems in turn contain smaller overlapping subproblems, and the pattern repeats at deeper levels of the recursion tree. Most of the computation is wasted on solving the same subproblems repeatedly.

    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1412-method-2-memoized-search","level":2,"title":"14.1.2   Method 2: Memoized search","text":"

    To improve efficiency, we want every overlapping subproblem to be solved only once. Introduce an array mem to record each solution and prune repeated subproblems during the search.

    1. The first time we calculate \\(dp[i]\\), store it in mem[i] for later use.
    2. When \\(dp[i]\\) is needed again, retrieve it directly from mem[i] instead of recomputing it.

    The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs_mem.py
    def dfs(i: int, mem: list[int]) -> int:\n    \"\"\"Memoized search\"\"\"\n    # The values of dp[1] and dp[2] are known; return the corresponding value\n    if i == 1 or i == 2:\n        return i\n    # If dp[i] has been recorded, return it directly\n    if mem[i] != -1:\n        return mem[i]\n    # dp[i] = dp[i-1] + dp[i-2]\n    count = dfs(i - 1, mem) + dfs(i - 2, mem)\n    # Record dp[i]\n    mem[i] = count\n    return count\n\ndef climbing_stairs_dfs_mem(n: int) -> int:\n    \"\"\"Climbing stairs: memoized search\"\"\"\n    # mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    mem = [-1] * (n + 1)\n    return dfs(n, mem)\n
    climbing_stairs_dfs_mem.cpp
    /* Memoized search */\nint dfs(int i, vector<int> &mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    vector<int> mem(n + 1, -1);\n    return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.java
    /* Memoized search */\nint dfs(int i, int[] mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    int[] mem = new int[n + 1];\n    Arrays.fill(mem, -1);\n    return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.cs
    /* Memoized search */\nint DFS(int i, int[] mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = DFS(i - 1, mem) + DFS(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nint ClimbingStairsDFSMem(int n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    int[] mem = new int[n + 1];\n    Array.Fill(mem, -1);\n    return DFS(n, mem);\n}\n
    climbing_stairs_dfs_mem.go
    /* Memoized search */\nfunc dfsMem(i int, mem []int) int {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // If dp[i] has been recorded, return it directly\n    if mem[i] != -1 {\n        return mem[i]\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    count := dfsMem(i-1, mem) + dfsMem(i-2, mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: memoized search */\nfunc climbingStairsDFSMem(n int) int {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    mem := make([]int, n+1)\n    for i := range mem {\n        mem[i] = -1\n    }\n    return dfsMem(n, mem)\n}\n
    climbing_stairs_dfs_mem.swift
    /* Memoized search */\nfunc dfs(i: Int, mem: inout [Int]) -> Int {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i\n    }\n    // If dp[i] has been recorded, return it directly\n    if mem[i] != -1 {\n        return mem[i]\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: memoized search */\nfunc climbingStairsDFSMem(n: Int) -> Int {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    var mem = Array(repeating: -1, count: n + 1)\n    return dfs(i: n, mem: &mem)\n}\n
    climbing_stairs_dfs_mem.js
    /* Memoized search */\nfunction dfs(i, mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i === 1 || i === 2) return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1) return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nfunction climbingStairsDFSMem(n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    const mem = new Array(n + 1).fill(-1);\n    return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.ts
    /* Memoized search */\nfunction dfs(i: number, mem: number[]): number {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i === 1 || i === 2) return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1) return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nfunction climbingStairsDFSMem(n: number): number {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    const mem = new Array(n + 1).fill(-1);\n    return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.dart
    /* Memoized search */\nint dfs(int i, List<int> mem) {\n  // The values of dp[1] and dp[2] are known; return them\n  if (i == 1 || i == 2) return i;\n  // If dp[i] has been recorded, return it directly\n  if (mem[i] != -1) return mem[i];\n  // dp[i] = dp[i-1] + dp[i-2]\n  int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n  // Record dp[i]\n  mem[i] = count;\n  return count;\n}\n\n/* Climbing stairs: memoized search */\nint climbingStairsDFSMem(int n) {\n  // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n  List<int> mem = List.filled(n + 1, -1);\n  return dfs(n, mem);\n}\n
    climbing_stairs_dfs_mem.rs
    /* Memoized search */\nfn dfs(i: usize, mem: &mut [i32]) -> i32 {\n    // The values of dp[1] and dp[2] are known; return them\n    if i == 1 || i == 2 {\n        return i as i32;\n    }\n    // If dp[i] has been recorded, return it directly\n    if mem[i] != -1 {\n        return mem[i];\n    }\n    // dp[i] = dp[i-1] + dp[i-2]\n    let count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    count\n}\n\n/* Climbing stairs: memoized search */\nfn climbing_stairs_dfs_mem(n: usize) -> i32 {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    let mut mem = vec![-1; n + 1];\n    dfs(n, &mut mem)\n}\n
    climbing_stairs_dfs_mem.c
    /* Memoized search */\nint dfs(int i, int *mem) {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2)\n        return i;\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1)\n        return mem[i];\n    // dp[i] = dp[i-1] + dp[i-2]\n    int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n    // Record dp[i]\n    mem[i] = count;\n    return count;\n}\n\n/* Climbing stairs: memoized search */\nint climbingStairsDFSMem(int n) {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    int *mem = (int *)malloc((n + 1) * sizeof(int));\n    for (int i = 0; i <= n; i++) {\n        mem[i] = -1;\n    }\n    int result = dfs(n, mem);\n    free(mem);\n    return result;\n}\n
    climbing_stairs_dfs_mem.kt
    /* Memoized search */\nfun dfs(i: Int, mem: IntArray): Int {\n    // The values of dp[1] and dp[2] are known; return them\n    if (i == 1 || i == 2) return i\n    // If dp[i] has been recorded, return it directly\n    if (mem[i] != -1) return mem[i]\n    // dp[i] = dp[i-1] + dp[i-2]\n    val count = dfs(i - 1, mem) + dfs(i - 2, mem)\n    // Record dp[i]\n    mem[i] = count\n    return count\n}\n\n/* Climbing stairs: memoized search */\nfun climbingStairsDFSMem(n: Int): Int {\n    // mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n    val mem = IntArray(n + 1)\n    mem.fill(-1)\n    return dfs(n, mem)\n}\n
    climbing_stairs_dfs_mem.rb
    ### Memoized search ###\ndef dfs(i, mem)\n  # The values of dp[1] and dp[2] are known; return them\n  return i if i == 1 || i == 2\n  # If dp[i] has been recorded, return it directly\n  return mem[i] if mem[i] != -1\n\n  # dp[i] = dp[i-1] + dp[i-2]\n  count = dfs(i - 1, mem) + dfs(i - 2, mem)\n  # Record dp[i]\n  mem[i] = count\nend\n\n### Climbing stairs: memoized search ###\ndef climbing_stairs_dfs_mem(n)\n  # mem[i] stores the number of ways to reach step i; -1 means no result has been recorded\n  mem = Array.new(n + 1, -1)\n  dfs(n, mem)\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-4, memoization makes every overlapping subproblem require only one computation, reducing the time complexity to \\(O(n)\\): a major improvement.

    Figure 14-4   Recursion tree for memoized search

    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1413-method-3-dynamic-programming","level":2,"title":"14.1.3   Method 3: Dynamic programming","text":"

    Memoized search is a top-down approach: start with the original problem (the root), recursively divide larger subproblems into smaller ones until reaching the smallest subproblems with known solutions (the leaves), then collect their solutions as recursion returns to construct the solution to the original problem.

    In contrast, dynamic programming is a bottom-up approach: start with the smallest subproblem solutions and iteratively build solutions to larger subproblems until the original problem is solved.

    Because this dynamic programming process does not backtrack, it needs only iterative loops, not recursion. In the following code, the array dp stores subproblem solutions, serving the same role as mem in memoized search:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py
    def climbing_stairs_dp(n: int) -> int:\n    \"\"\"Climbing stairs: dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return n\n    # Initialize the dp table to store subproblem solutions\n    dp = [0] * (n + 1)\n    # Initial states: set the solutions to the smallest subproblems\n    dp[1], dp[2] = 1, 2\n    # State transitions: solve progressively larger subproblems using smaller ones\n    for i in range(3, n + 1):\n        dp[i] = dp[i - 1] + dp[i - 2]\n    return dp[n]\n
    climbing_stairs_dp.cpp
    /* Climbing stairs: dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize the dp table to store subproblem solutions\n    vector<int> dp(n + 1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.java
    /* Climbing stairs: dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize the dp table to store subproblem solutions\n    int[] dp = new int[n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.cs
    /* Climbing stairs: dynamic programming */\nint ClimbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize the dp table to store subproblem solutions\n    int[] dp = new int[n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.go
    /* Climbing stairs: dynamic programming */\nfunc climbingStairsDP(n int) int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    // Initialize the dp table to store subproblem solutions\n    dp := make([]int, n+1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1\n    dp[2] = 2\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        dp[i] = dp[i-1] + dp[i-2]\n    }\n    return dp[n]\n}\n
    climbing_stairs_dp.swift
    /* Climbing stairs: dynamic programming */\nfunc climbingStairsDP(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    // Initialize the dp table to store subproblem solutions\n    var dp = Array(repeating: 0, count: n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1\n    dp[2] = 2\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3 ... n {\n        dp[i] = dp[i - 1] + dp[i - 2]\n    }\n    return dp[n]\n}\n
    climbing_stairs_dp.js
    /* Climbing stairs: dynamic programming */\nfunction climbingStairsDP(n) {\n    if (n === 1 || n === 2) return n;\n    // Initialize the dp table to store subproblem solutions\n    const dp = new Array(n + 1).fill(-1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.ts
    /* Climbing stairs: dynamic programming */\nfunction climbingStairsDP(n: number): number {\n    if (n === 1 || n === 2) return n;\n    // Initialize the dp table to store subproblem solutions\n    const dp = new Array(n + 1).fill(-1);\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (let i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    return dp[n];\n}\n
    climbing_stairs_dp.dart
    /* Climbing stairs: dynamic programming */\nint climbingStairsDP(int n) {\n  if (n == 1 || n == 2) return n;\n  // Initialize the dp table to store subproblem solutions\n  List<int> dp = List.filled(n + 1, 0);\n  // Initial states: set the solutions to the smallest subproblems\n  dp[1] = 1;\n  dp[2] = 2;\n  // State transitions: solve progressively larger subproblems using smaller ones\n  for (int i = 3; i <= n; i++) {\n    dp[i] = dp[i - 1] + dp[i - 2];\n  }\n  return dp[n];\n}\n
    climbing_stairs_dp.rs
    /* Climbing stairs: dynamic programming */\nfn climbing_stairs_dp(n: usize) -> i32 {\n    // The values of dp[1] and dp[2] are known; return them\n    if n == 1 || n == 2 {\n        return n as i32;\n    }\n    // Initialize the dp table to store subproblem solutions\n    let mut dp = vec![-1; n + 1];\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i in 3..=n {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    dp[n]\n}\n
    climbing_stairs_dp.c
    /* Climbing stairs: dynamic programming */\nint climbingStairsDP(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    // Initialize the dp table to store subproblem solutions\n    int *dp = (int *)malloc((n + 1) * sizeof(int));\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1;\n    dp[2] = 2;\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (int i = 3; i <= n; i++) {\n        dp[i] = dp[i - 1] + dp[i - 2];\n    }\n    int result = dp[n];\n    free(dp);\n    return result;\n}\n
    climbing_stairs_dp.kt
    /* Climbing stairs: dynamic programming */\nfun climbingStairsDP(n: Int): Int {\n    if (n == 1 || n == 2) return n\n    // Initialize the dp table to store subproblem solutions\n    val dp = IntArray(n + 1)\n    // Initial states: set the solutions to the smallest subproblems\n    dp[1] = 1\n    dp[2] = 2\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for (i in 3..n) {\n        dp[i] = dp[i - 1] + dp[i - 2]\n    }\n    return dp[n]\n}\n
    climbing_stairs_dp.rb
    ### Climbing stairs: dynamic programming ###\ndef climbing_stairs_dp(n)\n  return n  if n == 1 || n == 2\n\n  # Initialize the dp table to store subproblem solutions\n  dp = Array.new(n + 1, 0)\n  # Initial states: set the solutions to the smallest subproblems\n  dp[1], dp[2] = 1, 2\n  # State transitions: solve progressively larger subproblems using smaller ones\n  (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }\n\n  dp[n]\nend\n
    Visualize execution

    View full screen >

    Figure 14-5 traces the code's execution.

    Figure 14-5   Dynamic programming for climbing stairs

    Like backtracking, dynamic programming uses states to represent stages of solving a problem. Each state corresponds to a subproblem and its locally optimal solution. For climbing stairs, the state is the current step number \\(i\\).

    We can now summarize some common dynamic programming terminology.

    • The array dp is called the dp table, and \\(dp[i]\\) stores the solution to the subproblem for state \\(i\\).
    • The states for the smallest subproblems (steps \\(1\\) and \\(2\\)) are called the initial states.
    • The recurrence \\(dp[i] = dp[i-1] + dp[i-2]\\) is called the state transition equation.
    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1414-space-optimization","level":2,"title":"14.1.4   Space optimization","text":"

    You may have noticed that \\(dp[i]\\) depends only on \\(dp[i-1]\\) and \\(dp[i-2]\\), so we do not need an entire dp array storing every subproblem solution. Two variables updated as we proceed are enough. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py
    def climbing_stairs_dp_comp(n: int) -> int:\n    \"\"\"Climbing stairs: space-optimized dynamic programming\"\"\"\n    if n == 1 or n == 2:\n        return n\n    a, b = 1, 2\n    for _ in range(3, n + 1):\n        a, b = b, a + b\n    return b\n
    climbing_stairs_dp.cpp
    /* Climbing stairs: space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.java
    /* Climbing stairs: space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.cs
    /* Climbing stairs: space-optimized dynamic programming */\nint ClimbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.go
    /* Climbing stairs: space-optimized dynamic programming */\nfunc climbingStairsDPComp(n int) int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    a, b := 1, 2\n    // State transitions: solve progressively larger subproblems using smaller ones\n    for i := 3; i <= n; i++ {\n        a, b = b, a+b\n    }\n    return b\n}\n
    climbing_stairs_dp.swift
    /* Climbing stairs: space-optimized dynamic programming */\nfunc climbingStairsDPComp(n: Int) -> Int {\n    if n == 1 || n == 2 {\n        return n\n    }\n    var a = 1\n    var b = 2\n    for _ in 3 ... n {\n        (a, b) = (b, a + b)\n    }\n    return b\n}\n
    climbing_stairs_dp.js
    /* Climbing stairs: space-optimized dynamic programming */\nfunction climbingStairsDPComp(n) {\n    if (n === 1 || n === 2) return n;\n    let a = 1,\n        b = 2;\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.ts
    /* Climbing stairs: space-optimized dynamic programming */\nfunction climbingStairsDPComp(n: number): number {\n    if (n === 1 || n === 2) return n;\n    let a = 1,\n        b = 2;\n    for (let i = 3; i <= n; i++) {\n        const tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.dart
    /* Climbing stairs: space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n  if (n == 1 || n == 2) return n;\n  int a = 1, b = 2;\n  for (int i = 3; i <= n; i++) {\n    int tmp = b;\n    b = a + b;\n    a = tmp;\n  }\n  return b;\n}\n
    climbing_stairs_dp.rs
    /* Climbing stairs: space-optimized dynamic programming */\nfn climbing_stairs_dp_comp(n: usize) -> i32 {\n    if n == 1 || n == 2 {\n        return n as i32;\n    }\n    let (mut a, mut b) = (1, 2);\n    for _ in 3..=n {\n        let tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    b\n}\n
    climbing_stairs_dp.c
    /* Climbing stairs: space-optimized dynamic programming */\nint climbingStairsDPComp(int n) {\n    if (n == 1 || n == 2)\n        return n;\n    int a = 1, b = 2;\n    for (int i = 3; i <= n; i++) {\n        int tmp = b;\n        b = a + b;\n        a = tmp;\n    }\n    return b;\n}\n
    climbing_stairs_dp.kt
    /* Climbing stairs: space-optimized dynamic programming */\nfun climbingStairsDPComp(n: Int): Int {\n    if (n == 1 || n == 2) return n\n    var a = 1\n    var b = 2\n    for (i in 3..n) {\n        val temp = b\n        b += a\n        a = temp\n    }\n    return b\n}\n
    climbing_stairs_dp.rb
    ### Climbing stairs: space-optimized dynamic programming ###\ndef climbing_stairs_dp_comp(n)\n  return n if n == 1 || n == 2\n\n  a, b = 1, 2\n  (3...(n + 1)).each { a, b = b, a + b }\n\n  b\nend\n
    Visualize execution

    View full screen >

    Removing the dp array reduces space complexity from \\(O(n)\\) to \\(O(1)\\).

    In dynamic programming, a state often depends on only a limited number of preceding states. We can keep just those needed, reducing the storage dimensions to save memory. This technique is called using rolling variables or a rolling array.

    ","path":["Chapter 14.   Dynamic programming","14.1   Introduction to dynamic programming"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/","level":1,"title":"14.4   0-1 knapsack problem","text":"

    The knapsack problem is an excellent introduction to dynamic programming and one of its most common problem families. Variants include 0-1 knapsack, unbounded knapsack, and bounded knapsack.

    We will begin with the most common variant, 0-1 knapsack.

    Question

    Given \\(n\\) items, where item \\(i\\) has weight \\(wgt[i-1]\\) and value \\(val[i-1]\\), and a knapsack of capacity \\(cap\\), each item may be chosen only once. What is the maximum total value that fits within the capacity?

    As shown in Figure 14-17, item numbers \\(i\\) start at \\(1\\), while array indices start at \\(0\\). Item \\(i\\) therefore has weight \\(wgt[i-1]\\) and value \\(val[i-1]\\).

    Figure 14-17   Example input for 0-1 knapsack

    We can view 0-1 knapsack as \\(n\\) rounds of decisions, with two choices for each item: leave it out or put it in. It therefore fits the decision tree model.

    The objective is to maximize value within a capacity limit, making dynamic programming a promising approach.

    Step 1: Identify each decision, define the state, and establish the \\(dp\\) table

    Leaving an item out keeps the available capacity unchanged; putting it in reduces the capacity. The state therefore consists of the current item number \\(i\\) and capacity \\(c\\), denoted by \\([i, c]\\).

    State \\([i, c]\\) represents the subproblem of finding the maximum value obtainable from the first \\(i\\) items with capacity \\(c\\), denoted by \\(dp[i, c]\\).

    We seek \\(dp[n, cap]\\), so we need a two-dimensional \\(dp\\) table of size \\((n+1) \\times (cap+1)\\).

    Step 2: Identify optimal substructure and derive the state transition equation

    After deciding what to do with item \\(i\\), the remaining subproblem concerns the first \\(i-1\\) items. There are two cases.

    • Leave item \\(i\\) out: Capacity stays unchanged, and the remaining state is \\([i-1, c]\\).
    • Put item \\(i\\) in: Capacity decreases by \\(wgt[i-1]\\), value increases by \\(val[i-1]\\), and the remaining state is \\([i-1, c-wgt[i-1]]\\).

    This reveals the optimal substructure: the maximum value \\(dp[i, c]\\) is the larger value obtained by leaving item \\(i\\) out or putting item \\(i\\) in. The state transition equation is:

    \\[ dp[i, c] = \\max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) \\]

    If the current item's weight \\(wgt[i - 1]\\) exceeds the remaining capacity \\(c\\), the only option is to leave it out.

    Step 3: Determine the boundary conditions and evaluation order

    With no items or capacity \\(0\\), the maximum value is \\(0\\). Thus, the first column \\(dp[i, 0]\\) and first row \\(dp[0, c]\\) are both \\(0\\).

    The current state \\([i, c]\\) depends on \\([i-1, c]\\) above and \\([i-1, c-wgt[i-1]]\\) to the upper left, so two nested loops can traverse the \\(dp\\) table in increasing index order.

    We will now implement brute-force search, memoized search, and dynamic programming in that order.

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#1-method-1-brute-force-search","level":3,"title":"1.   Method 1: Brute-force search","text":"

    The search has the following elements.

    • Parameters: The state \\([i, c]\\).
    • Return value: The subproblem solution \\(dp[i, c]\\).
    • Base case: If there are no items left, \\(i = 0\\), or the remaining capacity is \\(0\\), stop recursion and return value \\(0\\).
    • Pruning: If the current item is heavier than the remaining capacity, only the choice to leave it out is allowed.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
    def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int:\n    \"\"\"0-1 knapsack: brute-force search\"\"\"\n    # If all items have been considered or no capacity remains, return value 0\n    if i == 0 or c == 0:\n        return 0\n    # If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c:\n        return knapsack_dfs(wgt, val, i - 1, c)\n    # Calculate the maximum values obtained by excluding and including item i\n    no = knapsack_dfs(wgt, val, i - 1, c)\n    yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n    # Return the larger value from the two choices\n    return max(no, yes)\n
    knapsack.cpp
    /* 0-1 knapsack: brute-force search */\nint knapsackDFS(vector<int> &wgt, vector<int> &val, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return max(no, yes);\n}\n
    knapsack.java
    /* 0-1 knapsack: brute-force search */\nint knapsackDFS(int[] wgt, int[] val, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return Math.max(no, yes);\n}\n
    knapsack.cs
    /* 0-1 knapsack: brute-force search */\nint KnapsackDFS(int[] weight, int[] val, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (weight[i - 1] > c) {\n        return KnapsackDFS(weight, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = KnapsackDFS(weight, val, i - 1, c);\n    int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return Math.Max(no, yes);\n}\n
    knapsack.go
    /* 0-1 knapsack: brute-force search */\nfunc knapsackDFS(wgt, val []int, i, c int) int {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i-1] > c {\n        return knapsackDFS(wgt, val, i-1, c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    no := knapsackDFS(wgt, val, i-1, c)\n    yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1]\n    // Return the larger value from the two choices\n    return int(math.Max(float64(no), float64(yes)))\n}\n
    knapsack.swift
    /* 0-1 knapsack: brute-force search */\nfunc knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c {\n        return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n    let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n    // Return the larger value from the two choices\n    return max(no, yes)\n}\n
    knapsack.js
    /* 0-1 knapsack: brute-force search */\nfunction knapsackDFS(wgt, val, i, c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    const no = knapsackDFS(wgt, val, i - 1, c);\n    const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return Math.max(no, yes);\n}\n
    knapsack.ts
    /* 0-1 knapsack: brute-force search */\nfunction knapsackDFS(\n    wgt: Array<number>,\n    val: Array<number>,\n    i: number,\n    c: number\n): number {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    const no = knapsackDFS(wgt, val, i - 1, c);\n    const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return Math.max(no, yes);\n}\n
    knapsack.dart
    /* 0-1 knapsack: brute-force search */\nint knapsackDFS(List<int> wgt, List<int> val, int i, int c) {\n  // If all items have been considered or no capacity remains, return value 0\n  if (i == 0 || c == 0) {\n    return 0;\n  }\n  // If the item exceeds the remaining capacity, it must be excluded\n  if (wgt[i - 1] > c) {\n    return knapsackDFS(wgt, val, i - 1, c);\n  }\n  // Calculate the maximum values obtained by excluding and including item i\n  int no = knapsackDFS(wgt, val, i - 1, c);\n  int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n  // Return the larger value from the two choices\n  return max(no, yes);\n}\n
    knapsack.rs
    /* 0-1 knapsack: brute-force search */\nfn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c as i32 {\n        return knapsack_dfs(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    let no = knapsack_dfs(wgt, val, i - 1, c);\n    let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n    // Return the larger value from the two choices\n    std::cmp::max(no, yes)\n}\n
    knapsack.c
    /* 0-1 knapsack: brute-force search */\nint knapsackDFS(int wgt[], int val[], int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, val, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFS(wgt, val, i - 1, c);\n    int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Return the larger value from the two choices\n    return myMax(no, yes);\n}\n
    knapsack.kt
    /* 0-1 knapsack: brute-force search */\nfun knapsackDFS(\n    wgt: IntArray,\n    _val: IntArray,\n    i: Int,\n    c: Int\n): Int {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFS(wgt, _val, i - 1, c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    val no = knapsackDFS(wgt, _val, i - 1, c)\n    val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1]\n    // Return the larger value from the two choices\n    return max(no, yes)\n}\n
    knapsack.rb
    ### 0-1 knapsack: brute-force search ###\ndef knapsack_dfs(wgt, val, i, c)\n  # If all items have been considered or no capacity remains, return value 0\n  return 0 if i == 0 || c == 0\n  # If the item exceeds the remaining capacity, it must be excluded\n  return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c\n  # Calculate the maximum values obtained by excluding and including item i\n  no = knapsack_dfs(wgt, val, i - 1, c)\n  yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n  # Return the larger value from the two choices\n  [no, yes].max\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-18, each item creates two branches, excluding it or including it, so the time complexity is \\(O(2^n)\\).

    The recursion tree contains overlapping subproblems, such as \\(dp[1, 10]\\). Their number grows substantially with more items and larger capacity, especially when many items share the same weight.

    Figure 14-18   Brute-force recursion tree for 0-1 knapsack

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#2-method-2-memoized-search","level":3,"title":"2.   Method 2: Memoized search","text":"

    To solve each overlapping subproblem only once, use a memoization table mem, where mem[i][c] stores \\(dp[i, c]\\).

    With memoization, the time complexity depends on the number of subproblems, giving \\(O(n \\times cap)\\). The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
    def knapsack_dfs_mem(\n    wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int\n) -> int:\n    \"\"\"0-1 knapsack: memoized search\"\"\"\n    # If all items have been considered or no capacity remains, return value 0\n    if i == 0 or c == 0:\n        return 0\n    # If a result has already been recorded, return it directly\n    if mem[i][c] != -1:\n        return mem[i][c]\n    # If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c:\n        return knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n    # Calculate the maximum values obtained by excluding and including item i\n    no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n    yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n    # Record and return the larger value from the two choices\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n
    knapsack.cpp
    /* 0-1 knapsack: memoized search */\nint knapsackDFSMem(vector<int> &wgt, vector<int> &val, vector<vector<int>> &mem, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.java
    /* 0-1 knapsack: memoized search */\nint knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.cs
    /* 0-1 knapsack: memoized search */\nint KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (weight[i - 1] > c) {\n        return KnapsackDFSMem(weight, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = KnapsackDFSMem(weight, val, mem, i - 1, c);\n    int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = Math.Max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.go
    /* 0-1 knapsack: memoized search */\nfunc knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c]\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i-1] > c {\n        return knapsackDFSMem(wgt, val, mem, i-1, c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    no := knapsackDFSMem(wgt, val, mem, i-1, c)\n    yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1]\n    // Return the larger value from the two choices\n    mem[i][c] = int(math.Max(float64(no), float64(yes)))\n    return mem[i][c]\n}\n
    knapsack.swift
    /* 0-1 knapsack: memoized search */\nfunc knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c]\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c {\n        return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c)\n    let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n    // Record and return the larger value from the two choices\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n}\n
    knapsack.js
    /* 0-1 knapsack: memoized search */\nfunction knapsackDFSMem(wgt, val, mem, i, c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] !== -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    const yes =\n        knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.ts
    /* 0-1 knapsack: memoized search */\nfunction knapsackDFSMem(\n    wgt: Array<number>,\n    val: Array<number>,\n    mem: Array<Array<number>>,\n    i: number,\n    c: number\n): number {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i === 0 || c === 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] !== -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n    const yes =\n        knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = Math.max(no, yes);\n    return mem[i][c];\n}\n
    knapsack.dart
    /* 0-1 knapsack: memoized search */\nint knapsackDFSMem(\n  List<int> wgt,\n  List<int> val,\n  List<List<int>> mem,\n  int i,\n  int c,\n) {\n  // If all items have been considered or no capacity remains, return value 0\n  if (i == 0 || c == 0) {\n    return 0;\n  }\n  // If a result has already been recorded, return it directly\n  if (mem[i][c] != -1) {\n    return mem[i][c];\n  }\n  // If the item exceeds the remaining capacity, it must be excluded\n  if (wgt[i - 1] > c) {\n    return knapsackDFSMem(wgt, val, mem, i - 1, c);\n  }\n  // Calculate the maximum values obtained by excluding and including item i\n  int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n  int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n  // Record and return the larger value from the two choices\n  mem[i][c] = max(no, yes);\n  return mem[i][c];\n}\n
    knapsack.rs
    /* 0-1 knapsack: memoized search */\nfn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec<Vec<i32>>, i: usize, c: usize) -> i32 {\n    // If all items have been considered or no capacity remains, return value 0\n    if i == 0 || c == 0 {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if mem[i][c] != -1 {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if wgt[i - 1] > c as i32 {\n        return knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n    let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = std::cmp::max(no, yes);\n    mem[i][c]\n}\n
    knapsack.c
    /* 0-1 knapsack: memoized search */\nint knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0;\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c];\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n    int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n    // Record and return the larger value from the two choices\n    mem[i][c] = myMax(no, yes);\n    return mem[i][c];\n}\n
    knapsack.kt
    /* 0-1 knapsack: memoized search */\nfun knapsackDFSMem(\n    wgt: IntArray,\n    _val: IntArray,\n    mem: Array<IntArray>,\n    i: Int,\n    c: Int\n): Int {\n    // If all items have been considered or no capacity remains, return value 0\n    if (i == 0 || c == 0) {\n        return 0\n    }\n    // If a result has already been recorded, return it directly\n    if (mem[i][c] != -1) {\n        return mem[i][c]\n    }\n    // If the item exceeds the remaining capacity, it must be excluded\n    if (wgt[i - 1] > c) {\n        return knapsackDFSMem(wgt, _val, mem, i - 1, c)\n    }\n    // Calculate the maximum values obtained by excluding and including item i\n    val no = knapsackDFSMem(wgt, _val, mem, i - 1, c)\n    val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1]\n    // Record and return the larger value from the two choices\n    mem[i][c] = max(no, yes)\n    return mem[i][c]\n}\n
    knapsack.rb
    ### 0-1 knapsack: memoized search ###\ndef knapsack_dfs_mem(wgt, val, mem, i, c)\n  # If all items have been considered or no capacity remains, return value 0\n  return 0 if i == 0 || c == 0\n  # If a result has already been recorded, return it directly\n  return mem[i][c] if mem[i][c] != -1\n  # If the item exceeds the remaining capacity, it must be excluded\n  return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c\n  # Calculate the maximum values obtained by excluding and including item i\n  no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n  yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n  # Record and return the larger value from the two choices\n  mem[i][c] = [no, yes].max\nend\n
    Visualize execution

    View full screen >

    Figure 14-19 shows the branches pruned by memoized search.

    Figure 14-19   Memoized recursion tree for 0-1 knapsack

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#3-method-3-dynamic-programming","level":3,"title":"3.   Method 3: Dynamic programming","text":"

    Dynamic programming essentially fills the \\(dp\\) table using state transitions, as shown in the code below:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
    def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"0-1 knapsack: dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize the dp table\n    dp = [[0] * (cap + 1) for _ in range(n + 1)]\n    # State transitions\n    for i in range(1, n + 1):\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            else:\n                # Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n    return dp[n][cap]\n
    knapsack.cpp
    /* 0-1 knapsack: dynamic programming */\nint knapsackDP(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize the dp table\n    vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    knapsack.java
    /* 0-1 knapsack: dynamic programming */\nint knapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize the dp table\n    int[][] dp = new int[n + 1][cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    knapsack.cs
    /* 0-1 knapsack: dynamic programming */\nint KnapsackDP(int[] weight, int[] val, int cap) {\n    int n = weight.Length;\n    // Initialize the dp table\n    int[,] dp = new int[n + 1, cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (weight[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i, c] = dp[i - 1, c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]);\n            }\n        }\n    }\n    return dp[n, cap];\n}\n
    knapsack.go
    /* 0-1 knapsack: dynamic programming */\nfunc knapsackDP(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize the dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, cap+1)\n    }\n    // State transitions\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i-1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    knapsack.swift
    /* 0-1 knapsack: dynamic programming */\nfunc knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n    // State transitions\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    knapsack.js
    /* 0-1 knapsack: dynamic programming */\nfunction knapsackDP(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array(n + 1)\n        .fill(0)\n        .map(() => Array(cap + 1).fill(0));\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    knapsack.ts
    /* 0-1 knapsack: dynamic programming */\nfunction knapsackDP(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    knapsack.dart
    /* 0-1 knapsack: dynamic programming */\nint knapsackDP(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0));\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If item i exceeds the remaining capacity, do not choose it\n        dp[i][c] = dp[i - 1][c];\n      } else {\n        // Take the larger value obtained by excluding and including item i\n        dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[n][cap];\n}\n
    knapsack.rs
    /* 0-1 knapsack: dynamic programming */\nfn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize the dp table\n    let mut dp = vec![vec![0; cap + 1]; n + 1];\n    // State transitions\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = std::cmp::max(\n                    dp[i - 1][c],\n                    dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1],\n                );\n            }\n        }\n    }\n    dp[n][cap]\n}\n
    knapsack.c
    /* 0-1 knapsack: dynamic programming */\nint knapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize the dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(cap + 1, sizeof(int));\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[n][cap];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
    knapsack.kt
    /* 0-1 knapsack: dynamic programming */\nfun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize the dp table\n    val dp = Array(n + 1) { IntArray(cap + 1) }\n    // State transitions\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    knapsack.rb
    ### 0-1 knapsack: dynamic programming ###\ndef knapsack_dp(wgt, val, cap)\n  n = wgt.length\n  # Initialize the dp table\n  dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n  # State transitions\n  for i in 1...(n + 1)\n    for c in 1...(cap + 1)\n      if wgt[i - 1] > c\n        # If item i exceeds the remaining capacity, do not choose it\n        dp[i][c] = dp[i - 1][c]\n      else\n        # Take the larger value obtained by excluding and including item i\n        dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[n][cap]\nend\n
    Visualize execution

    View full screen >

    As shown in Figure 14-20, both time and space complexity are determined by the size of dp: \\(O(n \\times cap)\\).

    <1><2><3><4><5><6><7><8><9><10><11><12><13><14>

    Figure 14-20   Dynamic programming for 0-1 knapsack

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#4-space-optimization","level":3,"title":"4.   Space optimization","text":"

    Each state depends only on the previous row, so two rolling arrays reduce space complexity from \\(O(n \\times cap)\\) to \\(O(cap)\\).

    Can we go further and use just one array? Each state depends on entries directly above or to the upper left. With one array, when we begin processing row \\(i\\), it still holds the states from row \\(i-1\\).

    • With forward iteration, by the time we reach \\(dp[i, j]\\), the upper-left values \\(dp[i-1, 1]\\) ~ \\(dp[i-1, j-1]\\) may have been overwritten, making the transition incorrect.
    • With backward iteration, these values are not overwritten before they are needed, so transitions remain correct.

    Figure 14-21 shows one array changing from row \\(i = 1\\) to row \\(i = 2\\). Consider how forward and backward iteration differ.

    <1><2><3><4><5><6>

    Figure 14-21   Space-optimized dynamic programming for 0-1 knapsack

    In the implementation, simply remove the first dimension \\(i\\) from dp and reverse the inner loop:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py
    def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"0-1 knapsack: space-optimized dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize the dp table\n    dp = [0] * (cap + 1)\n    # State transitions\n    for i in range(1, n + 1):\n        # Traverse in decreasing order\n        for c in range(cap, 0, -1):\n            if wgt[i - 1] > c:\n                # If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            else:\n                # Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n    return dp[cap]\n
    knapsack.cpp
    /* 0-1 knapsack: space-optimized dynamic programming */\nint knapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize the dp table\n    vector<int> dp(cap + 1, 0);\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.java
    /* 0-1 knapsack: space-optimized dynamic programming */\nint knapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize the dp table\n    int[] dp = new int[cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.cs
    /* 0-1 knapsack: space-optimized dynamic programming */\nint KnapsackDPComp(int[] weight, int[] val, int cap) {\n    int n = weight.Length;\n    // Initialize the dp table\n    int[] dp = new int[cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (int c = cap; c > 0; c--) {\n            if (weight[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.go
    /* 0-1 knapsack: space-optimized dynamic programming */\nfunc knapsackDPComp(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize the dp table\n    dp := make([]int, cap+1)\n    // State transitions\n    for i := 1; i <= n; i++ {\n        // Traverse in decreasing order\n        for c := cap; c >= 1; c-- {\n            if wgt[i-1] <= c {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[cap]\n}\n
    knapsack.swift
    /* 0-1 knapsack: space-optimized dynamic programming */\nfunc knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize the dp table\n    var dp = Array(repeating: 0, count: cap + 1)\n    // State transitions\n    for i in 1 ... n {\n        // Traverse in decreasing order\n        for c in (1 ... cap).reversed() {\n            if wgt[i - 1] <= c {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
    knapsack.js
    /* 0-1 knapsack: space-optimized dynamic programming */\nfunction knapsackDPComp(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array(cap + 1).fill(0);\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (let c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.ts
    /* 0-1 knapsack: space-optimized dynamic programming */\nfunction knapsackDPComp(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array(cap + 1).fill(0);\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (let c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    knapsack.dart
    /* 0-1 knapsack: space-optimized dynamic programming */\nint knapsackDPComp(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize the dp table\n  List<int> dp = List.filled(cap + 1, 0);\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    // Traverse in decreasing order\n    for (int c = cap; c >= 1; c--) {\n      if (wgt[i - 1] <= c) {\n        // Take the larger value obtained by excluding and including item i\n        dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[cap];\n}\n
    knapsack.rs
    /* 0-1 knapsack: space-optimized dynamic programming */\nfn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize the dp table\n    let mut dp = vec![0; cap + 1];\n    // State transitions\n    for i in 1..=n {\n        // Traverse in decreasing order\n        for c in (1..=cap).rev() {\n            if wgt[i - 1] <= c as i32 {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    dp[cap]\n}\n
    knapsack.c
    /* 0-1 knapsack: space-optimized dynamic programming */\nint knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize the dp table\n    int *dp = calloc(cap + 1, sizeof(int));\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        // Traverse in decreasing order\n        for (int c = cap; c >= 1; c--) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[cap];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    knapsack.kt
    /* 0-1 knapsack: space-optimized dynamic programming */\nfun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize the dp table\n    val dp = IntArray(cap + 1)\n    // State transitions\n    for (i in 1..n) {\n        // Traverse in decreasing order\n        for (c in cap downTo 1) {\n            if (wgt[i - 1] <= c) {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
    knapsack.rb
    ### 0-1 knapsack: space-optimized dynamic programming ###\ndef knapsack_dp_comp(wgt, val, cap)\n  n = wgt.length\n  # Initialize the dp table\n  dp = Array.new(cap + 1, 0)\n  # State transitions\n  for i in 1...(n + 1)\n    # Traverse in decreasing order\n    for c in cap.downto(1)\n      if wgt[i - 1] > c\n        # If item i exceeds the remaining capacity, do not choose it\n        dp[c] = dp[c]\n      else\n        # Take the larger value obtained by excluding and including item i\n        dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[cap]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.4   0-1 knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/summary/","level":1,"title":"14.7   Summary","text":"","path":["Chapter 14.   Dynamic programming","14.7   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Dynamic programming decomposes problems and stores subproblem solutions to avoid repeated computation and improve efficiency.
    • Without a time limit, every dynamic programming problem can be solved by backtracking (brute-force search). However, the recursion tree contains many overlapping subproblems, making it very inefficient. Memoization stores computed solutions so that each overlapping subproblem is solved only once.
    • Memoized search is a top-down recursive approach, while the corresponding dynamic programming solution works bottom up, like filling a table. Since the current state depends only on certain nearby states, removing a dimension from the \\(dp\\) table can reduce space complexity.
    • Decomposing a problem into subproblems is a general algorithmic idea with different characteristics in divide and conquer, dynamic programming, and backtracking.
    • Dynamic programming problems have three key properties: overlapping subproblems, optimal substructure, and the no-aftereffect property.
    • A problem has optimal substructure if its optimal solution can be constructed from optimal solutions to subproblems.
    • The no-aftereffect property means a state's future depends only on that state, not on the history leading to it. Many combinatorial optimization problems lack this property and cannot be solved quickly with dynamic programming.

    Knapsack problems

    • Knapsack is one of the most typical dynamic programming problem families, with variants including 0-1, unbounded, and bounded knapsack.
    • In 0-1 knapsack, the state value is the maximum value obtainable from the first \\(i\\) items within capacity \\(c\\). The choices to exclude or include an item reveal optimal substructure and give the state transition equation. After space optimization, backward iteration is necessary because each state depends on entries above and to the upper left, which must not be overwritten prematurely.
    • Unbounded knapsack allows unlimited copies of each item, changing the transition for including an item. Because a state depends on entries directly above and to the left, space optimization requires forward iteration.
    • Coin change is a variant of unbounded knapsack. Minimizing the number of coins instead of maximizing value changes \\(\\max()\\) to \\(\\min()\\) in the transition. Requiring the target amount exactly, rather than staying within a capacity, calls for using \\(amt + 1\\) to represent an infeasible solution.
    • Coin change II counts combinations instead of minimizing coin count, so the transition replaces \\(\\min()\\) with addition.

    Edit distance

    • Edit distance (Levenshtein distance) measures similarity between strings. It is the minimum number of edits needed to turn one string into another, allowing insertion, deletion, and replacement.
    • The edit distance state is the minimum number of edits needed to transform the first \\(i\\) characters of \\(s\\) into the first \\(j\\) characters of \\(t\\). When \\(s[i-1] \\ne t[j-1]\\), insertion, deletion, and replacement each leave a corresponding subproblem, revealing optimal substructure and the state transition equation. When \\(s[i-1] = t[j-1]\\), no edit is needed for those characters.
    • Edit distance depends on states above, to the left, and to the upper left, so neither forward nor backward iteration alone works after space optimization. A temporary variable preserves the upper-left state, reducing the situation to one like unbounded knapsack and allowing forward iteration.
    ","path":["Chapter 14.   Dynamic programming","14.7   Summary"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/","level":1,"title":"14.5   Unbounded knapsack problem","text":"

    We will now solve another common knapsack variant, unbounded knapsack, and then examine a special case: coin change.

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1451-unbounded-knapsack","level":2,"title":"14.5.1   Unbounded knapsack","text":"

    Question

    Given \\(n\\) items, where item \\(i\\) has weight \\(wgt[i-1]\\) and value \\(val[i-1]\\), and a knapsack of capacity \\(cap\\), each item may be chosen repeatedly. What is the maximum value that fits within the capacity? An example is shown in Figure 14-22.

    Figure 14-22   Example input for unbounded knapsack

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach","level":3,"title":"1.   Dynamic programming approach","text":"

    Unbounded knapsack is very similar to 0-1 knapsack. The only difference is that there is no limit on how many times an item may be chosen.

    • In 0-1 knapsack, there is only one of each item, so after including item \\(i\\), we can choose only from the first \\(i-1\\) items.
    • In unbounded knapsack, each item has an unlimited supply, so after including item \\(i\\), we can still choose from the first \\(i\\) items.

    For unbounded knapsack, state \\([i, c]\\) has two cases.

    • Leave item \\(i\\) out: As in 0-1 knapsack, the remaining state is \\([i-1, c]\\).
    • Put item \\(i\\) in: Unlike 0-1 knapsack, the remaining state is \\([i, c-wgt[i-1]]\\).

    The state transition equation becomes:

    \\[ dp[i, c] = \\max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) \\]","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    Compared with 0-1 knapsack, only one \\(i-1\\) in the transition changes to \\(i\\); the rest of the code is identical:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py
    def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Unbounded knapsack: dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize the dp table\n    dp = [[0] * (cap + 1) for _ in range(n + 1)]\n    # State transitions\n    for i in range(1, n + 1):\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            else:\n                # Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n    return dp[n][cap]\n
    unbounded_knapsack.cpp
    /* Unbounded knapsack: dynamic programming */\nint unboundedKnapsackDP(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize the dp table\n    vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.java
    /* Unbounded knapsack: dynamic programming */\nint unboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize the dp table\n    int[][] dp = new int[n + 1][cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.cs
    /* Unbounded knapsack: dynamic programming */\nint UnboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n    int n = wgt.Length;\n    // Initialize the dp table\n    int[,] dp = new int[n + 1, cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i, c] = dp[i - 1, c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n, cap];\n}\n
    unbounded_knapsack.go
    /* Unbounded knapsack: dynamic programming */\nfunc unboundedKnapsackDP(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize the dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, cap+1)\n    }\n    // State transitions\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i-1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    unbounded_knapsack.swift
    /* Unbounded knapsack: dynamic programming */\nfunc unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n    // State transitions\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    unbounded_knapsack.js
    /* Unbounded knapsack: dynamic programming */\nfunction unboundedKnapsackDP(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.ts
    /* Unbounded knapsack: dynamic programming */\nfunction unboundedKnapsackDP(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: cap + 1 }, () => 0)\n    );\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = Math.max(\n                    dp[i - 1][c],\n                    dp[i][c - wgt[i - 1]] + val[i - 1]\n                );\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.dart
    /* Unbounded knapsack: dynamic programming */\nint unboundedKnapsackDP(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0));\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If item i exceeds the remaining capacity, do not choose it\n        dp[i][c] = dp[i - 1][c];\n      } else {\n        // Take the larger value obtained by excluding and including item i\n        dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[n][cap];\n}\n
    unbounded_knapsack.rs
    /* Unbounded knapsack: dynamic programming */\nfn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize the dp table\n    let mut dp = vec![vec![0; cap + 1]; n + 1];\n    // State transitions\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    return dp[n][cap];\n}\n
    unbounded_knapsack.c
    /* Unbounded knapsack: dynamic programming */\nint unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize the dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(cap + 1, sizeof(int));\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[n][cap];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    return res;\n}\n
    unbounded_knapsack.kt
    /* Unbounded knapsack: dynamic programming */\nfun unboundedKnapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n    val n = wgt.size\n    // Initialize the dp table\n    val dp = Array(n + 1) { IntArray(cap + 1) }\n    // State transitions\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[i][c] = dp[i - 1][c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[n][cap]\n}\n
    unbounded_knapsack.rb
    ### Unbounded knapsack: dynamic programming ###\ndef unbounded_knapsack_dp(wgt, val, cap)\n  n = wgt.length\n  # Initialize the dp table\n  dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n  # State transitions\n  for i in 1...(n + 1)\n    for c in 1...(cap + 1)\n      if wgt[i - 1] > c\n        # If item i exceeds the remaining capacity, do not choose it\n        dp[i][c] = dp[i - 1][c]\n      else\n        # Take the larger value obtained by excluding and including item i\n        dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[n][cap]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization","level":3,"title":"3.   Space optimization","text":"

    The current state depends on states to its left and above, so after space optimization, each row of the \\(dp\\) table must be processed in forward order.

    This is the opposite of 0-1 knapsack. Use Figure 14-23 to understand the difference.

    <1><2><3><4><5><6>

    Figure 14-23   Space-optimized dynamic programming for unbounded knapsack

    The implementation simply removes the first dimension of dp:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py
    def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Unbounded knapsack: space-optimized dynamic programming\"\"\"\n    n = len(wgt)\n    # Initialize the dp table\n    dp = [0] * (cap + 1)\n    # State transitions\n    for i in range(1, n + 1):\n        # Traverse in increasing order\n        for c in range(1, cap + 1):\n            if wgt[i - 1] > c:\n                # If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            else:\n                # Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n    return dp[cap]\n
    unbounded_knapsack.cpp
    /* Unbounded knapsack: space-optimized dynamic programming */\nint unboundedKnapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {\n    int n = wgt.size();\n    // Initialize the dp table\n    vector<int> dp(cap + 1, 0);\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.java
    /* Unbounded knapsack: space-optimized dynamic programming */\nint unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.length;\n    // Initialize the dp table\n    int[] dp = new int[cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.cs
    /* Unbounded knapsack: space-optimized dynamic programming */\nint UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n    int n = wgt.Length;\n    // Initialize the dp table\n    int[] dp = new int[cap + 1];\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.go
    /* Unbounded knapsack: space-optimized dynamic programming */\nfunc unboundedKnapsackDPComp(wgt, val []int, cap int) int {\n    n := len(wgt)\n    // Initialize the dp table\n    dp := make([]int, cap+1)\n    // State transitions\n    for i := 1; i <= n; i++ {\n        for c := 1; c <= cap; c++ {\n            if wgt[i-1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n            }\n        }\n    }\n    return dp[cap]\n}\n
    unbounded_knapsack.swift
    /* Unbounded knapsack: space-optimized dynamic programming */\nfunc unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int {\n    let n = wgt.count\n    // Initialize the dp table\n    var dp = Array(repeating: 0, count: cap + 1)\n    // State transitions\n    for i in 1 ... n {\n        for c in 1 ... cap {\n            if wgt[i - 1] > c {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
    unbounded_knapsack.js
    /* Unbounded knapsack: space-optimized dynamic programming */\nfunction unboundedKnapsackDPComp(wgt, val, cap) {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: cap + 1 }, () => 0);\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.ts
    /* Unbounded knapsack: space-optimized dynamic programming */\nfunction unboundedKnapsackDPComp(\n    wgt: Array<number>,\n    val: Array<number>,\n    cap: number\n): number {\n    const n = wgt.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: cap + 1 }, () => 0);\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    return dp[cap];\n}\n
    unbounded_knapsack.dart
    /* Unbounded knapsack: space-optimized dynamic programming */\nint unboundedKnapsackDPComp(List<int> wgt, List<int> val, int cap) {\n  int n = wgt.length;\n  // Initialize the dp table\n  List<int> dp = List.filled(cap + 1, 0);\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int c = 1; c <= cap; c++) {\n      if (wgt[i - 1] > c) {\n        // If item i exceeds the remaining capacity, do not choose it\n        dp[c] = dp[c];\n      } else {\n        // Take the larger value obtained by excluding and including item i\n        dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n      }\n    }\n  }\n  return dp[cap];\n}\n
    unbounded_knapsack.rs
    /* Unbounded knapsack: space-optimized dynamic programming */\nfn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 {\n    let n = wgt.len();\n    // Initialize the dp table\n    let mut dp = vec![0; cap + 1];\n    // State transitions\n    for i in 1..=n {\n        for c in 1..=cap {\n            if wgt[i - 1] > c as i32 {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n            }\n        }\n    }\n    dp[cap]\n}\n
    unbounded_knapsack.c
    /* Unbounded knapsack: space-optimized dynamic programming */\nint unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n    int n = wgtSize;\n    // Initialize the dp table\n    int *dp = calloc(cap + 1, sizeof(int));\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int c = 1; c <= cap; c++) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c];\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n            }\n        }\n    }\n    int res = dp[cap];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    unbounded_knapsack.kt
    /* Unbounded knapsack: space-optimized dynamic programming */\nfun unboundedKnapsackDPComp(\n    wgt: IntArray,\n    _val: IntArray,\n    cap: Int\n): Int {\n    val n = wgt.size\n    // Initialize the dp table\n    val dp = IntArray(cap + 1)\n    // State transitions\n    for (i in 1..n) {\n        for (c in 1..cap) {\n            if (wgt[i - 1] > c) {\n                // If item i exceeds the remaining capacity, do not choose it\n                dp[c] = dp[c]\n            } else {\n                // Take the larger value obtained by excluding and including item i\n                dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n            }\n        }\n    }\n    return dp[cap]\n}\n
    unbounded_knapsack.rb
    ### Unbounded knapsack: space-optimized dynamic programming ###\ndef unbounded_knapsack_dp_comp(wgt, val, cap)\n  n = wgt.length\n  # Initialize the dp table\n  dp = Array.new(cap + 1, 0)\n  # State transitions\n  for i in 1...(n + 1)\n    # Traverse in increasing order\n    for c in 1...(cap + 1)\n      if wgt[i -1] > c\n        # If item i exceeds the remaining capacity, do not choose it\n        dp[c] = dp[c]\n      else\n        # Take the larger value obtained by excluding and including item i\n        dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n      end\n    end\n  end\n  dp[cap]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1452-coin-change-problem","level":2,"title":"14.5.2   Coin change problem","text":"

    Knapsack represents a broad family of dynamic programming problems, with many variants such as coin change.

    Question

    Given \\(n\\) coin denominations, where denomination \\(i\\) has value \\(coins[i - 1]\\), and a target amount \\(amt\\), each denomination may be used repeatedly. What is the minimum number of coins needed to make the target? Return \\(-1\\) if it cannot be made. An example is shown in Figure 14-24.

    Figure 14-24   Example input for coin change

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach_1","level":3,"title":"1.   Dynamic programming approach","text":"

    Coin change can be viewed as a special case of unbounded knapsack, with the following connections and differences.

    • The two problems can be translated into one another: items correspond to coins, item weights to coin denominations, and knapsack capacity to the target amount.
    • The optimization objectives are opposite: unbounded knapsack maximizes item value, while coin change minimizes the number of coins.
    • Unbounded knapsack must stay within a capacity limit, whereas coin change must reach the target amount exactly.

    Step 1: Identify each decision, define the state, and establish the \\(dp\\) table

    State \\([i, a]\\) represents the subproblem of finding the minimum number of coins needed to make amount \\(a\\) using the first \\(i\\) denominations, denoted by \\(dp[i, a]\\).

    The two-dimensional \\(dp\\) table has size \\((n+1) \\times (amt+1)\\).

    Step 2: Identify optimal substructure and derive the state transition equation

    The state transition differs from unbounded knapsack in two ways.

    • We want a minimum, so replace \\(\\max()\\) with \\(\\min()\\).
    • We optimize the coin count rather than item value, so choosing a coin adds \\(+1\\).
    \\[ dp[i, a] = \\min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) \\]

    Step 3: Determine the boundary conditions and evaluation order

    A target amount of \\(0\\) requires \\(0\\) coins, so every entry \\(dp[i, 0]\\) in the first column is \\(0\\).

    With no coins, any target amount \\(> 0\\) is impossible. To let \\(\\min()\\) recognize and discard these infeasible solutions, represent them by \\(+ \\infty\\): set the first-row entries \\(dp[0, a]\\) to \\(+ \\infty\\).

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation_1","level":3,"title":"2.   Code implementation","text":"

    Most programming languages do not provide an integer value for \\(+ \\infty\\), so the maximum int value is used instead. However, the \\(+ 1\\) in the transition may then overflow.

    Instead, use \\(amt + 1\\) to represent an infeasible solution, since making \\(amt\\) requires at most \\(amt\\) coins. Before returning, check whether \\(dp[n, amt]\\) equals \\(amt + 1\\). If so, return \\(-1\\) to indicate that the target cannot be made. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py
    def coin_change_dp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: dynamic programming\"\"\"\n    n = len(coins)\n    MAX = amt + 1\n    # Initialize the dp table\n    dp = [[0] * (amt + 1) for _ in range(n + 1)]\n    # State transitions: first row and first column\n    for a in range(1, amt + 1):\n        dp[0][a] = MAX\n    # State transitions: remaining rows and columns\n    for i in range(1, n + 1):\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If the coin value exceeds the current amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            else:\n                # Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n    return dp[n][amt] if dp[n][amt] != MAX else -1\n
    coin_change.cpp
    /* Coin change: dynamic programming */\nint coinChangeDP(vector<int> &coins, int amt) {\n    int n = coins.size();\n    int MAX = amt + 1;\n    // Initialize the dp table\n    vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));\n    // State transitions: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
    coin_change.java
    /* Coin change: dynamic programming */\nint coinChangeDP(int[] coins, int amt) {\n    int n = coins.length;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int[][] dp = new int[n + 1][amt + 1];\n    // State transitions: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
    coin_change.cs
    /* Coin change: dynamic programming */\nint CoinChangeDP(int[] coins, int amt) {\n    int n = coins.Length;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int[,] dp = new int[n + 1, amt + 1];\n    // State transitions: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0, a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i, a] = dp[i - 1, a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n, amt] != MAX ? dp[n, amt] : -1;\n}\n
    coin_change.go
    /* Coin change: dynamic programming */\nfunc coinChangeDP(coins []int, amt int) int {\n    n := len(coins)\n    max := amt + 1\n    // Initialize the dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, amt+1)\n    }\n    // State transitions: first row and first column\n    for a := 1; a <= amt; a++ {\n        dp[0][a] = max\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i <= n; i++ {\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[i][a] = dp[i-1][a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1)))\n            }\n        }\n    }\n    if dp[n][amt] != max {\n        return dp[n][amt]\n    }\n    return -1\n}\n
    coin_change.swift
    /* Coin change: dynamic programming */\nfunc coinChangeDP(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    let MAX = amt + 1\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n    // State transitions: first row and first column\n    for a in 1 ... amt {\n        dp[0][a] = MAX\n    }\n    // State transitions: remaining rows and columns\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return dp[n][amt] != MAX ? dp[n][amt] : -1\n}\n
    coin_change.js
    /* Coin change: dynamic programming */\nfunction coinChangeDP(coins, amt) {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // State transitions: first row and first column\n    for (let a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n
    coin_change.ts
    /* Coin change: dynamic programming */\nfunction coinChangeDP(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // State transitions: first row and first column\n    for (let a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n
    coin_change.dart
    /* Coin change: dynamic programming */\nint coinChangeDP(List<int> coins, int amt) {\n  int n = coins.length;\n  int MAX = amt + 1;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0));\n  // State transitions: first row and first column\n  for (int a = 1; a <= amt; a++) {\n    dp[0][a] = MAX;\n  }\n  // State transitions: remaining rows and columns\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If the coin exceeds the current amount a, do not choose coin i\n        dp[i][a] = dp[i - 1][a];\n      } else {\n        // Take the smaller result of not choosing and choosing coin i\n        dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n      }\n    }\n  }\n  return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n
    coin_change.rs
    /* Coin change: dynamic programming */\nfn coin_change_dp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    let max = amt + 1;\n    // Initialize the dp table\n    let mut dp = vec![vec![0; amt + 1]; n + 1];\n    // State transitions: first row and first column\n    for a in 1..=amt {\n        dp[0][a] = max;\n    }\n    // State transitions: remaining rows and columns\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1);\n            }\n        }\n    }\n    if dp[n][amt] != max {\n        return dp[n][amt] as i32;\n    } else {\n        -1\n    }\n}\n
    coin_change.c
    /* Coin change: dynamic programming */\nint coinChangeDP(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(amt + 1, sizeof(int));\n    }\n    // State transitions: first row and first column\n    for (int a = 1; a <= amt; a++) {\n        dp[0][a] = MAX;\n    }\n    // State transitions: remaining rows and columns\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    int res = dp[n][amt] != MAX ? dp[n][amt] : -1;\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
    coin_change.kt
    /* Coin change: dynamic programming */\nfun coinChangeDP(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    val MAX = amt + 1\n    // Initialize the dp table\n    val dp = Array(n + 1) { IntArray(amt + 1) }\n    // State transitions: first row and first column\n    for (a in 1..amt) {\n        dp[0][a] = MAX\n    }\n    // State transitions: remaining rows and columns\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If the coin value exceeds the current amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return if (dp[n][amt] != MAX) dp[n][amt] else -1\n}\n
    coin_change.rb
    ### Coin change: dynamic programming ###\ndef coin_change_dp(coins, amt)\n  n = coins.length\n  _MAX = amt + 1\n  # Initialize the dp table\n  dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n  # State transitions: first row and first column\n  (1...(amt + 1)).each { |a| dp[0][a] = _MAX }\n  # State transitions: remaining rows and columns\n  for i in 1...(n + 1)\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If the coin exceeds the current amount a, do not choose coin i\n        dp[i][a] = dp[i - 1][a]\n      else\n        # Take the smaller result of not choosing and choosing coin i\n        dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min\n      end\n    end\n  end\n  dp[n][amt] != _MAX ? dp[n][amt] : -1\nend\n
    Visualize execution

    View full screen >

    Figure 14-25 shows dynamic programming for coin change, which closely resembles unbounded knapsack.

    <1><2><3><4><5><6><7><8><9><10><11><12><13><14><15>

    Figure 14-25   Dynamic programming for coin change

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization_1","level":3,"title":"3.   Space optimization","text":"

    Space optimization is the same as for unbounded knapsack:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py
    def coin_change_dp_comp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: space-optimized dynamic programming\"\"\"\n    n = len(coins)\n    MAX = amt + 1\n    # Initialize the dp table\n    dp = [MAX] * (amt + 1)\n    dp[0] = 0\n    # State transitions\n    for i in range(1, n + 1):\n        # Traverse in increasing order\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If the coin value exceeds the current amount a, do not choose coin i\n                dp[a] = dp[a]\n            else:\n                # Take the smaller result of not choosing and choosing coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n    return dp[amt] if dp[amt] != MAX else -1\n
    coin_change.cpp
    /* Coin change: space-optimized dynamic programming */\nint coinChangeDPComp(vector<int> &coins, int amt) {\n    int n = coins.size();\n    int MAX = amt + 1;\n    // Initialize the dp table\n    vector<int> dp(amt + 1, MAX);\n    dp[0] = 0;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
    coin_change.java
    /* Coin change: space-optimized dynamic programming */\nint coinChangeDPComp(int[] coins, int amt) {\n    int n = coins.length;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int[] dp = new int[amt + 1];\n    Arrays.fill(dp, MAX);\n    dp[0] = 0;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
    coin_change.cs
    /* Coin change: space-optimized dynamic programming */\nint CoinChangeDPComp(int[] coins, int amt) {\n    int n = coins.Length;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int[] dp = new int[amt + 1];\n    Array.Fill(dp, MAX);\n    dp[0] = 0;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1;\n}\n
    coin_change.go
    /* Coin change: dynamic programming */\nfunc coinChangeDPComp(coins []int, amt int) int {\n    n := len(coins)\n    max := amt + 1\n    // Initialize the dp table\n    dp := make([]int, amt+1)\n    for i := 1; i <= amt; i++ {\n        dp[i] = max\n    }\n    // State transitions\n    for i := 1; i <= n; i++ {\n        // Traverse in increasing order\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1)))\n            }\n        }\n    }\n    if dp[amt] != max {\n        return dp[amt]\n    }\n    return -1\n}\n
    coin_change.swift
    /* Coin change: space-optimized dynamic programming */\nfunc coinChangeDPComp(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    let MAX = amt + 1\n    // Initialize the dp table\n    var dp = Array(repeating: MAX, count: amt + 1)\n    dp[0] = 0\n    // State transitions\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return dp[amt] != MAX ? dp[amt] : -1\n}\n
    coin_change.js
    /* Coin change: space-optimized dynamic programming */\nfunction coinChangeDPComp(coins, amt) {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize the dp table\n    const dp = Array.from({ length: amt + 1 }, () => MAX);\n    dp[0] = 0;\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] !== MAX ? dp[amt] : -1;\n}\n
    coin_change.ts
    /* Coin change: space-optimized dynamic programming */\nfunction coinChangeDPComp(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    const MAX = amt + 1;\n    // Initialize the dp table\n    const dp = Array.from({ length: amt + 1 }, () => MAX);\n    dp[0] = 0;\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    return dp[amt] !== MAX ? dp[amt] : -1;\n}\n
    coin_change.dart
    /* Coin change: space-optimized dynamic programming */\nint coinChangeDPComp(List<int> coins, int amt) {\n  int n = coins.length;\n  int MAX = amt + 1;\n  // Initialize the dp table\n  List<int> dp = List.filled(amt + 1, MAX);\n  dp[0] = 0;\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If the coin exceeds the current amount a, do not choose coin i\n        dp[a] = dp[a];\n      } else {\n        // Take the smaller result of not choosing and choosing coin i\n        dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n      }\n    }\n  }\n  return dp[amt] != MAX ? dp[amt] : -1;\n}\n
    coin_change.rs
    /* Coin change: space-optimized dynamic programming */\nfn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    let max = amt + 1;\n    // Initialize the dp table\n    let mut dp = vec![0; amt + 1];\n    dp.fill(max);\n    dp[0] = 0;\n    // State transitions\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1);\n            }\n        }\n    }\n    if dp[amt] != max {\n        return dp[amt] as i32;\n    } else {\n        -1\n    }\n}\n
    coin_change.c
    /* Coin change: space-optimized dynamic programming */\nint coinChangeDPComp(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    int MAX = amt + 1;\n    // Initialize the dp table\n    int *dp = malloc((amt + 1) * sizeof(int));\n    for (int j = 1; j <= amt; j++) {\n        dp[j] = MAX;\n    } \n    dp[0] = 0;\n\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1);\n            }\n        }\n    }\n    int res = dp[amt] != MAX ? dp[amt] : -1;\n    // Free memory\n    free(dp);\n    return res;\n}\n
    coin_change.kt
    /* Coin change: space-optimized dynamic programming */\nfun coinChangeDPComp(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    val MAX = amt + 1\n    // Initialize the dp table\n    val dp = IntArray(amt + 1)\n    dp.fill(MAX)\n    dp[0] = 0\n    // State transitions\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If the coin value exceeds the current amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Take the smaller result of not choosing and choosing coin i\n                dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n            }\n        }\n    }\n    return if (dp[amt] != MAX) dp[amt] else -1\n}\n
    coin_change.rb
    ### Coin change: space-optimized dynamic programming ###\ndef coin_change_dp_comp(coins, amt)\n  n = coins.length\n  _MAX = amt + 1\n  # Initialize the dp table\n  dp = Array.new(amt + 1, _MAX)\n  dp[0] = 0\n  # State transitions\n  for i in 1...(n + 1)\n    # Traverse in increasing order\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If the coin exceeds the current amount a, do not choose coin i\n        dp[a] = dp[a]\n      else\n        # Take the smaller result of not choosing and choosing coin i\n        dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min\n      end\n    end\n  end\n  dp[amt] != _MAX ? dp[amt] : -1\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1453-coin-change-problem-ii","level":2,"title":"14.5.3   Coin change problem II","text":"

    Question

    Given \\(n\\) coin denominations, where denomination \\(i\\) has value \\(coins[i - 1]\\), and a target amount \\(amt\\), each denomination may be used repeatedly. How many combinations of coins make the target amount? An example is shown in Figure 14-26.

    Figure 14-26   Example input for coin change II

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1-dynamic-programming-approach_2","level":3,"title":"1.   Dynamic programming approach","text":"

    This time, we count combinations, so the subproblem becomes the number of combinations that make amount \\(a\\) using the first \\(i\\) denominations. The \\(dp\\) table remains a two-dimensional matrix of size \\((n+1) \\times (amt + 1)\\).

    The number of combinations for the current state is the sum of those that exclude the current coin and those that include it. The state transition equation is:

    \\[ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] \\]

    A target of \\(0\\) can be made by choosing no coins, so initialize every first-column entry \\(dp[i, 0]\\) to \\(1\\). With no coins, any target \\(>0\\) is impossible, so the first-row entries \\(dp[0, a]\\) are \\(0\\).

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2-code-implementation_2","level":3,"title":"2.   Code implementation","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py
    def coin_change_ii_dp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change II: dynamic programming\"\"\"\n    n = len(coins)\n    # Initialize the dp table\n    dp = [[0] * (amt + 1) for _ in range(n + 1)]\n    # Initialize the first column\n    for i in range(n + 1):\n        dp[i][0] = 1\n    # State transitions\n    for i in range(1, n + 1):\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If the coin value exceeds the current amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            else:\n                # Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n    return dp[n][amt]\n
    coin_change_ii.cpp
    /* Coin change II: dynamic programming */\nint coinChangeIIDP(vector<int> &coins, int amt) {\n    int n = coins.size();\n    // Initialize the dp table\n    vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));\n    // Initialize the first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
    coin_change_ii.java
    /* Coin change II: dynamic programming */\nint coinChangeIIDP(int[] coins, int amt) {\n    int n = coins.length;\n    // Initialize the dp table\n    int[][] dp = new int[n + 1][amt + 1];\n    // Initialize the first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
    coin_change_ii.cs
    /* Coin change II: dynamic programming */\nint CoinChangeIIDP(int[] coins, int amt) {\n    int n = coins.Length;\n    // Initialize the dp table\n    int[,] dp = new int[n + 1, amt + 1];\n    // Initialize the first column\n    for (int i = 0; i <= n; i++) {\n        dp[i, 0] = 1;\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i, a] = dp[i - 1, a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n, amt];\n}\n
    coin_change_ii.go
    /* Coin change II: dynamic programming */\nfunc coinChangeIIDP(coins []int, amt int) int {\n    n := len(coins)\n    // Initialize the dp table\n    dp := make([][]int, n+1)\n    for i := 0; i <= n; i++ {\n        dp[i] = make([]int, amt+1)\n    }\n    // Initialize the first column\n    for i := 0; i <= n; i++ {\n        dp[i][0] = 1\n    }\n    // State transitions: remaining rows and columns\n    for i := 1; i <= n; i++ {\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[i][a] = dp[i-1][a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
    coin_change_ii.swift
    /* Coin change II: dynamic programming */\nfunc coinChangeIIDP(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    // Initialize the dp table\n    var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n    // Initialize the first column\n    for i in 0 ... n {\n        dp[i][0] = 1\n    }\n    // State transitions\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
    coin_change_ii.js
    /* Coin change II: dynamic programming */\nfunction coinChangeIIDP(coins, amt) {\n    const n = coins.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // Initialize the first column\n    for (let i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
    coin_change_ii.ts
    /* Coin change II: dynamic programming */\nfunction coinChangeIIDP(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: n + 1 }, () =>\n        Array.from({ length: amt + 1 }, () => 0)\n    );\n    // Initialize the first column\n    for (let i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[n][amt];\n}\n
    coin_change_ii.dart
    /* Coin change II: dynamic programming */\nint coinChangeIIDP(List<int> coins, int amt) {\n  int n = coins.length;\n  // Initialize the dp table\n  List<List<int>> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0));\n  // Initialize the first column\n  for (int i = 0; i <= n; i++) {\n    dp[i][0] = 1;\n  }\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If the coin exceeds the current amount a, do not choose coin i\n        dp[i][a] = dp[i - 1][a];\n      } else {\n        // Add the numbers of ways without and with coin i\n        dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n      }\n    }\n  }\n  return dp[n][amt];\n}\n
    coin_change_ii.rs
    /* Coin change II: dynamic programming */\nfn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    // Initialize the dp table\n    let mut dp = vec![vec![0; amt + 1]; n + 1];\n    // Initialize the first column\n    for i in 0..=n {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize];\n            }\n        }\n    }\n    dp[n][amt]\n}\n
    coin_change_ii.c
    /* Coin change II: dynamic programming */\nint coinChangeIIDP(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    // Initialize the dp table\n    int **dp = malloc((n + 1) * sizeof(int *));\n    for (int i = 0; i <= n; i++) {\n        dp[i] = calloc(amt + 1, sizeof(int));\n    }\n    // Initialize the first column\n    for (int i = 0; i <= n; i++) {\n        dp[i][0] = 1;\n    }\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[i][a] = dp[i - 1][a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n            }\n        }\n    }\n    int res = dp[n][amt];\n    // Free memory\n    for (int i = 0; i <= n; i++) {\n        free(dp[i]);\n    }\n    free(dp);\n    return res;\n}\n
    coin_change_ii.kt
    /* Coin change II: dynamic programming */\nfun coinChangeIIDP(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    // Initialize the dp table\n    val dp = Array(n + 1) { IntArray(amt + 1) }\n    // Initialize the first column\n    for (i in 0..n) {\n        dp[i][0] = 1\n    }\n    // State transitions\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If the coin value exceeds the current amount a, do not choose coin i\n                dp[i][a] = dp[i - 1][a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[n][amt]\n}\n
    coin_change_ii.rb
    ### Coin change II: dynamic programming ###\ndef coin_change_ii_dp(coins, amt)\n  n = coins.length\n  # Initialize the dp table\n  dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n  # Initialize the first column\n  (0...(n + 1)).each { |i| dp[i][0] = 1 }\n  # State transitions\n  for i in 1...(n + 1)\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If the coin exceeds the current amount a, do not choose coin i\n        dp[i][a] = dp[i - 1][a]\n      else\n        # Add the numbers of ways without and with coin i\n        dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n      end\n    end\n  end\n  dp[n][amt]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3-space-optimization_2","level":3,"title":"3.   Space optimization","text":"

    The space optimization is the same: remove the coin dimension:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py
    def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change II: space-optimized dynamic programming\"\"\"\n    n = len(coins)\n    # Initialize the dp table\n    dp = [0] * (amt + 1)\n    dp[0] = 1\n    # State transitions\n    for i in range(1, n + 1):\n        # Traverse in increasing order\n        for a in range(1, amt + 1):\n            if coins[i - 1] > a:\n                # If the coin value exceeds the current amount a, do not choose coin i\n                dp[a] = dp[a]\n            else:\n                # Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n    return dp[amt]\n
    coin_change_ii.cpp
    /* Coin change II: space-optimized dynamic programming */\nint coinChangeIIDPComp(vector<int> &coins, int amt) {\n    int n = coins.size();\n    // Initialize the dp table\n    vector<int> dp(amt + 1, 0);\n    dp[0] = 1;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.java
    /* Coin change II: space-optimized dynamic programming */\nint coinChangeIIDPComp(int[] coins, int amt) {\n    int n = coins.length;\n    // Initialize the dp table\n    int[] dp = new int[amt + 1];\n    dp[0] = 1;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.cs
    /* Coin change II: space-optimized dynamic programming */\nint CoinChangeIIDPComp(int[] coins, int amt) {\n    int n = coins.Length;\n    // Initialize the dp table\n    int[] dp = new int[amt + 1];\n    dp[0] = 1;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.go
    /* Coin change II: space-optimized dynamic programming */\nfunc coinChangeIIDPComp(coins []int, amt int) int {\n    n := len(coins)\n    // Initialize the dp table\n    dp := make([]int, amt+1)\n    dp[0] = 1\n    // State transitions\n    for i := 1; i <= n; i++ {\n        // Traverse in increasing order\n        for a := 1; a <= amt; a++ {\n            if coins[i-1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a-coins[i-1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
    coin_change_ii.swift
    /* Coin change II: space-optimized dynamic programming */\nfunc coinChangeIIDPComp(coins: [Int], amt: Int) -> Int {\n    let n = coins.count\n    // Initialize the dp table\n    var dp = Array(repeating: 0, count: amt + 1)\n    dp[0] = 1\n    // State transitions\n    for i in 1 ... n {\n        for a in 1 ... amt {\n            if coins[i - 1] > a {\n                // If the coin exceeds the current target amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
    coin_change_ii.js
    /* Coin change II: space-optimized dynamic programming */\nfunction coinChangeIIDPComp(coins, amt) {\n    const n = coins.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: amt + 1 }, () => 0);\n    dp[0] = 1;\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.ts
    /* Coin change II: space-optimized dynamic programming */\nfunction coinChangeIIDPComp(coins: Array<number>, amt: number): number {\n    const n = coins.length;\n    // Initialize the dp table\n    const dp = Array.from({ length: amt + 1 }, () => 0);\n    dp[0] = 1;\n    // State transitions\n    for (let i = 1; i <= n; i++) {\n        for (let a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    return dp[amt];\n}\n
    coin_change_ii.dart
    /* Coin change II: space-optimized dynamic programming */\nint coinChangeIIDPComp(List<int> coins, int amt) {\n  int n = coins.length;\n  // Initialize the dp table\n  List<int> dp = List.filled(amt + 1, 0);\n  dp[0] = 1;\n  // State transitions\n  for (int i = 1; i <= n; i++) {\n    for (int a = 1; a <= amt; a++) {\n      if (coins[i - 1] > a) {\n        // If the coin exceeds the current amount a, do not choose coin i\n        dp[a] = dp[a];\n      } else {\n        // Add the numbers of ways without and with coin i\n        dp[a] = dp[a] + dp[a - coins[i - 1]];\n      }\n    }\n  }\n  return dp[amt];\n}\n
    coin_change_ii.rs
    /* Coin change II: space-optimized dynamic programming */\nfn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 {\n    let n = coins.len();\n    // Initialize the dp table\n    let mut dp = vec![0; amt + 1];\n    dp[0] = 1;\n    // State transitions\n    for i in 1..=n {\n        for a in 1..=amt {\n            if coins[i - 1] > a as i32 {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1] as usize];\n            }\n        }\n    }\n    dp[amt]\n}\n
    coin_change_ii.c
    /* Coin change II: space-optimized dynamic programming */\nint coinChangeIIDPComp(int coins[], int amt, int coinsSize) {\n    int n = coinsSize;\n    // Initialize the dp table\n    int *dp = calloc(amt + 1, sizeof(int));\n    dp[0] = 1;\n    // State transitions\n    for (int i = 1; i <= n; i++) {\n        for (int a = 1; a <= amt; a++) {\n            if (coins[i - 1] > a) {\n                // If the coin exceeds the target amount, do not choose coin i\n                dp[a] = dp[a];\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]];\n            }\n        }\n    }\n    int res = dp[amt];\n    // Free memory\n    free(dp);\n    return res;\n}\n
    coin_change_ii.kt
    /* Coin change II: space-optimized dynamic programming */\nfun coinChangeIIDPComp(coins: IntArray, amt: Int): Int {\n    val n = coins.size\n    // Initialize the dp table\n    val dp = IntArray(amt + 1)\n    dp[0] = 1\n    // State transitions\n    for (i in 1..n) {\n        for (a in 1..amt) {\n            if (coins[i - 1] > a) {\n                // If the coin value exceeds the current amount a, do not choose coin i\n                dp[a] = dp[a]\n            } else {\n                // Add the numbers of ways without and with coin i\n                dp[a] = dp[a] + dp[a - coins[i - 1]]\n            }\n        }\n    }\n    return dp[amt]\n}\n
    coin_change_ii.rb
    ### Coin change II: space-optimized dynamic programming ###\ndef coin_change_ii_dp_comp(coins, amt)\n  n = coins.length\n  # Initialize the dp table\n  dp = Array.new(amt + 1, 0)\n  dp[0] = 1\n  # State transitions\n  for i in 1...(n + 1)\n    # Traverse in increasing order\n    for a in 1...(amt + 1)\n      if coins[i - 1] > a\n        # If the coin exceeds the current amount a, do not choose coin i\n        dp[a] = dp[a]\n      else\n        # Add the numbers of ways without and with coin i\n        dp[a] = dp[a] + dp[a - coins[i - 1]]\n      end\n    end\n  end\n  dp[amt]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 14.   Dynamic programming","14.5   Unbounded knapsack problem"],"tags":[]},{"location":"chapter_graph/","level":1,"title":"Chapter 9.   Graphs","text":"

    Abstract

    On the journey of life, we are like nodes joined by countless invisible edges.

    Every meeting and parting leaves a unique mark on this vast network.

    ","path":["Chapter 9.   Graphs"],"tags":[]},{"location":"chapter_graph/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 9.1   Graphs
    • 9.2   Basic graph operations
    • 9.3   Graph traversal
    • 9.4   Summary
    • 9.5   Exercises
    ","path":["Chapter 9.   Graphs"],"tags":[]},{"location":"chapter_graph/exercises/","level":1,"title":"9.5   Exercises","text":"","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#951-check-your-understanding","level":2,"title":"9.5.1   Check your understanding","text":"","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#1-representing-the-same-graph-in-two-ways","level":3,"title":"1.   Representing the same graph in two ways","text":"

    An undirected graph has four vertices A, B, C, D and edges A-B, A-C, B-C, C-D.

    1. Write its adjacency list.
    2. Fill in its adjacency matrix using only 0 and 1.
    3. Which representation lets us determine whether A and D are directly connected by inspecting just one stored entry?
    4. If a graph has many vertices but few edges, which representation usually uses less space?
    Reference answer
    1. The adjacency list is:

      A: B, C\nB: A, C\nC: A, B, D\nD: C\n
    2. The adjacency matrix is:

      A B C D A 0 1 1 0 B 1 0 1 0 C 1 1 0 1 D 0 0 1 0
    3. In the adjacency matrix, we can inspect row A, column D directly. This makes it well suited to checking whether any two vertices are directly connected.

    4. When there are many vertices but few edges, an adjacency list records only existing edges. It usually uses less space than an adjacency matrix, which reserves an entry for every pair of vertices.

    ","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#2-breadth-first-and-depth-first-traversal-orders","level":3,"title":"2.   Breadth-first and depth-first traversal orders","text":"

    An undirected graph has vertices A, B, C, D, E and edges A-B, A-C, B-D, C-D, D-E.

    Start at A. Whenever several unvisited neighbors are available, choose them in alphabetical order:

    1. Write the breadth-first traversal (BFS) order.
    2. Write the recursive depth-first traversal (DFS) order.
    3. Why must both traversals keep track of vertices already visited?
    Reference answer
    1. The BFS order is A, B, C, D, E. It first visits B and C, which are one edge away from A, and then the more distant D and E.

    2. The DFS order is A, B, D, C, E. It proceeds into an unvisited neighbor of the current vertex, first following A → B → D → C. When C has no new neighbors to visit, it returns to D and then visits E.

    3. The graph contains cycles, such as A-B-D-C-A. Without recording visited vertices, traversal could repeatedly follow a cycle through the same vertices and fail to terminate.

    ","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#3-can-one-bfs-visit-the-entire-graph","level":3,"title":"3.   Can one BFS visit the entire graph?","text":"

    An undirected graph has vertices A, B, C, D, E, F and only the edges A-B, B-C, D-E.

    1. Which vertices can a single BFS starting from A visit?
    2. Based on your answer, has this BFS visited every vertex in the graph? Why?
    3. Suppose we scan all vertices alphabetically and start a new BFS whenever we encounter an unvisited vertex. What are the starting vertices of these searches? How many separate connected parts, or connected components, does the graph have?
    Reference answer
    1. Starting from A reaches only A, B, C.

    2. Not every vertex is visited. D, E form another connected part, and F is an isolated vertex. None has a path to A, so they cannot be reached from A.

    3. The three BFS starting vertices are A, D, F, in order, visiting {A, B, C}, {D, E}, and {F}, respectively. Thus, the graph has three connected components.

    ","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#952-programming-exercises","level":2,"title":"9.5.2   Programming exercises","text":"","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/exercises/#1-checking-for-a-path-in-an-undirected-graph","level":3,"title":"1.   Checking for a path in an undirected graph","text":"

    You are given an undirected graph with \\(n\\) vertices numbered from \\(0\\) to \\(n-1\\). Each entry [u, v] in the array edges represents an undirected edge between vertices u and v.

    You are also given a starting vertex source and an ending vertex destination. First build an adjacency list from edges, then use BFS or DFS to determine whether a path exists from source to destination. Return true if it does, or false otherwise. The graph may contain cycles and may be disconnected.

    Hints
    1. Add each undirected edge in both directions.
    2. The graph may contain cycles, so keep track of vertices already visited.
    3. Starting from source, return true if you reach destination. Return false if traversal ends without reaching it.

    LeetCode

    ","path":["Chapter 9.   Graphs","9.5   Exercises"],"tags":[]},{"location":"chapter_graph/graph/","level":1,"title":"9.1   Graphs","text":"

    A graph is a nonlinear data structure made up of vertices and edges. We can represent a graph \\(G\\) abstractly using a set of vertices \\(V\\) and a set of edges \\(E\\). The following example describes a graph with 5 vertices and 7 edges.

    \\[ \\begin{aligned} V & = \\{ 1, 2, 3, 4, 5 \\} \\newline E & = \\{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \\} \\newline G & = \\{ V, E \\} \\newline \\end{aligned} \\]

    If we think of vertices as nodes and edges as references (pointers) connecting those nodes, a graph can be viewed as an extension of a linked list. As Figure 9-1 shows, network relationships (graphs) allow more freedom than linear relationships (linked lists) or divide-and-conquer relationships (trees), making them more complex.

    Figure 9-1   Relationships between linked lists, trees, and graphs

    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#911-common-graph-types-and-terminology","level":2,"title":"9.1.1   Common graph types and terminology","text":"

    Depending on whether their edges have a direction, graphs are classified as undirected graphs or directed graphs, as shown in Figure 9-2.

    • In an undirected graph, an edge represents a two-way connection between two vertices, such as a friendship on WeChat or QQ.
    • In a directed graph, edges have a direction: the edges \\(A \\rightarrow B\\) and \\(A \\leftarrow B\\) are independent of each other. Examples include following and being followed on Weibo or Douyin.

    Figure 9-2   Directed and undirected graphs

    Depending on whether all vertices are connected, graphs are classified as connected graphs or disconnected graphs, as shown in Figure 9-3.

    • In a connected graph, every other vertex can be reached from any starting vertex.
    • In a disconnected graph, at least one vertex cannot be reached from a given starting vertex.

    Figure 9-3   Connected and disconnected graphs

    We can also assign a weight to each edge to obtain a weighted graph, as shown in Figure 9-4. For example, mobile games such as Honor of Kings calculate an affinity score between players based on the time they spend playing together. A weighted graph can represent this network of affinities.

    Figure 9-4   Weighted and unweighted graphs

    The following terms are commonly used when discussing graphs.

    • Adjacency: Two vertices are adjacent if an edge connects them. In Figure 9-4, the vertices adjacent to vertex 1 are 2, 3, and 5.
    • Path: A sequence of edges followed from vertex A to vertex B is called a path from A to B. In Figure 9-4, the edge sequence 1-5-2-4 forms a path from vertex 1 to vertex 4.
    • Degree: The number of edges incident to a vertex. In a directed graph, the in-degree is the number of edges pointing to the vertex, and the out-degree is the number of edges pointing away from it.
    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#912-representing-a-graph","level":2,"title":"9.1.2   Representing a graph","text":"

    Two common ways to represent a graph are an adjacency matrix and an adjacency list. We will use undirected graphs in the following examples.

    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#1-adjacency-matrix","level":3,"title":"1.   Adjacency matrix","text":"

    For a graph with \\(n\\) vertices, an adjacency matrix represents the graph using an \\(n \\times n\\) matrix. Each row (or column) represents a vertex, and the matrix entries represent edges: \\(1\\) or \\(0\\) indicates whether an edge exists between two vertices.

    As shown in Figure 9-5, let \\(M\\) be the adjacency matrix and \\(V\\) the vertex list. The entry \\(M[i, j] = 1\\) means that an edge connects vertices \\(V[i]\\) and \\(V[j]\\), whereas \\(M[i, j] = 0\\) means that no edge connects them.

    Figure 9-5   Representing a graph with an adjacency matrix

    An adjacency matrix has the following properties.

    • In a simple graph, a vertex cannot connect to itself, so the entries on the main diagonal do not represent meaningful connections.
    • In an undirected graph, edges in the two directions are equivalent, so the adjacency matrix is symmetric about its main diagonal.
    • Replacing the \\(1\\) and \\(0\\) entries with weights allows the matrix to represent a weighted graph.

    With an adjacency matrix, we can access a matrix entry directly to retrieve an edge. Adding, removing, looking up, and modifying edges are therefore efficient, each taking \\(O(1)\\) time. However, the matrix requires \\(O(n^2)\\) space and uses a substantial amount of memory.

    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#2-adjacency-list","level":3,"title":"2.   Adjacency list","text":"

    An adjacency list represents a graph using \\(n\\) linked lists whose nodes represent vertices. The \\(i\\)-th linked list corresponds to vertex \\(i\\) and stores all of its adjacent vertices (the vertices connected to it). Figure 9-6 shows an example of a graph stored as an adjacency list.

    Figure 9-6   Representing a graph with an adjacency list

    An adjacency list stores only edges that actually exist. Since the total number of edges is usually much smaller than \\(n^2\\), it saves space. However, finding an edge requires traversing a linked list, so lookups are slower than with an adjacency matrix.

    As Figure 9-6 shows, an adjacency list closely resembles separate chaining in a hash table, so similar techniques can improve its efficiency. For example, a long linked list can be replaced with an AVL tree or a red-black tree to improve lookup time from \\(O(n)\\) to \\(O(\\log n)\\). Replacing it with a hash table can further reduce the time complexity to \\(O(1)\\).

    ","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph/#913-common-applications-of-graphs","level":2,"title":"9.1.3   Common applications of graphs","text":"

    As shown in Table 9-1, many real-world systems can be modeled as graphs, and their associated problems can be reduced to graph computation problems.

    Table 9-1   Common graphs in everyday life

    Vertices Edges Graph computation problem Social network Users Friendships Recommending potential friends Subway network Stations Connections between stations Recommending the shortest route Solar system Celestial bodies Gravitational interactions between bodies Calculating planetary orbits","path":["Chapter 9.   Graphs","9.1   Graphs"],"tags":[]},{"location":"chapter_graph/graph_operations/","level":1,"title":"9.2   Basic graph operations","text":"

    Basic graph operations fall into two categories: operations on edges and operations on vertices. Their implementations differ depending on whether the graph is represented by an adjacency matrix or an adjacency list.

    ","path":["Chapter 9.   Graphs","9.2   Basic graph operations"],"tags":[]},{"location":"chapter_graph/graph_operations/#921-adjacency-matrix-implementation","level":2,"title":"9.2.1   Adjacency matrix implementation","text":"

    For an undirected graph with \\(n\\) vertices, the operations are implemented as shown in Figure 9-7.

    • Add or remove an edge: Modify the corresponding entry in the adjacency matrix directly, taking \\(O(1)\\) time. Since the graph is undirected, update the edge in both directions.
    • Add a vertex: Append one row and one column to the adjacency matrix and fill them with \\(0\\), taking \\(O(n)\\) time.
    • Remove a vertex: Delete the corresponding row and column from the adjacency matrix. The worst case occurs when deleting the first row and column: \\((n-1)^2\\) entries must be shifted up and to the left, taking \\(O(n^2)\\) time.
    • Initialize the graph: Given \\(n\\) vertices, initialize the vertex list vertices of length \\(n\\) in \\(O(n)\\) time, then initialize the \\(n \\times n\\) adjacency matrix adjMat in \\(O(n^2)\\) time.
    <1><2><3><4><5>

    Figure 9-7   Initializing an adjacency matrix and adding or removing edges and vertices

    The following code implements a graph using an adjacency matrix:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_matrix.py
    class GraphAdjMat:\n    \"\"\"Undirected graph class implemented with an adjacency matrix\"\"\"\n\n    def __init__(self, vertices: list[int], edges: list[list[int]]):\n        \"\"\"Constructor\"\"\"\n        # Vertex list: elements are vertex values, and indices are vertex indices\n        self.vertices: list[int] = []\n        # Adjacency matrix: row and column indices correspond to vertex indices\n        self.adj_mat: list[list[int]] = []\n        # Add a vertex\n        for val in vertices:\n            self.add_vertex(val)\n        # Add an edge\n        # Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for e in edges:\n            self.add_edge(e[0], e[1])\n\n    def size(self) -> int:\n        \"\"\"Get the number of vertices\"\"\"\n        return len(self.vertices)\n\n    def add_vertex(self, val: int):\n        \"\"\"Add a vertex\"\"\"\n        n = self.size()\n        # Append the new vertex value to the vertex list\n        self.vertices.append(val)\n        # Add a row to the adjacency matrix\n        new_row = [0] * n\n        self.adj_mat.append(new_row)\n        # Add a column to the adjacency matrix\n        for row in self.adj_mat:\n            row.append(0)\n\n    def remove_vertex(self, index: int):\n        \"\"\"Remove a vertex\"\"\"\n        if index >= self.size():\n            raise IndexError()\n        # Remove the vertex at index `index` from the vertex list\n        self.vertices.pop(index)\n        # Remove row `index` from the adjacency matrix\n        self.adj_mat.pop(index)\n        # Remove column `index` from the adjacency matrix\n        for row in self.adj_mat:\n            row.pop(index)\n\n    def add_edge(self, i: int, j: int):\n        \"\"\"Add an edge\"\"\"\n        # Parameters i and j are element indices in vertices\n        # Handle out-of-bounds or equal indices\n        if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:\n            raise IndexError()\n        # For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        self.adj_mat[i][j] = 1\n        self.adj_mat[j][i] = 1\n\n    def remove_edge(self, i: int, j: int):\n        \"\"\"Remove an edge\"\"\"\n        # Parameters i and j are element indices in vertices\n        # Handle out-of-bounds or equal indices\n        if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j:\n            raise IndexError()\n        self.adj_mat[i][j] = 0\n        self.adj_mat[j][i] = 0\n\n    def print(self):\n        \"\"\"Print the adjacency matrix\"\"\"\n        print(\"Vertex list =\", self.vertices)\n        print(\"Adjacency matrix =\")\n        print_matrix(self.adj_mat)\n
    graph_adjacency_matrix.cpp
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    vector<int> vertices;       // Vertex list: elements are vertex values, and indices are vertex indices\n    vector<vector<int>> adjMat; // Adjacency matrix: row and column indices correspond to vertex indices\n\n  public:\n    /* Constructor */\n    GraphAdjMat(const vector<int> &vertices, const vector<vector<int>> &edges) {\n        // Add a vertex\n        for (int val : vertices) {\n            addVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (const vector<int> &edge : edges) {\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int size() const {\n        return vertices.size();\n    }\n\n    /* Add a vertex */\n    void addVertex(int val) {\n        int n = size();\n        // Append the new vertex value to the vertex list\n        vertices.push_back(val);\n        // Add a row to the adjacency matrix\n        adjMat.emplace_back(vector<int>(n, 0));\n        // Add a column to the adjacency matrix\n        for (vector<int> &row : adjMat) {\n            row.push_back(0);\n        }\n    }\n\n    /* Remove a vertex */\n    void removeVertex(int index) {\n        if (index >= size()) {\n            throw out_of_range(\"Invalid vertex\");\n        }\n        // Remove the vertex at index `index` from the vertex list\n        vertices.erase(vertices.begin() + index);\n        // Remove row `index` from the adjacency matrix\n        adjMat.erase(adjMat.begin() + index);\n        // Remove column `index` from the adjacency matrix\n        for (vector<int> &row : adjMat) {\n            row.erase(row.begin() + index);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    void addEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n            throw out_of_range(\"Invalid vertex\");\n        }\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat[i][j] = 1;\n        adjMat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    void removeEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n            throw out_of_range(\"Invalid vertex\");\n        }\n        adjMat[i][j] = 0;\n        adjMat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    void print() {\n        cout << \"Vertex list = \";\n        printVector(vertices);\n        cout << \"Adjacency matrix =\" << endl;\n        printVectorMatrix(adjMat);\n    }\n};\n
    graph_adjacency_matrix.java
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    List<Integer> vertices; // Vertex list: elements are vertex values, and indices are vertex indices\n    List<List<Integer>> adjMat; // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    public GraphAdjMat(int[] vertices, int[][] edges) {\n        this.vertices = new ArrayList<>();\n        this.adjMat = new ArrayList<>();\n        // Add a vertex\n        for (int val : vertices) {\n            addVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (int[] e : edges) {\n            addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    public int size() {\n        return vertices.size();\n    }\n\n    /* Add a vertex */\n    public void addVertex(int val) {\n        int n = size();\n        // Append the new vertex value to the vertex list\n        vertices.add(val);\n        // Add a row to the adjacency matrix\n        List<Integer> newRow = new ArrayList<>(n);\n        for (int j = 0; j < n; j++) {\n            newRow.add(0);\n        }\n        adjMat.add(newRow);\n        // Add a column to the adjacency matrix\n        for (List<Integer> row : adjMat) {\n            row.add(0);\n        }\n    }\n\n    /* Remove a vertex */\n    public void removeVertex(int index) {\n        if (index >= size())\n            throw new IndexOutOfBoundsException();\n        // Remove the vertex at index `index` from the vertex list\n        vertices.remove(index);\n        // Remove row `index` from the adjacency matrix\n        adjMat.remove(index);\n        // Remove column `index` from the adjacency matrix\n        for (List<Integer> row : adjMat) {\n            row.remove(index);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    public void addEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw new IndexOutOfBoundsException();\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat.get(i).set(j, 1);\n        adjMat.get(j).set(i, 1);\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    public void removeEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw new IndexOutOfBoundsException();\n        adjMat.get(i).set(j, 0);\n        adjMat.get(j).set(i, 0);\n    }\n\n    /* Print the adjacency matrix */\n    public void print() {\n        System.out.print(\"Vertex list = \");\n        System.out.println(vertices);\n        System.out.println(\"Adjacency matrix =\");\n        PrintUtil.printMatrix(adjMat);\n    }\n}\n
    graph_adjacency_matrix.cs
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    List<int> vertices;     // Vertex list: elements are vertex values, and indices are vertex indices\n    List<List<int>> adjMat; // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    public GraphAdjMat(int[] vertices, int[][] edges) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add a vertex\n        foreach (int val in vertices) {\n            AddVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        foreach (int[] e in edges) {\n            AddEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int Size() {\n        return vertices.Count;\n    }\n\n    /* Add a vertex */\n    public void AddVertex(int val) {\n        int n = Size();\n        // Append the new vertex value to the vertex list\n        vertices.Add(val);\n        // Add a row to the adjacency matrix\n        List<int> newRow = new(n);\n        for (int j = 0; j < n; j++) {\n            newRow.Add(0);\n        }\n        adjMat.Add(newRow);\n        // Add a column to the adjacency matrix\n        foreach (List<int> row in adjMat) {\n            row.Add(0);\n        }\n    }\n\n    /* Remove a vertex */\n    public void RemoveVertex(int index) {\n        if (index >= Size())\n            throw new IndexOutOfRangeException();\n        // Remove the vertex at index `index` from the vertex list\n        vertices.RemoveAt(index);\n        // Remove row `index` from the adjacency matrix\n        adjMat.RemoveAt(index);\n        // Remove column `index` from the adjacency matrix\n        foreach (List<int> row in adjMat) {\n            row.RemoveAt(index);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    public void AddEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j)\n            throw new IndexOutOfRangeException();\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat[i][j] = 1;\n        adjMat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    public void RemoveEdge(int i, int j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j)\n            throw new IndexOutOfRangeException();\n        adjMat[i][j] = 0;\n        adjMat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    public void Print() {\n        Console.Write(\"Vertex list = \");\n        PrintUtil.PrintList(vertices);\n        Console.WriteLine(\"Adjacency matrix =\");\n        PrintUtil.PrintMatrix(adjMat);\n    }\n}\n
    graph_adjacency_matrix.go
    /* Undirected graph type implemented with an adjacency matrix */\ntype graphAdjMat struct {\n    // Vertex list: elements are vertex values, and indices are vertex indices\n    vertices []int\n    // Adjacency matrix: row and column indices correspond to vertex indices\n    adjMat [][]int\n}\n\n/* Constructor */\nfunc newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat {\n    // Allocate the adjacency matrix for all vertices\n    n := len(vertices)\n    adjMat := make([][]int, n)\n    for i := range adjMat {\n        adjMat[i] = make([]int, n)\n    }\n    // Initialize the graph\n    g := &graphAdjMat{\n        vertices: vertices,\n        adjMat:   adjMat,\n    }\n    // Add all edges\n    // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n    for i := range edges {\n        g.addEdge(edges[i][0], edges[i][1])\n    }\n    return g\n}\n\n/* Get the number of vertices */\nfunc (g *graphAdjMat) size() int {\n    return len(g.vertices)\n}\n\n/* Add a vertex */\nfunc (g *graphAdjMat) addVertex(val int) {\n    n := g.size()\n    // Append the new vertex value to the vertex list\n    g.vertices = append(g.vertices, val)\n    // Add a row to the adjacency matrix\n    newRow := make([]int, n)\n    g.adjMat = append(g.adjMat, newRow)\n    // Add a column to the adjacency matrix\n    for i := range g.adjMat {\n        g.adjMat[i] = append(g.adjMat[i], 0)\n    }\n}\n\n/* Remove a vertex */\nfunc (g *graphAdjMat) removeVertex(index int) {\n    if index >= g.size() {\n        return\n    }\n    // Remove the vertex at index `index` from the vertex list\n    g.vertices = append(g.vertices[:index], g.vertices[index+1:]...)\n    // Remove row `index` from the adjacency matrix\n    g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...)\n    // Remove column `index` from the adjacency matrix\n    for i := range g.adjMat {\n        g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...)\n    }\n}\n\n/* Add an edge */\n// Parameters i and j are element indices in vertices\nfunc (g *graphAdjMat) addEdge(i, j int) {\n    // Check for out-of-bounds or equal indices (the constructed error is not returned)\n    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\n        fmt.Errorf(\"%s\", \"Index out of bounds\")\n    }\n    // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n    g.adjMat[i][j] = 1\n    g.adjMat[j][i] = 1\n}\n\n/* Remove an edge */\n// Parameters i and j are element indices in vertices\nfunc (g *graphAdjMat) removeEdge(i, j int) {\n    // Check for out-of-bounds or equal indices (the constructed error is not returned)\n    if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j {\n        fmt.Errorf(\"%s\", \"Index out of bounds\")\n    }\n    g.adjMat[i][j] = 0\n    g.adjMat[j][i] = 0\n}\n\n/* Print the adjacency matrix */\nfunc (g *graphAdjMat) print() {\n    fmt.Printf(\"\\tVertex list = %v\\n\", g.vertices)\n    fmt.Printf(\"\\tAdjacency matrix = \\n\")\n    for i := range g.adjMat {\n        fmt.Printf(\"\\t\\t\\t%v\\n\", g.adjMat[i])\n    }\n}\n
    graph_adjacency_matrix.swift
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    private var vertices: [Int] // Vertex list: elements are vertex values, and indices are vertex indices\n    private var adjMat: [[Int]] // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    init(vertices: [Int], edges: [[Int]]) {\n        self.vertices = []\n        adjMat = []\n        // Add all vertices\n        for val in vertices {\n            addVertex(val: val)\n        }\n        // Add all edges\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for e in edges {\n            addEdge(i: e[0], j: e[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    func size() -> Int {\n        vertices.count\n    }\n\n    /* Add a vertex */\n    func addVertex(val: Int) {\n        let n = size()\n        // Append the new vertex value to the vertex list\n        vertices.append(val)\n        // Add a row to the adjacency matrix\n        let newRow = Array(repeating: 0, count: n)\n        adjMat.append(newRow)\n        // Add a column to the adjacency matrix\n        for i in adjMat.indices {\n            adjMat[i].append(0)\n        }\n    }\n\n    /* Remove a vertex */\n    func removeVertex(index: Int) {\n        if index >= size() {\n            fatalError(\"Invalid index\")\n        }\n        // Remove the vertex at index `index` from the vertex list\n        vertices.remove(at: index)\n        // Remove row `index` from the adjacency matrix\n        adjMat.remove(at: index)\n        // Remove column `index` from the adjacency matrix\n        for i in adjMat.indices {\n            adjMat[i].remove(at: index)\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    func addEdge(i: Int, j: Int) {\n        // Handle out-of-bounds or equal indices\n        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {\n            fatalError(\"Invalid index\")\n        }\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat[i][j] = 1\n        adjMat[j][i] = 1\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    func removeEdge(i: Int, j: Int) {\n        // Handle out-of-bounds or equal indices\n        if i < 0 || j < 0 || i >= size() || j >= size() || i == j {\n            fatalError(\"Invalid index\")\n        }\n        adjMat[i][j] = 0\n        adjMat[j][i] = 0\n    }\n\n    /* Print the adjacency matrix */\n    func print() {\n        Swift.print(\"Vertex list = \", terminator: \"\")\n        Swift.print(vertices)\n        Swift.print(\"Adjacency matrix =\")\n        PrintUtil.printMatrix(matrix: adjMat)\n    }\n}\n
    graph_adjacency_matrix.js
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    vertices; // Vertex list: elements are vertex values, and indices are vertex indices\n    adjMat; // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    constructor(vertices, edges) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add a vertex\n        for (const val of vertices) {\n            this.addVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (const e of edges) {\n            this.addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size() {\n        return this.vertices.length;\n    }\n\n    /* Add a vertex */\n    addVertex(val) {\n        const n = this.size();\n        // Append the new vertex value to the vertex list\n        this.vertices.push(val);\n        // Add a row to the adjacency matrix\n        const newRow = [];\n        for (let j = 0; j < n; j++) {\n            newRow.push(0);\n        }\n        this.adjMat.push(newRow);\n        // Add a column to the adjacency matrix\n        for (const row of this.adjMat) {\n            row.push(0);\n        }\n    }\n\n    /* Remove a vertex */\n    removeVertex(index) {\n        if (index >= this.size()) {\n            throw new RangeError('Index out of bounds');\n        }\n        // Remove the vertex at index `index` from the vertex list\n        this.vertices.splice(index, 1);\n\n        // Remove row `index` from the adjacency matrix\n        this.adjMat.splice(index, 1);\n        // Remove column `index` from the adjacency matrix\n        for (const row of this.adjMat) {\n            row.splice(index, 1);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    addEdge(i, j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index out of bounds');\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about its main diagonal: (i, j) === (j, i)\n        this.adjMat[i][j] = 1;\n        this.adjMat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    removeEdge(i, j) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index out of bounds');\n        }\n        this.adjMat[i][j] = 0;\n        this.adjMat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    print() {\n        console.log('Vertex list = ', this.vertices);\n        console.log('Adjacency matrix =', this.adjMat);\n    }\n}\n
    graph_adjacency_matrix.ts
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n    vertices: number[]; // Vertex list: elements are vertex values, and indices are vertex indices\n    adjMat: number[][]; // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    constructor(vertices: number[], edges: number[][]) {\n        this.vertices = [];\n        this.adjMat = [];\n        // Add a vertex\n        for (const val of vertices) {\n            this.addVertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (const e of edges) {\n            this.addEdge(e[0], e[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size(): number {\n        return this.vertices.length;\n    }\n\n    /* Add a vertex */\n    addVertex(val: number): void {\n        const n: number = this.size();\n        // Append the new vertex value to the vertex list\n        this.vertices.push(val);\n        // Add a row to the adjacency matrix\n        const newRow: number[] = [];\n        for (let j: number = 0; j < n; j++) {\n            newRow.push(0);\n        }\n        this.adjMat.push(newRow);\n        // Add a column to the adjacency matrix\n        for (const row of this.adjMat) {\n            row.push(0);\n        }\n    }\n\n    /* Remove a vertex */\n    removeVertex(index: number): void {\n        if (index >= this.size()) {\n            throw new RangeError('Index out of bounds');\n        }\n        // Remove the vertex at index `index` from the vertex list\n        this.vertices.splice(index, 1);\n\n        // Remove row `index` from the adjacency matrix\n        this.adjMat.splice(index, 1);\n        // Remove column `index` from the adjacency matrix\n        for (const row of this.adjMat) {\n            row.splice(index, 1);\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    addEdge(i: number, j: number): void {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index out of bounds');\n        }\n        // In an undirected graph, the adjacency matrix is symmetric about its main diagonal: (i, j) === (j, i)\n        this.adjMat[i][j] = 1;\n        this.adjMat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    removeEdge(i: number, j: number): void {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) {\n            throw new RangeError('Index out of bounds');\n        }\n        this.adjMat[i][j] = 0;\n        this.adjMat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    print(): void {\n        console.log('Vertex list = ', this.vertices);\n        console.log('Adjacency matrix =', this.adjMat);\n    }\n}\n
    graph_adjacency_matrix.dart
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat {\n  List<int> vertices = []; // Vertex entries: each element is a vertex value, and its index is the vertex index\n  List<List<int>> adjMat = []; //Adjacency matrix: row and column indices correspond to vertex indices\n\n  /* Constructor */\n  GraphAdjMat(List<int> vertices, List<List<int>> edges) {\n    this.vertices = [];\n    this.adjMat = [];\n    // Add a vertex\n    for (int val in vertices) {\n      addVertex(val);\n    }\n    // Add an edge\n    // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n    for (List<int> e in edges) {\n      addEdge(e[0], e[1]);\n    }\n  }\n\n  /* Get the number of vertices */\n  int size() {\n    return vertices.length;\n  }\n\n  /* Add a vertex */\n  void addVertex(int val) {\n    int n = size();\n    // Append the new vertex value to the vertex list\n    vertices.add(val);\n    // Add a row to the adjacency matrix\n    List<int> newRow = List.filled(n, 0, growable: true);\n    adjMat.add(newRow);\n    // Add a column to the adjacency matrix\n    for (List<int> row in adjMat) {\n      row.add(0);\n    }\n  }\n\n  /* Remove a vertex */\n  void removeVertex(int index) {\n    if (index >= size()) {\n      throw IndexError;\n    }\n    // Remove the vertex at index `index` from the vertex list\n    vertices.removeAt(index);\n    // Remove row `index` from the adjacency matrix\n    adjMat.removeAt(index);\n    // Remove column `index` from the adjacency matrix\n    for (List<int> row in adjMat) {\n      row.removeAt(index);\n    }\n  }\n\n  /* Add an edge */\n  // Parameters i and j are element indices in vertices\n  void addEdge(int i, int j) {\n    // Handle out-of-bounds or equal indices\n    if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n      throw IndexError;\n    }\n    // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n    adjMat[i][j] = 1;\n    adjMat[j][i] = 1;\n  }\n\n  /* Remove an edge */\n  // Parameters i and j are element indices in vertices\n  void removeEdge(int i, int j) {\n    // Handle out-of-bounds or equal indices\n    if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {\n      throw IndexError;\n    }\n    adjMat[i][j] = 0;\n    adjMat[j][i] = 0;\n  }\n\n  /* Print the adjacency matrix */\n  void printAdjMat() {\n    print(\"Vertex list = $vertices\");\n    print(\"Adjacency matrix = \");\n    printMatrix(adjMat);\n  }\n}\n
    graph_adjacency_matrix.rs
    /* Undirected graph type implemented with an adjacency matrix */\npub struct GraphAdjMat {\n    // Vertex list: elements are vertex values, and indices are vertex indices\n    pub vertices: Vec<i32>,\n    // Adjacency matrix: row and column indices correspond to vertex indices\n    pub adj_mat: Vec<Vec<i32>>,\n}\n\nimpl GraphAdjMat {\n    /* Constructor */\n    pub fn new(vertices: Vec<i32>, edges: Vec<[usize; 2]>) -> Self {\n        let mut graph = GraphAdjMat {\n            vertices: vec![],\n            adj_mat: vec![],\n        };\n        // Add a vertex\n        for val in vertices {\n            graph.add_vertex(val);\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for edge in edges {\n            graph.add_edge(edge[0], edge[1])\n        }\n\n        graph\n    }\n\n    /* Get the number of vertices */\n    pub fn size(&self) -> usize {\n        self.vertices.len()\n    }\n\n    /* Add a vertex */\n    pub fn add_vertex(&mut self, val: i32) {\n        let n = self.size();\n        // Append the new vertex value to the vertex list\n        self.vertices.push(val);\n        // Add a row to the adjacency matrix\n        self.adj_mat.push(vec![0; n]);\n        // Add a column to the adjacency matrix\n        for row in self.adj_mat.iter_mut() {\n            row.push(0);\n        }\n    }\n\n    /* Remove a vertex */\n    pub fn remove_vertex(&mut self, index: usize) {\n        if index >= self.size() {\n            panic!(\"Invalid vertex index\")\n        }\n        // Remove the vertex at index `index` from the vertex list\n        self.vertices.remove(index);\n        // Remove row `index` from the adjacency matrix\n        self.adj_mat.remove(index);\n        // Remove column `index` from the adjacency matrix\n        for row in self.adj_mat.iter_mut() {\n            row.remove(index);\n        }\n    }\n\n    /* Add an edge */\n    pub fn add_edge(&mut self, i: usize, j: usize) {\n        // Parameters i and j are element indices in vertices\n        // Handle out-of-bounds or equal indices\n        if i >= self.size() || j >= self.size() || i == j {\n            panic!(\"Invalid vertex index\")\n        }\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        self.adj_mat[i][j] = 1;\n        self.adj_mat[j][i] = 1;\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    pub fn remove_edge(&mut self, i: usize, j: usize) {\n        // Parameters i and j are element indices in vertices\n        // Handle out-of-bounds or equal indices\n        if i >= self.size() || j >= self.size() || i == j {\n            panic!(\"Invalid vertex index\")\n        }\n        self.adj_mat[i][j] = 0;\n        self.adj_mat[j][i] = 0;\n    }\n\n    /* Print the adjacency matrix */\n    pub fn print(&self) {\n        println!(\"Vertex list = {:?}\", self.vertices);\n        println!(\"Adjacency matrix =\");\n        println!(\"[\");\n        for row in &self.adj_mat {\n            println!(\"  {:?},\", row);\n        }\n        println!(\"]\")\n    }\n}\n
    graph_adjacency_matrix.c
    /* Undirected graph structure implemented with an adjacency matrix */\ntypedef struct {\n    int vertices[MAX_SIZE];\n    int adjMat[MAX_SIZE][MAX_SIZE];\n    int size;\n} GraphAdjMat;\n\n/* Constructor */\nGraphAdjMat *newGraphAdjMat() {\n    GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat));\n    graph->size = 0;\n    for (int i = 0; i < MAX_SIZE; i++) {\n        for (int j = 0; j < MAX_SIZE; j++) {\n            graph->adjMat[i][j] = 0;\n        }\n    }\n    return graph;\n}\n\n/* Destructor */\nvoid delGraphAdjMat(GraphAdjMat *graph) {\n    free(graph);\n}\n\n/* Add a vertex */\nvoid addVertex(GraphAdjMat *graph, int val) {\n    if (graph->size == MAX_SIZE) {\n        fprintf(stderr, \"The graph has reached its maximum number of vertices\\n\");\n        return;\n    }\n    // Add the vertex at index n and set row n and column n to zero\n    int n = graph->size;\n    graph->vertices[n] = val;\n    for (int i = 0; i <= n; i++) {\n        graph->adjMat[n][i] = graph->adjMat[i][n] = 0;\n    }\n    graph->size++;\n}\n\n/* Remove a vertex */\nvoid removeVertex(GraphAdjMat *graph, int index) {\n    if (index < 0 || index >= graph->size) {\n        fprintf(stderr, \"Vertex index out of bounds\\n\");\n        return;\n    }\n    // Remove the vertex at index `index` from the vertex list\n    for (int i = index; i < graph->size - 1; i++) {\n        graph->vertices[i] = graph->vertices[i + 1];\n    }\n    // Remove row `index` from the adjacency matrix\n    for (int i = index; i < graph->size - 1; i++) {\n        for (int j = 0; j < graph->size; j++) {\n            graph->adjMat[i][j] = graph->adjMat[i + 1][j];\n        }\n    }\n    // Remove column `index` from the adjacency matrix\n    for (int i = 0; i < graph->size; i++) {\n        for (int j = index; j < graph->size - 1; j++) {\n            graph->adjMat[i][j] = graph->adjMat[i][j + 1];\n        }\n    }\n    graph->size--;\n}\n\n/* Add an edge */\n// Parameters i and j are element indices in vertices\nvoid addEdge(GraphAdjMat *graph, int i, int j) {\n    if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {\n        fprintf(stderr, \"Edge endpoint indices are out of bounds or equal\\n\");\n        return;\n    }\n    graph->adjMat[i][j] = 1;\n    graph->adjMat[j][i] = 1;\n}\n\n/* Remove an edge */\n// Parameters i and j are element indices in vertices\nvoid removeEdge(GraphAdjMat *graph, int i, int j) {\n    if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {\n        fprintf(stderr, \"Edge endpoint indices are out of bounds or equal\\n\");\n        return;\n    }\n    graph->adjMat[i][j] = 0;\n    graph->adjMat[j][i] = 0;\n}\n\n/* Print the adjacency matrix */\nvoid printGraphAdjMat(GraphAdjMat *graph) {\n    printf(\"Vertex list = \");\n    printArray(graph->vertices, graph->size);\n    printf(\"Adjacency matrix =\\n\");\n    for (int i = 0; i < graph->size; i++) {\n        printArray(graph->adjMat[i], graph->size);\n    }\n}\n
    graph_adjacency_matrix.kt
    /* Undirected graph class implemented with an adjacency matrix */\nclass GraphAdjMat(vertices: IntArray, edges: Array<IntArray>) {\n    val vertices = mutableListOf<Int>() // Vertex list: elements are vertex values, and indices are vertex indices\n    val adjMat = mutableListOf<MutableList<Int>>() // Adjacency matrix: row and column indices correspond to vertex indices\n\n    /* Constructor */\n    init {\n        // Add a vertex\n        for (vertex in vertices) {\n            addVertex(vertex)\n        }\n        // Add an edge\n        // Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n        for (edge in edges) {\n            addEdge(edge[0], edge[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    fun size(): Int {\n        return vertices.size\n    }\n\n    /* Add a vertex */\n    fun addVertex(_val: Int) {\n        val n = size()\n        // Append the new vertex value to the vertex list\n        vertices.add(_val)\n        // Add a row to the adjacency matrix\n        val newRow = mutableListOf<Int>()\n        for (j in 0..<n) {\n            newRow.add(0)\n        }\n        adjMat.add(newRow)\n        // Add a column to the adjacency matrix\n        for (row in adjMat) {\n            row.add(0)\n        }\n    }\n\n    /* Remove a vertex */\n    fun removeVertex(index: Int) {\n        if (index >= size())\n            throw IndexOutOfBoundsException()\n        // Remove the vertex at index `index` from the vertex list\n        vertices.removeAt(index)\n        // Remove row `index` from the adjacency matrix\n        adjMat.removeAt(index)\n        // Remove column `index` from the adjacency matrix\n        for (row in adjMat) {\n            row.removeAt(index)\n        }\n    }\n\n    /* Add an edge */\n    // Parameters i and j are element indices in vertices\n    fun addEdge(i: Int, j: Int) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw IndexOutOfBoundsException()\n        // For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n        adjMat[i][j] = 1\n        adjMat[j][i] = 1\n    }\n\n    /* Remove an edge */\n    // Parameters i and j are element indices in vertices\n    fun removeEdge(i: Int, j: Int) {\n        // Handle out-of-bounds or equal indices\n        if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)\n            throw IndexOutOfBoundsException()\n        adjMat[i][j] = 0\n        adjMat[j][i] = 0\n    }\n\n    /* Print the adjacency matrix */\n    fun print() {\n        print(\"Vertex list = \")\n        println(vertices)\n        println(\"Adjacency matrix =\")\n        printMatrix(adjMat)\n    }\n}\n
    graph_adjacency_matrix.rb
    ### Undirected graph class implemented with an adjacency matrix ###\nclass GraphAdjMat\n  def initialize(vertices, edges)\n    ### Constructor ###\n    # Vertex list: elements are vertex values, and indices are vertex indices\n    @vertices = []\n    # Adjacency matrix: row and column indices correspond to vertex indices\n    @adj_mat = []\n    # Add a vertex\n    vertices.each { |val| add_vertex(val) }\n    # Add an edge\n    # Note that the entries in edges are vertex indices, corresponding to element indices in vertices\n    edges.each { |e| add_edge(e[0], e[1]) }\n  end\n\n  ### Get the number of vertices ###\n  def size\n    @vertices.length\n  end\n\n  ### Add a vertex ###\n  def add_vertex(val)\n    n = size\n    # Append the new vertex value to the vertex list\n    @vertices << val\n    # Add a row to the adjacency matrix\n    new_row = Array.new(n, 0)\n    @adj_mat << new_row\n    # Add a column to the adjacency matrix\n    @adj_mat.each { |row| row << 0 }\n  end\n\n  ### Remove a vertex ###\n  def remove_vertex(index)\n    raise IndexError if index >= size\n\n    # Remove the vertex at index `index` from the vertex list\n    @vertices.delete_at(index)\n    # Remove row `index` from the adjacency matrix\n    @adj_mat.delete_at(index)\n    # Remove column `index` from the adjacency matrix\n    @adj_mat.each { |row| row.delete_at(index) }\n  end\n\n  ### Add an edge ###\n  def add_edge(i, j)\n    # Parameters i and j are element indices in vertices\n    # Handle out-of-bounds or equal indices\n    if i < 0 || j < 0 || i >= size || j >= size || i == j\n      raise IndexError\n    end\n    # For an undirected graph, the adjacency matrix is symmetric about its main diagonal: entries (i, j) and (j, i) are equal\n    @adj_mat[i][j] = 1\n    @adj_mat[j][i] = 1\n  end\n\n  ### Remove an edge ###\n  def remove_edge(i, j)\n    # Parameters i and j are element indices in vertices\n    # Handle out-of-bounds or equal indices\n    if i < 0 || j < 0 || i >= size || j >= size || i == j\n      raise IndexError\n    end\n    @adj_mat[i][j] = 0\n    @adj_mat[j][i] = 0\n  end\n\n  ### Print the adjacency matrix ###\n  def __print__\n    puts \"Vertex list = #{@vertices}\"\n    puts 'Adjacency matrix ='\n    print_matrix(@adj_mat)\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 9.   Graphs","9.2   Basic graph operations"],"tags":[]},{"location":"chapter_graph/graph_operations/#922-adjacency-list-implementation","level":2,"title":"9.2.2   Adjacency list implementation","text":"

    For an undirected graph with \\(n\\) vertices and \\(m\\) edges, the operations can be implemented as shown in Figure 9-8.

    • Add an edge: Append the edge to the linked list of the corresponding vertex, taking \\(O(1)\\) time. Since the graph is undirected, add the edge in both directions.
    • Remove an edge: Find and remove the specified edge in the corresponding vertex's linked list, taking \\(O(m)\\) time. For an undirected graph, remove the edge in both directions.
    • Add a vertex: Add a linked list to the adjacency list with the new vertex as its head node, taking \\(O(1)\\) time.
    • Remove a vertex: Traverse the entire adjacency list and remove every edge involving the specified vertex, taking \\(O(n + m)\\) time.
    • Initialize the graph: Create \\(n\\) vertices and \\(2m\\) edge entries in the adjacency list, taking \\(O(n + m)\\) time.
    <1><2><3><4><5>

    Figure 9-8   Initializing an adjacency list and adding or removing edges and vertices

    The following code implements an adjacency list, with these differences from Figure 9-8.

    • To make adding and removing vertices easier and simplify the code, we use lists (dynamic arrays) in place of linked lists.
    • A hash table stores the adjacency list. Each key is a vertex instance, and its value is the list (linked list) of adjacent vertices.

    We also use the Vertex class to represent vertices in the adjacency list. If we identified vertices by list indices, as in the adjacency matrix, deleting the vertex at index \\(i\\) would require traversing the entire adjacency list and subtracting \\(1\\) from every index greater than \\(i\\). This would be inefficient. When each vertex is a unique Vertex instance, deleting one vertex requires no changes to the other vertices.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_list.py
    class GraphAdjList:\n    \"\"\"Undirected graph class implemented with an adjacency list\"\"\"\n\n    def __init__(self, edges: list[list[Vertex]]):\n        \"\"\"Constructor\"\"\"\n        # Adjacency list: key is a vertex, value is the list of all its neighbors\n        self.adj_list = dict[Vertex, list[Vertex]]()\n        # Add all vertices and edges\n        for edge in edges:\n            self.add_vertex(edge[0])\n            self.add_vertex(edge[1])\n            self.add_edge(edge[0], edge[1])\n\n    def size(self) -> int:\n        \"\"\"Get the number of vertices\"\"\"\n        return len(self.adj_list)\n\n    def add_edge(self, vet1: Vertex, vet2: Vertex):\n        \"\"\"Add an edge\"\"\"\n        if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n            raise ValueError()\n        # Add edge vet1 - vet2\n        self.adj_list[vet1].append(vet2)\n        self.adj_list[vet2].append(vet1)\n\n    def remove_edge(self, vet1: Vertex, vet2: Vertex):\n        \"\"\"Remove an edge\"\"\"\n        if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n            raise ValueError()\n        # Remove edge vet1 - vet2\n        self.adj_list[vet1].remove(vet2)\n        self.adj_list[vet2].remove(vet1)\n\n    def add_vertex(self, vet: Vertex):\n        \"\"\"Add a vertex\"\"\"\n        if vet in self.adj_list:\n            return\n        # Add a new list to the adjacency list\n        self.adj_list[vet] = []\n\n    def remove_vertex(self, vet: Vertex):\n        \"\"\"Remove a vertex\"\"\"\n        if vet not in self.adj_list:\n            raise ValueError()\n        # Remove the list corresponding to vertex vet from the adjacency list\n        self.adj_list.pop(vet)\n        # Traverse the lists of the other vertices and remove all edges incident to vet\n        for vertex in self.adj_list:\n            if vet in self.adj_list[vertex]:\n                self.adj_list[vertex].remove(vet)\n\n    def print(self):\n        \"\"\"Print the adjacency list\"\"\"\n        print(\"Adjacency list =\")\n        for vertex in self.adj_list:\n            tmp = [v.val for v in self.adj_list[vertex]]\n            print(f\"{vertex.val}: {tmp},\")\n
    graph_adjacency_list.cpp
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n  public:\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    unordered_map<Vertex *, vector<Vertex *>> adjList;\n\n    /* Remove the specified vertex from the vector */\n    void remove(vector<Vertex *> &vec, Vertex *vet) {\n        for (int i = 0; i < vec.size(); i++) {\n            if (vec[i] == vet) {\n                vec.erase(vec.begin() + i);\n                break;\n            }\n        }\n    }\n\n    /* Constructor */\n    GraphAdjList(const vector<vector<Vertex *>> &edges) {\n        // Add all vertices and edges\n        for (const vector<Vertex *> &edge : edges) {\n            addVertex(edge[0]);\n            addVertex(edge[1]);\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int size() {\n        return adjList.size();\n    }\n\n    /* Add an edge */\n    void addEdge(Vertex *vet1, Vertex *vet2) {\n        if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n            throw invalid_argument(\"Invalid vertex\");\n        // Add edge vet1 - vet2\n        adjList[vet1].push_back(vet2);\n        adjList[vet2].push_back(vet1);\n    }\n\n    /* Remove an edge */\n    void removeEdge(Vertex *vet1, Vertex *vet2) {\n        if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n            throw invalid_argument(\"Invalid vertex\");\n        // Remove edge vet1 - vet2\n        remove(adjList[vet1], vet2);\n        remove(adjList[vet2], vet1);\n    }\n\n    /* Add a vertex */\n    void addVertex(Vertex *vet) {\n        if (adjList.count(vet))\n            return;\n        // Add a new list to the adjacency list\n        adjList[vet] = vector<Vertex *>();\n    }\n\n    /* Remove a vertex */\n    void removeVertex(Vertex *vet) {\n        if (!adjList.count(vet))\n            throw invalid_argument(\"Invalid vertex\");\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.erase(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (auto &adj : adjList) {\n            remove(adj.second, vet);\n        }\n    }\n\n    /* Print the adjacency list */\n    void print() {\n        cout << \"Adjacency list =\" << endl;\n        for (auto &adj : adjList) {\n            const auto &key = adj.first;\n            const auto &vec = adj.second;\n            cout << key->val << \": \";\n            printVector(vetsToVals(vec));\n        }\n    }\n};\n
    graph_adjacency_list.java
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    Map<Vertex, List<Vertex>> adjList;\n\n    /* Constructor */\n    public GraphAdjList(Vertex[][] edges) {\n        this.adjList = new HashMap<>();\n        // Add all vertices and edges\n        for (Vertex[] edge : edges) {\n            addVertex(edge[0]);\n            addVertex(edge[1]);\n            addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    public int size() {\n        return adjList.size();\n    }\n\n    /* Add an edge */\n    public void addEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw new IllegalArgumentException();\n        // Add edge vet1 - vet2\n        adjList.get(vet1).add(vet2);\n        adjList.get(vet2).add(vet1);\n    }\n\n    /* Remove an edge */\n    public void removeEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw new IllegalArgumentException();\n        // Remove edge vet1 - vet2\n        adjList.get(vet1).remove(vet2);\n        adjList.get(vet2).remove(vet1);\n    }\n\n    /* Add a vertex */\n    public void addVertex(Vertex vet) {\n        if (adjList.containsKey(vet))\n            return;\n        // Add a new list to the adjacency list\n        adjList.put(vet, new ArrayList<>());\n    }\n\n    /* Remove a vertex */\n    public void removeVertex(Vertex vet) {\n        if (!adjList.containsKey(vet))\n            throw new IllegalArgumentException();\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.remove(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (List<Vertex> list : adjList.values()) {\n            list.remove(vet);\n        }\n    }\n\n    /* Print the adjacency list */\n    public void print() {\n        System.out.println(\"Adjacency list =\");\n        for (Map.Entry<Vertex, List<Vertex>> pair : adjList.entrySet()) {\n            List<Integer> tmp = new ArrayList<>();\n            for (Vertex vertex : pair.getValue())\n                tmp.add(vertex.val);\n            System.out.println(pair.getKey().val + \": \" + tmp + \",\");\n        }\n    }\n}\n
    graph_adjacency_list.cs
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    public Dictionary<Vertex, List<Vertex>> adjList;\n\n    /* Constructor */\n    public GraphAdjList(Vertex[][] edges) {\n        adjList = [];\n        // Add all vertices and edges\n        foreach (Vertex[] edge in edges) {\n            AddVertex(edge[0]);\n            AddVertex(edge[1]);\n            AddEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    int Size() {\n        return adjList.Count;\n    }\n\n    /* Add an edge */\n    public void AddEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n            throw new InvalidOperationException();\n        // Add edge vet1 - vet2\n        adjList[vet1].Add(vet2);\n        adjList[vet2].Add(vet1);\n    }\n\n    /* Remove an edge */\n    public void RemoveEdge(Vertex vet1, Vertex vet2) {\n        if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n            throw new InvalidOperationException();\n        // Remove edge vet1 - vet2\n        adjList[vet1].Remove(vet2);\n        adjList[vet2].Remove(vet1);\n    }\n\n    /* Add a vertex */\n    public void AddVertex(Vertex vet) {\n        if (adjList.ContainsKey(vet))\n            return;\n        // Add a new list to the adjacency list\n        adjList.Add(vet, []);\n    }\n\n    /* Remove a vertex */\n    public void RemoveVertex(Vertex vet) {\n        if (!adjList.ContainsKey(vet))\n            throw new InvalidOperationException();\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.Remove(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        foreach (List<Vertex> list in adjList.Values) {\n            list.Remove(vet);\n        }\n    }\n\n    /* Print the adjacency list */\n    public void Print() {\n        Console.WriteLine(\"Adjacency list =\");\n        foreach (KeyValuePair<Vertex, List<Vertex>> pair in adjList) {\n            List<int> tmp = [];\n            foreach (Vertex vertex in pair.Value)\n                tmp.Add(vertex.val);\n            Console.WriteLine(pair.Key.val + \": [\" + string.Join(\", \", tmp) + \"],\");\n        }\n    }\n}\n
    graph_adjacency_list.go
    /* Undirected graph type implemented with an adjacency list */\ntype graphAdjList struct {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    adjList map[Vertex][]Vertex\n}\n\n/* Constructor */\nfunc newGraphAdjList(edges [][]Vertex) *graphAdjList {\n    g := &graphAdjList{\n        adjList: make(map[Vertex][]Vertex),\n    }\n    // Add all vertices and edges\n    for _, edge := range edges {\n        g.addVertex(edge[0])\n        g.addVertex(edge[1])\n        g.addEdge(edge[0], edge[1])\n    }\n    return g\n}\n\n/* Get the number of vertices */\nfunc (g *graphAdjList) size() int {\n    return len(g.adjList)\n}\n\n/* Add an edge */\nfunc (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) {\n    _, ok1 := g.adjList[vet1]\n    _, ok2 := g.adjList[vet2]\n    if !ok1 || !ok2 || vet1 == vet2 {\n        panic(\"error\")\n    }\n    // Add edge vet1 - vet2 by appending each vertex to the other vertex's neighbor list\n    g.adjList[vet1] = append(g.adjList[vet1], vet2)\n    g.adjList[vet2] = append(g.adjList[vet2], vet1)\n}\n\n/* Remove an edge */\nfunc (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) {\n    _, ok1 := g.adjList[vet1]\n    _, ok2 := g.adjList[vet2]\n    if !ok1 || !ok2 || vet1 == vet2 {\n        panic(\"error\")\n    }\n    // Remove edge vet1 - vet2\n    g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2)\n    g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1)\n}\n\n/* Add a vertex */\nfunc (g *graphAdjList) addVertex(vet Vertex) {\n    _, ok := g.adjList[vet]\n    if ok {\n        return\n    }\n    // Add a new list to the adjacency list\n    g.adjList[vet] = make([]Vertex, 0)\n}\n\n/* Remove a vertex */\nfunc (g *graphAdjList) removeVertex(vet Vertex) {\n    _, ok := g.adjList[vet]\n    if !ok {\n        panic(\"error\")\n    }\n    // Remove the list corresponding to vertex vet from the adjacency list\n    delete(g.adjList, vet)\n    // Traverse the lists of the other vertices and remove all edges incident to vet\n    for v, list := range g.adjList {\n        g.adjList[v] = DeleteSliceElms(list, vet)\n    }\n}\n\n/* Print the adjacency list */\nfunc (g *graphAdjList) print() {\n    var builder strings.Builder\n    fmt.Printf(\"Adjacency list = \\n\")\n    for k, v := range g.adjList {\n        builder.WriteString(\"\\t\\t\" + strconv.Itoa(k.Val) + \": \")\n        for _, vet := range v {\n            builder.WriteString(strconv.Itoa(vet.Val) + \" \")\n        }\n        fmt.Println(builder.String())\n        builder.Reset()\n    }\n}\n
    graph_adjacency_list.swift
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    public private(set) var adjList: [Vertex: [Vertex]]\n\n    /* Constructor */\n    public init(edges: [[Vertex]]) {\n        adjList = [:]\n        // Add all vertices and edges\n        for edge in edges {\n            addVertex(vet: edge[0])\n            addVertex(vet: edge[1])\n            addEdge(vet1: edge[0], vet2: edge[1])\n        }\n    }\n\n    /* Get the number of vertices */\n    public func size() -> Int {\n        adjList.count\n    }\n\n    /* Add an edge */\n    public func addEdge(vet1: Vertex, vet2: Vertex) {\n        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n            fatalError(\"Invalid arguments\")\n        }\n        // Add edge vet1 - vet2\n        adjList[vet1]?.append(vet2)\n        adjList[vet2]?.append(vet1)\n    }\n\n    /* Remove an edge */\n    public func removeEdge(vet1: Vertex, vet2: Vertex) {\n        if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n            fatalError(\"Invalid arguments\")\n        }\n        // Remove edge vet1 - vet2\n        adjList[vet1]?.removeAll { $0 == vet2 }\n        adjList[vet2]?.removeAll { $0 == vet1 }\n    }\n\n    /* Add a vertex */\n    public func addVertex(vet: Vertex) {\n        if adjList[vet] != nil {\n            return\n        }\n        // Add a new list to the adjacency list\n        adjList[vet] = []\n    }\n\n    /* Remove a vertex */\n    public func removeVertex(vet: Vertex) {\n        if adjList[vet] == nil {\n            fatalError(\"Invalid arguments\")\n        }\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.removeValue(forKey: vet)\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for key in adjList.keys {\n            adjList[key]?.removeAll { $0 == vet }\n        }\n    }\n\n    /* Print the adjacency list */\n    public func print() {\n        Swift.print(\"Adjacency list =\")\n        for (vertex, list) in adjList {\n            let list = list.map { $0.val }\n            Swift.print(\"\\(vertex.val): \\(list),\")\n        }\n    }\n}\n
    graph_adjacency_list.js
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    adjList;\n\n    /* Constructor */\n    constructor(edges) {\n        this.adjList = new Map();\n        // Add all vertices and edges\n        for (const edge of edges) {\n            this.addVertex(edge[0]);\n            this.addVertex(edge[1]);\n            this.addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size() {\n        return this.adjList.size;\n    }\n\n    /* Add an edge */\n    addEdge(vet1, vet2) {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2\n        ) {\n            throw new Error('Invalid argument');\n        }\n        // Add edge vet1 - vet2\n        this.adjList.get(vet1).push(vet2);\n        this.adjList.get(vet2).push(vet1);\n    }\n\n    /* Remove an edge */\n    removeEdge(vet1, vet2) {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2 ||\n            this.adjList.get(vet1).indexOf(vet2) === -1\n        ) {\n            throw new Error('Invalid argument');\n        }\n        // Remove edge vet1 - vet2\n        this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n        this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n    }\n\n    /* Add a vertex */\n    addVertex(vet) {\n        if (this.adjList.has(vet)) return;\n        // Add a new list to the adjacency list\n        this.adjList.set(vet, []);\n    }\n\n    /* Remove a vertex */\n    removeVertex(vet) {\n        if (!this.adjList.has(vet)) {\n            throw new Error('Invalid argument');\n        }\n        // Remove the list corresponding to vertex vet from the adjacency list\n        this.adjList.delete(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (const set of this.adjList.values()) {\n            const index = set.indexOf(vet);\n            if (index > -1) {\n                set.splice(index, 1);\n            }\n        }\n    }\n\n    /* Print the adjacency list */\n    print() {\n        console.log('Adjacency list =');\n        for (const [key, value] of this.adjList) {\n            const tmp = [];\n            for (const vertex of value) {\n                tmp.push(vertex.val);\n            }\n            console.log(key.val + ': ' + tmp.join());\n        }\n    }\n}\n
    graph_adjacency_list.ts
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    adjList: Map<Vertex, Vertex[]>;\n\n    /* Constructor */\n    constructor(edges: Vertex[][]) {\n        this.adjList = new Map();\n        // Add all vertices and edges\n        for (const edge of edges) {\n            this.addVertex(edge[0]);\n            this.addVertex(edge[1]);\n            this.addEdge(edge[0], edge[1]);\n        }\n    }\n\n    /* Get the number of vertices */\n    size(): number {\n        return this.adjList.size;\n    }\n\n    /* Add an edge */\n    addEdge(vet1: Vertex, vet2: Vertex): void {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2\n        ) {\n            throw new Error('Invalid argument');\n        }\n        // Add edge vet1 - vet2\n        this.adjList.get(vet1).push(vet2);\n        this.adjList.get(vet2).push(vet1);\n    }\n\n    /* Remove an edge */\n    removeEdge(vet1: Vertex, vet2: Vertex): void {\n        if (\n            !this.adjList.has(vet1) ||\n            !this.adjList.has(vet2) ||\n            vet1 === vet2 ||\n            this.adjList.get(vet1).indexOf(vet2) === -1\n        ) {\n            throw new Error('Invalid argument');\n        }\n        // Remove edge vet1 - vet2\n        this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n        this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n    }\n\n    /* Add a vertex */\n    addVertex(vet: Vertex): void {\n        if (this.adjList.has(vet)) return;\n        // Add a new list to the adjacency list\n        this.adjList.set(vet, []);\n    }\n\n    /* Remove a vertex */\n    removeVertex(vet: Vertex): void {\n        if (!this.adjList.has(vet)) {\n            throw new Error('Invalid argument');\n        }\n        // Remove the list corresponding to vertex vet from the adjacency list\n        this.adjList.delete(vet);\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (const set of this.adjList.values()) {\n            const index: number = set.indexOf(vet);\n            if (index > -1) {\n                set.splice(index, 1);\n            }\n        }\n    }\n\n    /* Print the adjacency list */\n    print(): void {\n        console.log('Adjacency list =');\n        for (const [key, value] of this.adjList.entries()) {\n            const tmp = [];\n            for (const vertex of value) {\n                tmp.push(vertex.val);\n            }\n            console.log(key.val + ': ' + tmp.join());\n        }\n    }\n}\n
    graph_adjacency_list.dart
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList {\n  // Adjacency list: key is a vertex, value is the list of all its neighbors\n  Map<Vertex, List<Vertex>> adjList = {};\n\n  /* Constructor */\n  GraphAdjList(List<List<Vertex>> edges) {\n    for (List<Vertex> edge in edges) {\n      addVertex(edge[0]);\n      addVertex(edge[1]);\n      addEdge(edge[0], edge[1]);\n    }\n  }\n\n  /* Get the number of vertices */\n  int size() {\n    return adjList.length;\n  }\n\n  /* Add an edge */\n  void addEdge(Vertex vet1, Vertex vet2) {\n    if (!adjList.containsKey(vet1) ||\n        !adjList.containsKey(vet2) ||\n        vet1 == vet2) {\n      throw ArgumentError;\n    }\n    // Add edge vet1 - vet2\n    adjList[vet1]!.add(vet2);\n    adjList[vet2]!.add(vet1);\n  }\n\n  /* Remove an edge */\n  void removeEdge(Vertex vet1, Vertex vet2) {\n    if (!adjList.containsKey(vet1) ||\n        !adjList.containsKey(vet2) ||\n        vet1 == vet2) {\n      throw ArgumentError;\n    }\n    // Remove edge vet1 - vet2\n    adjList[vet1]!.remove(vet2);\n    adjList[vet2]!.remove(vet1);\n  }\n\n  /* Add a vertex */\n  void addVertex(Vertex vet) {\n    if (adjList.containsKey(vet)) return;\n    // Add a new list to the adjacency list\n    adjList[vet] = [];\n  }\n\n  /* Remove a vertex */\n  void removeVertex(Vertex vet) {\n    if (!adjList.containsKey(vet)) {\n      throw ArgumentError;\n    }\n    // Remove the list corresponding to vertex vet from the adjacency list\n    adjList.remove(vet);\n    // Traverse the lists of the other vertices and remove all edges incident to vet\n    adjList.forEach((key, value) {\n      value.remove(vet);\n    });\n  }\n\n  /* Print the adjacency list */\n  void printAdjList() {\n    print(\"Adjacency list =\");\n    adjList.forEach((key, value) {\n      List<int> tmp = [];\n      for (Vertex vertex in value) {\n        tmp.add(vertex.val);\n      }\n      print(\"${key.val}: $tmp,\");\n    });\n  }\n}\n
    graph_adjacency_list.rs
    /* Undirected graph type implemented with an adjacency list */\npub struct GraphAdjList {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    pub adj_list: HashMap<Vertex, Vec<Vertex>>, // A HashSet<Vertex> may be a better choice for the neighbor collection\n}\n\nimpl GraphAdjList {\n    /* Constructor */\n    pub fn new(edges: Vec<[Vertex; 2]>) -> Self {\n        let mut graph = GraphAdjList {\n            adj_list: HashMap::new(),\n        };\n        // Add all vertices and edges\n        for edge in edges {\n            graph.add_vertex(edge[0]);\n            graph.add_vertex(edge[1]);\n            graph.add_edge(edge[0], edge[1]);\n        }\n\n        graph\n    }\n\n    /* Get the number of vertices */\n    #[allow(unused)]\n    pub fn size(&self) -> usize {\n        self.adj_list.len()\n    }\n\n    /* Add an edge */\n    pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) {\n        if vet1 == vet2 {\n            panic!(\"Vertices must be distinct\");\n        }\n        // Add edge vet1 - vet2\n        self.adj_list.entry(vet1).or_default().push(vet2);\n        self.adj_list.entry(vet2).or_default().push(vet1);\n    }\n\n    /* Remove an edge */\n    #[allow(unused)]\n    pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) {\n        if vet1 == vet2 {\n            panic!(\"Vertices must be distinct\");\n        }\n        // Remove edge vet1 - vet2\n        self.adj_list\n            .entry(vet1)\n            .and_modify(|v| v.retain(|&e| e != vet2));\n        self.adj_list\n            .entry(vet2)\n            .and_modify(|v| v.retain(|&e| e != vet1));\n    }\n\n    /* Add a vertex */\n    pub fn add_vertex(&mut self, vet: Vertex) {\n        if self.adj_list.contains_key(&vet) {\n            return;\n        }\n        // Add a new neighbor list to the adjacency list\n        self.adj_list.insert(vet, vec![]);\n    }\n\n    /* Remove a vertex */\n    #[allow(unused)]\n    pub fn remove_vertex(&mut self, vet: Vertex) {\n        // Remove the neighbor list for vertex vet from the adjacency list\n        self.adj_list.remove(&vet);\n        // Traverse the other neighbor lists and remove every edge incident to vet\n        for list in self.adj_list.values_mut() {\n            list.retain(|&v| v != vet);\n        }\n    }\n\n    /* Print the adjacency list */\n    pub fn print(&self) {\n        println!(\"Adjacency list =\");\n        for (vertex, list) in &self.adj_list {\n            let list = list.iter().map(|vertex| vertex.val).collect::<Vec<i32>>();\n            println!(\"{}: {:?},\", vertex.val, list);\n        }\n    }\n}\n
    graph_adjacency_list.c
    /* Adjacency-list node structure */\ntypedef struct AdjListNode {\n    Vertex *vertex;           // Vertex\n    struct AdjListNode *next; // Next node\n} AdjListNode;\n\n/* Find the list node corresponding to a vertex */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n    for (int i = 0; i < graph->size; i++) {\n        if (graph->heads[i]->vertex == vet) {\n            return graph->heads[i];\n        }\n    }\n    return NULL;\n}\n\n/* Helper for adding an edge */\nvoid addEdgeHelper(AdjListNode *head, Vertex *vet) {\n    AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode));\n    node->vertex = vet;\n    // Insert at the front of the neighbor list\n    node->next = head->next;\n    head->next = node;\n}\n\n/* Helper for removing an edge */\nvoid removeEdgeHelper(AdjListNode *head, Vertex *vet) {\n    AdjListNode *pre = head;\n    AdjListNode *cur = head->next;\n    // Search the linked list for the node corresponding to vet\n    while (cur != NULL && cur->vertex != vet) {\n        pre = cur;\n        cur = cur->next;\n    }\n    if (cur == NULL)\n        return;\n    // Remove the node corresponding to vet from the linked list\n    pre->next = cur->next;\n    // Free memory\n    free(cur);\n}\n\n/* Undirected graph structure implemented with an adjacency list */\ntypedef struct {\n    AdjListNode *heads[MAX_SIZE]; // Array of list heads\n    int size;                     // Number of vertices\n} GraphAdjList;\n\n/* Constructor */\nGraphAdjList *newGraphAdjList() {\n    GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList));\n    if (!graph) {\n        return NULL;\n    }\n    graph->size = 0;\n    for (int i = 0; i < MAX_SIZE; i++) {\n        graph->heads[i] = NULL;\n    }\n    return graph;\n}\n\n/* Destructor */\nvoid delGraphAdjList(GraphAdjList *graph) {\n    for (int i = 0; i < graph->size; i++) {\n        AdjListNode *cur = graph->heads[i];\n        while (cur != NULL) {\n            AdjListNode *next = cur->next;\n            if (cur != graph->heads[i]) {\n                free(cur);\n            }\n            cur = next;\n        }\n        free(graph->heads[i]->vertex);\n        free(graph->heads[i]);\n    }\n    free(graph);\n}\n\n/* Find the list node corresponding to a vertex */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n    for (int i = 0; i < graph->size; i++) {\n        if (graph->heads[i]->vertex == vet) {\n            return graph->heads[i];\n        }\n    }\n    return NULL;\n}\n\n/* Add an edge */\nvoid addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n    AdjListNode *head1 = findNode(graph, vet1);\n    AdjListNode *head2 = findNode(graph, vet2);\n    assert(head1 != NULL && head2 != NULL && head1 != head2);\n    // Add edge vet1 - vet2\n    addEdgeHelper(head1, vet2);\n    addEdgeHelper(head2, vet1);\n}\n\n/* Remove an edge */\nvoid removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n    AdjListNode *head1 = findNode(graph, vet1);\n    AdjListNode *head2 = findNode(graph, vet2);\n    assert(head1 != NULL && head2 != NULL);\n    // Remove edge vet1 - vet2\n    removeEdgeHelper(head1, head2->vertex);\n    removeEdgeHelper(head2, head1->vertex);\n}\n\n/* Add a vertex */\nvoid addVertex(GraphAdjList *graph, Vertex *vet) {\n    assert(graph != NULL && graph->size < MAX_SIZE);\n    AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode));\n    head->vertex = vet;\n    head->next = NULL;\n    // Add a new list to the adjacency list\n    graph->heads[graph->size++] = head;\n}\n\n/* Remove a vertex */\nvoid removeVertex(GraphAdjList *graph, Vertex *vet) {\n    AdjListNode *node = findNode(graph, vet);\n    assert(node != NULL);\n    // Remove the list corresponding to vertex vet from the adjacency list\n    AdjListNode *cur = node, *pre = NULL;\n    while (cur) {\n        pre = cur;\n        cur = cur->next;\n        free(pre);\n    }\n    // Traverse the lists of the other vertices and remove all edges incident to vet\n    for (int i = 0; i < graph->size; i++) {\n        cur = graph->heads[i];\n        pre = NULL;\n        while (cur) {\n            pre = cur;\n            cur = cur->next;\n            if (cur && cur->vertex == vet) {\n                pre->next = cur->next;\n                free(cur);\n                break;\n            }\n        }\n    }\n    // Shift subsequent vertices forward to fill the gap\n    int i;\n    for (i = 0; i < graph->size; i++) {\n        if (graph->heads[i] == node)\n            break;\n    }\n    for (int j = i; j < graph->size - 1; j++) {\n        graph->heads[j] = graph->heads[j + 1];\n    }\n    graph->size--;\n    free(vet);\n}\n
    graph_adjacency_list.kt
    /* Undirected graph class implemented with an adjacency list */\nclass GraphAdjList(edges: Array<Array<Vertex?>>) {\n    // Adjacency list: key is a vertex, value is the list of all its neighbors\n    val adjList = HashMap<Vertex, MutableList<Vertex>>()\n\n    /* Constructor */\n    init {\n        // Add all vertices and edges\n        for (edge in edges) {\n            addVertex(edge[0]!!)\n            addVertex(edge[1]!!)\n            addEdge(edge[0]!!, edge[1]!!)\n        }\n    }\n\n    /* Get the number of vertices */\n    fun size(): Int {\n        return adjList.size\n    }\n\n    /* Add an edge */\n    fun addEdge(vet1: Vertex, vet2: Vertex) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw IllegalArgumentException()\n        // Add edge vet1 - vet2\n        adjList[vet1]?.add(vet2)\n        adjList[vet2]?.add(vet1)\n    }\n\n    /* Remove an edge */\n    fun removeEdge(vet1: Vertex, vet2: Vertex) {\n        if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n            throw IllegalArgumentException()\n        // Remove edge vet1 - vet2\n        adjList[vet1]?.remove(vet2)\n        adjList[vet2]?.remove(vet1)\n    }\n\n    /* Add a vertex */\n    fun addVertex(vet: Vertex) {\n        if (adjList.containsKey(vet))\n            return\n        // Add a new list to the adjacency list\n        adjList[vet] = mutableListOf()\n    }\n\n    /* Remove a vertex */\n    fun removeVertex(vet: Vertex) {\n        if (!adjList.containsKey(vet))\n            throw IllegalArgumentException()\n        // Remove the list corresponding to vertex vet from the adjacency list\n        adjList.remove(vet)\n        // Traverse the lists of the other vertices and remove all edges incident to vet\n        for (list in adjList.values) {\n            list.remove(vet)\n        }\n    }\n\n    /* Print the adjacency list */\n    fun print() {\n        println(\"Adjacency list =\")\n        for (pair in adjList.entries) {\n            val tmp = mutableListOf<Int>()\n            for (vertex in pair.value) {\n                tmp.add(vertex._val)\n            }\n            println(\"${pair.key._val}: $tmp,\")\n        }\n    }\n}\n
    graph_adjacency_list.rb
    ### Undirected graph class implemented with an adjacency list ###\nclass GraphAdjList\n  attr_reader :adj_list\n\n  ### Constructor ###\n  def initialize(edges)\n    # Adjacency list: key is a vertex, value is the list of all its neighbors\n    @adj_list = {}\n    # Add all vertices and edges\n    for edge in edges\n      add_vertex(edge[0])\n      add_vertex(edge[1])\n      add_edge(edge[0], edge[1])\n    end\n  end\n\n  ### Get the number of vertices ###\n  def size\n    @adj_list.length\n  end\n\n  ### Add an edge ###\n  def add_edge(vet1, vet2)\n    raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n    @adj_list[vet1] << vet2\n    @adj_list[vet2] << vet1\n  end\n\n  ### Remove an edge ###\n  def remove_edge(vet1, vet2)\n    raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n    # Remove edge vet1 - vet2\n    @adj_list[vet1].delete(vet2)\n    @adj_list[vet2].delete(vet1)\n  end\n\n  ### Add a vertex ###\n  def add_vertex(vet)\n    return if @adj_list.include?(vet)\n\n    # Add a new neighbor list to the adjacency list\n    @adj_list[vet] = []\n  end\n\n  ### Remove a vertex ###\n  def remove_vertex(vet)\n    raise ArgumentError unless @adj_list.include?(vet)\n\n    # Remove the neighbor list for vertex vet from the adjacency list\n    @adj_list.delete(vet)\n    # Traverse the other neighbor lists and remove all edges incident to vet\n    for vertex in @adj_list\n      @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet)\n    end\n  end\n\n  ### Print the adjacency list ###\n  def __print__\n    puts 'Adjacency list ='\n    for vertex in @adj_list\n      tmp = @adj_list[vertex.first].map { |v| v.val }\n      puts \"#{vertex.first.val}: #{tmp},\"\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 9.   Graphs","9.2   Basic graph operations"],"tags":[]},{"location":"chapter_graph/graph_operations/#923-comparing-efficiency","level":2,"title":"9.2.3   Comparing efficiency","text":"

    For a graph with \\(n\\) vertices and \\(m\\) edges, Table 9-2 compares the time and space efficiency of adjacency matrices and adjacency lists. Here, \"adjacency list (linked lists)\" refers to the implementation in this section, while \"adjacency list (hash tables)\" specifically refers to an implementation in which every linked list is replaced by a hash table.

    Table 9-2   Comparing adjacency matrices and adjacency lists

    Adjacency matrix Adjacency list (linked lists) Adjacency list (hash tables) Check adjacency \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) Add an edge \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) Remove an edge \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) Add a vertex \\(O(n)\\) \\(O(1)\\) \\(O(1)\\) Remove a vertex \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n)\\) Memory usage \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n + m)\\)

    Table 9-2 suggests that an adjacency list using hash tables is the most efficient in both time and space. In practice, however, edge operations in an adjacency matrix are faster: each requires only a single array access or assignment. Overall, an adjacency matrix trades space for time, while an adjacency list trades time for space.

    ","path":["Chapter 9.   Graphs","9.2   Basic graph operations"],"tags":[]},{"location":"chapter_graph/graph_traversal/","level":1,"title":"9.3   Graph traversal","text":"

    A tree represents one-to-many relationships, whereas a graph allows more freedom and can represent arbitrary many-to-many relationships. We can therefore view a tree as a special case of a graph. Accordingly, tree traversal is also a special case of graph traversal.

    Both graphs and trees use search algorithms for traversal. Graph traversal can likewise be divided into two types: breadth-first traversal and depth-first traversal.

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#931-breadth-first-traversal","level":2,"title":"9.3.1   Breadth-first traversal","text":"

    Breadth-first traversal visits vertices from near to far. Starting from a vertex, it always visits the nearest vertices first and expands outward layer by layer. In Figure 9-9, we start at the top-left vertex and visit all its adjacent vertices, then visit all the adjacent vertices of the next vertex, and so on until every vertex has been visited.

    Figure 9-9   Breadth-first traversal of a graph

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1-algorithm-implementation","level":3,"title":"1.   Algorithm implementation","text":"

    BFS is usually implemented with a queue, as shown in the code below. A queue's first-in, first-out behavior fits the near-to-far order of BFS.

    1. Enqueue the starting vertex startVet and begin the loop.
    2. In each iteration, dequeue the vertex at the front and record the visit, then enqueue all its adjacent vertices at the rear.
    3. Repeat step 2. until every vertex has been visited.

    To avoid visiting a vertex more than once, we use a hash set visited to track which vertices have already been visited.

    Tip

    A hash set can be viewed as a hash table that stores only key entries, with no value entries. Adding, removing, looking up, and modifying a key take \\(O(1)\\) time. Because every key is unique, hash sets are commonly used to remove duplicate data.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_bfs.py
    def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:\n    \"\"\"Breadth-first traversal\"\"\"\n    # Represent the graph with an adjacency list to access all neighbors of a given vertex\n    # Vertex traversal sequence\n    res = []\n    # Hash set recording visited vertices\n    visited = set[Vertex]([start_vet])\n    # Use a queue to implement BFS\n    que = deque[Vertex]([start_vet])\n    # Starting from start_vet, loop until all reachable vertices have been visited\n    while len(que) > 0:\n        vet = que.popleft()  # Dequeue the front vertex\n        res.append(vet)  # Record the visited vertex\n        # Traverse all neighbors of this vertex\n        for adj_vet in graph.adj_list[vet]:\n            if adj_vet in visited:\n                continue  # Skip vertices that have already been visited\n            que.append(adj_vet)  # Enqueue only unvisited vertices\n            visited.add(adj_vet)  # Mark this vertex as visited\n    # Return the vertex traversal sequence\n    return res\n
    graph_bfs.cpp
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nvector<Vertex *> graphBFS(GraphAdjList &graph, Vertex *startVet) {\n    // Vertex traversal sequence\n    vector<Vertex *> res;\n    // Hash set recording visited vertices\n    unordered_set<Vertex *> visited = {startVet};\n    // Use a queue to implement BFS\n    queue<Vertex *> que;\n    que.push(startVet);\n    // Starting from startVet, continue until all reachable vertices have been visited\n    while (!que.empty()) {\n        Vertex *vet = que.front();\n        que.pop();          // Dequeue the front vertex\n        res.push_back(vet); // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (auto adjVet : graph.adjList[vet]) {\n            if (visited.count(adjVet))\n                continue;            // Skip vertices that have already been visited\n            que.push(adjVet);        // Enqueue only unvisited vertices\n            visited.emplace(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.java
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nList<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = new ArrayList<>();\n    // Hash set recording visited vertices\n    Set<Vertex> visited = new HashSet<>();\n    visited.add(startVet);\n    // Use a queue to implement BFS\n    Queue<Vertex> que = new LinkedList<>();\n    que.offer(startVet);\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (!que.isEmpty()) {\n        Vertex vet = que.poll(); // Dequeue the front vertex\n        res.add(vet);            // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (Vertex adjVet : graph.adjList.get(vet)) {\n            if (visited.contains(adjVet))\n                continue;        // Skip vertices that have already been visited\n            que.offer(adjVet);   // Enqueue only unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.cs
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nList<Vertex> GraphBFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = [];\n    // Hash set recording visited vertices\n    HashSet<Vertex> visited = [startVet];\n    // Use a queue to implement BFS\n    Queue<Vertex> que = new();\n    que.Enqueue(startVet);\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (que.Count > 0) {\n        Vertex vet = que.Dequeue(); // Dequeue the front vertex\n        res.Add(vet);               // Record the visited vertex\n        foreach (Vertex adjVet in graph.adjList[vet]) {\n            if (visited.Contains(adjVet)) {\n                continue;          // Skip vertices that have already been visited\n            }\n            que.Enqueue(adjVet);   // Enqueue only unvisited vertices\n            visited.Add(adjVet);   // Mark this vertex as visited\n        }\n    }\n\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.go
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunc graphBFS(g *graphAdjList, startVet Vertex) []Vertex {\n    // Vertex traversal sequence\n    res := make([]Vertex, 0)\n    // Hash set recording visited vertices\n    visited := make(map[Vertex]struct{})\n    visited[startVet] = struct{}{}\n    // Use a queue for BFS, represented here by a slice\n    queue := make([]Vertex, 0)\n    queue = append(queue, startVet)\n    // Starting from startVet, loop until all reachable vertices have been visited\n    for len(queue) > 0 {\n        // Dequeue the front vertex\n        vet := queue[0]\n        queue = queue[1:]\n        // Record the visited vertex\n        res = append(res, vet)\n        // Traverse all neighbors of this vertex\n        for _, adjVet := range g.adjList[vet] {\n            _, isExist := visited[adjVet]\n            // Enqueue only unvisited vertices\n            if !isExist {\n                queue = append(queue, adjVet)\n                visited[adjVet] = struct{}{}\n            }\n        }\n    }\n    // Return the vertex traversal sequence\n    return res\n}\n
    graph_bfs.swift
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunc graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {\n    // Vertex traversal sequence\n    var res: [Vertex] = []\n    // Hash set recording visited vertices\n    var visited: Set<Vertex> = [startVet]\n    // Use a queue to implement BFS\n    var que: [Vertex] = [startVet]\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while !que.isEmpty {\n        let vet = que.removeFirst() // Dequeue the front vertex\n        res.append(vet) // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for adjVet in graph.adjList[vet] ?? [] {\n            if visited.contains(adjVet) {\n                continue // Skip vertices that have already been visited\n            }\n            que.append(adjVet) // Enqueue only unvisited vertices\n            visited.insert(adjVet) // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res\n}\n
    graph_bfs.js
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction graphBFS(graph, startVet) {\n    // Vertex traversal sequence\n    const res = [];\n    // Hash set recording visited vertices\n    const visited = new Set();\n    visited.add(startVet);\n    // Use a queue to implement BFS\n    const que = [startVet];\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (que.length) {\n        const vet = que.shift(); // Dequeue the front vertex\n        res.push(vet); // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (const adjVet of graph.adjList.get(vet) ?? []) {\n            if (visited.has(adjVet)) {\n                continue; // Skip vertices that have already been visited\n            }\n            que.push(adjVet); // Enqueue only unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.ts
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n    // Vertex traversal sequence\n    const res: Vertex[] = [];\n    // Hash set recording visited vertices\n    const visited: Set<Vertex> = new Set();\n    visited.add(startVet);\n    // Use a queue to implement BFS\n    const que = [startVet];\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (que.length) {\n        const vet = que.shift(); // Dequeue the front vertex\n        res.push(vet); // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (const adjVet of graph.adjList.get(vet) ?? []) {\n            if (visited.has(adjVet)) {\n                continue; // Skip vertices that have already been visited\n            }\n            que.push(adjVet); // Enqueue only unvisited vertices\n            visited.add(adjVet); // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res;\n}\n
    graph_bfs.dart
    /* Breadth-first traversal */\nList<Vertex> graphBFS(GraphAdjList graph, Vertex startVet) {\n  // Represent the graph with an adjacency list to access all neighbors of a given vertex\n  // Vertex traversal sequence\n  List<Vertex> res = [];\n  // Hash set recording visited vertices\n  Set<Vertex> visited = {};\n  visited.add(startVet);\n  // Use a queue to implement BFS\n  Queue<Vertex> que = Queue();\n  que.add(startVet);\n  // Starting from startVet, loop until all reachable vertices have been visited\n  while (que.isNotEmpty) {\n    Vertex vet = que.removeFirst(); // Dequeue the front vertex\n    res.add(vet); // Record the visited vertex\n    // Traverse all neighbors of this vertex\n    for (Vertex adjVet in graph.adjList[vet]!) {\n      if (visited.contains(adjVet)) {\n        continue; // Skip vertices that have already been visited\n      }\n      que.add(adjVet); // Enqueue only unvisited vertices\n      visited.add(adjVet); // Mark this vertex as visited\n    }\n  }\n  // Return the vertex traversal sequence\n  return res;\n}\n
    graph_bfs.rs
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {\n    // Vertex traversal sequence\n    let mut res = vec![];\n    // Hash set recording visited vertices\n    let mut visited = HashSet::new();\n    visited.insert(start_vet);\n    // Use a queue to implement BFS\n    let mut que = VecDeque::new();\n    que.push_back(start_vet);\n    // Start at start_vet and loop until all reachable vertices have been visited\n    while let Some(vet) = que.pop_front() {\n        res.push(vet); // Record the visited vertex\n\n        // Traverse all neighbors of this vertex\n        if let Some(adj_vets) = graph.adj_list.get(&vet) {\n            for &adj_vet in adj_vets {\n                if visited.contains(&adj_vet) {\n                    continue; // Skip vertices that have already been visited\n                }\n                que.push_back(adj_vet); // Enqueue only unvisited vertices\n                visited.insert(adj_vet); // Mark this vertex as visited\n            }\n        }\n    }\n    // Return the vertex traversal sequence\n    res\n}\n
    graph_bfs.c
    /* Vertex queue structure */\ntypedef struct {\n    Vertex *vertices[MAX_SIZE];\n    int front, rear, size;\n} Queue;\n\n/* Constructor */\nQueue *newQueue() {\n    Queue *q = (Queue *)malloc(sizeof(Queue));\n    q->front = q->rear = q->size = 0;\n    return q;\n}\n\n/* Check whether the queue is empty */\nint isEmpty(Queue *q) {\n    return q->size == 0;\n}\n\n/* Enqueue operation */\nvoid enqueue(Queue *q, Vertex *vet) {\n    q->vertices[q->rear] = vet;\n    q->rear = (q->rear + 1) % MAX_SIZE;\n    q->size++;\n}\n\n/* Dequeue operation */\nVertex *dequeue(Queue *q) {\n    Vertex *vet = q->vertices[q->front];\n    q->front = (q->front + 1) % MAX_SIZE;\n    q->size--;\n    return vet;\n}\n\n/* Check whether a vertex has been visited */\nint isVisited(Vertex **visited, int size, Vertex *vet) {\n    // Search for the vertex with a linear scan in O(n) time\n    for (int i = 0; i < size; i++) {\n        if (visited[i] == vet)\n            return 1;\n    }\n    return 0;\n}\n\n/* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nvoid graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) {\n    // Use a queue to implement BFS\n    Queue *queue = newQueue();\n    enqueue(queue, startVet);\n    visited[(*visitedSize)++] = startVet;\n    // Starting from startVet, continue until all reachable vertices have been visited\n    while (!isEmpty(queue)) {\n        Vertex *vet = dequeue(queue); // Dequeue the front vertex\n        res[(*resSize)++] = vet;      // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        AdjListNode *node = findNode(graph, vet);\n        while (node != NULL) {\n            // Skip vertices that have already been visited\n            if (!isVisited(visited, *visitedSize, node->vertex)) {\n                enqueue(queue, node->vertex);             // Enqueue only unvisited vertices\n                visited[(*visitedSize)++] = node->vertex; // Mark this vertex as visited\n            }\n            node = node->next;\n        }\n    }\n    // Free memory\n    free(queue);\n}\n
    graph_bfs.kt
    /* Breadth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList<Vertex?> {\n    // Vertex traversal sequence\n    val res = mutableListOf<Vertex?>()\n    // Hash set recording visited vertices\n    val visited = HashSet<Vertex>()\n    visited.add(startVet)\n    // Use a queue to implement BFS\n    val que = LinkedList<Vertex>()\n    que.offer(startVet)\n    // Starting from startVet, loop until all reachable vertices have been visited\n    while (!que.isEmpty()) {\n        val vet = que.poll() // Dequeue the front vertex\n        res.add(vet)         // Record the visited vertex\n        // Traverse all neighbors of this vertex\n        for (adjVet in graph.adjList[vet]!!) {\n            if (visited.contains(adjVet))\n                continue        // Skip vertices that have already been visited\n            que.offer(adjVet)   // Enqueue only unvisited vertices\n            visited.add(adjVet) // Mark this vertex as visited\n        }\n    }\n    // Return the vertex traversal sequence\n    return res\n}\n
    graph_bfs.rb
    ### Breadth-first traversal ###\ndef graph_bfs(graph, start_vet)\n  # Represent the graph with an adjacency list to access all neighbors of a given vertex\n  # Vertex traversal sequence\n  res = []\n  # Hash set recording visited vertices\n  visited = Set.new([start_vet])\n  # Use a queue to implement BFS\n  que = [start_vet]\n  # Start from start_vet and loop until all reachable vertices have been visited\n  while que.length > 0\n    vet = que.shift # Dequeue the front vertex\n    res << vet # Record the visited vertex\n    # Traverse all neighbors of this vertex\n    for adj_vet in graph.adj_list[vet]\n      next if visited.include?(adj_vet) # Skip vertices that have already been visited\n      que << adj_vet # Enqueue only unvisited vertices\n      visited.add(adj_vet) # Mark this vertex as visited\n    end\n  end\n  # Return the vertex traversal sequence\n  res\nend\n
    Visualize execution

    View full screen >

    The code is fairly abstract; compare it with Figure 9-10 to build your understanding.

    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 9-10   Steps in breadth-first traversal of a graph

    Is the breadth-first traversal order unique?

    No. Breadth-first traversal only requires vertices to be visited from near to far; vertices at the same distance may be visited in any order. In Figure 9-10, vertices \\(1\\) and \\(3\\) can be visited in either order, and vertices \\(2\\), \\(4\\), and \\(6\\) can also be visited in any order.

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2-complexity-analysis","level":3,"title":"2.   Complexity analysis","text":"

    Time complexity: Every vertex is enqueued and dequeued once, taking \\(O(|V|)\\) time. While traversing adjacent vertices, every edge is visited \\(2\\) times because the graph is undirected, taking \\(O(2|E|)\\) time. The total time is \\(O(|V| + |E|)\\).

    Space complexity: The list res, hash set visited, and queue que each contain at most \\(|V|\\) vertices, requiring \\(O(|V|)\\) space.

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#932-depth-first-traversal","level":2,"title":"9.3.2   Depth-first traversal","text":"

    Depth-first traversal follows a path as far as possible and returns when it can go no farther. In Figure 9-11, we start at the top-left vertex and move to an adjacent vertex of the current vertex, continuing until we reach a dead end. We then return and explore another path as far as possible, repeating this process until every vertex has been visited.

    Figure 9-11   Depth-first traversal of a graph

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1-algorithm-implementation_1","level":3,"title":"1.   Algorithm implementation","text":"

    This pattern of going as far as possible and then returning is usually implemented with recursion. As with breadth-first traversal, we use a hash set visited to track visited vertices and avoid visiting them again.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_dfs.py
    def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):\n    \"\"\"Depth-first traversal helper function\"\"\"\n    res.append(vet)  # Record the visited vertex\n    visited.add(vet)  # Mark this vertex as visited\n    # Traverse all neighbors of this vertex\n    for adjVet in graph.adj_list[vet]:\n        if adjVet in visited:\n            continue  # Skip vertices that have already been visited\n        # Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet)\n\ndef graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:\n    \"\"\"Depth-first traversal\"\"\"\n    # Represent the graph with an adjacency list to access all neighbors of a given vertex\n    # Vertex traversal sequence\n    res = []\n    # Hash set recording visited vertices\n    visited = set[Vertex]()\n    dfs(graph, visited, res, start_vet)\n    return res\n
    graph_dfs.cpp
    /* Depth-first traversal helper function */\nvoid dfs(GraphAdjList &graph, unordered_set<Vertex *> &visited, vector<Vertex *> &res, Vertex *vet) {\n    res.push_back(vet);   // Record the visited vertex\n    visited.emplace(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (Vertex *adjVet : graph.adjList[vet]) {\n        if (visited.count(adjVet))\n            continue; // Skip vertices that have already been visited\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nvector<Vertex *> graphDFS(GraphAdjList &graph, Vertex *startVet) {\n    // Vertex traversal sequence\n    vector<Vertex *> res;\n    // Hash set recording visited vertices\n    unordered_set<Vertex *> visited;\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.java
    /* Depth-first traversal helper function */\nvoid dfs(GraphAdjList graph, Set<Vertex> visited, List<Vertex> res, Vertex vet) {\n    res.add(vet);     // Record the visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (Vertex adjVet : graph.adjList.get(vet)) {\n        if (visited.contains(adjVet))\n            continue; // Skip vertices that have already been visited\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nList<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = new ArrayList<>();\n    // Hash set recording visited vertices\n    Set<Vertex> visited = new HashSet<>();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.cs
    /* Depth-first traversal helper function */\nvoid DFS(GraphAdjList graph, HashSet<Vertex> visited, List<Vertex> res, Vertex vet) {\n    res.Add(vet);     // Record the visited vertex\n    visited.Add(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    foreach (Vertex adjVet in graph.adjList[vet]) {\n        if (visited.Contains(adjVet)) {\n            continue; // Skip vertices that have already been visited                             \n        }\n        // Recursively visit the neighboring vertex\n        DFS(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nList<Vertex> GraphDFS(GraphAdjList graph, Vertex startVet) {\n    // Vertex traversal sequence\n    List<Vertex> res = [];\n    // Hash set recording visited vertices\n    HashSet<Vertex> visited = [];\n    DFS(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.go
    /* Depth-first traversal helper function */\nfunc dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) {\n    // append returns an updated slice, which must be assigned back through the original slice pointer\n    *res = append(*res, vet)\n    visited[vet] = struct{}{}\n    // Traverse all neighbors of this vertex\n    for _, adjVet := range g.adjList[vet] {\n        _, isExist := visited[adjVet]\n        // Recursively visit the neighboring vertex\n        if !isExist {\n            dfs(g, visited, res, adjVet)\n        }\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunc graphDFS(g *graphAdjList, startVet Vertex) []Vertex {\n    // Vertex traversal sequence\n    res := make([]Vertex, 0)\n    // Hash set recording visited vertices\n    visited := make(map[Vertex]struct{})\n    dfs(g, visited, &res, startVet)\n    // Return the vertex traversal sequence\n    return res\n}\n
    graph_dfs.swift
    /* Depth-first traversal helper function */\nfunc dfs(graph: GraphAdjList, visited: inout Set<Vertex>, res: inout [Vertex], vet: Vertex) {\n    res.append(vet) // Record the visited vertex\n    visited.insert(vet) // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for adjVet in graph.adjList[vet] ?? [] {\n        if visited.contains(adjVet) {\n            continue // Skip vertices that have already been visited\n        }\n        // Recursively visit the neighboring vertex\n        dfs(graph: graph, visited: &visited, res: &res, vet: adjVet)\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunc graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] {\n    // Vertex traversal sequence\n    var res: [Vertex] = []\n    // Hash set recording visited vertices\n    var visited: Set<Vertex> = []\n    dfs(graph: graph, visited: &visited, res: &res, vet: startVet)\n    return res\n}\n
    graph_dfs.js
    /* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction dfs(graph, visited, res, vet) {\n    res.push(vet); // Record the visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (const adjVet of graph.adjList.get(vet)) {\n        if (visited.has(adjVet)) {\n            continue; // Skip vertices that have already been visited\n        }\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction graphDFS(graph, startVet) {\n    // Vertex traversal sequence\n    const res = [];\n    // Hash set recording visited vertices\n    const visited = new Set();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.ts
    /* Depth-first traversal helper function */\nfunction dfs(\n    graph: GraphAdjList,\n    visited: Set<Vertex>,\n    res: Vertex[],\n    vet: Vertex\n): void {\n    res.push(vet); // Record the visited vertex\n    visited.add(vet); // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (const adjVet of graph.adjList.get(vet)) {\n        if (visited.has(adjVet)) {\n            continue; // Skip vertices that have already been visited\n        }\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet);\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfunction graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n    // Vertex traversal sequence\n    const res: Vertex[] = [];\n    // Hash set recording visited vertices\n    const visited: Set<Vertex> = new Set();\n    dfs(graph, visited, res, startVet);\n    return res;\n}\n
    graph_dfs.dart
    /* Depth-first traversal helper function */\nvoid dfs(\n  GraphAdjList graph,\n  Set<Vertex> visited,\n  List<Vertex> res,\n  Vertex vet,\n) {\n  res.add(vet); // Record the visited vertex\n  visited.add(vet); // Mark this vertex as visited\n  // Traverse all neighbors of this vertex\n  for (Vertex adjVet in graph.adjList[vet]!) {\n    if (visited.contains(adjVet)) {\n      continue; // Skip vertices that have already been visited\n    }\n    // Recursively visit the neighboring vertex\n    dfs(graph, visited, res, adjVet);\n  }\n}\n\n/* Depth-first traversal */\nList<Vertex> graphDFS(GraphAdjList graph, Vertex startVet) {\n  // Vertex traversal sequence\n  List<Vertex> res = [];\n  // Hash set recording visited vertices\n  Set<Vertex> visited = {};\n  dfs(graph, visited, res, startVet);\n  return res;\n}\n
    graph_dfs.rs
    /* Depth-first traversal helper function */\nfn dfs(graph: &GraphAdjList, visited: &mut HashSet<Vertex>, res: &mut Vec<Vertex>, vet: Vertex) {\n    res.push(vet); // Record the visited vertex\n    visited.insert(vet); // Mark this vertex as visited\n                         // Traverse all neighbors of this vertex\n    if let Some(adj_vets) = graph.adj_list.get(&vet) {\n        for &adj_vet in adj_vets {\n            if visited.contains(&adj_vet) {\n                continue; // Skip vertices that have already been visited\n            }\n            // Recursively visit the neighboring vertex\n            dfs(graph, visited, res, adj_vet);\n        }\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec<Vertex> {\n    // Vertex traversal sequence\n    let mut res = vec![];\n    // Hash set recording visited vertices\n    let mut visited = HashSet::new();\n    dfs(&graph, &mut visited, &mut res, start_vet);\n\n    res\n}\n
    graph_dfs.c
    /* Check whether a vertex has been visited */\nint isVisited(Vertex **res, int size, Vertex *vet) {\n    // Search for the vertex with a linear scan in O(n) time\n    for (int i = 0; i < size; i++) {\n        if (res[i] == vet) {\n            return 1;\n        }\n    }\n    return 0;\n}\n\n/* Depth-first traversal helper function */\nvoid dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) {\n    // Record the visited vertex\n    res[(*resSize)++] = vet;\n    // Traverse all neighbors of this vertex\n    AdjListNode *node = findNode(graph, vet);\n    while (node != NULL) {\n        // Skip vertices that have already been visited\n        if (!isVisited(res, *resSize, node->vertex)) {\n            // Recursively visit the neighboring vertex\n            dfs(graph, res, resSize, node->vertex);\n        }\n        node = node->next;\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nvoid graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) {\n    dfs(graph, res, resSize, startVet);\n}\n
    graph_dfs.kt
    /* Depth-first traversal helper function */\nfun dfs(\n    graph: GraphAdjList,\n    visited: MutableSet<Vertex?>,\n    res: MutableList<Vertex?>,\n    vet: Vertex?\n) {\n    res.add(vet)     // Record the visited vertex\n    visited.add(vet) // Mark this vertex as visited\n    // Traverse all neighbors of this vertex\n    for (adjVet in graph.adjList[vet]!!) {\n        if (visited.contains(adjVet))\n            continue  // Skip vertices that have already been visited\n        // Recursively visit the neighboring vertex\n        dfs(graph, visited, res, adjVet)\n    }\n}\n\n/* Depth-first traversal */\n// Represent the graph with an adjacency list to access all neighbors of a given vertex\nfun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList<Vertex?> {\n    // Vertex traversal sequence\n    val res = mutableListOf<Vertex?>()\n    // Hash set recording visited vertices\n    val visited = HashSet<Vertex?>()\n    dfs(graph, visited, res, startVet)\n    return res\n}\n
    graph_dfs.rb
    ### Depth-first traversal helper function ###\ndef dfs(graph, visited, res, vet)\n  res << vet # Record the visited vertex\n  visited.add(vet) # Mark this vertex as visited\n  # Traverse all neighbors of this vertex\n  for adj_vet in graph.adj_list[vet]\n    next if visited.include?(adj_vet) # Skip vertices that have already been visited\n    # Recursively visit the neighboring vertex\n    dfs(graph, visited, res, adj_vet)\n  end\nend\n\n### Depth-first traversal ###\ndef graph_dfs(graph, start_vet)\n  # Represent the graph with an adjacency list to access all neighbors of a given vertex\n  # Vertex traversal sequence\n  res = []\n  # Hash set recording visited vertices\n  visited = Set.new\n  dfs(graph, visited, res, start_vet)\n  res\nend\n
    Visualize execution

    View full screen >

    Figure 9-12 shows the steps of depth-first traversal.

    • Straight dashed lines show recursive descent, where a new recursive call begins to visit another vertex.
    • Curved dashed lines show backtracking, where a recursive call has returned to the point from which it was made.

    To deepen your understanding, follow Figure 9-12 alongside the code and mentally simulate (or draw on paper) the entire DFS process, including when each recursive call begins and returns.

    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 9-12   Steps in depth-first traversal of a graph

    Is the depth-first traversal order unique?

    Like breadth-first traversal, depth-first traversal does not have a unique order. From any vertex, we can explore in any direction first: rearranging the order of adjacent vertices still produces a depth-first traversal.

    For example, in tree traversal, \"root \\(\\rightarrow\\) left \\(\\rightarrow\\) right\", \"left \\(\\rightarrow\\) root \\(\\rightarrow\\) right\", and \"left \\(\\rightarrow\\) right \\(\\rightarrow\\) root\" correspond to preorder, inorder, and postorder traversal. They use three different visiting priorities, yet all are depth-first traversals.

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2-complexity-analysis_1","level":3,"title":"2.   Complexity analysis","text":"

    Time complexity: Every vertex is visited \\(1\\) time, taking \\(O(|V|)\\) time, and every edge is visited \\(2\\) times, taking \\(O(2|E|)\\) time. The total time is \\(O(|V| + |E|)\\).

    Space complexity: The list res and hash set visited each contain at most \\(|V|\\) vertices, and the maximum recursion depth is \\(|V|\\), so the space complexity is \\(O(|V|)\\).

    ","path":["Chapter 9.   Graphs","9.3   Graph traversal"],"tags":[]},{"location":"chapter_graph/summary/","level":1,"title":"9.4   Summary","text":"","path":["Chapter 9.   Graphs","9.4   Summary"],"tags":[]},{"location":"chapter_graph/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • A graph consists of vertices and edges and can be represented using a vertex set and an edge set.
    • Network relationships (graphs) allow more freedom than linear relationships (linked lists) or divide-and-conquer relationships (trees), making them more complex.
    • Edges in a directed graph have a direction; all vertices in a connected graph are reachable; and each edge in a weighted graph has a weight.
    • An adjacency matrix represents a graph with a matrix. Each row (or column) represents a vertex, and entries represent edges, with \\(1\\) or \\(0\\) indicating whether an edge exists between two vertices. Adding, removing, looking up, and modifying edges are efficient, but the matrix uses substantial space.
    • An adjacency list represents a graph with multiple linked lists. The \\(i\\)-th linked list corresponds to vertex \\(i\\) and stores all of its adjacent vertices. It uses less space than an adjacency matrix, but finding an edge requires traversing a linked list, making lookups slower.
    • A long linked list in an adjacency list can be replaced with a red-black tree or a hash table to speed up lookups.
    • In terms of algorithm design, an adjacency matrix trades space for time, while an adjacency list trades time for space.
    • Graphs can model many real-world systems, such as social networks and subway networks.
    • A tree is a special case of a graph, and tree traversal is a special case of graph traversal.
    • Breadth-first graph traversal searches from near to far, expanding layer by layer. It is usually implemented with a queue.
    • Depth-first graph traversal follows a path as far as possible and backtracks when it can go no farther. It is often implemented with recursion.
    ","path":["Chapter 9.   Graphs","9.4   Summary"],"tags":[]},{"location":"chapter_graph/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Is a path defined as a sequence of vertices or a sequence of edges?

    Wikipedia's language editions give different definitions: the English edition defines a path as a sequence of edges, while the Chinese edition defines it as a sequence of vertices. The English wording is: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.

    In this book, a path is treated as a sequence of edges rather than vertices. This is because multiple edges can connect the same two vertices, in which case each edge corresponds to a path.

    Q: Can some vertices in a disconnected graph remain unvisited?

    In a disconnected graph, at least one vertex cannot be reached from a given starting vertex. Traversing the entire graph requires multiple starting points so that all connected components are visited.

    Q: Must the adjacent vertices in an adjacency list appear in a particular order?

    They may appear in any order. In practice, however, it can be useful to sort them by a specific rule, such as the order in which vertices were added or their values. This helps us quickly find a vertex with a particular extreme value.

    ","path":["Chapter 9.   Graphs","9.4   Summary"],"tags":[]},{"location":"chapter_greedy/","level":1,"title":"Chapter 15.   Greedy algorithms","text":"

    Abstract

    A sunflower turns toward the sun, always seeking the greatest opportunity to grow.

    Through a series of simple choices, a greedy strategy leads us step by step toward the best answer.

    ","path":["Chapter 15.   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 15.1   Greedy algorithms
    • 15.2   Fractional knapsack problem
    • 15.3   Maximum capacity problem
    • 15.4   Maximum product cutting problem
    • 15.5   Summary
    • 15.6   Exercises
    ","path":["Chapter 15.   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/exercises/","level":1,"title":"15.6   Exercises","text":"","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#1561-check-your-understanding","level":2,"title":"15.6.1   Check your understanding","text":"","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#1-is-choosing-the-largest-coin-always-best","level":3,"title":"1.   Is choosing the largest coin always best?","text":"

    The coin denominations are [1, 7, 10], and the target amount is 14.

    1. Follow the rule “always take the largest denomination that does not exceed the remaining amount,” and list the coins chosen.
    2. Is there a way to use fewer coins? If so, give one; if not, explain why.
    3. Can you conclude that this greedy strategy is correct for any set of coin denominations?
    Reference answer
    1. The greedy strategy chooses 10 + 1 + 1 + 1 + 1, using 5 coins in total.

    2. There is a solution with fewer coins: 7 + 7, which uses only 2.

    3. No. This counterexample shows that choosing the largest available denomination at each step does not always minimize the number of coins for arbitrary denominations. The largest immediate choice can rule out a better combination later.

    ","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#2-which-item-should-go-into-the-knapsack-first","level":3,"title":"2.   Which item should go into the knapsack first?","text":"

    A knapsack with a capacity of 4 kg can hold the following items. You may take part of an item, and the value obtained is proportional to the weight taken.

    • Item A weighs 4 kg and has a value of 20.
    • Item B weighs 3 kg and has a value of 18.
    1. What is each item's value per kilogram? Which item should you take first?
    2. If you fill the knapsack using the greedy strategy for fractional knapsack, what is the final value?
    3. When items can be divided and the knapsack limits total weight, should you compare total value or value per kilogram? Why?
    Reference answer
    1. A's value per kilogram is 20 ÷ 4 = 5, and B's is 18 ÷ 3 = 6. Take B first because it has the higher value per unit of weight.

    2. Take all of B first, using 3 kg of capacity and gaining a value of 18. With 1 kg of capacity left, take 1 kg of A for a value of 5. The final value is 18 + 5 = 23.

    3. The knapsack limits total weight, and items can be divided, so compare value per unit of weight. Although A has a higher total value, its value per kilogram is lower than B's. Taking all of A first would give a value of only 20.

    ","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#3-which-pointer-should-move-next","level":3,"title":"3.   Which pointer should move next?","text":"

    The wall heights are [1, 8, 6, 2, 5]. Use two pointers, initially at opposite ends, to find the maximum container capacity. Capacity equals the height of the shorter of the two walls multiplied by the difference between their indices.

    1. Initially, the left pointer is at index 0 and the right pointer is at index 4. What is the current capacity? Which pointer should move next?
    2. After making the move you chose in question 1, what are the two pointer indices? What is the capacity now, and which pointer should move next?
    3. For the current pair of walls, you could move the pointer at the shorter wall or the pointer at the taller wall. Which move still has a chance of producing a larger capacity? Why?
    Reference answer
    1. The current capacity is min(1, 5) × (4 - 0) = 4. The left wall is shorter, so move the left pointer.

    2. After moving the left pointer, the pointers are at indices 1 and 4. The current capacity is min(8, 5) × (4 - 1) = 15. The right wall is shorter, so move the right pointer next.

    3. Only moving the pointer at the shorter wall can still produce a larger capacity. Moving the pointer at the taller wall reduces the distance between the walls, while the container's height remains limited by the shorter wall that has not moved. That height can only stay the same or decrease, so the capacity cannot exceed its previous value. Moving the pointer at the shorter wall is the only way to have a chance of finding a taller wall.

    ","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#1562-programming-exercises","level":2,"title":"15.6.2   Programming exercises","text":"","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/exercises/#1-fractional-knapsack","level":3,"title":"1.   Fractional knapsack","text":"

    You are given arrays wgt and val of equal length, where wgt[i] > 0 and val[i] >= 0, and a knapsack capacity cap >= 0. There is one of each item, but you may take just part of it. The value obtained is proportional to the fraction of the item's total weight that you take. Use a greedy algorithm to return the maximum total value the knapsack can hold, as a real number.

    Hints
    1. First calculate each item's value per unit of weight, val[i] / wgt[i]. Preserve the fractional part of the division result.
    2. Take items with a higher value per unit of weight first.
    3. If the remaining capacity is less than the current item's weight, take just enough of the item to fill the knapsack, then stop.
    ","path":["Chapter 15.   Greedy algorithms","15.6   Exercises"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/","level":1,"title":"15.2   Fractional knapsack problem","text":"

    Question

    Given \\(n\\) items, where item \\(i\\) has weight \\(wgt[i-1]\\) and value \\(val[i-1]\\), and a knapsack with capacity \\(cap\\), each item may be selected only once. However, you may take a fraction of an item, with its value proportional to the weight taken. What is the maximum total value that fits within the knapsack's capacity? An example is shown in Figure 15-3.

    Figure 15-3   Example input for the fractional knapsack problem

    The fractional knapsack problem is very similar to the 0-1 knapsack problem. The state consists of the current item \\(i\\) and capacity \\(c\\), and the goal is to maximize value within the knapsack's capacity.

    The difference is that we may take only part of an item. As shown in Figure 15-4, we can divide items into arbitrary fractions and calculate their values in proportion to their weights.

    1. For item \\(i\\), the value per unit weight is \\(val[i-1] / wgt[i-1]\\), which we call its unit value.
    2. If we take a portion of item \\(i\\) weighing \\(w\\), the value added to the knapsack is \\(w \\times val[i-1] / wgt[i-1]\\).

    Figure 15-4   Value per unit weight of each item

    ","path":["Chapter 15.   Greedy algorithms","15.2   Fractional knapsack problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#1-choosing-a-greedy-strategy","level":3,"title":"1.   Choosing a greedy strategy","text":"

    Maximizing the total value in the knapsack essentially means maximizing the value obtained per unit weight. This leads to the greedy strategy shown in Figure 15-5.

    1. Sort the items by unit value in descending order.
    2. Iterate through the items, greedily choosing the item with the highest unit value at each step.
    3. If the remaining capacity is insufficient, take just enough of the current item to fill the knapsack.

    Figure 15-5   Greedy strategy for the fractional knapsack problem

    ","path":["Chapter 15.   Greedy algorithms","15.2   Fractional knapsack problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    We define an Item class to make it easier to sort items by unit value. We repeatedly make greedy choices, stopping and returning the result when the knapsack is full:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby fractional_knapsack.py
    class Item:\n    \"\"\"Item\"\"\"\n\n    def __init__(self, w: int, v: int):\n        self.w = w  # Item weight\n        self.v = v  # Item value\n\ndef fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int:\n    \"\"\"Fractional knapsack: greedy algorithm\"\"\"\n    # Create a list of items, each with two attributes: weight and value\n    items = [Item(w, v) for w, v in zip(wgt, val)]\n    # Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort(key=lambda item: item.v / item.w, reverse=True)\n    # Repeatedly make greedy choices\n    res = 0\n    for item in items:\n        if item.w <= cap:\n            # If enough capacity remains, put the entire current item in the knapsack\n            res += item.v\n            cap -= item.w\n        else:\n            # If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (item.v / item.w) * cap\n            # No capacity remains, so exit the loop\n            break\n    return res\n
    fractional_knapsack.cpp
    /* Item */\nclass Item {\n  public:\n    int w; // Item weight\n    int v; // Item value\n\n    Item(int w, int v) : w(w), v(v) {\n    }\n};\n\n/* Fractional knapsack: greedy algorithm */\ndouble fractionalKnapsack(vector<int> &wgt, vector<int> &val, int cap) {\n    // Create a list of items, each with two attributes: weight and value\n    vector<Item> items;\n    for (int i = 0; i < wgt.size(); i++) {\n        items.push_back(Item(wgt[i], val[i]));\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; });\n    // Repeatedly make greedy choices\n    double res = 0;\n    for (auto &item : items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (double)item.v / item.w * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.java
    /* Item */\nclass Item {\n    int w; // Item weight\n    int v; // Item value\n\n    public Item(int w, int v) {\n        this.w = w;\n        this.v = v;\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\ndouble fractionalKnapsack(int[] wgt, int[] val, int cap) {\n    // Create a list of items, each with two attributes: weight and value\n    Item[] items = new Item[wgt.length];\n    for (int i = 0; i < wgt.length; i++) {\n        items[i] = new Item(wgt[i], val[i]);\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w)));\n    // Repeatedly make greedy choices\n    double res = 0;\n    for (Item item : items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (double) item.v / item.w * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.cs
    /* Item */\nclass Item(int w, int v) {\n    public int w = w; // Item weight\n    public int v = v; // Item value\n}\n\n/* Fractional knapsack: greedy algorithm */\ndouble FractionalKnapsack(int[] wgt, int[] val, int cap) {\n    // Create a list of items, each with two attributes: weight and value\n    Item[] items = new Item[wgt.Length];\n    for (int i = 0; i < wgt.Length; i++) {\n        items[i] = new Item(wgt[i], val[i]);\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w));\n    // Repeatedly make greedy choices\n    double res = 0;\n    foreach (Item item in items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (double)item.v / item.w * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.go
    /* Item */\ntype Item struct {\n    w int // Item weight\n    v int // Item value\n}\n\n/* Fractional knapsack: greedy algorithm */\nfunc fractionalKnapsack(wgt []int, val []int, cap int) float64 {\n    // Create a list of items, each with two attributes: weight and value\n    items := make([]Item, len(wgt))\n    for i := 0; i < len(wgt); i++ {\n        items[i] = Item{wgt[i], val[i]}\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    sort.Slice(items, func(i, j int) bool {\n        return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w)\n    })\n    // Repeatedly make greedy choices\n    res := 0.0\n    for _, item := range items {\n        if item.w <= cap {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += float64(item.v)\n            cap -= item.w\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += float64(item.v) / float64(item.w) * float64(cap)\n            // No capacity remains, so exit the loop\n            break\n        }\n    }\n    return res\n}\n
    fractional_knapsack.swift
    /* Item */\nclass Item {\n    var w: Int // Item weight\n    var v: Int // Item value\n\n    init(w: Int, v: Int) {\n        self.w = w\n        self.v = v\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\nfunc fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double {\n    // Create a list of items, each with two attributes: weight and value\n    var items = zip(wgt, val).map { Item(w: $0, v: $1) }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) }\n    // Repeatedly make greedy choices\n    var res = 0.0\n    var cap = cap\n    for item in items {\n        if item.w <= cap {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += Double(item.v)\n            cap -= item.w\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += Double(item.v) / Double(item.w) * Double(cap)\n            // No capacity remains, so exit the loop\n            break\n        }\n    }\n    return res\n}\n
    fractional_knapsack.js
    /* Item */\nclass Item {\n    constructor(w, v) {\n        this.w = w; // Item weight\n        this.v = v; // Item value\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\nfunction fractionalKnapsack(wgt, val, cap) {\n    // Create a list of items, each with two attributes: weight and value\n    const items = wgt.map((w, i) => new Item(w, val[i]));\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort((a, b) => b.v / b.w - a.v / a.w);\n    // Repeatedly make greedy choices\n    let res = 0;\n    for (const item of items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (item.v / item.w) * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.ts
    /* Item */\nclass Item {\n    w: number; // Item weight\n    v: number; // Item value\n\n    constructor(w: number, v: number) {\n        this.w = w;\n        this.v = v;\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\nfunction fractionalKnapsack(wgt: number[], val: number[], cap: number): number {\n    // Create a list of items, each with two attributes: weight and value\n    const items: Item[] = wgt.map((w, i) => new Item(w, val[i]));\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort((a, b) => b.v / b.w - a.v / a.w);\n    // Repeatedly make greedy choices\n    let res = 0;\n    for (const item of items) {\n        if (item.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (item.v / item.w) * cap;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    return res;\n}\n
    fractional_knapsack.dart
    /* Item */\nclass Item {\n  int w; // Item weight\n  int v; // Item value\n\n  Item(this.w, this.v);\n}\n\n/* Fractional knapsack: greedy algorithm */\ndouble fractionalKnapsack(List<int> wgt, List<int> val, int cap) {\n  // Create a list of items, each with two attributes: weight and value\n  List<Item> items = List.generate(wgt.length, (i) => Item(wgt[i], val[i]));\n  // Sort by value per unit weight, item.v / item.w, in descending order\n  items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w));\n  // Repeatedly make greedy choices\n  double res = 0;\n  for (Item item in items) {\n    if (item.w <= cap) {\n      // If enough capacity remains, put the entire current item in the knapsack\n      res += item.v;\n      cap -= item.w;\n    } else {\n      // If the remaining capacity is insufficient, put part of the current item in the knapsack\n      res += item.v / item.w * cap;\n      // No capacity remains, so exit the loop\n      break;\n    }\n  }\n  return res;\n}\n
    fractional_knapsack.rs
    /* Item */\nstruct Item {\n    w: i32, // Item weight\n    v: i32, // Item value\n}\n\nimpl Item {\n    fn new(w: i32, v: i32) -> Self {\n        Self { w, v }\n    }\n}\n\n/* Fractional knapsack: greedy algorithm */\nfn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 {\n    // Create a list of items, each with two attributes: weight and value\n    let mut items = wgt\n        .iter()\n        .zip(val.iter())\n        .map(|(&w, &v)| Item::new(w, v))\n        .collect::<Vec<Item>>();\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sort_by(|a, b| {\n        (b.v as f64 / b.w as f64)\n            .partial_cmp(&(a.v as f64 / a.w as f64))\n            .unwrap()\n    });\n    // Repeatedly make greedy choices\n    let mut res = 0.0;\n    for item in &items {\n        if item.w <= cap {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v as f64;\n            cap -= item.w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += item.v as f64 / item.w as f64 * cap as f64;\n            // No capacity remains, so exit the loop\n            break;\n        }\n    }\n    res\n}\n
    fractional_knapsack.c
    /* Item */\ntypedef struct {\n    int w; // Item weight\n    int v; // Item value\n} Item;\n\n/* Fractional knapsack: greedy algorithm */\nfloat fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) {\n    // Create a list of items, each with two attributes: weight and value\n    Item *items = malloc(sizeof(Item) * itemCount);\n    for (int i = 0; i < itemCount; i++) {\n        items[i] = (Item){.w = wgt[i], .v = val[i]};\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity);\n    // Repeatedly make greedy choices\n    float res = 0.0;\n    for (int i = 0; i < itemCount; i++) {\n        if (items[i].w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += items[i].v;\n            cap -= items[i].w;\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += (float)cap / items[i].w * items[i].v;\n            cap = 0;\n            break;\n        }\n    }\n    free(items);\n    return res;\n}\n
    fractional_knapsack.kt
    /* Item */\nclass Item(\n    val w: Int, // Item weight\n    val v: Int  // Item value\n)\n\n/* Fractional knapsack: greedy algorithm */\nfun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double {\n    // Create a list of items, each with two attributes: weight and value\n    var cap = c\n    val items = arrayOfNulls<Item>(wgt.size)\n    for (i in wgt.indices) {\n        items[i] = Item(wgt[i], _val[i])\n    }\n    // Sort by value per unit weight, item.v / item.w, in descending order\n    items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) }\n    // Repeatedly make greedy choices\n    var res = 0.0\n    for (item in items) {\n        if (item!!.w <= cap) {\n            // If enough capacity remains, put the entire current item in the knapsack\n            res += item.v\n            cap -= item.w\n        } else {\n            // If the remaining capacity is insufficient, put part of the current item in the knapsack\n            res += item.v.toDouble() / item.w * cap\n            // No capacity remains, so exit the loop\n            break\n        }\n    }\n    return res\n}\n
    fractional_knapsack.rb
    ### Item ###\nclass Item\n  attr_accessor :w # Item weight\n  attr_accessor :v # Item value\n\n  def initialize(w, v)\n    @w = w\n    @v = v\n  end\nend\n\n### Fractional knapsack: greedy algorithm ###\ndef fractional_knapsack(wgt, val, cap)\n  # Create a list of items, each with two attributes: weight and value\n  items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) }\n  # Sort by value per unit weight, item.v / item.w, in descending order\n  items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) }\n  # Repeatedly make greedy choices\n  res = 0\n  for item in items\n    if item.w <= cap\n      # If enough capacity remains, put the entire current item in the knapsack\n      res += item.v\n      cap -= item.w\n    else\n      # If the remaining capacity is insufficient, put part of the current item in the knapsack\n      res += (item.v.to_f / item.w) * cap\n      # No capacity remains, so exit the loop\n      break\n    end\n  end\n  res\nend\n
    Visualize execution

    View full screen >

    Built-in sorting algorithms typically take \\(O(n \\log n)\\) time and \\(O(\\log n)\\) or \\(O(n)\\) space, depending on the programming language's implementation.

    Apart from sorting, the worst case requires traversing the entire item list, so the time complexity is \\(O(n)\\), where \\(n\\) is the number of items.

    Initializing a list of Item objects requires \\(O(n)\\) space.

    ","path":["Chapter 15.   Greedy algorithms","15.2   Fractional knapsack problem"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#3-proof-of-correctness","level":3,"title":"3.   Proof of correctness","text":"

    We use proof by contradiction. Suppose item \\(x\\) has the highest unit value, and an algorithm produces a maximum value of res, but its solution does not include item \\(x\\).

    Now remove one unit of weight of any item from the knapsack and replace it with one unit of weight of item \\(x\\). Since \\(x\\) has the highest unit value, the total value after replacement must exceed res. This contradicts the assumption that res is optimal, showing that an optimal solution must include item \\(x\\).

    We can construct the same contradiction for other items in the solution. Thus, an item with a higher unit value is always a better choice, which establishes the greedy strategy.

    As shown in Figure 15-6, if we plot item weight on the horizontal axis and unit value on the vertical axis, the fractional knapsack problem becomes a problem of maximizing the area within a limited horizontal interval. This analogy provides a geometric view of why the greedy strategy works.

    Figure 15-6   Geometric representation of the fractional knapsack problem

    ","path":["Chapter 15.   Greedy algorithms","15.2   Fractional knapsack problem"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/","level":1,"title":"15.1   Greedy algorithms","text":"

    A greedy algorithm is a common approach to optimization problems. At each decision point, it chooses the option that looks best at that moment: it makes a locally optimal choice in the hope of reaching a globally optimal solution. Greedy algorithms are simple and efficient, with many practical applications.

    Both greedy algorithms and dynamic programming are commonly used for optimization. They share some features, such as relying on optimal substructure, but work differently.

    • Dynamic programming considers the current decision in light of all decisions from earlier stages, using solutions to earlier subproblems to build the solution to the current subproblem.
    • A greedy algorithm does not reconsider past decisions. It keeps moving forward, making greedy choices and shrinking the problem until it is solved.

    Let's explore how greedy algorithms work through the coin change problem, which you have already encountered in the section on the unbounded knapsack problem.

    Question

    Given \\(n\\) coin denominations, where denomination \\(i\\) has value \\(coins[i - 1]\\), and a target amount \\(amt\\), you may use each denomination any number of times. What is the minimum number of coins needed to make the target amount? Return \\(-1\\) if it cannot be made.

    The greedy strategy is shown in Figure 15-1. Given the remaining target amount, we greedily choose the largest coin that does not exceed it, repeating this step until we have made the target amount.

    Figure 15-1   Greedy strategy for coin change

    The implementation is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_greedy.py
    def coin_change_greedy(coins: list[int], amt: int) -> int:\n    \"\"\"Coin change: greedy algorithm\"\"\"\n    # Assume the coins list is sorted\n    i = len(coins) - 1\n    count = 0\n    # Repeatedly make greedy choices until no amount remains\n    while amt > 0:\n        # Find the largest coin no greater than the remaining amount\n        while i > 0 and coins[i] > amt:\n            i -= 1\n        # Choose coins[i]\n        amt -= coins[i]\n        count += 1\n    # Return -1 if no feasible solution was found\n    return count if amt == 0 else -1\n
    coin_change_greedy.cpp
    /* Coin change: greedy algorithm */\nint coinChangeGreedy(vector<int> &coins, int amt) {\n    // Assume the coins list is sorted\n    int i = coins.size() - 1;\n    int count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin that does not exceed the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.java
    /* Coin change: greedy algorithm */\nint coinChangeGreedy(int[] coins, int amt) {\n    // Assume the coins list is sorted\n    int i = coins.length - 1;\n    int count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.cs
    /* Coin change: greedy algorithm */\nint CoinChangeGreedy(int[] coins, int amt) {\n    // Assume the coins list is sorted\n    int i = coins.Length - 1;\n    int count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.go
    /* Coin change: greedy algorithm */\nfunc coinChangeGreedy(coins []int, amt int) int {\n    // Assume the coins list is sorted in ascending order\n    i := len(coins) - 1\n    count := 0\n    // Repeatedly make greedy choices until no amount remains\n    for amt > 0 {\n        // Find the largest coin no greater than the remaining amount\n        for i > 0 && coins[i] > amt {\n            i--\n        }\n        // Choose coins[i]\n        amt -= coins[i]\n        count++\n    }\n    // Return -1 if no feasible solution was found\n    if amt != 0 {\n        return -1\n    }\n    return count\n}\n
    coin_change_greedy.swift
    /* Coin change: greedy algorithm */\nfunc coinChangeGreedy(coins: [Int], amt: Int) -> Int {\n    // Assume the coins list is sorted in ascending order\n    var i = coins.count - 1\n    var count = 0\n    var amt = amt\n    // Repeatedly make greedy choices until no amount remains\n    while amt > 0 {\n        // Find the largest coin no greater than the remaining amount\n        while i > 0 && coins[i] > amt {\n            i -= 1\n        }\n        // Choose coins[i]\n        amt -= coins[i]\n        count += 1\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1\n}\n
    coin_change_greedy.js
    /* Coin change: greedy algorithm */\nfunction coinChangeGreedy(coins, amt) {\n    // Assume coins is sorted in ascending order\n    let i = coins.length - 1;\n    let count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt === 0 ? count : -1;\n}\n
    coin_change_greedy.ts
    /* Coin change: greedy algorithm */\nfunction coinChangeGreedy(coins: number[], amt: number): number {\n    // Assume coins is sorted in ascending order\n    let i = coins.length - 1;\n    let count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt === 0 ? count : -1;\n}\n
    coin_change_greedy.dart
    /* Coin change: greedy algorithm */\nint coinChangeGreedy(List<int> coins, int amt) {\n  // Assume the coins list is sorted in ascending order\n  int i = coins.length - 1;\n  int count = 0;\n  // Repeatedly make greedy choices until no amount remains\n  while (amt > 0) {\n    // Find the largest coin no greater than the remaining amount\n    while (i > 0 && coins[i] > amt) {\n      i--;\n    }\n    // Choose coins[i]\n    amt -= coins[i];\n    count++;\n  }\n  // Return -1 if no feasible solution was found\n  return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.rs
    /* Coin change: greedy algorithm */\nfn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 {\n    // Assume the coins list is sorted\n    let mut i = coins.len() - 1;\n    let mut count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while amt > 0 {\n        // Find the largest coin that does not exceed the remaining amount\n        while i > 0 && coins[i] > amt {\n            i -= 1;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count += 1;\n    }\n    // Return -1 if no feasible solution was found\n    if amt == 0 {\n        count\n    } else {\n        -1\n    }\n}\n
    coin_change_greedy.c
    /* Coin change: greedy algorithm */\nint coinChangeGreedy(int *coins, int size, int amt) {\n    // Assume the coins list is sorted\n    int i = size - 1;\n    int count = 0;\n    // Repeatedly make greedy choices until no amount remains\n    while (amt > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > amt) {\n            i--;\n        }\n        // Choose coins[i]\n        amt -= coins[i];\n        count++;\n    }\n    // Return -1 if no feasible solution was found\n    return amt == 0 ? count : -1;\n}\n
    coin_change_greedy.kt
    /* Coin change: greedy algorithm */\nfun coinChangeGreedy(coins: IntArray, amt: Int): Int {\n    // Assume coins is sorted in ascending order\n    var am = amt\n    var i = coins.size - 1\n    var count = 0\n    // Repeatedly make greedy choices until no amount remains\n    while (am > 0) {\n        // Find the largest coin no greater than the remaining amount\n        while (i > 0 && coins[i] > am) {\n            i--\n        }\n        // Choose coins[i]\n        am -= coins[i]\n        count++\n    }\n    // Return -1 if no feasible solution was found\n    return if (am == 0) count else -1\n}\n
    coin_change_greedy.rb
    ### Coin change: greedy algorithm ###\ndef coin_change_greedy(coins, amt)\n  # Assume the coins list is sorted in ascending order\n  i = coins.length - 1\n  count = 0\n  # Repeatedly make greedy choices until no amount remains\n  while amt > 0\n    # Find the largest coin no greater than the remaining amount\n    while i > 0 && coins[i] > amt\n      i -= 1\n    end\n    # Choose coins[i]\n    amt -= coins[i]\n    count += 1\n  end\n  # Return -1 if no feasible solution was found\n  amt == 0 ? count : -1\nend\n
    Visualize execution

    View full screen >

    You might find yourself thinking, \"So clean!\" The greedy algorithm solves coin change in only about ten lines of code.

    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1511-strengths-and-limitations-of-greedy-algorithms","level":2,"title":"15.1.1   Strengths and limitations of greedy algorithms","text":"

    Greedy algorithms are straightforward, easy to implement, and usually efficient. In the code above, let \\(\\min(coins)\\) be the smallest coin denomination. At most \\(amt / \\min(coins)\\) greedy choices are made, giving a time complexity of \\(O(amt / \\min(coins))\\). This is an order of magnitude lower than the dynamic programming solution's \\(O(n \\times amt)\\) time complexity.

    However, for some sets of coin denominations, a greedy algorithm cannot find the optimal solution. Figure 15-2 illustrates two examples.

    • Successful case: \\(coins = [1, 5, 10, 20, 50, 100]\\). For these denominations, the greedy algorithm finds an optimal solution for any \\(amt\\).
    • Counterexample: \\(coins = [1, 20, 50]\\). For \\(amt = 60\\), the greedy algorithm produces \\(50 + 1 \\times 10\\), using \\(11\\) coins. Dynamic programming finds the optimal solution \\(20 + 20 + 20\\), using only \\(3\\) coins.
    • Counterexample: \\(coins = [1, 49, 50]\\). For \\(amt = 98\\), the greedy algorithm produces \\(50 + 1 \\times 48\\), using \\(49\\) coins. Dynamic programming finds the optimal solution \\(49 + 49\\), using only \\(2\\) coins.

    Figure 15-2   Examples where the greedy algorithm fails to find the optimal solution

    In other words, a greedy algorithm cannot guarantee a globally optimal solution to coin change and may produce a very poor solution. Dynamic programming is better suited to this problem.

    In general, greedy algorithms are useful in two situations.

    1. An optimal solution is guaranteed: A greedy algorithm is often the best choice because it is usually more efficient than backtracking or dynamic programming.
    2. A near-optimal solution can be found: A greedy algorithm can still be useful. For many complex problems, finding a globally optimal solution is very difficult, so finding a good suboptimal solution efficiently is valuable.
    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1512-properties-required-by-greedy-algorithms","level":2,"title":"15.1.2   Properties required by greedy algorithms","text":"

    What kinds of problems are suitable for greedy algorithms? More specifically, when can a greedy algorithm guarantee an optimal solution?

    Greedy algorithms have stricter requirements than dynamic programming. Two properties are particularly important.

    • Greedy-choice property: A greedy algorithm can guarantee an optimal solution only when locally optimal choices always lead to a globally optimal solution.
    • Optimal substructure: An optimal solution to the original problem contains optimal solutions to its subproblems.

    Optimal substructure was covered in the dynamic programming chapter, so we will not repeat that discussion here. Some problems have optimal substructure that is not immediately apparent, yet can still be solved greedily.

    We will focus on how to determine whether the greedy-choice property holds. Its description may sound simple, but proving it is difficult for many problems.

    For coin change, for example, it is easy to give counterexamples that disprove the greedy-choice property, but proving it is harder. If asked which conditions on the coin denominations allow a greedy solution, we may be able to give only a vague answer based on intuition or examples, rather than a rigorous mathematical proof.

    Quote

    One paper gives an \\(O(n^3)\\)-time algorithm for determining whether a set of coin denominations allows a greedy algorithm to find an optimal solution for every amount.

    Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.

    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1513-steps-for-solving-a-problem-greedily","level":2,"title":"15.1.3   Steps for solving a problem greedily","text":"

    Solving a greedy problem generally involves three steps.

    1. Analyze the problem: Understand its properties, including the state definition, optimization objective, and constraints. This step also appears in backtracking and dynamic programming.
    2. Choose a greedy strategy: Decide how to make a greedy choice at each step. Each choice should reduce the problem's size, eventually solving the whole problem.
    3. Prove correctness: Usually, we must establish the greedy-choice property and optimal substructure. This may require a mathematical proof, such as induction or proof by contradiction.

    Choosing the greedy strategy is the central step, but it can be difficult for the following reasons.

    • Greedy strategies vary greatly from problem to problem. For many problems, the strategy is straightforward and can be found with a little thought and experimentation. For more complex problems, it may be well hidden, testing our problem-solving experience and algorithmic skills.
    • Some greedy strategies are misleading. We may confidently devise a strategy, implement it, and submit the code, only to find that it fails some test cases. The strategy is only partially correct; the coin change problem above is a typical example.

    To ensure correctness, we should give the greedy strategy a rigorous mathematical proof, usually using contradiction or mathematical induction.

    However, such a proof can itself be difficult. When we do not know where to start, we often debug against test cases, gradually refining and checking the greedy strategy.

    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1514-typical-greedy-problems","level":2,"title":"15.1.4   Typical greedy problems","text":"

    Greedy algorithms often solve optimization problems with the greedy-choice property and optimal substructure. Some typical examples follow.

    • Coin change: For certain sets of coin denominations, a greedy algorithm always finds an optimal solution.
    • Interval scheduling: Given tasks that each occupy a time interval, the goal is to complete as many tasks as possible. Always choosing the available task that finishes earliest yields an optimal solution.
    • Fractional knapsack: Given items and a weight limit, choose items to maximize total value without exceeding the limit. Choosing the item with the highest value-to-weight ratio at each step can yield an optimal solution in some settings.
    • Stock trading: Given historical stock prices, you may buy and sell multiple times, but cannot buy again while holding stock until you sell it. The goal is to maximize profit.
    • Huffman coding: This greedy algorithm is used for lossless data compression. It builds a Huffman tree by repeatedly merging the two nodes with the lowest frequencies. The resulting tree has the minimum weighted path length (encoding length).
    • Dijkstra's algorithm: This greedy algorithm finds shortest paths from a given source vertex to every other vertex in a graph whose edge weights are all nonnegative.
    ","path":["Chapter 15.   Greedy algorithms","15.1   Greedy algorithms"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/","level":1,"title":"15.3   Maximum capacity problem","text":"

    Question

    Given an array \\(ht\\), each element represents the height of a vertical wall. Any two walls, together with the space between them, form a container.

    The container's capacity is its height multiplied by its width (an area). Its height is determined by the shorter wall, and its width is the difference between the two walls' array indices.

    Choose two walls from the array to maximize the container's capacity, and return that maximum capacity. An example is shown in Figure 15-7.

    Figure 15-7   Example input for the maximum capacity problem

    A container is formed by two walls, so the state of this problem is the pair of wall indices, denoted by \\([i, j]\\).

    Capacity is height times width, with the height determined by the shorter wall and the width equal to the difference between the wall indices. Writing the capacity as \\(cap[i, j]\\), we have:

    \\[ cap[i, j] = \\min(ht[i], ht[j]) \\times (j - i) \\]

    For an array of length \\(n\\), there are \\(C_n^2 = \\frac{n(n - 1)}{2}\\) pairs of walls (possible states). The most direct approach is to enumerate all states and find the maximum capacity, taking \\(O(n^2)\\) time.

    ","path":["Chapter 15.   Greedy algorithms","15.3   Maximum capacity problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#1-choosing-a-greedy-strategy","level":3,"title":"1.   Choosing a greedy strategy","text":"

    A more efficient solution is possible. Consider the state \\([i, j]\\) in Figure 15-8, where \\(i < j\\) and \\(ht[i] < ht[j]\\): wall \\(i\\) is shorter and wall \\(j\\) is taller.

    Figure 15-8   Initial state

    As shown in Figure 15-9, moving the taller wall \\(j\\) inward toward the shorter wall \\(i\\) must reduce the capacity.

    Moving wall \\(j\\) inward always reduces the width \\(j-i\\). Since the shorter wall determines the height, the height can only stay the same (if \\(i\\) remains shorter) or decrease (if the new wall \\(j\\) is shorter).

    Figure 15-9   State after moving the taller wall inward

    Conversely, only moving the shorter wall \\(i\\) inward can potentially increase the capacity. Although the width must decrease, the height may increase if the new wall \\(i\\) is taller. Figure 15-10 shows an example where moving the shorter wall increases the area.

    Figure 15-10   State after moving the shorter wall inward

    This gives us a greedy strategy: initialize two pointers at opposite ends of the container. At each step, move the pointer at the shorter wall inward until the pointers meet.

    Figure 15-11 illustrates the greedy process.

    1. Initially, pointers \\(i\\) and \\(j\\) are at opposite ends of the array.
    2. Calculate the current capacity \\(cap[i, j]\\) and update the maximum capacity.
    3. Compare the heights of walls \\(i\\) and \\(j\\), and move the shorter wall inward by one position.
    4. Repeat steps 2. and 3. until \\(i\\) and \\(j\\) meet.
    <1><2><3><4><5><6><7><8><9>

    Figure 15-11   Greedy steps for the maximum capacity problem

    ","path":["Chapter 15.   Greedy algorithms","15.3   Maximum capacity problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    The loop runs at most \\(n\\) times, so the time complexity is \\(O(n)\\).

    The variables \\(i\\), \\(j\\), and \\(res\\) require constant extra space, so the space complexity is \\(O(1)\\).

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_capacity.py
    def max_capacity(ht: list[int]) -> int:\n    \"\"\"Maximum container capacity: greedy algorithm\"\"\"\n    # Initialize i and j at opposite ends of the array\n    i, j = 0, len(ht) - 1\n    # The initial maximum capacity is 0\n    res = 0\n    # Repeatedly make greedy choices until the two sides meet\n    while i < j:\n        # Update the maximum capacity\n        cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        # Move the shorter side inward\n        if ht[i] < ht[j]:\n            i += 1\n        else:\n            j -= 1\n    return res\n
    max_capacity.cpp
    /* Maximum container capacity: greedy algorithm */\nint maxCapacity(vector<int> &ht) {\n    // Initialize i and j at opposite ends of the array\n    int i = 0, j = ht.size() - 1;\n    // The initial maximum capacity is 0\n    int res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        int cap = min(ht[i], ht[j]) * (j - i);\n        res = max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
    max_capacity.java
    /* Maximum container capacity: greedy algorithm */\nint maxCapacity(int[] ht) {\n    // Initialize i and j at opposite ends of the array\n    int i = 0, j = ht.length - 1;\n    // The initial maximum capacity is 0\n    int res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        int cap = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
    max_capacity.cs
    /* Maximum container capacity: greedy algorithm */\nint MaxCapacity(int[] ht) {\n    // Initialize i and j at opposite ends of the array\n    int i = 0, j = ht.Length - 1;\n    // The initial maximum capacity is 0\n    int res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        int cap = Math.Min(ht[i], ht[j]) * (j - i);\n        res = Math.Max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
    max_capacity.go
    /* Maximum container capacity: greedy algorithm */\nfunc maxCapacity(ht []int) int {\n    // Initialize i and j at opposite ends of the array\n    i, j := 0, len(ht)-1\n    // The initial maximum capacity is 0\n    res := 0\n    // Repeatedly make greedy choices until the two sides meet\n    for i < j {\n        // Update the maximum capacity\n        capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i)\n        res = int(math.Max(float64(res), float64(capacity)))\n        // Move the shorter side inward\n        if ht[i] < ht[j] {\n            i++\n        } else {\n            j--\n        }\n    }\n    return res\n}\n
    max_capacity.swift
    /* Maximum container capacity: greedy algorithm */\nfunc maxCapacity(ht: [Int]) -> Int {\n    // Initialize i and j at opposite ends of the array\n    var i = ht.startIndex, j = ht.endIndex - 1\n    // The initial maximum capacity is 0\n    var res = 0\n    // Repeatedly make greedy choices until the two sides meet\n    while i < j {\n        // Update the maximum capacity\n        let cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        // Move the shorter side inward\n        if ht[i] < ht[j] {\n            i += 1\n        } else {\n            j -= 1\n        }\n    }\n    return res\n}\n
    max_capacity.js
    /* Maximum container capacity: greedy algorithm */\nfunction maxCapacity(ht) {\n    // Initialize i and j at opposite ends of the array\n    let i = 0,\n        j = ht.length - 1;\n    // The initial maximum capacity is 0\n    let res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        const cap = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    return res;\n}\n
    max_capacity.ts
    /* Maximum container capacity: greedy algorithm */\nfunction maxCapacity(ht: number[]): number {\n    // Initialize i and j at opposite ends of the array\n    let i = 0,\n        j = ht.length - 1;\n    // The initial maximum capacity is 0\n    let res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        const cap: number = Math.min(ht[i], ht[j]) * (j - i);\n        res = Math.max(res, cap);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    return res;\n}\n
    max_capacity.dart
    /* Maximum container capacity: greedy algorithm */\nint maxCapacity(List<int> ht) {\n  // Initialize i and j at opposite ends of the array\n  int i = 0, j = ht.length - 1;\n  // The initial maximum capacity is 0\n  int res = 0;\n  // Repeatedly make greedy choices until the two sides meet\n  while (i < j) {\n    // Update the maximum capacity\n    int cap = min(ht[i], ht[j]) * (j - i);\n    res = max(res, cap);\n    // Move the shorter side inward\n    if (ht[i] < ht[j]) {\n      i++;\n    } else {\n      j--;\n    }\n  }\n  return res;\n}\n
    max_capacity.rs
    /* Maximum container capacity: greedy algorithm */\nfn max_capacity(ht: &[i32]) -> i32 {\n    // Initialize i and j at opposite ends of the array\n    let mut i = 0;\n    let mut j = ht.len() - 1;\n    // The initial maximum capacity is 0\n    let mut res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while i < j {\n        // Update the maximum capacity\n        let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32;\n        res = std::cmp::max(res, cap);\n        // Move the shorter side inward\n        if ht[i] < ht[j] {\n            i += 1;\n        } else {\n            j -= 1;\n        }\n    }\n    res\n}\n
    max_capacity.c
    /* Maximum container capacity: greedy algorithm */\nint maxCapacity(int ht[], int htLength) {\n    // Initialize i and j at opposite ends of the array\n    int i = 0;\n    int j = htLength - 1;\n    // The initial maximum capacity is 0\n    int res = 0;\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        int capacity = myMin(ht[i], ht[j]) * (j - i);\n        res = myMax(res, capacity);\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++;\n        } else {\n            j--;\n        }\n    }\n    return res;\n}\n
    max_capacity.kt
    /* Maximum container capacity: greedy algorithm */\nfun maxCapacity(ht: IntArray): Int {\n    // Initialize i and j at opposite ends of the array\n    var i = 0\n    var j = ht.size - 1\n    // The initial maximum capacity is 0\n    var res = 0\n    // Repeatedly make greedy choices until the two sides meet\n    while (i < j) {\n        // Update the maximum capacity\n        val cap = min(ht[i], ht[j]) * (j - i)\n        res = max(res, cap)\n        // Move the shorter side inward\n        if (ht[i] < ht[j]) {\n            i++\n        } else {\n            j--\n        }\n    }\n    return res\n}\n
    max_capacity.rb
    ### Maximum container capacity: greedy algorithm ###\ndef max_capacity(ht)\n  # Initialize i and j at opposite ends of the array\n  i, j = 0, ht.length - 1\n  # The initial maximum capacity is 0\n  res = 0\n\n  # Repeatedly make greedy choices until the two sides meet\n  while i < j\n    # Update the maximum capacity\n    cap = [ht[i], ht[j]].min * (j - i)\n    res = [res, cap].max\n    # Move the shorter side inward\n    if ht[i] < ht[j]\n      i += 1\n    else\n      j -= 1\n    end\n  end\n\n  res\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 15.   Greedy algorithms","15.3   Maximum capacity problem"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#3-proof-of-correctness","level":3,"title":"3.   Proof of correctness","text":"

    The greedy algorithm is faster than exhaustive search because each greedy choice skips some states.

    For example, in state \\(cap[i, j]\\), suppose \\(i\\) is the shorter wall and \\(j\\) the taller one. Moving the shorter wall \\(i\\) inward by one position skips the states shown in Figure 15-12. Their capacities will never be checked later.

    \\[ cap[i, i+1], cap[i, i+2], \\dots, cap[i, j-2], cap[i, j-1] \\]

    Figure 15-12   States skipped by moving the shorter wall

    Observe that the skipped states are precisely those obtained by moving the taller wall \\(j\\) inward. We have already shown that moving the taller wall inward must reduce capacity. None of these skipped states can therefore be optimal, and skipping them cannot cause us to miss the optimal solution.

    This shows that moving the shorter wall is safe, and that the greedy strategy is valid.

    ","path":["Chapter 15.   Greedy algorithms","15.3   Maximum capacity problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/","level":1,"title":"15.4   Maximum product cutting problem","text":"

    Question

    Given a positive integer \\(n\\), split it into a sum of at least two positive integers. What is the largest possible product of those integers? The problem is illustrated in Figure 15-13.

    Figure 15-13   Definition of the maximum product cutting problem

    Suppose we split \\(n\\) into \\(m\\) integers that will be factors in the product. Denote the \\(i\\)-th factor by \\(n_i\\), so that

    \\[ n = \\sum_{i=1}^{m}n_i \\]

    Our goal is to maximize the product of all these integer factors:

    \\[ \\max(\\prod_{i=1}^{m}n_i) \\]

    We need to determine how many parts \\(m\\) to create and what each \\(n_i\\) should be.

    ","path":["Chapter 15.   Greedy algorithms","15.4   Maximum product cutting problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#1-choosing-a-greedy-strategy","level":3,"title":"1.   Choosing a greedy strategy","text":"

    The product of two integers is often greater than their sum. Suppose we split off a factor of \\(2\\) from \\(n\\), giving the product \\(2(n-2)\\). Comparing this product with \\(n\\) gives:

    \\[ \\begin{aligned} 2(n-2) & \\geq n \\newline 2n - n - 4 & \\geq 0 \\newline n & \\geq 4 \\end{aligned} \\]

    As shown in Figure 15-14, when \\(n \\geq 4\\), splitting off a \\(2\\) increases the product. This suggests that every integer greater than or equal to \\(4\\) should be split further.

    Greedy strategy 1: If a factor \\(\\geq 4\\) appears in a split, divide it further. The final split should contain only the factors \\(1\\), \\(2\\), and \\(3\\).

    Figure 15-14   Splitting increases the product

    Next, consider which factor is best. Of \\(1\\), \\(2\\), and \\(3\\), clearly \\(1\\) is the worst: \\(1 \\times (n-1) < n\\) always holds, so splitting off a \\(1\\) reduces the product.

    As shown in Figure 15-15, when \\(n = 6\\), we have \\(3 \\times 3 > 2 \\times 2 \\times 2\\). This means that splitting into factors of \\(3\\) is better than splitting into factors of \\(2\\).

    Greedy strategy 2: A split should contain at most two factors of \\(2\\), because three \\(2\\)s can always be replaced with two \\(3\\)s to obtain a larger product.

    Figure 15-15   Optimal factors

    These observations lead to the following greedy strategy.

    1. Starting with \\(n\\), keep splitting off factors of \\(3\\) until the remainder is \\(0\\), \\(1\\), or \\(2\\).
    2. If the remainder is \\(0\\), then \\(n\\) is a multiple of \\(3\\), and no further action is needed.
    3. If the remainder is \\(2\\), keep it without splitting it further.
    4. If the remainder is \\(1\\), replace the last \\(3\\) and the remaining \\(1\\) with two \\(2\\)s, since \\(2 \\times 2 > 1 \\times 3\\).
    ","path":["Chapter 15.   Greedy algorithms","15.4   Maximum product cutting problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#2-code-implementation","level":3,"title":"2.   Code implementation","text":"

    As shown in Figure 15-16, we do not need a loop to split the integer. Floor division gives the number of \\(3\\)s, denoted by \\(a\\), and the modulo operation gives the remainder \\(b\\). Thus:

    \\[ n = 3 a + b \\]

    For the boundary cases \\(n \\leq 3\\), we must split off a \\(1\\), giving a product of \\(1 \\times (n - 1)\\).

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_product_cutting.py
    def max_product_cutting(n: int) -> int:\n    \"\"\"Maximum product after splitting: greedy algorithm\"\"\"\n    # When n <= 3, one part must be 1\n    if n <= 3:\n        return 1 * (n - 1)\n    # Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    a, b = n // 3, n % 3\n    if b == 1:\n        # When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return int(math.pow(3, a - 1)) * 2 * 2\n    if b == 2:\n        # When the remainder is 2, no adjustment is needed\n        return int(math.pow(3, a)) * 2\n    # When the remainder is 0, no adjustment is needed\n    return int(math.pow(3, a))\n
    max_product_cutting.cpp
    /* Maximum product after splitting: greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return (int)pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return (int)pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return (int)pow(3, a);\n}\n
    max_product_cutting.java
    /* Maximum product after splitting: greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return (int) Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return (int) Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return (int) Math.pow(3, a);\n}\n
    max_product_cutting.cs
    /* Maximum product after splitting: greedy algorithm */\nint MaxProductCutting(int n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return (int)Math.Pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return (int)Math.Pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return (int)Math.Pow(3, a);\n}\n
    max_product_cutting.go
    /* Maximum product after splitting: greedy algorithm */\nfunc maxProductCutting(n int) int {\n    // When n <= 3, one part must be 1\n    if n <= 3 {\n        return 1 * (n - 1)\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    a := n / 3\n    b := n % 3\n    if b == 1 {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return int(math.Pow(3, float64(a-1))) * 2 * 2\n    }\n    if b == 2 {\n        // When the remainder is 2, no adjustment is needed\n        return int(math.Pow(3, float64(a))) * 2\n    }\n    // When the remainder is 0, no adjustment is needed\n    return int(math.Pow(3, float64(a)))\n}\n
    max_product_cutting.swift
    /* Maximum product after splitting: greedy algorithm */\nfunc maxProductCutting(n: Int) -> Int {\n    // When n <= 3, one part must be 1\n    if n <= 3 {\n        return 1 * (n - 1)\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    let a = n / 3\n    let b = n % 3\n    if b == 1 {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return pow(3, a - 1) * 2 * 2\n    }\n    if b == 2 {\n        // When the remainder is 2, no adjustment is needed\n        return pow(3, a) * 2\n    }\n    // When the remainder is 0, no adjustment is needed\n    return pow(3, a)\n}\n
    max_product_cutting.js
    /* Maximum product after splitting: greedy algorithm */\nfunction maxProductCutting(n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    let a = Math.floor(n / 3);\n    let b = n % 3;\n    if (b === 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b === 2) {\n        // When the remainder is 2, no adjustment is needed\n        return Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return Math.pow(3, a);\n}\n
    max_product_cutting.ts
    /* Maximum product after splitting: greedy algorithm */\nfunction maxProductCutting(n: number): number {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    let a: number = Math.floor(n / 3);\n    let b: number = n % 3;\n    if (b === 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return Math.pow(3, a - 1) * 2 * 2;\n    }\n    if (b === 2) {\n        // When the remainder is 2, no adjustment is needed\n        return Math.pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return Math.pow(3, a);\n}\n
    max_product_cutting.dart
    /* Maximum product after splitting: greedy algorithm */\nint maxProductCutting(int n) {\n  // When n <= 3, one part must be 1\n  if (n <= 3) {\n    return 1 * (n - 1);\n  }\n  // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n  int a = n ~/ 3;\n  int b = n % 3;\n  if (b == 1) {\n    // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n    return (pow(3, a - 1) * 2 * 2).toInt();\n  }\n  if (b == 2) {\n    // When the remainder is 2, no adjustment is needed\n    return (pow(3, a) * 2).toInt();\n  }\n  // When the remainder is 0, no adjustment is needed\n  return pow(3, a).toInt();\n}\n
    max_product_cutting.rs
    /* Maximum product after splitting: greedy algorithm */\nfn max_product_cutting(n: i32) -> i32 {\n    // When n <= 3, one part must be 1\n    if n <= 3 {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    let a = n / 3;\n    let b = n % 3;\n    if b == 1 {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        3_i32.pow(a as u32 - 1) * 2 * 2\n    } else if b == 2 {\n        // When the remainder is 2, no adjustment is needed\n        3_i32.pow(a as u32) * 2\n    } else {\n        // When the remainder is 0, no adjustment is needed\n        3_i32.pow(a as u32)\n    }\n}\n
    max_product_cutting.c
    /* Maximum product after splitting: greedy algorithm */\nint maxProductCutting(int n) {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1);\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    int a = n / 3;\n    int b = n % 3;\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return pow(3, a - 1) * 2 * 2;\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return pow(3, a) * 2;\n    }\n    // When the remainder is 0, no adjustment is needed\n    return pow(3, a);\n}\n
    max_product_cutting.kt
    /* Maximum product after splitting: greedy algorithm */\nfun maxProductCutting(n: Int): Int {\n    // When n <= 3, one part must be 1\n    if (n <= 3) {\n        return 1 * (n - 1)\n    }\n    // Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n    val a = n / 3\n    val b = n % 3\n    if (b == 1) {\n        // When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n        return 3.0.pow((a - 1)).toInt() * 2 * 2\n    }\n    if (b == 2) {\n        // When the remainder is 2, no adjustment is needed\n        return 3.0.pow(a).toInt() * 2 * 2\n    }\n    // When the remainder is 0, no adjustment is needed\n    return 3.0.pow(a).toInt()\n}\n
    max_product_cutting.rb
    ### Maximum product after splitting: greedy algorithm ###\ndef max_product_cutting(n)\n  # When n <= 3, one part must be 1\n  return 1 * (n - 1) if n <= 3\n  # Greedily split off parts of size 3; a is the number of 3s and b is the remainder\n  a, b = n / 3, n % 3\n  # When the remainder is 1, replace one pair 1 * 3 with 2 * 2\n  return (3.pow(a - 1) * 2 * 2).to_i if b == 1\n  # When the remainder is 2, no adjustment is needed\n  return (3.pow(a) * 2).to_i if b == 2\n  # When the remainder is 0, no adjustment is needed\n  3.pow(a).to_i\nend\n
    Visualize execution

    View full screen >

    Figure 15-16   Calculating the maximum product

    The time complexity depends on how the programming language implements exponentiation. Python, for example, offers three common ways to compute powers.

    • The ** operator and the pow() function both have a time complexity of \\(O(\\log⁡ a)\\).
    • The math.pow() function calls the C library's pow() function internally, performing floating-point exponentiation in \\(O(1)\\) time.

    The variables \\(a\\) and \\(b\\) use constant extra space, so the space complexity is \\(O(1)\\).

    ","path":["Chapter 15.   Greedy algorithms","15.4   Maximum product cutting problem"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#3-proof-of-correctness","level":3,"title":"3.   Proof of correctness","text":"

    We use proof by contradiction, considering only \\(n \\geq 4\\).

    1. All factors are \\(\\leq 3\\): Suppose an optimal split contains a factor \\(x\\) that is \\(\\geq 4\\). We can split it further to obtain \\(2(x-2)\\), giving a greater (or equal) product. This contradicts the assumption.
    2. The split contains no \\(1\\): Suppose an optimal split contains a factor of \\(1\\). We can merge it into another factor to obtain a larger product, contradicting the assumption.
    3. The split contains at most two \\(2\\)s: Suppose an optimal split contains three \\(2\\)s. Replacing them with two \\(3\\)s gives a larger product, contradicting the assumption.
    ","path":["Chapter 15.   Greedy algorithms","15.4   Maximum product cutting problem"],"tags":[]},{"location":"chapter_greedy/summary/","level":1,"title":"15.5   Summary","text":"","path":["Chapter 15.   Greedy algorithms","15.5   Summary"],"tags":[]},{"location":"chapter_greedy/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Greedy algorithms are commonly used for optimization. At each decision point, they make a locally optimal choice in the hope of reaching a globally optimal solution.
    • A greedy algorithm repeatedly makes greedy choices, turning the problem into a smaller subproblem at each step until it is solved.
    • Greedy algorithms are easy to implement and efficient. Their time complexity is usually lower than that of dynamic programming.
    • For some coin denominations, a greedy algorithm is guaranteed to find an optimal coin change solution. For others, it may produce a very poor solution.
    • Problems suited to greedy algorithms have two properties: the greedy-choice property and optimal substructure. The greedy-choice property establishes that the strategy works.
    • Proving the greedy-choice property can be difficult for complex problems. Disproving it is often easier, as coin change illustrates.
    • Solving a greedy problem involves three main steps: analyze the problem, choose a greedy strategy, and prove correctness. Choosing the strategy is the central step, while proving correctness is often the hardest.
    • The fractional knapsack problem extends 0-1 knapsack by allowing fractions of items, making a greedy solution possible. The strategy can be proved correct by contradiction.
    • The maximum capacity problem can be solved by exhaustive search in \\(O(n^2)\\) time. A greedy strategy that moves the shorter wall inward at each step reduces this to \\(O(n)\\).
    • For the maximum product cutting problem, we derived two greedy strategies: continue splitting integers \\(\\geq 4\\), and favor factors of \\(3\\). The implementation uses exponentiation, so its time complexity depends on the exponentiation method, usually \\(O(1)\\) or \\(O(\\log n)\\).
    ","path":["Chapter 15.   Greedy algorithms","15.5   Summary"],"tags":[]},{"location":"chapter_hashing/","level":1,"title":"Chapter 6.   Hash tables","text":"

    Abstract

    In the world of computers, a hash table is like a knowledgeable librarian.

    Knowing how to calculate a call number lets the librarian find the desired book quickly.

    ","path":["Chapter 6.   Hash tables"],"tags":[]},{"location":"chapter_hashing/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 6.1   Hash tables
    • 6.2   Hash collisions
    • 6.3   Hash algorithms
    • 6.4   Summary
    • 6.5   Exercises
    ","path":["Chapter 6.   Hash tables"],"tags":[]},{"location":"chapter_hashing/exercises/","level":1,"title":"6.5   Exercises","text":"","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#651-check-your-understanding","level":2,"title":"6.5.1   Check your understanding","text":"","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#1-searching-after-a-hash-collision","level":3,"title":"1.   Searching after a hash collision","text":"

    A hash table has five buckets and uses the hash function \\(h(x)=x \\bmod 5\\). When elements collide, they are added in order to the list in that bucket. Insert [1, 6, 11, 7] in sequence:

    1. Write out the contents of buckets 0 through 4.
    2. When searching for 6, which bucket is visited first, and which elements are checked in order?
    3. Based on the bucket contents from question 1, did later elements overwrite earlier ones? Explain using this collision-handling method.
    Reference answer
    1. Since \\(1\\bmod5=6\\bmod5=11\\bmod5=1\\) and \\(7\\bmod5=2\\), the buckets contain:

      0: []\n1: [1, 6, 11]\n2: [7]\n3: []\n4: []\n
    2. Searching for 6 first visits bucket 1 and compares against 1 and then 6, finding the target on the second comparison.

    3. Equal hash values mean only that the elements land in the same bucket, not that the elements themselves are equal. Separate chaining keeps all colliding elements in the bucket and compares them one by one during a search, so 1, 6, and 11 do not overwrite one another.

    ","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#2-where-do-elements-go-when-a-hash-table-grows","level":3,"title":"2.   Where do elements go when a hash table grows?","text":"

    A hash table using separate chaining initially has five buckets and the hash function \\(h(x)=x\\bmod5\\). The keys [1, 6, 11] are all in bucket 1.

    The table is now expanded to seven buckets, and the hash function becomes \\(h(x)=x\\bmod7\\):

    1. Calculate the new bucket number for each of 1, 6, and 11.
    2. Which buckets contain elements after the expansion?
    3. Can the list in the old bucket 1 simply be copied unchanged into the new bucket 1? Explain using your results from questions 1 and 2.
    Reference answer
    1. The new bucket numbers are:

      • \\(1\\bmod7=1\\);
      • \\(6\\bmod7=6\\);
      • \\(11\\bmod7=4\\).
    2. Bucket 1 holds 1, bucket 4 holds 11, and bucket 6 holds 6. The three keys no longer share one bucket.

    3. The list cannot simply be copied unchanged. A bucket number is the remainder of the key divided by the number of buckets. When that number changes from 5 to 7, a key's bucket may change, so every key's position must be recalculated. If we copied the old bucket 1 unchanged, subsequent searches for 6 and 11 using the new formula would visit buckets 6 and 4, respectively, and fail to find them.

    ","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#3-can-we-still-find-11-after-deleting-6","level":3,"title":"3.   Can we still find 11 after deleting 6?","text":"

    A hash table has five positions with indices 0-4 and uses the hash function \\(h(x)=x\\bmod5\\). When a collision occurs, it starts at the computed index and searches to the right for the first empty position.

    Insert [1, 6, 11] in order:

    1. At which index is each number ultimately stored?
    2. Which indices are checked, in order, when searching for 11?
    3. Suppose deleting 6 marks its position as an empty slot that has never been used, and a search stops on reaching an empty slot. What happens when we search for 11 again? Is the result correct? If not, how can we avoid the problem?
    Reference answer
    1. The number 1 is stored at index 1. The number 6 also maps to index 1, so after the collision it is stored at index 2. The number 11 likewise starts at index 1, skips occupied indices 1 and 2, and is finally stored at index 3.

    2. Searching for 11 checks indices 1, 2, 3 in order and finds it at index 3.

    3. If index 2 is marked as an empty slot that has never been used, a search for 11 checks index 1 and then stops at index 2, incorrectly concluding that 11 is absent. Deletion should leave a deleted marker instead: a search continues to the next index when it encounters this marker, wrapping from index 4 to index 0. Later insertions can still reuse the position.

    ","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#652-programming-exercises","level":2,"title":"6.5.2   Programming exercises","text":"","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/exercises/#1-comparing-the-characters-in-two-strings","level":3,"title":"1.   Comparing the characters in two strings","text":"

    Given two strings s and t containing only lowercase English letters, you may rearrange the characters in s freely, but may not add, delete, or replace any characters.

    Determine whether the rearrangement can produce t. Return true if it can, or false otherwise. Use a hash table to record the frequency of each letter; do not sort the characters in either string.

    Hints
    1. Strings of different lengths cannot contain the same characters with the same frequencies.
    2. Use a hash table to record each letter's count; increment the appropriate count by 1 while scanning s.
    3. Decrement the appropriate count by 1 while scanning t. The character frequencies match only if all counts end at 0.

    LeetCode

    ","path":["Chapter 6.   Hash tables","6.5   Exercises"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/","level":1,"title":"6.3   Hash algorithms","text":"

    The previous two sections explained how hash tables work and how collisions are resolved. Whether we use open addressing or separate chaining, these techniques keep the table working when collisions occur, but do not reduce how often collisions happen.

    Frequent collisions can severely degrade performance. In the separate-chaining table shown below, the ideal case distributes pairs evenly among buckets for the best lookup efficiency. In the worst case, all pairs occupy the same bucket, degrading the time complexity to \\(O(n)\\).

    Figure 6-8   Best and worst cases for hash collisions

    The hash function determines how key-value pairs are distributed. Recall its two steps: calculate a hash value, then take it modulo the array length:

    index = hash(key) % capacity\n

    For a fixed capacity, the hash algorithm hash() determines the output, and therefore the distribution of pairs in the table.

    To lower the likelihood of collisions, we should therefore focus on designing hash().

    ","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#631-goals-of-a-hash-algorithm","level":2,"title":"6.3.1   Goals of a hash algorithm","text":"

    To build a hash table that is both fast and reliable, its hash algorithm should have these properties.

    • Determinism: The same input must always produce the same output, ensuring reliable lookup.
    • Efficiency: Calculating a hash should be fast. Lower computational overhead makes the table more practical.
    • Uniform distribution: The algorithm should distribute pairs evenly across the table. More uniform distribution means fewer collisions.

    Hash algorithms also have many applications beyond hash tables.

    • Password storage: To protect passwords, systems usually store password hashes rather than plaintext passwords. When a user enters a password, the system hashes it and compares the result with the stored hash. A match means the password is accepted.
    • Data-integrity checking: A sender can calculate a hash of the data and send it alongside the data. The receiver recomputes the hash and compares it with the received hash. A match indicates that the data is intact.

    Cryptographic applications require stronger security properties to prevent reverse engineering, such as recovering an original password from its hash.

    • One-wayness: The hash should not reveal any information about the input data.
    • Collision resistance: Finding two different inputs with the same hash should be extremely difficult.
    • Avalanche effect: A small input change should produce a large, unpredictable output change.

    Uniform distribution and collision resistance are independent concepts. Uniform output does not guarantee collision resistance. For example, with random keys, key % 100 can produce uniform outputs. But this algorithm is too simple: all keys with the same last two digits give the same output. It is therefore easy to infer a usable key from a hash and crack a password.

    ","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#632-designing-a-hash-algorithm","level":2,"title":"6.3.2   Designing a hash algorithm","text":"

    Designing a hash algorithm is complex and involves many factors. For less demanding uses, however, we can devise simple algorithms.

    • Additive hashing: Add the ASCII codes of all input characters and use the sum as the hash.
    • Multiplicative hashing: Use multiplication to mix values, multiplying by a constant each round and accumulating character ASCII codes into the hash.
    • XOR hashing: Accumulate each input element into a hash using exclusive OR.
    • Rotational hashing: Accumulate each character's ASCII code into a hash, rotating the hash before each accumulation.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby simple_hash.py
    def add_hash(key: str) -> int:\n    \"\"\"Additive hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash += ord(c)\n    return hash % modulus\n\ndef mul_hash(key: str) -> int:\n    \"\"\"Multiplicative hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash = 31 * hash + ord(c)\n    return hash % modulus\n\ndef xor_hash(key: str) -> int:\n    \"\"\"XOR hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash ^= ord(c)\n    return hash % modulus\n\ndef rot_hash(key: str) -> int:\n    \"\"\"Rotating hash\"\"\"\n    hash = 0\n    modulus = 1000000007\n    for c in key:\n        hash = (hash << 4) ^ (hash >> 28) ^ ord(c)\n    return hash % modulus\n
    simple_hash.cpp
    /* Additive hash */\nint addHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = (hash + (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint mulHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = (31 * hash + (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint xorHash(string key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash ^= (int)c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotating hash */\nint rotHash(string key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (unsigned char c : key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS;\n    }\n    return (int)hash;\n}\n
    simple_hash.java
    /* Additive hash */\nint addHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = (hash + (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n\n/* Multiplicative hash */\nint mulHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = (31 * hash + (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n\n/* XOR hash */\nint xorHash(String key) {\n    int hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash ^= (int) c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotating hash */\nint rotHash(String key) {\n    long hash = 0;\n    final int MODULUS = 1000000007;\n    for (char c : key.toCharArray()) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS;\n    }\n    return (int) hash;\n}\n
    simple_hash.cs
    /* Additive hash */\nint AddHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = (hash + c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint MulHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = (31 * hash + c) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint XorHash(string key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash ^= c;\n    }\n    return hash & MODULUS;\n}\n\n/* Rotating hash */\nint RotHash(string key) {\n    long hash = 0;\n    const int MODULUS = 1000000007;\n    foreach (char c in key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS;\n    }\n    return (int)hash;\n}\n
    simple_hash.go
    /* Additive hash */\nfunc addHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = (hash + int64(b)) % modulus\n    }\n    return int(hash)\n}\n\n/* Multiplicative hash */\nfunc mulHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = (31*hash + int64(b)) % modulus\n    }\n    return int(hash)\n}\n\n/* XOR hash */\nfunc xorHash(key string) int {\n    hash := 0\n    modulus := 1000000007\n    for _, b := range []byte(key) {\n        fmt.Println(int(b))\n        hash ^= int(b)\n        hash = (31*hash + int(b)) % modulus\n    }\n    return hash & modulus\n}\n\n/* Rotating hash */\nfunc rotHash(key string) int {\n    var hash int64\n    var modulus int64\n\n    modulus = 1000000007\n    for _, b := range []byte(key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus\n    }\n    return int(hash)\n}\n
    simple_hash.swift
    /* Additive hash */\nfunc addHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = (hash + Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n\n/* Multiplicative hash */\nfunc mulHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = (31 * hash + Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n\n/* XOR hash */\nfunc xorHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash ^= Int(scalar.value)\n        }\n    }\n    return hash & MODULUS\n}\n\n/* Rotating hash */\nfunc rotHash(key: String) -> Int {\n    var hash = 0\n    let MODULUS = 1_000_000_007\n    for c in key {\n        for scalar in c.unicodeScalars {\n            hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS\n        }\n    }\n    return hash\n}\n
    simple_hash.js
    /* Additive hash */\nfunction addHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* Multiplicative hash */\nfunction mulHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* XOR hash */\nfunction xorHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash ^= c.charCodeAt(0);\n    }\n    return hash % MODULUS;\n}\n\n/* Rotating hash */\nfunction rotHash(key) {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n
    simple_hash.ts
    /* Additive hash */\nfunction addHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* Multiplicative hash */\nfunction mulHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n\n/* XOR hash */\nfunction xorHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash ^= c.charCodeAt(0);\n    }\n    return hash % MODULUS;\n}\n\n/* Rotating hash */\nfunction rotHash(key: string): number {\n    let hash = 0;\n    const MODULUS = 1000000007;\n    for (const c of key) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS;\n    }\n    return hash;\n}\n
    simple_hash.dart
    /* Additive hash */\nint addHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = (hash + key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n\n/* Multiplicative hash */\nint mulHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = (31 * hash + key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n\n/* XOR hash */\nint xorHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash ^= key.codeUnitAt(i);\n  }\n  return hash & MODULUS;\n}\n\n/* Rotating hash */\nint rotHash(String key) {\n  int hash = 0;\n  final int MODULUS = 1000000007;\n  for (int i = 0; i < key.length; i++) {\n    hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS;\n  }\n  return hash;\n}\n
    simple_hash.rs
    /* Additive hash */\nfn add_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = (hash + c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n\n/* Multiplicative hash */\nfn mul_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = (31 * hash + c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n\n/* XOR hash */\nfn xor_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash ^= c as i64;\n    }\n\n    (hash & MODULUS) as i32\n}\n\n/* Rotating hash */\nfn rot_hash(key: &str) -> i32 {\n    let mut hash = 0_i64;\n    const MODULUS: i64 = 1000000007;\n\n    for c in key.chars() {\n        hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS;\n    }\n\n    hash as i32\n}\n
    simple_hash.c
    /* Additive hash */\nint addHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = (hash + (unsigned char)key[i]) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* Multiplicative hash */\nint mulHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = (31 * hash + (unsigned char)key[i]) % MODULUS;\n    }\n    return (int)hash;\n}\n\n/* XOR hash */\nint xorHash(char *key) {\n    int hash = 0;\n    const int MODULUS = 1000000007;\n\n    for (int i = 0; i < strlen(key); i++) {\n        hash ^= (unsigned char)key[i];\n    }\n    return hash & MODULUS;\n}\n\n/* Rotating hash */\nint rotHash(char *key) {\n    long long hash = 0;\n    const int MODULUS = 1000000007;\n    for (int i = 0; i < strlen(key); i++) {\n        hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS;\n    }\n\n    return (int)hash;\n}\n
    simple_hash.kt
    /* Additive hash */\nfun addHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = (hash + c.code) % MODULUS\n    }\n    return hash.toInt()\n}\n\n/* Multiplicative hash */\nfun mulHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = (31 * hash + c.code) % MODULUS\n    }\n    return hash.toInt()\n}\n\n/* XOR hash */\nfun xorHash(key: String): Int {\n    var hash = 0\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = hash xor c.code\n    }\n    return hash and MODULUS\n}\n\n/* Rotating hash */\nfun rotHash(key: String): Int {\n    var hash = 0L\n    val MODULUS = 1000000007\n    for (c in key.toCharArray()) {\n        hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS\n    }\n    return hash.toInt()\n}\n
    simple_hash.rb
    ### Additive hash ###\ndef add_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash += c.ord }\n\n  hash % modulus\nend\n\n### Multiplicative hash ###\ndef mul_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash = 31 * hash + c.ord }\n\n  hash % modulus\nend\n\n### XOR hash ###\ndef xor_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash ^= c.ord }\n\n  hash % modulus\nend\n\n### Rotating hash ###\ndef rot_hash(key)\n  hash = 0\n  modulus = 1_000_000_007\n\n  key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord }\n\n  hash % modulus\nend\n
    Visualize execution

    View full screen >

    Each algorithm ends by taking the result modulo the large prime \\(1000000007\\) to keep it within a suitable range. Why emphasize a prime modulus? What goes wrong with a composite modulus? This is an interesting question.

    The conclusion is that using a large prime modulus helps make hash values as uniformly distributed as possible. Because a prime shares no nontrivial factors with other numbers, it reduces periodic patterns introduced by the modulo operation and thus avoids collisions.

    For example, choose the composite modulus \\(9\\), which is divisible by \\(3\\). Every key divisible by \\(3\\) then maps to one of just three hash values: \\(0\\), \\(3\\), or \\(6\\).

    \\[ \\begin{aligned} \\text{modulus} & = 9 \\newline \\text{key} & = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} & = \\{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\\dots \\} \\end{aligned} \\]

    If the input keys follow this arithmetic progression, the hash values cluster, increasing collisions. Now replace the modulus with the prime \\(13\\). With no common factor between the key pattern and the modulus, the output becomes noticeably more uniform.

    \\[ \\begin{aligned} \\text{modulus} & = 13 \\newline \\text{key} & = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} & = \\{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \\dots \\} \\end{aligned} \\]

    If the keys are already uniformly random, either a prime or a composite modulus can produce uniformly distributed hashes. When the keys have a periodic pattern, however, a composite modulus is more likely to cause clustering.

    We therefore usually choose a prime modulus, preferably a large one, to suppress periodic patterns as much as possible and make the algorithm more robust.

    ","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#633-common-hash-algorithms","level":2,"title":"6.3.3   Common hash algorithms","text":"

    The simple algorithms above are fragile and fall well short of our design goals. Addition and XOR are commutative, for example, so additive and XOR hashing cannot distinguish strings containing the same characters in different orders. This can increase collisions and create security problems.

    In practice, standard algorithms such as MD5, SHA-1, SHA-2, and SHA-3 are commonly used. They map inputs of arbitrary length to fixed-length hash values.

    Over nearly a century, hash algorithms have continually evolved. Some researchers work to improve their performance, while others, along with hackers, search for security weaknesses. Table 6-2 lists commonly used algorithms.

    • MD5 and SHA-1 have been successfully attacked repeatedly and have been retired from security applications.
    • SHA-256, part of the SHA-2 family, is among the most secure hash algorithms. No successful attack has been reported, so it is widely used in security applications and protocols.
    • SHA-3 has lower implementation overhead and higher computational efficiency than SHA-2, but is not yet as widely used.

    Table 6-2   Common hash algorithms

    MD5 SHA-1 SHA-2 SHA-3 Introduced 1992 1995 2002 2008 Output length 128 bit 160 bit 256/512 bit 224/256/384/512 bit Hash collisions More frequent More frequent Rare Rare Security Low; successfully attacked Low; successfully attacked High High Applications Deprecated; still used for integrity checks Deprecated Cryptocurrency transaction verification, digital signatures, and more Can replace SHA-2","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#634-hash-values-of-data-structures","level":2,"title":"6.3.4   Hash values of data structures","text":"

    Hash-table keys can be integers, floating-point numbers, strings, and other types. Programming languages usually provide built-in hashing for these types to calculate bucket indices. In Python, for example, hash() calculates hash values for various types.

    • Integers and Booleans have hash values equal to their own values.
    • Hashing floating-point numbers and strings is more complex; interested readers can explore the details.
    • A tuple's hash combines the hashes of its individual elements into one value.
    • An object's hash is based on its memory address. Overriding its hash method allows hashing by content instead.

    Tip

    Built-in hash functions differ in their definitions and implementations across programming languages.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby built_in_hash.py
    num = 3\nhash_num = hash(num)\n# The hash of the integer 3 is 3\n\nbol = True\nhash_bol = hash(bol)\n# The hash of the Boolean True is 1\n\ndec = 3.14159\nhash_dec = hash(dec)\n# The hash of the floating-point number 3.14159 is 326484311674566659\n\nstr = \"Hello 算法\"\nhash_str = hash(str)\n# The hash of the string “Hello 算法” is 4617003410720528961\n\ntup = (12836, \"小哈\")\nhash_tup = hash(tup)\n# The hash of the tuple (12836, '小哈') is 1029005403108185979\n\nobj = ListNode(0)\nhash_obj = hash(obj)\n# The hash of the node object <ListNode object at 0x1058fd810> is 274267521\n
    built_in_hash.cpp
    int num = 3;\nsize_t hashNum = hash<int>()(num);\n// The hash of the integer 3 is 3\n\nbool bol = true;\nsize_t hashBol = hash<bool>()(bol);\n// The hash of the Boolean 1 is 1\n\ndouble dec = 3.14159;\nsize_t hashDec = hash<double>()(dec);\n// The hash of the floating-point number 3.14159 is 4614256650576692846\n\nstring str = \"Hello 算法\";\nsize_t hashStr = hash<string>()(str);\n// The hash of the string “Hello 算法” is 15466937326284535026\n\n// In C++, built-in std::hash() provides hashing only for basic data types\n// Hashing for arrays and objects must be implemented separately\n
    built_in_hash.java
    int num = 3;\nint hashNum = Integer.hashCode(num);\n// The hash of the integer 3 is 3\n\nboolean bol = true;\nint hashBol = Boolean.hashCode(bol);\n// The hash of the Boolean true is 1231\n\ndouble dec = 3.14159;\nint hashDec = Double.hashCode(dec);\n// The hash of the floating-point number 3.14159 is -1340954729\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode();\n// The hash of the string “Hello 算法” is -727081396\n\nObject[] arr = { 12836, \"小哈\" };\nint hashTup = Arrays.hashCode(arr);\n// The hash of the array [12836, 小哈] is 1151158\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode();\n// The hash of the node object utils.ListNode@7dc5e7b4 is 2110121908\n
    built_in_hash.cs
    int num = 3;\nint hashNum = num.GetHashCode();\n// The hash of the integer 3 is 3;\n\nbool bol = true;\nint hashBol = bol.GetHashCode();\n// The hash of the Boolean true is 1;\n\ndouble dec = 3.14159;\nint hashDec = dec.GetHashCode();\n// The hash of the floating-point number 3.14159 is -1340954729;\n\nstring str = \"Hello 算法\";\nint hashStr = str.GetHashCode();\n// The hash of the string “Hello 算法” is -586107568;\n\nobject[] arr = [12836, \"小哈\"];\nint hashTup = arr.GetHashCode();\n// The hash of the array [12836, 小哈] is 42931033;\n\nListNode obj = new(0);\nint hashObj = obj.GetHashCode();\n// The hash of the node object 0 is 39053774;\n
    built_in_hash.go
    // Go provides no built-in hash-code function\n
    built_in_hash.swift
    let num = 3\nlet hashNum = num.hashValue\n// The hash of the integer 3 is 9047044699613009734\n\nlet bol = true\nlet hashBol = bol.hashValue\n// The hash of the Boolean true is -4431640247352757451\n\nlet dec = 3.14159\nlet hashDec = dec.hashValue\n// The hash of the floating-point number 3.14159 is -2465384235396674631\n\nlet str = \"Hello 算法\"\nlet hashStr = str.hashValue\n// The hash of the string “Hello 算法” is -7850626797806988787\n\nlet arr = [AnyHashable(12836), AnyHashable(\"小哈\")]\nlet hashTup = arr.hashValue\n// The hash of the array [AnyHashable(12836), AnyHashable(\"小哈\")] is -2308633508154532996\n\nlet obj = ListNode(x: 0)\nlet hashObj = obj.hashValue\n// The hash of the node object utils.ListNode is -2434780518035996159\n
    built_in_hash.js
    // JavaScript provides no built-in hash-code function\n
    built_in_hash.ts
    // TypeScript provides no built-in hash-code function\n
    built_in_hash.dart
    int num = 3;\nint hashNum = num.hashCode;\n// The hash of the integer 3 is 34803\n\nbool bol = true;\nint hashBol = bol.hashCode;\n// The hash of the Boolean true is 1231\n\ndouble dec = 3.14159;\nint hashDec = dec.hashCode;\n// The hash of the floating-point number 3.14159 is 2570631074981783\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode;\n// The hash of the string “Hello 算法” is 468167534\n\nList arr = [12836, \"小哈\"];\nint hashArr = arr.hashCode;\n// The hash of the array [12836, 小哈] is 976512528\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode;\n// The hash of the node object Instance of 'ListNode' is 1033450432\n
    built_in_hash.rs
    use std::collections::hash_map::DefaultHasher;\nuse std::hash::{Hash, Hasher};\n\nlet num = 3;\nlet mut num_hasher = DefaultHasher::new();\nnum.hash(&mut num_hasher);\nlet hash_num = num_hasher.finish();\n// The hash of the integer 3 is 568126464209439262\n\nlet bol = true;\nlet mut bol_hasher = DefaultHasher::new();\nbol.hash(&mut bol_hasher);\nlet hash_bol = bol_hasher.finish();\n// The hash of the Boolean true is 4952851536318644461\n\nlet dec: f32 = 3.14159;\nlet mut dec_hasher = DefaultHasher::new();\ndec.to_bits().hash(&mut dec_hasher);\nlet hash_dec = dec_hasher.finish();\n// The hash of the floating-point number 3.14159 is 2566941990314602357\n\nlet str = \"Hello 算法\";\nlet mut str_hasher = DefaultHasher::new();\nstr.hash(&mut str_hasher);\nlet hash_str = str_hasher.finish();\n// The hash of the string “Hello 算法” is 16092673739211250988\n\nlet arr = (&12836, &\"小哈\");\nlet mut tup_hasher = DefaultHasher::new();\narr.hash(&mut tup_hasher);\nlet hash_tup = tup_hasher.finish();\n// The hash of the tuple (12836, \"小哈\") is 1885128010422702749\n\nlet node = ListNode::new(42);\nlet mut hasher = DefaultHasher::new();\nnode.borrow().val.hash(&mut hasher);\nlet hash = hasher.finish();\n// The hash of the node object RefCell { value: ListNode { val: 42, next: None } } is 15387811073369036852\n
    built_in_hash.c
    // C provides no built-in hash-code function\n
    built_in_hash.kt
    val num = 3\nval hashNum = num.hashCode()\n// The hash of the integer 3 is 3\n\nval bol = true\nval hashBol = bol.hashCode()\n// The hash of the Boolean true is 1231\n\nval dec = 3.14159\nval hashDec = dec.hashCode()\n// The hash of the floating-point number 3.14159 is -1340954729\n\nval str = \"Hello 算法\"\nval hashStr = str.hashCode()\n// The hash of the string “Hello 算法” is -727081396\n\nval arr = arrayOf<Any>(12836, \"小哈\")\nval hashTup = arr.hashCode()\n// The hash of the array [12836, 小哈] is 189568618\n\nval obj = ListNode(0)\nval hashObj = obj.hashCode()\n// The hash of the node object utils.ListNode@1d81eb93 is 495053715\n
    built_in_hash.rb
    num = 3\nhash_num = num.hash\n# The hash of the integer 3 is -4385856518450339636\n\nbol = true\nhash_bol = bol.hash\n# The hash of the Boolean true is -1617938112149317027\n\ndec = 3.14159\nhash_dec = dec.hash\n# The hash of the floating-point number 3.14159 is -1479186995943067893\n\nstr = \"Hello 算法\"\nhash_str = str.hash\n# The hash of the string “Hello 算法” is -4075943250025831763\n\ntup = [12836, '小哈']\nhash_tup = tup.hash\n# The hash of the tuple (12836, '小哈') is 1999544809202288822\n\nobj = ListNode.new(0)\nhash_obj = obj.hash\n# The hash of the node object #<ListNode:0x000078133140ab70> is 4302940560806366381\n
    Visualize execution

    View full screen >

    In many programming languages, only immutable objects can serve as hash-table keys. If a list, a dynamic array, were used as a key, changing its contents would change its hash, making the original value impossible to retrieve from the table.

    Custom objects, such as linked-list nodes, can be hashable even though their member variables are mutable. Their hash is usually based on their memory address, which stays the same even when their contents change.

    You may notice different hash values when running the program in different consoles. Each time the Python interpreter starts, it adds a random salt to its string hash function. This helps prevent HashDoS attacks and improves security.

    ","path":["Chapter 6.   Hash tables","6.3   Hash algorithms"],"tags":[]},{"location":"chapter_hashing/hash_collision/","level":1,"title":"6.2   Hash collisions","text":"

    As discussed in the previous section, a hash function's input space is usually much larger than its output space, so collisions are theoretically unavoidable. For example, if the input space is all integers and the output space is limited to the array's capacity, multiple integers must map to the same bucket index.

    Hash collisions can produce incorrect lookup results and make a hash table unusable. One solution is to expand the table whenever a collision occurs until the collision disappears. This simple, brute-force approach works, but is inefficient because expansion requires substantial data movement and hash computation. We can improve efficiency with the following strategies.

    1. Improve the data structure so that it works correctly even when collisions occur.
    2. Expand only when necessary, when collisions become sufficiently frequent.

    The main structural approaches are “separate chaining” and “open addressing.”

    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#621-separate-chaining","level":2,"title":"6.2.1   Separate chaining","text":"

    In the original hash table, each bucket holds only one key-value pair. Separate chaining replaces this single element with a linked list. Each pair becomes a node, and all colliding pairs are stored in the same list. An example is shown below.

    Figure 6-5   A hash table using separate chaining

    The operations change as follows.

    • Lookup: Hash the input key to obtain a bucket index, access the list's head, and traverse the list comparing keys to find the target pair.
    • Insertion: Use the hash function to access the list's head, then add the node containing the key-value pair to the list.
    • Deletion: Use the hash function to locate the list, traverse it to find the target node, and remove that node.

    Separate chaining has the following limitations.

    • More space: Linked lists store node pointers and consume more memory than arrays.
    • Slower lookup: Finding an element requires a linear scan of the list.

    The code below gives a simple implementation. Keep two points in mind.

    • Lists, implemented as dynamic arrays, replace linked lists to simplify the code. The hash table is an array of buckets, each of which is a list.
    • The implementation includes expansion: when the load factor exceeds \\(\\frac{2}{3}\\), the table's capacity is doubled to \\(2\\) times its original size.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_chaining.py
    class HashMapChaining:\n    \"\"\"Hash table with separate chaining\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self.size = 0  # Number of key-value pairs\n        self.capacity = 4  # Hash table capacity\n        self.load_thres = 2.0 / 3.0  # Load factor threshold that triggers capacity growth\n        self.extend_ratio = 2  # Capacity growth factor\n        self.buckets = [[] for _ in range(self.capacity)]  # Bucket array\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        return key % self.capacity\n\n    def load_factor(self) -> float:\n        \"\"\"Load factor\"\"\"\n        return self.size / self.capacity\n\n    def get(self, key: int) -> str | None:\n        \"\"\"Lookup operation\"\"\"\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse the bucket; if key is found, return its corresponding val\n        for pair in bucket:\n            if pair.key == key:\n                return pair.val\n        # Return None if key is not found\n        return None\n\n    def put(self, key: int, val: str):\n        \"\"\"Insert operation\"\"\"\n        # Grow the capacity when the load factor exceeds the threshold\n        if self.load_factor() > self.load_thres:\n            self.extend()\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse the bucket; if the specified key is found, update its val and return\n        for pair in bucket:\n            if pair.key == key:\n                pair.val = val\n                return\n        # If key is absent, append the key-value pair at the end\n        pair = Pair(key, val)\n        bucket.append(pair)\n        self.size += 1\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        index = self.hash_func(key)\n        bucket = self.buckets[index]\n        # Traverse the bucket and remove the key-value pair\n        for pair in bucket:\n            if pair.key == key:\n                bucket.remove(pair)\n                self.size -= 1\n                break\n\n    def extend(self):\n        \"\"\"Grow the hash table capacity\"\"\"\n        # Keep a reference to the original hash table\n        buckets = self.buckets\n        # Initialize a new hash table with the expanded capacity\n        self.capacity *= self.extend_ratio\n        self.buckets = [[] for _ in range(self.capacity)]\n        self.size = 0\n        # Move the key-value pairs from the original hash table to the new one\n        for bucket in buckets:\n            for pair in bucket:\n                self.put(pair.key, pair.val)\n\n    def print(self):\n        \"\"\"Print the hash table\"\"\"\n        for bucket in self.buckets:\n            res = []\n            for pair in bucket:\n                res.append(str(pair.key) + \" -> \" + pair.val)\n            print(res)\n
    hash_map_chaining.cpp
    /* Hash table with separate chaining */\nclass HashMapChaining {\n  private:\n    int size;                       // Number of key-value pairs\n    int capacity;                   // Hash table capacity\n    double loadThres;               // Load factor threshold that triggers capacity growth\n    int extendRatio;                // Capacity growth factor\n    vector<vector<Pair *>> buckets; // Bucket array\n\n  public:\n    /* Constructor */\n    HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) {\n        buckets.resize(capacity);\n    }\n\n    /* Destructor */\n    ~HashMapChaining() {\n        for (auto &bucket : buckets) {\n            for (Pair *pair : bucket) {\n                // Free memory\n                delete pair;\n            }\n        }\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double)size / (double)capacity;\n    }\n\n    /* Lookup operation */\n    string get(int key) {\n        int index = hashFunc(key);\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (Pair *pair : buckets[index]) {\n            if (pair->key == key) {\n                return pair->val;\n            }\n        }\n        // Return an empty string if key is not found\n        return \"\";\n    }\n\n    /* Insert operation */\n    void put(int key, string val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        int index = hashFunc(key);\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (Pair *pair : buckets[index]) {\n            if (pair->key == key) {\n                pair->val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        buckets[index].push_back(new Pair(key, val));\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        auto &bucket = buckets[index];\n        // Traverse the bucket and remove the key-value pair\n        for (int i = 0; i < bucket.size(); i++) {\n            if (bucket[i]->key == key) {\n                Pair *tmp = bucket[i];\n                bucket.erase(bucket.begin() + i); // Remove the key-value pair from the list\n                delete tmp;                       // Free memory\n                size--;\n                return;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void extend() {\n        // Keep a copy of the original bucket array\n        vector<vector<Pair *>> bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets.clear();\n        buckets.resize(capacity);\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (auto &bucket : bucketsTmp) {\n            for (Pair *pair : bucket) {\n                put(pair->key, pair->val);\n                // Free memory\n                delete pair;\n            }\n        }\n    }\n\n    /* Print the hash table */\n    void print() {\n        for (auto &bucket : buckets) {\n            cout << \"[\";\n            for (Pair *pair : bucket) {\n                cout << pair->key << \" -> \" << pair->val << \", \";\n            }\n            cout << \"]\\n\";\n        }\n    }\n};\n
    hash_map_chaining.java
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    int size; // Number of key-value pairs\n    int capacity; // Hash table capacity\n    double loadThres; // Load factor threshold that triggers capacity growth\n    int extendRatio; // Capacity growth factor\n    List<List<Pair>> buckets; // Bucket array\n\n    /* Constructor */\n    public HashMapChaining() {\n        size = 0;\n        capacity = 4;\n        loadThres = 2.0 / 3.0;\n        extendRatio = 2;\n        buckets = new ArrayList<>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.add(new ArrayList<>());\n        }\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double) size / capacity;\n    }\n\n    /* Lookup operation */\n    String get(int key) {\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                return pair.val;\n            }\n        }\n        // Return null if key is not found\n        return null;\n    }\n\n    /* Insert operation */\n    void put(int key, String val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        Pair pair = new Pair(key, val);\n        bucket.add(pair);\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        List<Pair> bucket = buckets.get(index);\n        // Traverse the bucket and remove the key-value pair\n        for (Pair pair : bucket) {\n            if (pair.key == key) {\n                bucket.remove(pair);\n                size--;\n                break;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void extend() {\n        // Keep a reference to the original hash table\n        List<List<Pair>> bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = new ArrayList<>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.add(new ArrayList<>());\n        }\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (List<Pair> bucket : bucketsTmp) {\n            for (Pair pair : bucket) {\n                put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    void print() {\n        for (List<Pair> bucket : buckets) {\n            List<String> res = new ArrayList<>();\n            for (Pair pair : bucket) {\n                res.add(pair.key + \" -> \" + pair.val);\n            }\n            System.out.println(res);\n        }\n    }\n}\n
    hash_map_chaining.cs
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    int size; // Number of key-value pairs\n    int capacity; // Hash table capacity\n    double loadThres; // Load factor threshold that triggers capacity growth\n    int extendRatio; // Capacity growth factor\n    List<List<Pair>> buckets; // Bucket array\n\n    /* Constructor */\n    public HashMapChaining() {\n        size = 0;\n        capacity = 4;\n        loadThres = 2.0 / 3.0;\n        extendRatio = 2;\n        buckets = new List<List<Pair>>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.Add([]);\n        }\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double LoadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Lookup operation */\n    public string? Get(int key) {\n        int index = HashFunc(key);\n        // Traverse the bucket; if key is found, return its corresponding val\n        foreach (Pair pair in buckets[index]) {\n            if (pair.key == key) {\n                return pair.val;\n            }\n        }\n        // Return null if key is not found\n        return null;\n    }\n\n    /* Insert operation */\n    public void Put(int key, string val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (LoadFactor() > loadThres) {\n            Extend();\n        }\n        int index = HashFunc(key);\n        // Traverse the bucket; if the specified key is found, update its val and return\n        foreach (Pair pair in buckets[index]) {\n            if (pair.key == key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        buckets[index].Add(new Pair(key, val));\n        size++;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        int index = HashFunc(key);\n        // Traverse the bucket and remove the key-value pair\n        foreach (Pair pair in buckets[index].ToList()) {\n            if (pair.key == key) {\n                buckets[index].Remove(pair);\n                size--;\n                break;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void Extend() {\n        // Keep a reference to the original hash table\n        List<List<Pair>> bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = new List<List<Pair>>(capacity);\n        for (int i = 0; i < capacity; i++) {\n            buckets.Add([]);\n        }\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        foreach (List<Pair> bucket in bucketsTmp) {\n            foreach (Pair pair in bucket) {\n                Put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    public void Print() {\n        foreach (List<Pair> bucket in buckets) {\n            List<string> res = [];\n            foreach (Pair pair in bucket) {\n                res.Add(pair.key + \" -> \" + pair.val);\n            }\n            foreach (string kv in res) {\n                Console.WriteLine(kv);\n            }\n        }\n    }\n}\n
    hash_map_chaining.go
    /* Hash table with separate chaining */\ntype hashMapChaining struct {\n    size        int      // Number of key-value pairs\n    capacity    int      // Hash table capacity\n    loadThres   float64  // Load factor threshold that triggers capacity growth\n    extendRatio int      // Capacity growth factor\n    buckets     [][]pair // Bucket array\n}\n\n/* Constructor */\nfunc newHashMapChaining() *hashMapChaining {\n    buckets := make([][]pair, 4)\n    for i := 0; i < 4; i++ {\n        buckets[i] = make([]pair, 0)\n    }\n    return &hashMapChaining{\n        size:        0,\n        capacity:    4,\n        loadThres:   2.0 / 3.0,\n        extendRatio: 2,\n        buckets:     buckets,\n    }\n}\n\n/* Hash function */\nfunc (m *hashMapChaining) hashFunc(key int) int {\n    return key % m.capacity\n}\n\n/* Load factor */\nfunc (m *hashMapChaining) loadFactor() float64 {\n    return float64(m.size) / float64(m.capacity)\n}\n\n/* Lookup operation */\nfunc (m *hashMapChaining) get(key int) string {\n    idx := m.hashFunc(key)\n    bucket := m.buckets[idx]\n    // Traverse the bucket; if key is found, return its corresponding val\n    for _, p := range bucket {\n        if p.key == key {\n            return p.val\n        }\n    }\n    // If key is not found, return an empty string\n    return \"\"\n}\n\n/* Insert operation */\nfunc (m *hashMapChaining) put(key int, val string) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if m.loadFactor() > m.loadThres {\n        m.extend()\n    }\n    idx := m.hashFunc(key)\n    // Traverse the bucket; if the specified key is found, update its val and return\n    for i := range m.buckets[idx] {\n        if m.buckets[idx][i].key == key {\n            m.buckets[idx][i].val = val\n            return\n        }\n    }\n    // If key is absent, append the key-value pair at the end\n    p := pair{\n        key: key,\n        val: val,\n    }\n    m.buckets[idx] = append(m.buckets[idx], p)\n    m.size += 1\n}\n\n/* Remove operation */\nfunc (m *hashMapChaining) remove(key int) {\n    idx := m.hashFunc(key)\n    // Traverse the bucket and remove the key-value pair\n    for i, p := range m.buckets[idx] {\n        if p.key == key {\n            // Remove the element from the slice\n            m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...)\n            m.size -= 1\n            break\n        }\n    }\n}\n\n/* Grow the hash table capacity */\nfunc (m *hashMapChaining) extend() {\n    // Save a copy of the original hash table\n    tmpBuckets := make([][]pair, len(m.buckets))\n    for i := 0; i < len(m.buckets); i++ {\n        tmpBuckets[i] = make([]pair, len(m.buckets[i]))\n        copy(tmpBuckets[i], m.buckets[i])\n    }\n    // Initialize a new hash table with the expanded capacity\n    m.capacity *= m.extendRatio\n    m.buckets = make([][]pair, m.capacity)\n    for i := 0; i < m.capacity; i++ {\n        m.buckets[i] = make([]pair, 0)\n    }\n    m.size = 0\n    // Move the key-value pairs from the original hash table to the new one\n    for _, bucket := range tmpBuckets {\n        for _, p := range bucket {\n            m.put(p.key, p.val)\n        }\n    }\n}\n\n/* Print the hash table */\nfunc (m *hashMapChaining) print() {\n    var builder strings.Builder\n\n    for _, bucket := range m.buckets {\n        builder.WriteString(\"[\")\n        for _, p := range bucket {\n            builder.WriteString(strconv.Itoa(p.key) + \" -> \" + p.val + \" \")\n        }\n        builder.WriteString(\"]\")\n        fmt.Println(builder.String())\n        builder.Reset()\n    }\n}\n
    hash_map_chaining.swift
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    var loadThres: Double // Load factor threshold that triggers capacity growth\n    var extendRatio: Int // Capacity growth factor\n    var buckets: [[Pair]] // Bucket array\n\n    /* Constructor */\n    init() {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = Array(repeating: [], count: capacity)\n    }\n\n    /* Hash function */\n    func hashFunc(key: Int) -> Int {\n        key % capacity\n    }\n\n    /* Load factor */\n    func loadFactor() -> Double {\n        Double(size) / Double(capacity)\n    }\n\n    /* Lookup operation */\n    func get(key: Int) -> String? {\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse the bucket; if key is found, return its corresponding val\n        for pair in bucket {\n            if pair.key == key {\n                return pair.val\n            }\n        }\n        // Return nil if key is not found\n        return nil\n    }\n\n    /* Insert operation */\n    func put(key: Int, val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if loadFactor() > loadThres {\n            extend()\n        }\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for pair in bucket {\n            if pair.key == key {\n                pair.val = val\n                return\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        let pair = Pair(key: key, val: val)\n        buckets[index].append(pair)\n        size += 1\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        let index = hashFunc(key: key)\n        let bucket = buckets[index]\n        // Traverse the bucket and remove the key-value pair\n        for (pairIndex, pair) in bucket.enumerated() {\n            if pair.key == key {\n                buckets[index].remove(at: pairIndex)\n                size -= 1\n                break\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    func extend() {\n        // Save the original bucket array\n        let bucketsTmp = buckets\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio\n        buckets = Array(repeating: [], count: capacity)\n        size = 0\n        // Move the key-value pairs from the original hash table to the new one\n        for bucket in bucketsTmp {\n            for pair in bucket {\n                put(key: pair.key, val: pair.val)\n            }\n        }\n    }\n\n    /* Print the hash table */\n    func print() {\n        for bucket in buckets {\n            let res = bucket.map { \"\\($0.key) -> \\($0.val)\" }\n            Swift.print(res)\n        }\n    }\n}\n
    hash_map_chaining.js
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    #size; // Number of key-value pairs\n    #capacity; // Hash table capacity\n    #loadThres; // Load factor threshold that triggers capacity growth\n    #extendRatio; // Capacity growth factor\n    #buckets; // Bucket array\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0;\n        this.#capacity = 4;\n        this.#loadThres = 2.0 / 3.0;\n        this.#extendRatio = 2;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor() {\n        return this.#size / this.#capacity;\n    }\n\n    /* Lookup operation */\n    get(key) {\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                return pair.val;\n            }\n        }\n        // Return null if key was not found\n        return null;\n    }\n\n    /* Insert operation */\n    put(key, val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        const pair = new Pair(key, val);\n        bucket.push(pair);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key) {\n        const index = this.#hashFunc(key);\n        let bucket = this.#buckets[index];\n        // Traverse the bucket and remove the key-value pair\n        for (let i = 0; i < bucket.length; i++) {\n            if (bucket[i].key === key) {\n                bucket.splice(i, 1);\n                this.#size--;\n                break;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    #extend() {\n        // Keep a reference to the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize a new hash table with the expanded capacity\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n        this.#size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (const bucket of bucketsTmp) {\n            for (const pair of bucket) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    print() {\n        for (const bucket of this.#buckets) {\n            let res = [];\n            for (const pair of bucket) {\n                res.push(pair.key + ' -> ' + pair.val);\n            }\n            console.log(res);\n        }\n    }\n}\n
    hash_map_chaining.ts
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    #size: number; // Number of key-value pairs\n    #capacity: number; // Hash table capacity\n    #loadThres: number; // Load factor threshold that triggers capacity growth\n    #extendRatio: number; // Capacity growth factor\n    #buckets: Pair[][]; // Bucket array\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0;\n        this.#capacity = 4;\n        this.#loadThres = 2.0 / 3.0;\n        this.#extendRatio = 2;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n    }\n\n    /* Hash function */\n    #hashFunc(key: number): number {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor(): number {\n        return this.#size / this.#capacity;\n    }\n\n    /* Lookup operation */\n    get(key: number): string | null {\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                return pair.val;\n            }\n        }\n        // Return null if key was not found\n        return null;\n    }\n\n    /* Insert operation */\n    put(key: number, val: string): void {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        const index = this.#hashFunc(key);\n        const bucket = this.#buckets[index];\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (const pair of bucket) {\n            if (pair.key === key) {\n                pair.val = val;\n                return;\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        const pair = new Pair(key, val);\n        bucket.push(pair);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key: number): void {\n        const index = this.#hashFunc(key);\n        let bucket = this.#buckets[index];\n        // Traverse the bucket and remove the key-value pair\n        for (let i = 0; i < bucket.length; i++) {\n            if (bucket[i].key === key) {\n                bucket.splice(i, 1);\n                this.#size--;\n                break;\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    #extend(): void {\n        // Keep a reference to the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize a new hash table with the expanded capacity\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = new Array(this.#capacity).fill(null).map((x) => []);\n        this.#size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (const bucket of bucketsTmp) {\n            for (const pair of bucket) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    print(): void {\n        for (const bucket of this.#buckets) {\n            let res = [];\n            for (const pair of bucket) {\n                res.push(pair.key + ' -> ' + pair.val);\n            }\n            console.log(res);\n        }\n    }\n}\n
    hash_map_chaining.dart
    /* Hash table with separate chaining */\nclass HashMapChaining {\n  late int size; // Number of key-value pairs\n  late int capacity; // Hash table capacity\n  late double loadThres; // Load factor threshold that triggers capacity growth\n  late int extendRatio; // Capacity growth factor\n  late List<List<Pair>> buckets; // Bucket array\n\n  /* Constructor */\n  HashMapChaining() {\n    size = 0;\n    capacity = 4;\n    loadThres = 2.0 / 3.0;\n    extendRatio = 2;\n    buckets = List.generate(capacity, (_) => []);\n  }\n\n  /* Hash function */\n  int hashFunc(int key) {\n    return key % capacity;\n  }\n\n  /* Load factor */\n  double loadFactor() {\n    return size / capacity;\n  }\n\n  /* Lookup operation */\n  String? get(int key) {\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse the bucket; if key is found, return its corresponding val\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        return pair.val;\n      }\n    }\n    // Return null if key was not found\n    return null;\n  }\n\n  /* Insert operation */\n  void put(int key, String val) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if (loadFactor() > loadThres) {\n      extend();\n    }\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse the bucket; if the specified key is found, update its val and return\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        pair.val = val;\n        return;\n      }\n    }\n    // If key is absent, append the key-value pair at the end\n    Pair pair = Pair(key, val);\n    bucket.add(pair);\n    size++;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    int index = hashFunc(key);\n    List<Pair> bucket = buckets[index];\n    // Traverse the bucket and remove the key-value pair\n    for (Pair pair in bucket) {\n      if (pair.key == key) {\n        bucket.remove(pair);\n        size--;\n        break;\n      }\n    }\n  }\n\n  /* Grow the hash table capacity */\n  void extend() {\n    // Keep a reference to the original hash table\n    List<List<Pair>> bucketsTmp = buckets;\n    // Initialize a new hash table with the expanded capacity\n    capacity *= extendRatio;\n    buckets = List.generate(capacity, (_) => []);\n    size = 0;\n    // Move the key-value pairs from the original hash table to the new one\n    for (List<Pair> bucket in bucketsTmp) {\n      for (Pair pair in bucket) {\n        put(pair.key, pair.val);\n      }\n    }\n  }\n\n  /* Print the hash table */\n  void printHashMap() {\n    for (List<Pair> bucket in buckets) {\n      List<String> res = [];\n      for (Pair pair in bucket) {\n        res.add(\"${pair.key} -> ${pair.val}\");\n      }\n      print(res);\n    }\n  }\n}\n
    hash_map_chaining.rs
    /* Hash table with separate chaining */\nstruct HashMapChaining {\n    size: usize,\n    capacity: usize,\n    load_thres: f32,\n    extend_ratio: usize,\n    buckets: Vec<Vec<Pair>>,\n}\n\nimpl HashMapChaining {\n    /* Constructor */\n    fn new() -> Self {\n        Self {\n            size: 0,\n            capacity: 4,\n            load_thres: 2.0 / 3.0,\n            extend_ratio: 2,\n            buckets: vec![vec![]; 4],\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        key as usize % self.capacity\n    }\n\n    /* Load factor */\n    fn load_factor(&self) -> f32 {\n        self.size as f32 / self.capacity as f32\n    }\n\n    /* Remove operation */\n    fn remove(&mut self, key: i32) -> Option<String> {\n        let index = self.hash_func(key);\n\n        // Traverse the bucket and remove the key-value pair\n        for (i, p) in self.buckets[index].iter_mut().enumerate() {\n            if p.key == key {\n                let pair = self.buckets[index].remove(i);\n                self.size -= 1;\n                return Some(pair.val);\n            }\n        }\n\n        // Return None if key is not found\n        None\n    }\n\n    /* Grow the hash table capacity */\n    fn extend(&mut self) {\n        // Temporarily take ownership of the original buckets\n        let buckets_tmp = std::mem::take(&mut self.buckets);\n\n        // Initialize a new hash table with the expanded capacity\n        self.capacity *= self.extend_ratio;\n        self.buckets = vec![Vec::new(); self.capacity as usize];\n        self.size = 0;\n\n        // Move the key-value pairs from the original hash table to the new one\n        for bucket in buckets_tmp {\n            for pair in bucket {\n                self.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    fn print(&self) {\n        for bucket in &self.buckets {\n            let mut res = Vec::new();\n            for pair in bucket {\n                res.push(format!(\"{} -> {}\", pair.key, pair.val));\n            }\n            println!(\"{:?}\", res);\n        }\n    }\n\n    /* Insert operation */\n    fn put(&mut self, key: i32, val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if self.load_factor() > self.load_thres {\n            self.extend();\n        }\n\n        let index = self.hash_func(key);\n\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for pair in self.buckets[index].iter_mut() {\n            if pair.key == key {\n                pair.val = val;\n                return;\n            }\n        }\n\n        // If key is absent, append the key-value pair at the end\n        let pair = Pair { key, val };\n        self.buckets[index].push(pair);\n        self.size += 1;\n    }\n\n    /* Lookup operation */\n    fn get(&self, key: i32) -> Option<&str> {\n        let index = self.hash_func(key);\n\n        // Traverse the bucket; if key is found, return its corresponding val\n        for pair in self.buckets[index].iter() {\n            if pair.key == key {\n                return Some(&pair.val);\n            }\n        }\n\n        // Return None if key is not found\n        None\n    }\n}\n
    hash_map_chaining.c
    /* Linked list node */\ntypedef struct Node {\n    Pair *pair;\n    struct Node *next;\n} Node;\n\n/* Hash table with separate chaining */\ntypedef struct {\n    int size;         // Number of key-value pairs\n    int capacity;     // Hash table capacity\n    double loadThres; // Load factor threshold that triggers capacity growth\n    int extendRatio;  // Capacity growth factor\n    Node **buckets;   // Bucket array\n} HashMapChaining;\n\n/* Constructor */\nHashMapChaining *newHashMapChaining() {\n    HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining));\n    hashMap->size = 0;\n    hashMap->capacity = 4;\n    hashMap->loadThres = 2.0 / 3.0;\n    hashMap->extendRatio = 2;\n    hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));\n    for (int i = 0; i < hashMap->capacity; i++) {\n        hashMap->buckets[i] = NULL;\n    }\n    return hashMap;\n}\n\n/* Destructor */\nvoid delHashMapChaining(HashMapChaining *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Node *cur = hashMap->buckets[i];\n        while (cur) {\n            Node *tmp = cur;\n            cur = cur->next;\n            free(tmp->pair);\n            free(tmp);\n        }\n    }\n    free(hashMap->buckets);\n    free(hashMap);\n}\n\n/* Hash function */\nint hashFunc(HashMapChaining *hashMap, int key) {\n    return key % hashMap->capacity;\n}\n\n/* Load factor */\ndouble loadFactor(HashMapChaining *hashMap) {\n    return (double)hashMap->size / (double)hashMap->capacity;\n}\n\n/* Lookup operation */\nchar *get(HashMapChaining *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    // Traverse the bucket; if key is found, return its corresponding val\n    Node *cur = hashMap->buckets[index];\n    while (cur) {\n        if (cur->pair->key == key) {\n            return cur->pair->val;\n        }\n        cur = cur->next;\n    }\n    return \"\"; // Return an empty string if key is not found\n}\n\n/* Insert operation */\nvoid put(HashMapChaining *hashMap, int key, const char *val) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if (loadFactor(hashMap) > hashMap->loadThres) {\n        extend(hashMap);\n    }\n    int index = hashFunc(hashMap, key);\n    // Traverse the bucket; if the specified key is found, update its val and return\n    Node *cur = hashMap->buckets[index];\n    while (cur) {\n        if (cur->pair->key == key) {\n            strcpy(cur->pair->val, val); // If the specified key is found, update its val and return\n            return;\n        }\n        cur = cur->next;\n    }\n    // If key is absent, add the key-value pair at the head of the linked list\n    Pair *newPair = (Pair *)malloc(sizeof(Pair));\n    newPair->key = key;\n    strcpy(newPair->val, val);\n    Node *newNode = (Node *)malloc(sizeof(Node));\n    newNode->pair = newPair;\n    newNode->next = hashMap->buckets[index];\n    hashMap->buckets[index] = newNode;\n    hashMap->size++;\n}\n\n/* Grow the hash table capacity */\nvoid extend(HashMapChaining *hashMap) {\n    // Keep a reference to the original hash table\n    int oldCapacity = hashMap->capacity;\n    Node **oldBuckets = hashMap->buckets;\n    // Initialize a new hash table with the expanded capacity\n    hashMap->capacity *= hashMap->extendRatio;\n    hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));\n    for (int i = 0; i < hashMap->capacity; i++) {\n        hashMap->buckets[i] = NULL;\n    }\n    hashMap->size = 0;\n    // Move the key-value pairs from the original hash table to the new one\n    for (int i = 0; i < oldCapacity; i++) {\n        Node *cur = oldBuckets[i];\n        while (cur) {\n            put(hashMap, cur->pair->key, cur->pair->val);\n            Node *temp = cur;\n            cur = cur->next;\n            // Free memory\n            free(temp->pair);\n            free(temp);\n        }\n    }\n\n    free(oldBuckets);\n}\n\n/* Remove operation */\nvoid removeItem(HashMapChaining *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    Node *cur = hashMap->buckets[index];\n    Node *pre = NULL;\n    while (cur) {\n        if (cur->pair->key == key) {\n            // Remove the key-value pair from the list\n            if (pre) {\n                pre->next = cur->next;\n            } else {\n                hashMap->buckets[index] = cur->next;\n            }\n            // Free memory\n            free(cur->pair);\n            free(cur);\n            hashMap->size--;\n            return;\n        }\n        pre = cur;\n        cur = cur->next;\n    }\n}\n\n/* Print the hash table */\nvoid print(HashMapChaining *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Node *cur = hashMap->buckets[i];\n        printf(\"[\");\n        while (cur) {\n            printf(\"%d -> %s, \", cur->pair->key, cur->pair->val);\n            cur = cur->next;\n        }\n        printf(\"]\\n\");\n    }\n}\n
    hash_map_chaining.kt
    /* Hash table with separate chaining */\nclass HashMapChaining {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    val loadThres: Double // Load factor threshold that triggers capacity growth\n    val extendRatio: Int // Capacity growth factor\n    var buckets: MutableList<MutableList<Pair>> // Bucket array\n\n    /* Constructor */\n    init {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = mutableListOf()\n        for (i in 0..<capacity) {\n            buckets.add(mutableListOf())\n        }\n    }\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        return key % capacity\n    }\n\n    /* Load factor */\n    fun loadFactor(): Double {\n        return (size / capacity).toDouble()\n    }\n\n    /* Lookup operation */\n    fun get(key: Int): String? {\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse the bucket; if key is found, return its corresponding val\n        for (pair in bucket) {\n            if (pair.key == key) return pair._val\n        }\n        // Return null if key is not found\n        return null\n    }\n\n    /* Insert operation */\n    fun put(key: Int, _val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend()\n        }\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse the bucket; if the specified key is found, update its val and return\n        for (pair in bucket) {\n            if (pair.key == key) {\n                pair._val = _val\n                return\n            }\n        }\n        // If key is absent, append the key-value pair at the end\n        val pair = Pair(key, _val)\n        bucket.add(pair)\n        size++\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        val index = hashFunc(key)\n        val bucket = buckets[index]\n        // Traverse the bucket and remove the key-value pair\n        for (pair in bucket) {\n            if (pair.key == key) {\n                bucket.remove(pair)\n                size--\n                break\n            }\n        }\n    }\n\n    /* Grow the hash table capacity */\n    fun extend() {\n        // Keep a reference to the original hash table\n        val bucketsTmp = buckets\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio\n        // A mutable list has no fixed size\n        buckets = mutableListOf()\n        for (i in 0..<capacity) {\n            buckets.add(mutableListOf())\n        }\n        size = 0\n        // Move the key-value pairs from the original hash table to the new one\n        for (bucket in bucketsTmp) {\n            for (pair in bucket) {\n                put(pair.key, pair._val)\n            }\n        }\n    }\n\n    /* Print the hash table */\n    fun print() {\n        for (bucket in buckets) {\n            val res = mutableListOf<String>()\n            for (pair in bucket) {\n                val k = pair.key\n                val v = pair._val\n                res.add(\"$k -> $v\")\n            }\n            println(res)\n        }\n    }\n}\n
    hash_map_chaining.rb
    ### Hash table using chaining ###\nclass HashMapChaining\n  ### Constructor ###\n  def initialize\n    @size = 0 # Number of key-value pairs\n    @capacity = 4 # Hash table capacity\n    @load_thres = 2.0 / 3.0 # Load factor threshold that triggers capacity growth\n    @extend_ratio = 2 # Capacity growth factor\n    @buckets = Array.new(@capacity) { [] } # Bucket array\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    key % @capacity\n  end\n\n  ### Load factor ###\n  def load_factor\n    @size / @capacity\n  end\n\n  ### Lookup operation ###\n  def get(key)\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse the bucket; if key is found, return its corresponding val\n    for pair in bucket\n      return pair.val if pair.key == key\n    end\n    # Return nil if key is not found\n    nil\n  end\n\n  ### Insert operation ###\n  def put(key, val)\n    # Grow the capacity when the load factor exceeds the threshold\n    extend if load_factor > @load_thres\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse the bucket; if the specified key is found, update its val and return\n    for pair in bucket\n      if pair.key == key\n        pair.val = val\n        return\n      end\n    end\n    # If key is absent, append the key-value pair at the end\n    pair = Pair.new(key, val)\n    bucket << pair\n    @size += 1\n  end\n\n  ### Remove operation ###\n  def remove(key)\n    index = hash_func(key)\n    bucket = @buckets[index]\n    # Traverse the bucket and remove the key-value pair\n    for pair in bucket\n      if pair.key == key\n        bucket.delete(pair)\n        @size -= 1\n        break\n      end\n    end\n  end\n\n  ### Grow the hash table capacity ###\n  def extend\n    # Temporarily store the original hash table\n    buckets = @buckets\n    # Initialize a new hash table with the expanded capacity\n    @capacity *= @extend_ratio\n    @buckets = Array.new(@capacity) { [] }\n    @size = 0\n    # Move the key-value pairs from the original hash table to the new one\n    for bucket in buckets\n      for pair in bucket\n        put(pair.key, pair.val)\n      end\n    end\n  end\n\n  ### Print the hash table ###\n  def print\n    for bucket in @buckets\n      res = []\n      for pair in bucket\n        res << \"#{pair.key} -> #{pair.val}\"\n      end\n      pp res\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    When a list becomes long, its \\(O(n)\\) lookup time is poor. Replacing it with an AVL tree or red-black tree improves lookup time to \\(O(\\log n)\\).

    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#622-open-addressing","level":2,"title":"6.2.2   Open addressing","text":"

    Open addressing handles collisions by probing multiple locations without introducing another data structure. Common strategies include linear probing, quadratic probing, and multiple hashing.

    We use linear probing to explain how an open-addressing hash table works.

    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#1-linear-probing","level":3,"title":"1.   Linear probing","text":"

    Linear probing searches with a fixed step size. Its operations differ from those of a basic hash table.

    • Insertion: Calculate the bucket index using the hash function. If the bucket is occupied, scan forward from that position with a fixed step size, usually \\(1\\), until an empty bucket is found, then insert the element.
    • Lookup: If a collision is encountered, scan forward with the same step size until the target is found, then return its value. An empty bucket means the target is absent, so return None.

    Figure 6-6 shows key-value pairs in an open-addressing table using linear probing. With this hash function, keys sharing the same last two digits map to the same bucket. Linear probing stores them successively in that bucket and the following buckets.

    Figure 6-6   Distribution of key-value pairs with linear probing

    However, linear probing is prone to clustering. The longer a run of occupied positions becomes, the more likely a collision is to occur within it, causing the cluster to grow further. This feedback loop eventually degrades insertion, deletion, lookup, and update performance.

    We cannot simply delete an element from an open-addressing table. Deletion would leave an empty bucket, None. A lookup stops when linear probing reaches such a bucket, making later elements in the probe sequence inaccessible and potentially reporting them as absent, as shown below.

    Figure 6-7   Lookup problems caused by deletion in open addressing

    To solve this, use lazy deletion: rather than simply removing the entry, mark its bucket with the constant TOMBSTONE. Both None and TOMBSTONE represent available buckets that can hold a new pair. Unlike None, however, a TOMBSTONE does not stop a search, because further pairs may lie beyond it.

    However, lazy deletion can accelerate performance degradation. Each deletion leaves a marker. As TOMBSTONE entries accumulate, searches may need to skip many of them before reaching the target, increasing search time.

    To address this, record the first TOMBSTONE encountered during probing and swap the target entry with it when found. On each lookup or insertion, this moves the entry closer to its ideal location—the start of the probe sequence—and improves lookup efficiency.

    The code below implements linear probing with lazy deletion. To use the table's space more fully, treat the array as circular: after reaching the end, wrap around to the beginning.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_open_addressing.py
    class HashMapOpenAddressing:\n    \"\"\"Hash table with open addressing\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self.size = 0  # Number of key-value pairs\n        self.capacity = 4  # Hash table capacity\n        self.load_thres = 2.0 / 3.0  # Load factor threshold that triggers capacity growth\n        self.extend_ratio = 2  # Capacity growth factor\n        self.buckets: list[Pair | None] = [None] * self.capacity  # Bucket array\n        self.TOMBSTONE = Pair(-1, \"-1\")  # Tombstone marker\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        return key % self.capacity\n\n    def load_factor(self) -> float:\n        \"\"\"Load factor\"\"\"\n        return self.size / self.capacity\n\n    def find_bucket(self, key: int) -> int:\n        \"\"\"Find the bucket index for key\"\"\"\n        index = self.hash_func(key)\n        first_tombstone = -1\n        # Probe linearly until an empty bucket is reached\n        while self.buckets[index] is not None:\n            # If key is found, return its bucket index\n            if self.buckets[index].key == key:\n                # If a tombstone was encountered earlier, move the key-value pair to that index\n                if first_tombstone != -1:\n                    self.buckets[first_tombstone] = self.buckets[index]\n                    self.buckets[index] = self.TOMBSTONE\n                    return first_tombstone  # Return the bucket index after the move\n                return index  # Return the bucket index\n            # Record the first tombstone encountered\n            if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE:\n                first_tombstone = index\n            # Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % self.capacity\n        # If key is absent, return the index for insertion\n        return index if first_tombstone == -1 else first_tombstone\n\n    def get(self, key: int) -> str:\n        \"\"\"Lookup operation\"\"\"\n        # Find the bucket index for key\n        index = self.find_bucket(key)\n        # If the key-value pair is found, return its val\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            return self.buckets[index].val\n        # Return None if the key-value pair is absent\n        return None\n\n    def put(self, key: int, val: str):\n        \"\"\"Insert operation\"\"\"\n        # Grow the capacity when the load factor exceeds the threshold\n        if self.load_factor() > self.load_thres:\n            self.extend()\n        # Find the bucket index for key\n        index = self.find_bucket(key)\n        # If the key-value pair is found, overwrite val and return\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            self.buckets[index].val = val\n            return\n        # If the key-value pair is absent, insert it\n        self.buckets[index] = Pair(key, val)\n        self.size += 1\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        # Find the bucket index for key\n        index = self.find_bucket(key)\n        # If the key-value pair is found, replace it with a tombstone\n        if self.buckets[index] not in [None, self.TOMBSTONE]:\n            self.buckets[index] = self.TOMBSTONE\n            self.size -= 1\n\n    def extend(self):\n        \"\"\"Grow the hash table capacity\"\"\"\n        # Keep a reference to the original hash table\n        buckets_tmp = self.buckets\n        # Initialize a new hash table with the expanded capacity\n        self.capacity *= self.extend_ratio\n        self.buckets = [None] * self.capacity\n        self.size = 0\n        # Move the key-value pairs from the original hash table to the new one\n        for pair in buckets_tmp:\n            if pair not in [None, self.TOMBSTONE]:\n                self.put(pair.key, pair.val)\n\n    def print(self):\n        \"\"\"Print the hash table\"\"\"\n        for pair in self.buckets:\n            if pair is None:\n                print(\"None\")\n            elif pair is self.TOMBSTONE:\n                print(\"TOMBSTONE\")\n            else:\n                print(pair.key, \"->\", pair.val)\n
    hash_map_open_addressing.cpp
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n  private:\n    int size;                             // Number of key-value pairs\n    int capacity = 4;                     // Hash table capacity\n    const double loadThres = 2.0 / 3.0;     // Load factor threshold that triggers capacity growth\n    const int extendRatio = 2;            // Capacity growth factor\n    vector<Pair *> buckets;               // Bucket array\n    Pair *TOMBSTONE = new Pair(-1, \"-1\"); // Tombstone marker\n\n  public:\n    /* Constructor */\n    HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) {\n    }\n\n    /* Destructor */\n    ~HashMapOpenAddressing() {\n        for (Pair *pair : buckets) {\n            if (pair != nullptr && pair != TOMBSTONE) {\n                delete pair;\n            }\n        }\n        delete TOMBSTONE;\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double loadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Find the bucket index for key */\n    int findBucket(int key) {\n        int index = hashFunc(key);\n        int firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (buckets[index] != nullptr) {\n            // If key is found, return its bucket index\n            if (buckets[index]->key == key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    string get(int key) {\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, return its val\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            return buckets[index]->val;\n        }\n        // Return an empty string if the key-value pair does not exist\n        return \"\";\n    }\n\n    /* Insert operation */\n    void put(int key, string val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            buckets[index]->val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {\n            delete buckets[index];\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void extend() {\n        // Keep a copy of the original bucket array\n        vector<Pair *> bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = vector<Pair *>(capacity, nullptr);\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (Pair *pair : bucketsTmp) {\n            if (pair != nullptr && pair != TOMBSTONE) {\n                put(pair->key, pair->val);\n                delete pair;\n            }\n        }\n    }\n\n    /* Print the hash table */\n    void print() {\n        for (Pair *pair : buckets) {\n            if (pair == nullptr) {\n                cout << \"nullptr\" << endl;\n            } else if (pair == TOMBSTONE) {\n                cout << \"TOMBSTONE\" << endl;\n            } else {\n                cout << pair->key << \" -> \" << pair->val << endl;\n            }\n        }\n    }\n};\n
    hash_map_open_addressing.java
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private int size; // Number of key-value pairs\n    private int capacity = 4; // Hash table capacity\n    private final double loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n    private final int extendRatio = 2; // Capacity growth factor\n    private Pair[] buckets; // Bucket array\n    private final Pair TOMBSTONE = new Pair(-1, \"-1\"); // Tombstone marker\n\n    /* Constructor */\n    public HashMapOpenAddressing() {\n        size = 0;\n        buckets = new Pair[capacity];\n    }\n\n    /* Hash function */\n    private int hashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    private double loadFactor() {\n        return (double) size / capacity;\n    }\n\n    /* Find the bucket index for key */\n    private int findBucket(int key) {\n        int index = hashFunc(key);\n        int firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (buckets[index] != null) {\n            // If key is found, return its bucket index\n            if (buckets[index].key == key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    public String get(int key) {\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, return its val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index].val;\n        }\n        // Return null if the key-value pair is absent\n        return null;\n    }\n\n    /* Insert operation */\n    public void put(int key, String val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend();\n        }\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index].val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    public void remove(int key) {\n        // Find the bucket index for key\n        int index = findBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    private void extend() {\n        // Keep a reference to the original hash table\n        Pair[] bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = new Pair[capacity];\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (Pair pair : bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    public void print() {\n        for (Pair pair : buckets) {\n            if (pair == null) {\n                System.out.println(\"null\");\n            } else if (pair == TOMBSTONE) {\n                System.out.println(\"TOMBSTONE\");\n            } else {\n                System.out.println(pair.key + \" -> \" + pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.cs
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    int size; // Number of key-value pairs\n    int capacity = 4; // Hash table capacity\n    double loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n    int extendRatio = 2; // Capacity growth factor\n    Pair[] buckets; // Bucket array\n    Pair TOMBSTONE = new(-1, \"-1\"); // Tombstone marker\n\n    /* Constructor */\n    public HashMapOpenAddressing() {\n        size = 0;\n        buckets = new Pair[capacity];\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        return key % capacity;\n    }\n\n    /* Load factor */\n    double LoadFactor() {\n        return (double)size / capacity;\n    }\n\n    /* Find the bucket index for key */\n    int FindBucket(int key) {\n        int index = HashFunc(key);\n        int firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (buckets[index] != null) {\n            // If key is found, return its bucket index\n            if (buckets[index].key == key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index];\n                    buckets[index] = TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    public string? Get(int key) {\n        // Find the bucket index for key\n        int index = FindBucket(key);\n        // If the key-value pair is found, return its val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index].val;\n        }\n        // Return null if the key-value pair is absent\n        return null;\n    }\n\n    /* Insert operation */\n    public void Put(int key, string val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (LoadFactor() > loadThres) {\n            Extend();\n        }\n        // Find the bucket index for key\n        int index = FindBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index].val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = new Pair(key, val);\n        size++;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        // Find the bucket index for key\n        int index = FindBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE;\n            size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    void Extend() {\n        // Keep a reference to the original hash table\n        Pair[] bucketsTmp = buckets;\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio;\n        buckets = new Pair[capacity];\n        size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        foreach (Pair pair in bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                Put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    public void Print() {\n        foreach (Pair pair in buckets) {\n            if (pair == null) {\n                Console.WriteLine(\"null\");\n            } else if (pair == TOMBSTONE) {\n                Console.WriteLine(\"TOMBSTONE\");\n            } else {\n                Console.WriteLine(pair.key + \" -> \" + pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.go
    /* Hash table with open addressing */\ntype hashMapOpenAddressing struct {\n    size        int     // Number of key-value pairs\n    capacity    int     // Hash table capacity\n    loadThres   float64 // Load factor threshold that triggers capacity growth\n    extendRatio int     // Capacity growth factor\n    buckets     []*pair // Bucket array\n    TOMBSTONE   *pair   // Tombstone marker\n}\n\n/* Constructor */\nfunc newHashMapOpenAddressing() *hashMapOpenAddressing {\n    return &hashMapOpenAddressing{\n        size:        0,\n        capacity:    4,\n        loadThres:   2.0 / 3.0,\n        extendRatio: 2,\n        buckets:     make([]*pair, 4),\n        TOMBSTONE:   &pair{-1, \"-1\"},\n    }\n}\n\n/* Hash function */\nfunc (h *hashMapOpenAddressing) hashFunc(key int) int {\n    return key % h.capacity // Calculate the hash value from the key\n}\n\n/* Load factor */\nfunc (h *hashMapOpenAddressing) loadFactor() float64 {\n    return float64(h.size) / float64(h.capacity) // Calculate the current load factor\n}\n\n/* Find the bucket index for key */\nfunc (h *hashMapOpenAddressing) findBucket(key int) int {\n    index := h.hashFunc(key) // Get the initial index\n    firstTombstone := -1     // Record the position of the first TOMBSTONE encountered\n    for h.buckets[index] != nil {\n        if h.buckets[index].key == key {\n            if firstTombstone != -1 {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                h.buckets[firstTombstone] = h.buckets[index]\n                h.buckets[index] = h.TOMBSTONE\n                return firstTombstone // Return the bucket index after the move\n            }\n            return index // Return the index found\n        }\n        if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE {\n            firstTombstone = index // Record the position of the first tombstone encountered\n        }\n        index = (index + 1) % h.capacity // Probe linearly, wrapping to the beginning after the end\n    }\n    // If key is absent, return the index for insertion\n    if firstTombstone != -1 {\n        return firstTombstone\n    }\n    return index\n}\n\n/* Lookup operation */\nfunc (h *hashMapOpenAddressing) get(key int) string {\n    index := h.findBucket(key) // Find the bucket index for key\n    if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE {\n        return h.buckets[index].val // If the key-value pair is found, return its val\n    }\n    return \"\" // If the key-value pair is absent, return \"\"\n}\n\n/* Insert operation */\nfunc (h *hashMapOpenAddressing) put(key int, val string) {\n    if h.loadFactor() > h.loadThres {\n        h.extend() // Grow the capacity when the load factor exceeds the threshold\n    }\n    index := h.findBucket(key) // Find the bucket index for key\n    if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE {\n        h.buckets[index] = &pair{key, val} // If the key-value pair is absent, insert it\n        h.size++\n    } else {\n        h.buckets[index].val = val // If the key-value pair is found, overwrite val\n    }\n}\n\n/* Remove operation */\nfunc (h *hashMapOpenAddressing) remove(key int) {\n    index := h.findBucket(key) // Find the bucket index for key\n    if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE {\n        h.buckets[index] = h.TOMBSTONE // If the key-value pair is found, replace it with a tombstone\n        h.size--\n    }\n}\n\n/* Grow the hash table capacity */\nfunc (h *hashMapOpenAddressing) extend() {\n    oldBuckets := h.buckets               // Keep a reference to the original hash table\n    h.capacity *= h.extendRatio           // Update the capacity\n    h.buckets = make([]*pair, h.capacity) // Initialize a new hash table with the expanded capacity\n    h.size = 0                            // Reset the size\n    // Move the key-value pairs from the original hash table to the new one\n    for _, pair := range oldBuckets {\n        if pair != nil && pair != h.TOMBSTONE {\n            h.put(pair.key, pair.val)\n        }\n    }\n}\n\n/* Print the hash table */\nfunc (h *hashMapOpenAddressing) print() {\n    for _, pair := range h.buckets {\n        if pair == nil {\n            fmt.Println(\"nil\")\n        } else if pair == h.TOMBSTONE {\n            fmt.Println(\"TOMBSTONE\")\n        } else {\n            fmt.Printf(\"%d -> %s\\n\", pair.key, pair.val)\n        }\n    }\n}\n
    hash_map_open_addressing.swift
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    var size: Int // Number of key-value pairs\n    var capacity: Int // Hash table capacity\n    var loadThres: Double // Load factor threshold that triggers capacity growth\n    var extendRatio: Int // Capacity growth factor\n    var buckets: [Pair?] // Bucket array\n    var TOMBSTONE: Pair // Tombstone marker\n\n    /* Constructor */\n    init() {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = Array(repeating: nil, count: capacity)\n        TOMBSTONE = Pair(key: -1, val: \"-1\")\n    }\n\n    /* Hash function */\n    func hashFunc(key: Int) -> Int {\n        key % capacity\n    }\n\n    /* Load factor */\n    func loadFactor() -> Double {\n        Double(size) / Double(capacity)\n    }\n\n    /* Find the bucket index for key */\n    func findBucket(key: Int) -> Int {\n        var index = hashFunc(key: key)\n        var firstTombstone = -1\n        // Probe linearly until an empty bucket is reached\n        while buckets[index] != nil {\n            // If key is found, return its bucket index\n            if buckets[index]!.key == key {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if firstTombstone != -1 {\n                    buckets[firstTombstone] = buckets[index]\n                    buckets[index] = TOMBSTONE\n                    return firstTombstone // Return the bucket index after the move\n                }\n                return index // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if firstTombstone == -1 && buckets[index] == TOMBSTONE {\n                firstTombstone = index\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone == -1 ? index : firstTombstone\n    }\n\n    /* Lookup operation */\n    func get(key: Int) -> String? {\n        // Find the bucket index for key\n        let index = findBucket(key: key)\n        // If the key-value pair is found, return its val\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            return buckets[index]!.val\n        }\n        // Return nil if the key-value pair is absent\n        return nil\n    }\n\n    /* Insert operation */\n    func put(key: Int, val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if loadFactor() > loadThres {\n            extend()\n        }\n        // Find the bucket index for key\n        let index = findBucket(key: key)\n        // If the key-value pair is found, overwrite val and return\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            buckets[index]!.val = val\n            return\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = Pair(key: key, val: val)\n        size += 1\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        // Find the bucket index for key\n        let index = findBucket(key: key)\n        // If the key-value pair is found, replace it with a tombstone\n        if buckets[index] != nil, buckets[index] != TOMBSTONE {\n            buckets[index] = TOMBSTONE\n            size -= 1\n        }\n    }\n\n    /* Grow the hash table capacity */\n    func extend() {\n        // Save the original bucket array\n        let bucketsTmp = buckets\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio\n        buckets = Array(repeating: nil, count: capacity)\n        size = 0\n        // Move the key-value pairs from the original hash table to the new one\n        for pair in bucketsTmp {\n            if let pair, pair != TOMBSTONE {\n                put(key: pair.key, val: pair.val)\n            }\n        }\n    }\n\n    /* Print the hash table */\n    func print() {\n        for pair in buckets {\n            if pair == nil {\n                Swift.print(\"null\")\n            } else if pair == TOMBSTONE {\n                Swift.print(\"TOMBSTONE\")\n            } else {\n                Swift.print(\"\\(pair!.key) -> \\(pair!.val)\")\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.js
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    #size; // Number of key-value pairs\n    #capacity; // Hash table capacity\n    #loadThres; // Load factor threshold that triggers capacity growth\n    #extendRatio; // Capacity growth factor\n    #buckets; // Bucket array\n    #TOMBSTONE; // Tombstone marker\n\n    /* Constructor */\n    constructor() {\n        this.#size = 0; // Number of key-value pairs\n        this.#capacity = 4; // Hash table capacity\n        this.#loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n        this.#extendRatio = 2; // Capacity growth factor\n        this.#buckets = Array(this.#capacity).fill(null); // Bucket array\n        this.#TOMBSTONE = new Pair(-1, '-1'); // Tombstone marker\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % this.#capacity;\n    }\n\n    /* Load factor */\n    #loadFactor() {\n        return this.#size / this.#capacity;\n    }\n\n    /* Find the bucket index for key */\n    #findBucket(key) {\n        let index = this.#hashFunc(key);\n        let firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (this.#buckets[index] !== null) {\n            // If key is found, return its bucket index\n            if (this.#buckets[index].key === key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone !== -1) {\n                    this.#buckets[firstTombstone] = this.#buckets[index];\n                    this.#buckets[index] = this.#TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (\n                firstTombstone === -1 &&\n                this.#buckets[index] === this.#TOMBSTONE\n            ) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % this.#capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone === -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    get(key) {\n        // Find the bucket index for key\n        const index = this.#findBucket(key);\n        // If the key-value pair is found, return its val\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            return this.#buckets[index].val;\n        }\n        // Return null if the key-value pair does not exist\n        return null;\n    }\n\n    /* Insert operation */\n    put(key, val) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (this.#loadFactor() > this.#loadThres) {\n            this.#extend();\n        }\n        // Find the bucket index for key\n        const index = this.#findBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            this.#buckets[index].val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        this.#buckets[index] = new Pair(key, val);\n        this.#size++;\n    }\n\n    /* Remove operation */\n    remove(key) {\n        // Find the bucket index for key\n        const index = this.#findBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (\n            this.#buckets[index] !== null &&\n            this.#buckets[index] !== this.#TOMBSTONE\n        ) {\n            this.#buckets[index] = this.#TOMBSTONE;\n            this.#size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    #extend() {\n        // Keep a reference to the original hash table\n        const bucketsTmp = this.#buckets;\n        // Initialize a new hash table with the expanded capacity\n        this.#capacity *= this.#extendRatio;\n        this.#buckets = Array(this.#capacity).fill(null);\n        this.#size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (const pair of bucketsTmp) {\n            if (pair !== null && pair !== this.#TOMBSTONE) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    print() {\n        for (const pair of this.#buckets) {\n            if (pair === null) {\n                console.log('null');\n            } else if (pair === this.#TOMBSTONE) {\n                console.log('TOMBSTONE');\n            } else {\n                console.log(pair.key + ' -> ' + pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.ts
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private size: number; // Number of key-value pairs\n    private capacity: number; // Hash table capacity\n    private loadThres: number; // Load factor threshold that triggers capacity growth\n    private extendRatio: number; // Capacity growth factor\n    private buckets: Array<Pair | null>; // Bucket array\n    private TOMBSTONE: Pair; // Tombstone marker\n\n    /* Constructor */\n    constructor() {\n        this.size = 0; // Number of key-value pairs\n        this.capacity = 4; // Hash table capacity\n        this.loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n        this.extendRatio = 2; // Capacity growth factor\n        this.buckets = Array(this.capacity).fill(null); // Bucket array\n        this.TOMBSTONE = new Pair(-1, '-1'); // Tombstone marker\n    }\n\n    /* Hash function */\n    private hashFunc(key: number): number {\n        return key % this.capacity;\n    }\n\n    /* Load factor */\n    private loadFactor(): number {\n        return this.size / this.capacity;\n    }\n\n    /* Find the bucket index for key */\n    private findBucket(key: number): number {\n        let index = this.hashFunc(key);\n        let firstTombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while (this.buckets[index] !== null) {\n            // If key is found, return its bucket index\n            if (this.buckets[index]!.key === key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone !== -1) {\n                    this.buckets[firstTombstone] = this.buckets[index];\n                    this.buckets[index] = this.TOMBSTONE;\n                    return firstTombstone; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (\n                firstTombstone === -1 &&\n                this.buckets[index] === this.TOMBSTONE\n            ) {\n                firstTombstone = index;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % this.capacity;\n        }\n        // If key is absent, return the index for insertion\n        return firstTombstone === -1 ? index : firstTombstone;\n    }\n\n    /* Lookup operation */\n    get(key: number): string | null {\n        // Find the bucket index for key\n        const index = this.findBucket(key);\n        // If the key-value pair is found, return its val\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            return this.buckets[index]!.val;\n        }\n        // Return null if the key-value pair does not exist\n        return null;\n    }\n\n    /* Insert operation */\n    put(key: number, val: string): void {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (this.loadFactor() > this.loadThres) {\n            this.extend();\n        }\n        // Find the bucket index for key\n        const index = this.findBucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            this.buckets[index]!.val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        this.buckets[index] = new Pair(key, val);\n        this.size++;\n    }\n\n    /* Remove operation */\n    remove(key: number): void {\n        // Find the bucket index for key\n        const index = this.findBucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if (\n            this.buckets[index] !== null &&\n            this.buckets[index] !== this.TOMBSTONE\n        ) {\n            this.buckets[index] = this.TOMBSTONE;\n            this.size--;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    private extend(): void {\n        // Keep a reference to the original hash table\n        const bucketsTmp = this.buckets;\n        // Initialize a new hash table with the expanded capacity\n        this.capacity *= this.extendRatio;\n        this.buckets = Array(this.capacity).fill(null);\n        this.size = 0;\n        // Move the key-value pairs from the original hash table to the new one\n        for (const pair of bucketsTmp) {\n            if (pair !== null && pair !== this.TOMBSTONE) {\n                this.put(pair.key, pair.val);\n            }\n        }\n    }\n\n    /* Print the hash table */\n    print(): void {\n        for (const pair of this.buckets) {\n            if (pair === null) {\n                console.log('null');\n            } else if (pair === this.TOMBSTONE) {\n                console.log('TOMBSTONE');\n            } else {\n                console.log(pair.key + ' -> ' + pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.dart
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n  late int _size; // Number of key-value pairs\n  int _capacity = 4; // Hash table capacity\n  double _loadThres = 2.0 / 3.0; // Load factor threshold that triggers capacity growth\n  int _extendRatio = 2; // Capacity growth factor\n  late List<Pair?> _buckets; // Bucket array\n  Pair _TOMBSTONE = Pair(-1, \"-1\"); // Tombstone marker\n\n  /* Constructor */\n  HashMapOpenAddressing() {\n    _size = 0;\n    _buckets = List.generate(_capacity, (index) => null);\n  }\n\n  /* Hash function */\n  int hashFunc(int key) {\n    return key % _capacity;\n  }\n\n  /* Load factor */\n  double loadFactor() {\n    return _size / _capacity;\n  }\n\n  /* Find the bucket index for key */\n  int findBucket(int key) {\n    int index = hashFunc(key);\n    int firstTombstone = -1;\n    // Probe linearly until an empty bucket is reached\n    while (_buckets[index] != null) {\n      // If key is found, return its bucket index\n      if (_buckets[index]!.key == key) {\n        // If a tombstone was encountered earlier, move the key-value pair to that index\n        if (firstTombstone != -1) {\n          _buckets[firstTombstone] = _buckets[index];\n          _buckets[index] = _TOMBSTONE;\n          return firstTombstone; // Return the bucket index after the move\n        }\n        return index; // Return the bucket index\n      }\n      // Record the first tombstone encountered\n      if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) {\n        firstTombstone = index;\n      }\n      // Calculate the bucket index, wrapping to the beginning after the end\n      index = (index + 1) % _capacity;\n    }\n    // If key is absent, return the index for insertion\n    return firstTombstone == -1 ? index : firstTombstone;\n  }\n\n  /* Lookup operation */\n  String? get(int key) {\n    // Find the bucket index for key\n    int index = findBucket(key);\n    // If the key-value pair is found, return its val\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      return _buckets[index]!.val;\n    }\n    // Return null if the key-value pair does not exist\n    return null;\n  }\n\n  /* Insert operation */\n  void put(int key, String val) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if (loadFactor() > _loadThres) {\n      extend();\n    }\n    // Find the bucket index for key\n    int index = findBucket(key);\n    // If the key-value pair is found, overwrite val and return\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      _buckets[index]!.val = val;\n      return;\n    }\n    // If the key-value pair is absent, insert it\n    _buckets[index] = new Pair(key, val);\n    _size++;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    // Find the bucket index for key\n    int index = findBucket(key);\n    // If the key-value pair is found, replace it with a tombstone\n    if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) {\n      _buckets[index] = _TOMBSTONE;\n      _size--;\n    }\n  }\n\n  /* Grow the hash table capacity */\n  void extend() {\n    // Keep a reference to the original hash table\n    List<Pair?> bucketsTmp = _buckets;\n    // Initialize a new hash table with the expanded capacity\n    _capacity *= _extendRatio;\n    _buckets = List.generate(_capacity, (index) => null);\n    _size = 0;\n    // Move the key-value pairs from the original hash table to the new one\n    for (Pair? pair in bucketsTmp) {\n      if (pair != null && pair != _TOMBSTONE) {\n        put(pair.key, pair.val);\n      }\n    }\n  }\n\n  /* Print the hash table */\n  void printHashMap() {\n    for (Pair? pair in _buckets) {\n      if (pair == null) {\n        print(\"null\");\n      } else if (pair == _TOMBSTONE) {\n        print(\"TOMBSTONE\");\n      } else {\n        print(\"${pair.key} -> ${pair.val}\");\n      }\n    }\n  }\n}\n
    hash_map_open_addressing.rs
    /* Hash table with open addressing */\nstruct HashMapOpenAddressing {\n    size: usize,                // Number of key-value pairs\n    capacity: usize,            // Hash table capacity\n    load_thres: f64,            // Load factor threshold that triggers capacity growth\n    extend_ratio: usize,        // Capacity growth factor\n    buckets: Vec<Option<Pair>>, // Bucket array\n    TOMBSTONE: Option<Pair>,    // Tombstone marker\n}\n\nimpl HashMapOpenAddressing {\n    /* Constructor */\n    fn new() -> Self {\n        Self {\n            size: 0,\n            capacity: 4,\n            load_thres: 2.0 / 3.0,\n            extend_ratio: 2,\n            buckets: vec![None; 4],\n            TOMBSTONE: Some(Pair {\n                key: -1,\n                val: \"-1\".to_string(),\n            }),\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        (key % self.capacity as i32) as usize\n    }\n\n    /* Load factor */\n    fn load_factor(&self) -> f64 {\n        self.size as f64 / self.capacity as f64\n    }\n\n    /* Find the bucket index for key */\n    fn find_bucket(&mut self, key: i32) -> usize {\n        let mut index = self.hash_func(key);\n        let mut first_tombstone = -1;\n        // Probe linearly until an empty bucket is reached\n        while self.buckets[index].is_some() {\n            // If key is found, return the corresponding bucket index\n            if self.buckets[index].as_ref().unwrap().key == key {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if first_tombstone != -1 {\n                    self.buckets[first_tombstone as usize] = self.buckets[index].take();\n                    self.buckets[index] = self.TOMBSTONE.clone();\n                    return first_tombstone as usize; // Return the bucket index after the move\n                }\n                return index; // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE {\n                first_tombstone = index as i32;\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % self.capacity;\n        }\n        // If key is absent, return the index for insertion\n        if first_tombstone == -1 {\n            index\n        } else {\n            first_tombstone as usize\n        }\n    }\n\n    /* Lookup operation */\n    fn get(&mut self, key: i32) -> Option<&str> {\n        // Find the bucket index for key\n        let index = self.find_bucket(key);\n        // If the key-value pair is found, return its val\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            return self.buckets[index].as_ref().map(|pair| &pair.val as &str);\n        }\n        // If the key-value pair does not exist, return None\n        None\n    }\n\n    /* Insert operation */\n    fn put(&mut self, key: i32, val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if self.load_factor() > self.load_thres {\n            self.extend();\n        }\n        // Find the bucket index for key\n        let index = self.find_bucket(key);\n        // If the key-value pair is found, overwrite val and return\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            self.buckets[index].as_mut().unwrap().val = val;\n            return;\n        }\n        // If the key-value pair is absent, insert it\n        self.buckets[index] = Some(Pair { key, val });\n        self.size += 1;\n    }\n\n    /* Remove operation */\n    fn remove(&mut self, key: i32) {\n        // Find the bucket index for key\n        let index = self.find_bucket(key);\n        // If the key-value pair is found, replace it with a tombstone\n        if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE {\n            self.buckets[index] = self.TOMBSTONE.clone();\n            self.size -= 1;\n        }\n    }\n\n    /* Grow the hash table capacity */\n    fn extend(&mut self) {\n        // Temporarily save a copy of the original buckets\n        let buckets_tmp = self.buckets.clone();\n        // Initialize a new hash table with the expanded capacity\n        self.capacity *= self.extend_ratio;\n        self.buckets = vec![None; self.capacity];\n        self.size = 0;\n\n        // Move the key-value pairs from the original hash table to the new one\n        for pair in buckets_tmp {\n            if pair.is_none() || pair == self.TOMBSTONE {\n                continue;\n            }\n            let pair = pair.unwrap();\n\n            self.put(pair.key, pair.val);\n        }\n    }\n    /* Print the hash table */\n    fn print(&self) {\n        for pair in &self.buckets {\n            if pair.is_none() {\n                println!(\"null\");\n            } else if pair == &self.TOMBSTONE {\n                println!(\"TOMBSTONE\");\n            } else {\n                let pair = pair.as_ref().unwrap();\n                println!(\"{} -> {}\", pair.key, pair.val);\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.c
    /* Hash table with open addressing */\ntypedef struct {\n    int size;         // Number of key-value pairs\n    int capacity;     // Hash table capacity\n    double loadThres; // Load factor threshold that triggers capacity growth\n    int extendRatio;  // Capacity growth factor\n    Pair **buckets;   // Bucket array\n    Pair *TOMBSTONE;  // Tombstone marker\n} HashMapOpenAddressing;\n\n/* Constructor */\nHashMapOpenAddressing *newHashMapOpenAddressing() {\n    HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing));\n    hashMap->size = 0;\n    hashMap->capacity = 4;\n    hashMap->loadThres = 2.0 / 3.0;\n    hashMap->extendRatio = 2;\n    hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));\n    hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair));\n    hashMap->TOMBSTONE->key = -1;\n    hashMap->TOMBSTONE->val = \"-1\";\n\n    return hashMap;\n}\n\n/* Destructor */\nvoid delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Pair *pair = hashMap->buckets[i];\n        if (pair != NULL && pair != hashMap->TOMBSTONE) {\n            free(pair->val);\n            free(pair);\n        }\n    }\n    free(hashMap->buckets);\n    free(hashMap->TOMBSTONE);\n    free(hashMap);\n}\n\n/* Hash function */\nint hashFunc(HashMapOpenAddressing *hashMap, int key) {\n    return key % hashMap->capacity;\n}\n\n/* Load factor */\ndouble loadFactor(HashMapOpenAddressing *hashMap) {\n    return (double)hashMap->size / (double)hashMap->capacity;\n}\n\n/* Find the bucket index for key */\nint findBucket(HashMapOpenAddressing *hashMap, int key) {\n    int index = hashFunc(hashMap, key);\n    int firstTombstone = -1;\n    // Probe linearly until an empty bucket is reached\n    while (hashMap->buckets[index] != NULL) {\n        // If key is found, return its bucket index\n        if (hashMap->buckets[index]->key == key) {\n            // If a tombstone was encountered earlier, move the key-value pair to that index\n            if (firstTombstone != -1) {\n                hashMap->buckets[firstTombstone] = hashMap->buckets[index];\n                hashMap->buckets[index] = hashMap->TOMBSTONE;\n                return firstTombstone; // Return the bucket index after the move\n            }\n            return index; // Return the bucket index\n        }\n        // Record the first tombstone encountered\n        if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) {\n            firstTombstone = index;\n        }\n        // Calculate the bucket index, wrapping to the beginning after the end\n        index = (index + 1) % hashMap->capacity;\n    }\n    // If key is absent, return the index for insertion\n    return firstTombstone == -1 ? index : firstTombstone;\n}\n\n/* Lookup operation */\nchar *get(HashMapOpenAddressing *hashMap, int key) {\n    // Find the bucket index for key\n    int index = findBucket(hashMap, key);\n    // If the key-value pair is found, return its val\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        return hashMap->buckets[index]->val;\n    }\n    // Return an empty string if the key-value pair does not exist\n    return \"\";\n}\n\n/* Insert operation */\nvoid put(HashMapOpenAddressing *hashMap, int key, char *val) {\n    // Grow the capacity when the load factor exceeds the threshold\n    if (loadFactor(hashMap) > hashMap->loadThres) {\n        extend(hashMap);\n    }\n    // Find the bucket index for key\n    int index = findBucket(hashMap, key);\n    // If the key-value pair is found, overwrite val and return\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        free(hashMap->buckets[index]->val);\n        hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1));\n        strcpy(hashMap->buckets[index]->val, val);\n        hashMap->buckets[index]->val[strlen(val)] = '\\0';\n        return;\n    }\n    // If the key-value pair is absent, insert it\n    Pair *pair = (Pair *)malloc(sizeof(Pair));\n    pair->key = key;\n    pair->val = (char *)malloc(sizeof(strlen(val) + 1));\n    strcpy(pair->val, val);\n    pair->val[strlen(val)] = '\\0';\n\n    hashMap->buckets[index] = pair;\n    hashMap->size++;\n}\n\n/* Remove operation */\nvoid removeItem(HashMapOpenAddressing *hashMap, int key) {\n    // Find the bucket index for key\n    int index = findBucket(hashMap, key);\n    // If the key-value pair is found, replace it with a tombstone\n    if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {\n        Pair *pair = hashMap->buckets[index];\n        free(pair->val);\n        free(pair);\n        hashMap->buckets[index] = hashMap->TOMBSTONE;\n        hashMap->size--;\n    }\n}\n\n/* Grow the hash table capacity */\nvoid extend(HashMapOpenAddressing *hashMap) {\n    // Keep a reference to the original hash table\n    Pair **bucketsTmp = hashMap->buckets;\n    int oldCapacity = hashMap->capacity;\n    // Initialize a new hash table with the expanded capacity\n    hashMap->capacity *= hashMap->extendRatio;\n    hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));\n    hashMap->size = 0;\n    // Move the key-value pairs from the original hash table to the new one\n    for (int i = 0; i < oldCapacity; i++) {\n        Pair *pair = bucketsTmp[i];\n        if (pair != NULL && pair != hashMap->TOMBSTONE) {\n            put(hashMap, pair->key, pair->val);\n            free(pair->val);\n            free(pair);\n        }\n    }\n    free(bucketsTmp);\n}\n\n/* Print the hash table */\nvoid print(HashMapOpenAddressing *hashMap) {\n    for (int i = 0; i < hashMap->capacity; i++) {\n        Pair *pair = hashMap->buckets[i];\n        if (pair == NULL) {\n            printf(\"NULL\\n\");\n        } else if (pair == hashMap->TOMBSTONE) {\n            printf(\"TOMBSTONE\\n\");\n        } else {\n            printf(\"%d -> %s\\n\", pair->key, pair->val);\n        }\n    }\n}\n
    hash_map_open_addressing.kt
    /* Hash table with open addressing */\nclass HashMapOpenAddressing {\n    private var size: Int               // Number of key-value pairs\n    private var capacity: Int           // Hash table capacity\n    private val loadThres: Double       // Load factor threshold that triggers capacity growth\n    private val extendRatio: Int        // Capacity growth factor\n    private var buckets: Array<Pair?>   // Bucket array\n    private val TOMBSTONE: Pair         // Tombstone marker\n\n    /* Constructor */\n    init {\n        size = 0\n        capacity = 4\n        loadThres = 2.0 / 3.0\n        extendRatio = 2\n        buckets = arrayOfNulls(capacity)\n        TOMBSTONE = Pair(-1, \"-1\")\n    }\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        return key % capacity\n    }\n\n    /* Load factor */\n    fun loadFactor(): Double {\n        return (size / capacity).toDouble()\n    }\n\n    /* Find the bucket index for key */\n    fun findBucket(key: Int): Int {\n        var index = hashFunc(key)\n        var firstTombstone = -1\n        // Probe linearly until an empty bucket is reached\n        while (buckets[index] != null) {\n            // If key is found, return its bucket index\n            if (buckets[index]?.key == key) {\n                // If a tombstone was encountered earlier, move the key-value pair to that index\n                if (firstTombstone != -1) {\n                    buckets[firstTombstone] = buckets[index]\n                    buckets[index] = TOMBSTONE\n                    return firstTombstone // Return the bucket index after the move\n                }\n                return index // Return the bucket index\n            }\n            // Record the first tombstone encountered\n            if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {\n                firstTombstone = index\n            }\n            // Calculate the bucket index, wrapping to the beginning after the end\n            index = (index + 1) % capacity\n        }\n        // If key is absent, return the index for insertion\n        return if (firstTombstone == -1) index else firstTombstone\n    }\n\n    /* Lookup operation */\n    fun get(key: Int): String? {\n        // Find the bucket index for key\n        val index = findBucket(key)\n        // If the key-value pair is found, return its val\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            return buckets[index]?._val\n        }\n        // Return null if the key-value pair is absent\n        return null\n    }\n\n    /* Insert operation */\n    fun put(key: Int, _val: String) {\n        // Grow the capacity when the load factor exceeds the threshold\n        if (loadFactor() > loadThres) {\n            extend()\n        }\n        // Find the bucket index for key\n        val index = findBucket(key)\n        // If the key-value pair is found, overwrite val and return\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index]!!._val = _val\n            return\n        }\n        // If the key-value pair is absent, insert it\n        buckets[index] = Pair(key, _val)\n        size++\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        // Find the bucket index for key\n        val index = findBucket(key)\n        // If the key-value pair is found, replace it with a tombstone\n        if (buckets[index] != null && buckets[index] != TOMBSTONE) {\n            buckets[index] = TOMBSTONE\n            size--\n        }\n    }\n\n    /* Grow the hash table capacity */\n    fun extend() {\n        // Keep a reference to the original hash table\n        val bucketsTmp = buckets\n        // Initialize a new hash table with the expanded capacity\n        capacity *= extendRatio\n        buckets = arrayOfNulls(capacity)\n        size = 0\n        // Move the key-value pairs from the original hash table to the new one\n        for (pair in bucketsTmp) {\n            if (pair != null && pair != TOMBSTONE) {\n                put(pair.key, pair._val)\n            }\n        }\n    }\n\n    /* Print the hash table */\n    fun print() {\n        for (pair in buckets) {\n            if (pair == null) {\n                println(\"null\")\n            } else if (pair == TOMBSTONE) {\n                println(\"TOMESTOME\")\n            } else {\n                println(\"${pair.key} -> ${pair._val}\")\n            }\n        }\n    }\n}\n
    hash_map_open_addressing.rb
    ### Hash table with open addressing ###\nclass HashMapOpenAddressing\n  TOMBSTONE = Pair.new(-1, '-1') # Tombstone marker\n\n  ### Constructor ###\n  def initialize\n    @size = 0 # Number of key-value pairs\n    @capacity = 4 # Hash table capacity\n    @load_thres = 2.0 / 3.0 # Load factor threshold that triggers capacity growth\n    @extend_ratio = 2 # Capacity growth factor\n    @buckets = Array.new(@capacity) # Bucket array\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    key % @capacity\n  end\n\n  ### Load factor ###\n  def load_factor\n    @size / @capacity\n  end\n\n  ### Find the bucket index for key ###\n  def find_bucket(key)\n    index = hash_func(key)\n    first_tombstone = -1\n    # Probe linearly until an empty bucket is reached\n    while !@buckets[index].nil?\n      # If key is found, return its bucket index\n      if @buckets[index].key == key\n        # If a tombstone was encountered earlier, move the key-value pair to that index\n        if first_tombstone != -1\n          @buckets[first_tombstone] = @buckets[index]\n          @buckets[index] = TOMBSTONE\n          return first_tombstone # Return the bucket index after the move\n        end\n        return index # Return the bucket index\n      end\n      # Record the first tombstone encountered\n      first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE\n      # Calculate the bucket index, wrapping to the beginning after the end\n      index = (index + 1) % @capacity\n    end\n    # If key is absent, return the index for insertion\n    first_tombstone == -1 ? index : first_tombstone\n  end\n\n  ### Lookup operation ###\n  def get(key)\n    # Find the bucket index for key\n    index = find_bucket(key)\n    # If the key-value pair is found, return its val\n    return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])\n    # Return nil if the key-value pair does not exist\n    nil\n  end\n\n  ### Insert operation ###\n  def put(key, val)\n    # Grow the capacity when the load factor exceeds the threshold\n    extend if load_factor > @load_thres\n    # Find the bucket index for key\n    index = find_bucket(key)\n    # If the key-value pair is found, overwrite val and return\n    unless [nil, TOMBSTONE].include?(@buckets[index])\n      @buckets[index].val = val\n      return\n    end\n    # If the key-value pair is absent, insert it\n    @buckets[index] = Pair.new(key, val)\n    @size += 1\n  end\n\n  ### Remove operation ###\n  def remove(key)\n    # Find the bucket index for key\n    index = find_bucket(key)\n    # If the key-value pair is found, replace it with a tombstone\n    unless [nil, TOMBSTONE].include?(@buckets[index])\n      @buckets[index] = TOMBSTONE\n      @size -= 1\n    end\n  end\n\n  ### Grow the hash table capacity ###\n  def extend\n    # Keep a reference to the original hash table\n    buckets_tmp = @buckets\n    # Initialize a new hash table with the expanded capacity\n    @capacity *= @extend_ratio\n    @buckets = Array.new(@capacity)\n    @size = 0\n    # Move the key-value pairs from the original hash table to the new one\n    for pair in buckets_tmp\n      put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)\n    end\n  end\n\n  ### Print the hash table ###\n  def print\n    for pair in @buckets\n      if pair.nil?\n        puts \"Nil\"\n      elsif pair == TOMBSTONE\n        puts \"TOMBSTONE\"\n      else\n        puts \"#{pair.key} -> #{pair.val}\"\n      end\n    end\n  end\nend\n
    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#2-quadratic-probing","level":3,"title":"2.   Quadratic probing","text":"

    Like linear probing, quadratic probing is a common open-addressing strategy. On a collision, it probes at distances determined by the square of the probe number—\\(1, 4, 9, \\dots\\)—rather than a fixed step size.

    Its main advantages are as follows.

    • Squared probe distances aim to reduce the clustering caused by linear probing.
    • Larger jumps help distribute entries more evenly while searching for empty positions.

    However, quadratic probing is not perfect.

    • Clustering still occurs: some positions are more likely to be occupied than others.
    • The growing squared distances may prevent probing the entire table, so an empty bucket may remain unreachable.
    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#3-multiple-hashing","level":3,"title":"3.   Multiple hashing","text":"

    As its name suggests, multiple hashing probes using several hash functions, \\(f_1(x)\\), \\(f_2(x)\\), \\(f_3(x)\\), \\(\\dots\\).

    • Insertion: If \\(f_1(x)\\) produces a collision, try \\(f_2(x)\\), and so on, inserting the element when an empty position is found.
    • Lookup: Try the hash functions in the same order and return when the target is found. If an empty position is reached or all functions have been tried, the target is absent; return None.

    Multiple hashing is less prone to clustering than linear probing, but computing several hash functions adds overhead.

    Tip

    All open-addressing strategies—linear probing, quadratic probing, and multiple hashing—share the restriction that elements cannot simply be deleted.

    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_collision/#623-choices-made-by-programming-languages","level":2,"title":"6.2.3   Choices made by programming languages","text":"

    Programming languages use different hash-table implementations. Here are a few examples.

    • Python uses open addressing. Its dict probes using pseudorandom values.
    • Java uses separate chaining. Since JDK 1.8, when a HashMap's underlying array has length at least 64 and a chain reaches length 8, the chain is converted to a red-black tree to improve lookup performance.
    • Since Go 1.24, the built-in map uses open addressing based on Swiss Tables by default. Go 1.23 and earlier use separate chaining, with up to 8 key-value pairs per bucket and linked overflow buckets when that capacity is exceeded.
    ","path":["Chapter 6.   Hash tables","6.2   Hash collisions"],"tags":[]},{"location":"chapter_hashing/hash_map/","level":1,"title":"6.1   Hash tables","text":"

    A hash table, also called a hash map, supports efficient lookup by mapping a key to a value. Given a key, it can retrieve the corresponding value in \\(O(1)\\) time.

    Suppose we have \\(n\\) students, each with a name and a student ID. To support the query “given a student ID, return the corresponding name,” use the hash table shown below.

    Figure 6-1   An abstract view of hash-table lookup

    Arrays and linked lists also support lookup. Table 6-1 compares their efficiency.

    • Insertion: append an element to the end of the array or linked list in \\(O(1)\\) time.
    • Lookup: because the array or list is unsorted, searching may require visiting every element, taking \\(O(n)\\) time.
    • Deletion: first find the element, then remove it from the array or list, taking \\(O(n)\\) time.

    Table 6-1   Comparison of element-lookup efficiency

    Array Linked list Hash table Find an element \\(O(n)\\) \\(O(n)\\) \\(O(1)\\) Insert an element \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) Delete an element \\(O(n)\\) \\(O(n)\\) \\(O(1)\\)

    Hash-table insertion, deletion, lookup, and updating all take \\(O(1)\\) time, making them highly efficient.

    ","path":["Chapter 6.   Hash tables","6.1   Hash tables"],"tags":[]},{"location":"chapter_hashing/hash_map/#611-common-hash-table-operations","level":2,"title":"6.1.1   Common hash-table operations","text":"

    Common operations include initialization, lookup, adding key-value pairs, and deleting pairs. Examples follow:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py
    # Initialize a hash table\nhmap: dict = {}\n\n# Insertion\n# Add key-value pairs (key, value) to the hash table\nhmap[12836] = \"小哈\"\nhmap[15937] = \"小啰\"\nhmap[16750] = \"小算\"\nhmap[13276] = \"小法\"\nhmap[10583] = \"小鸭\"\n\n# Lookup\n# Look up key in the hash table to obtain value\nname: str = hmap[15937]\n\n# Deletion\n# Remove a key-value pair (key, value) from the hash table\nhmap.pop(10583)\n
    hash_map.cpp
    /* Initialize a hash table */\nunordered_map<int, string> map;\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap[12836] = \"小哈\";\nmap[15937] = \"小啰\";\nmap[16750] = \"小算\";\nmap[13276] = \"小法\";\nmap[10583] = \"小鸭\";\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nstring name = map[15937];\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.erase(10583);\n
    hash_map.java
    /* Initialize a hash table */\nMap<Integer, String> map = new HashMap<>();\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap.put(12836, \"小哈\");\nmap.put(15937, \"小啰\");\nmap.put(16750, \"小算\");\nmap.put(13276, \"小法\");\nmap.put(10583, \"小鸭\");\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nString name = map.get(15937);\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.remove(10583);\n
    hash_map.cs
    /* Initialize a hash table */\nDictionary<int, string> map = new() {\n    /* Insertion */\n    // Add key-value pairs (key, value) to the hash table\n    { 12836, \"小哈\" },\n    { 15937, \"小啰\" },\n    { 16750, \"小算\" },\n    { 13276, \"小法\" },\n    { 10583, \"小鸭\" }\n};\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nstring name = map[15937];\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.Remove(10583);\n
    hash_map_test.go
    /* Initialize a hash table */\nhmap := make(map[int]string)\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nhmap[12836] = \"小哈\"\nhmap[15937] = \"小啰\"\nhmap[16750] = \"小算\"\nhmap[13276] = \"小法\"\nhmap[10583] = \"小鸭\"\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nname := hmap[15937]\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\ndelete(hmap, 10583)\n
    hash_map.swift
    /* Initialize a hash table */\nvar map: [Int: String] = [:]\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap[12836] = \"小哈\"\nmap[15937] = \"小啰\"\nmap[16750] = \"小算\"\nmap[13276] = \"小法\"\nmap[10583] = \"小鸭\"\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nlet name = map[15937]!\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.removeValue(forKey: 10583)\n
    hash_map.js
    /* Initialize a hash table */\nconst map = new Map();\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap.set(12836, '小哈');\nmap.set(15937, '小啰');\nmap.set(16750, '小算');\nmap.set(13276, '小法');\nmap.set(10583, '小鸭');\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nlet name = map.get(15937);\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.delete(10583);\n
    hash_map.ts
    /* Initialize a hash table */\nconst map = new Map<number, string>();\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap.set(12836, '小哈');\nmap.set(15937, '小啰');\nmap.set(16750, '小算');\nmap.set(13276, '小法');\nmap.set(10583, '小鸭');\nconsole.info('\\nAfter insertion, the hash table is\\nKey -> Value');\nconsole.info(map);\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nlet name = map.get(15937);\nconsole.info('\\nLooking up student ID 15937 gives the name ' + name);\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.delete(10583);\nconsole.info('\\nAfter deleting 10583, the hash table is\\nKey -> Value');\nconsole.info(map);\n
    hash_map.dart
    /* Initialize a hash table */\nMap<int, String> map = {};\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap[12836] = \"小哈\";\nmap[15937] = \"小啰\";\nmap[16750] = \"小算\";\nmap[13276] = \"小法\";\nmap[10583] = \"小鸭\";\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nString name = map[15937];\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.remove(10583);\n
    hash_map.rs
    use std::collections::HashMap;\n\n/* Initialize a hash table */\nlet mut map: HashMap<i32, String> = HashMap::new();\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap.insert(12836, \"小哈\".to_string());\nmap.insert(15937, \"小啰\".to_string());\nmap.insert(16750, \"小算\".to_string());\nmap.insert(13279, \"小法\".to_string());\nmap.insert(10583, \"小鸭\".to_string());\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nlet _name: Option<&String> = map.get(&15937);\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nlet _removed_value: Option<String> = map.remove(&10583);\n
    hash_map.c
    // C provides no built-in hash table\n
    hash_map.kt
    /* Initialize a hash table */\nval map = HashMap<Int,String>()\n\n/* Insertion */\n// Add key-value pairs (key, value) to the hash table\nmap[12836] = \"小哈\"\nmap[15937] = \"小啰\"\nmap[16750] = \"小算\"\nmap[13276] = \"小法\"\nmap[10583] = \"小鸭\"\n\n/* Lookup */\n// Look up key in the hash table to obtain value\nval name = map[15937]\n\n/* Deletion */\n// Remove a key-value pair (key, value) from the hash table\nmap.remove(10583)\n
    hash_map.rb
    # Initialize a hash table\nhmap = {}\n\n# Insertion\n# Add key-value pairs (key, value) to the hash table\nhmap[12836] = \"小哈\"\nhmap[15937] = \"小啰\"\nhmap[16750] = \"小算\"\nhmap[13276] = \"小法\"\nhmap[10583] = \"小鸭\"\n\n# Lookup\n# Look up key in the hash table to obtain value\nname = hmap[15937]\n\n# Deletion\n# Remove a key-value pair (key, value) from the hash table\nhmap.delete(10583)\n
    Visualize execution

    View full screen >

    There are three common ways to traverse a hash table: visit key-value pairs, keys only, or values only. Examples follow:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py
    # Traverse the hash table\n# Traverse key-value pairs: key->value\nfor key, value in hmap.items():\n    print(key, \"->\", value)\n# Traverse keys only: key\nfor key in hmap.keys():\n    print(key)\n# Traverse values only: value\nfor value in hmap.values():\n    print(value)\n
    hash_map.cpp
    /* Traverse the hash table */\n// Traverse key-value pairs: key->value\nfor (auto kv: map) {\n    cout << kv.first << \" -> \" << kv.second << endl;\n}\n// Use an iterator to traverse key->value pairs\nfor (auto iter = map.begin(); iter != map.end(); iter++) {\n    cout << iter->first << \"->\" << iter->second << endl;\n}\n
    hash_map.java
    /* Traverse the hash table */\n// Traverse key-value pairs: key->value\nfor (Map.Entry <Integer, String> kv: map.entrySet()) {\n    System.out.println(kv.getKey() + \" -> \" + kv.getValue());\n}\n// Traverse keys only: key\nfor (int key: map.keySet()) {\n    System.out.println(key);\n}\n// Traverse values only: value\nfor (String val: map.values()) {\n    System.out.println(val);\n}\n
    hash_map.cs
    /* Traverse the hash table */\n// Traverse key-value pairs: Key->Value\nforeach (var kv in map) {\n    Console.WriteLine(kv.Key + \" -> \" + kv.Value);\n}\n// Traverse keys only: key\nforeach (int key in map.Keys) {\n    Console.WriteLine(key);\n}\n// Traverse values only: value\nforeach (string val in map.Values) {\n    Console.WriteLine(val);\n}\n
    hash_map_test.go
    /* Traverse the hash table */\n// Traverse key-value pairs: key->value\nfor key, value := range hmap {\n    fmt.Println(key, \"->\", value)\n}\n// Traverse keys only: key\nfor key := range hmap {\n    fmt.Println(key)\n}\n// Traverse values only: value\nfor _, value := range hmap {\n    fmt.Println(value)\n}\n
    hash_map.swift
    /* Traverse the hash table */\n// Traverse key-value pairs: Key->Value\nfor (key, value) in map {\n    print(\"\\(key) -> \\(value)\")\n}\n// Traverse keys only: Key\nfor key in map.keys {\n    print(key)\n}\n// Traverse values only: Value\nfor value in map.values {\n    print(value)\n}\n
    hash_map.js
    /* Traverse the hash table */\nconsole.info('\\nTraverse key-value pairs: Key->Value');\nfor (const [k, v] of map.entries()) {\n    console.info(k + ' -> ' + v);\n}\nconsole.info('\\nTraverse keys only: Key');\nfor (const k of map.keys()) {\n    console.info(k);\n}\nconsole.info('\\nTraverse values only: Value');\nfor (const v of map.values()) {\n    console.info(v);\n}\n
    hash_map.ts
    /* Traverse the hash table */\nconsole.info('\\nTraverse key-value pairs: Key->Value');\nfor (const [k, v] of map.entries()) {\n    console.info(k + ' -> ' + v);\n}\nconsole.info('\\nTraverse keys only: Key');\nfor (const k of map.keys()) {\n    console.info(k);\n}\nconsole.info('\\nTraverse values only: Value');\nfor (const v of map.values()) {\n    console.info(v);\n}\n
    hash_map.dart
    /* Traverse the hash table */\n// Traverse key-value pairs: Key->Value\nmap.forEach((key, value) {\n  print('$key -> $value');\n});\n\n// Traverse keys only: Key\nmap.keys.forEach((key) {\n  print(key);\n});\n\n// Traverse values only: Value\nmap.values.forEach((value) {\n  print(value);\n});\n
    hash_map.rs
    /* Traverse the hash table */\n// Traverse key-value pairs: Key->Value\nfor (key, value) in &map {\n    println!(\"{key} -> {value}\");\n}\n\n// Traverse keys only: Key\nfor key in map.keys() {\n    println!(\"{key}\");\n}\n\n// Traverse values only: Value\nfor value in map.values() {\n    println!(\"{value}\");\n}\n
    hash_map.c
    // C provides no built-in hash table\n
    hash_map.kt
    /* Traverse the hash table */\n// Traverse key-value pairs: key->value\nfor ((key, value) in map) {\n    println(\"$key -> $value\")\n}\n// Traverse keys only: key\nfor (key in map.keys) {\n    println(key)\n}\n// Traverse values only: value\nfor (_val in map.values) {\n    println(_val)\n}\n
    hash_map.rb
    # Traverse the hash table\n# Traverse key-value pairs: key->value\nhmap.entries.each { |key, value| puts \"#{key} -> #{value}\" }\n\n# Traverse keys only: key\nhmap.keys.each { |key| puts key }\n\n# Traverse values only: value\nhmap.values.each { |val| puts val }\n
    Visualize execution

    View full screen >

    ","path":["Chapter 6.   Hash tables","6.1   Hash tables"],"tags":[]},{"location":"chapter_hashing/hash_map/#612-a-simple-implementation","level":2,"title":"6.1.2   A simple implementation","text":"

    Start with the simplest case: implement a hash table using just one array. Each slot is called a bucket and holds one key-value pair. A lookup finds the bucket corresponding to key and retrieves its value.

    How do we locate the bucket for a key? A hash function maps a larger input space into a smaller output space. Here, the inputs are all possible keys, and the outputs are bucket indices. Given a key, the hash function tells us where its key-value pair is stored in the array.

    For an input key, the calculation has two steps.

    1. Use a hash algorithm, hash(), to calculate a hash value.
    2. Take that value modulo the number of buckets, the array length capacity, to obtain the bucket index index.
    index = hash(key) % capacity\n

    Use index to access the bucket and retrieve value.

    If capacity = 100 and hash(key) = key, the resulting hash function is key % 100. Figure 6-2 illustrates it with student IDs as keys and names as values.

    Figure 6-2   How a hash function works

    The code below implements a simple hash table. The Pair class groups a key and value into a key-value pair.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_hash_map.py
    class Pair:\n    \"\"\"Key-value pair\"\"\"\n\n    def __init__(self, key: int, val: str):\n        self.key = key\n        self.val = val\n\nclass ArrayHashMap:\n    \"\"\"Hash table implemented with an array\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        # Initialize an array containing 100 buckets\n        self.buckets: list[Pair | None] = [None] * 100\n\n    def hash_func(self, key: int) -> int:\n        \"\"\"Hash function\"\"\"\n        index = key % 100\n        return index\n\n    def get(self, key: int) -> str | None:\n        \"\"\"Lookup operation\"\"\"\n        index: int = self.hash_func(key)\n        pair: Pair = self.buckets[index]\n        if pair is None:\n            return None\n        return pair.val\n\n    def put(self, key: int, val: str):\n        \"\"\"Insert and update operations\"\"\"\n        pair = Pair(key, val)\n        index: int = self.hash_func(key)\n        self.buckets[index] = pair\n\n    def remove(self, key: int):\n        \"\"\"Remove operation\"\"\"\n        index: int = self.hash_func(key)\n        # Set to None to mark removal\n        self.buckets[index] = None\n\n    def entry_set(self) -> list[Pair]:\n        \"\"\"Get all key-value pairs\"\"\"\n        result: list[Pair] = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair)\n        return result\n\n    def key_set(self) -> list[int]:\n        \"\"\"Get all keys\"\"\"\n        result = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair.key)\n        return result\n\n    def value_set(self) -> list[str]:\n        \"\"\"Get all values\"\"\"\n        result = []\n        for pair in self.buckets:\n            if pair is not None:\n                result.append(pair.val)\n        return result\n\n    def print(self):\n        \"\"\"Print the hash table\"\"\"\n        for pair in self.buckets:\n            if pair is not None:\n                print(pair.key, \"->\", pair.val)\n
    array_hash_map.cpp
    /* Key-value pair */\nstruct Pair {\n  public:\n    int key;\n    string val;\n    Pair(int key, string val) {\n        this->key = key;\n        this->val = val;\n    }\n};\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n  private:\n    vector<Pair *> buckets;\n\n  public:\n    ArrayHashMap() {\n        // Initialize an array containing 100 buckets\n        buckets = vector<Pair *>(100);\n    }\n\n    ~ArrayHashMap() {\n        // Free memory\n        for (const auto &bucket : buckets) {\n            delete bucket;\n        }\n        buckets.clear();\n    }\n\n    /* Hash function */\n    int hashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Lookup operation */\n    string get(int key) {\n        int index = hashFunc(key);\n        Pair *pair = buckets[index];\n        if (pair == nullptr)\n            return \"\";\n        return pair->val;\n    }\n\n    /* Insert operation */\n    void put(int key, string val) {\n        Pair *pair = new Pair(key, val);\n        int index = hashFunc(key);\n        buckets[index] = pair;\n    }\n\n    /* Remove operation */\n    void remove(int key) {\n        int index = hashFunc(key);\n        // Free the memory and set the pointer to nullptr\n        delete buckets[index];\n        buckets[index] = nullptr;\n    }\n\n    /* Get all key-value pairs */\n    vector<Pair *> pairSet() {\n        vector<Pair *> pairSet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                pairSet.push_back(pair);\n            }\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    vector<int> keySet() {\n        vector<int> keySet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                keySet.push_back(pair->key);\n            }\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    vector<string> valueSet() {\n        vector<string> valueSet;\n        for (Pair *pair : buckets) {\n            if (pair != nullptr) {\n                valueSet.push_back(pair->val);\n            }\n        }\n        return valueSet;\n    }\n\n    /* Print the hash table */\n    void print() {\n        for (Pair *kv : pairSet()) {\n            cout << kv->key << \" -> \" << kv->val << endl;\n        }\n    }\n};\n
    array_hash_map.java
    /* Key-value pair */\nclass Pair {\n    public int key;\n    public String val;\n\n    public Pair(int key, String val) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    private List<Pair> buckets;\n\n    public ArrayHashMap() {\n        // Initialize an array containing 100 buckets\n        buckets = new ArrayList<>();\n        for (int i = 0; i < 100; i++) {\n            buckets.add(null);\n        }\n    }\n\n    /* Hash function */\n    private int hashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Lookup operation */\n    public String get(int key) {\n        int index = hashFunc(key);\n        Pair pair = buckets.get(index);\n        if (pair == null)\n            return null;\n        return pair.val;\n    }\n\n    /* Insert operation */\n    public void put(int key, String val) {\n        Pair pair = new Pair(key, val);\n        int index = hashFunc(key);\n        buckets.set(index, pair);\n    }\n\n    /* Remove operation */\n    public void remove(int key) {\n        int index = hashFunc(key);\n        // Set the bucket to null to mark it as removed\n        buckets.set(index, null);\n    }\n\n    /* Get all key-value pairs */\n    public List<Pair> pairSet() {\n        List<Pair> pairSet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                pairSet.add(pair);\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    public List<Integer> keySet() {\n        List<Integer> keySet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                keySet.add(pair.key);\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    public List<String> valueSet() {\n        List<String> valueSet = new ArrayList<>();\n        for (Pair pair : buckets) {\n            if (pair != null)\n                valueSet.add(pair.val);\n        }\n        return valueSet;\n    }\n\n    /* Print the hash table */\n    public void print() {\n        for (Pair kv : pairSet()) {\n            System.out.println(kv.key + \" -> \" + kv.val);\n        }\n    }\n}\n
    array_hash_map.cs
    /* Key-value pair: int->string */\nclass Pair(int key, string val) {\n    public int key = key;\n    public string val = val;\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    List<Pair?> buckets;\n    public ArrayHashMap() {\n        // Initialize an array containing 100 buckets\n        buckets = [];\n        for (int i = 0; i < 100; i++) {\n            buckets.Add(null);\n        }\n    }\n\n    /* Hash function */\n    int HashFunc(int key) {\n        int index = key % 100;\n        return index;\n    }\n\n    /* Lookup operation */\n    public string? Get(int key) {\n        int index = HashFunc(key);\n        Pair? pair = buckets[index];\n        if (pair == null) return null;\n        return pair.val;\n    }\n\n    /* Insert operation */\n    public void Put(int key, string val) {\n        Pair pair = new(key, val);\n        int index = HashFunc(key);\n        buckets[index] = pair;\n    }\n\n    /* Remove operation */\n    public void Remove(int key) {\n        int index = HashFunc(key);\n        // Set the bucket to null to mark it as removed\n        buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    public List<Pair> PairSet() {\n        List<Pair> pairSet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                pairSet.Add(pair);\n        }\n        return pairSet;\n    }\n\n    /* Get all keys */\n    public List<int> KeySet() {\n        List<int> keySet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                keySet.Add(pair.key);\n        }\n        return keySet;\n    }\n\n    /* Get all values */\n    public List<string> ValueSet() {\n        List<string> valueSet = [];\n        foreach (Pair? pair in buckets) {\n            if (pair != null)\n                valueSet.Add(pair.val);\n        }\n        return valueSet;\n    }\n\n    /* Print the hash table */\n    public void Print() {\n        foreach (Pair kv in PairSet()) {\n            Console.WriteLine(kv.key + \" -> \" + kv.val);\n        }\n    }\n}\n
    array_hash_map.go
    /* Key-value pair */\ntype pair struct {\n    key int\n    val string\n}\n\n/* Hash table implemented with an array */\ntype arrayHashMap struct {\n    buckets []*pair\n}\n\n/* Initialize the hash table */\nfunc newArrayHashMap() *arrayHashMap {\n    // Initialize an array containing 100 buckets\n    buckets := make([]*pair, 100)\n    return &arrayHashMap{buckets: buckets}\n}\n\n/* Hash function */\nfunc (a *arrayHashMap) hashFunc(key int) int {\n    index := key % 100\n    return index\n}\n\n/* Lookup operation */\nfunc (a *arrayHashMap) get(key int) string {\n    index := a.hashFunc(key)\n    pair := a.buckets[index]\n    if pair == nil {\n        return \"Not Found\"\n    }\n    return pair.val\n}\n\n/* Insert operation */\nfunc (a *arrayHashMap) put(key int, val string) {\n    pair := &pair{key: key, val: val}\n    index := a.hashFunc(key)\n    a.buckets[index] = pair\n}\n\n/* Remove operation */\nfunc (a *arrayHashMap) remove(key int) {\n    index := a.hashFunc(key)\n    // Set to nil to mark the entry as removed\n    a.buckets[index] = nil\n}\n\n/* Get all key-value pairs */\nfunc (a *arrayHashMap) pairSet() []*pair {\n    var pairs []*pair\n    for _, pair := range a.buckets {\n        if pair != nil {\n            pairs = append(pairs, pair)\n        }\n    }\n    return pairs\n}\n\n/* Get all keys */\nfunc (a *arrayHashMap) keySet() []int {\n    var keys []int\n    for _, pair := range a.buckets {\n        if pair != nil {\n            keys = append(keys, pair.key)\n        }\n    }\n    return keys\n}\n\n/* Get all values */\nfunc (a *arrayHashMap) valueSet() []string {\n    var values []string\n    for _, pair := range a.buckets {\n        if pair != nil {\n            values = append(values, pair.val)\n        }\n    }\n    return values\n}\n\n/* Print the hash table */\nfunc (a *arrayHashMap) print() {\n    for _, pair := range a.buckets {\n        if pair != nil {\n            fmt.Println(pair.key, \"->\", pair.val)\n        }\n    }\n}\n
    array_hash_map.swift
    /* Key-value pair */\nclass Pair: Equatable {\n    public var key: Int\n    public var val: String\n\n    public init(key: Int, val: String) {\n        self.key = key\n        self.val = val\n    }\n\n    public static func == (lhs: Pair, rhs: Pair) -> Bool {\n        lhs.key == rhs.key && lhs.val == rhs.val\n    }\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    private var buckets: [Pair?]\n\n    init() {\n        // Initialize an array containing 100 buckets\n        buckets = Array(repeating: nil, count: 100)\n    }\n\n    /* Hash function */\n    private func hashFunc(key: Int) -> Int {\n        let index = key % 100\n        return index\n    }\n\n    /* Lookup operation */\n    func get(key: Int) -> String? {\n        let index = hashFunc(key: key)\n        let pair = buckets[index]\n        return pair?.val\n    }\n\n    /* Insert operation */\n    func put(key: Int, val: String) {\n        let pair = Pair(key: key, val: val)\n        let index = hashFunc(key: key)\n        buckets[index] = pair\n    }\n\n    /* Remove operation */\n    func remove(key: Int) {\n        let index = hashFunc(key: key)\n        // Set to nil to mark the entry as removed\n        buckets[index] = nil\n    }\n\n    /* Get all key-value pairs */\n    func pairSet() -> [Pair] {\n        buckets.compactMap { $0 }\n    }\n\n    /* Get all keys */\n    func keySet() -> [Int] {\n        buckets.compactMap { $0?.key }\n    }\n\n    /* Get all values */\n    func valueSet() -> [String] {\n        buckets.compactMap { $0?.val }\n    }\n\n    /* Print the hash table */\n    func print() {\n        for pair in pairSet() {\n            Swift.print(\"\\(pair.key) -> \\(pair.val)\")\n        }\n    }\n}\n
    array_hash_map.js
    /* Key-value pair: Number -> String */\nclass Pair {\n    constructor(key, val) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    #buckets;\n    constructor() {\n        // Initialize an array containing 100 buckets\n        this.#buckets = new Array(100).fill(null);\n    }\n\n    /* Hash function */\n    #hashFunc(key) {\n        return key % 100;\n    }\n\n    /* Lookup operation */\n    get(key) {\n        let index = this.#hashFunc(key);\n        let pair = this.#buckets[index];\n        if (pair === null) return null;\n        return pair.val;\n    }\n\n    /* Insert operation */\n    set(key, val) {\n        let index = this.#hashFunc(key);\n        this.#buckets[index] = new Pair(key, val);\n    }\n\n    /* Remove operation */\n    delete(key) {\n        let index = this.#hashFunc(key);\n        // Set the bucket to null to mark it as deleted\n        this.#buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    entries() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i]);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all keys */\n    keys() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i].key);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all values */\n    values() {\n        let arr = [];\n        for (let i = 0; i < this.#buckets.length; i++) {\n            if (this.#buckets[i]) {\n                arr.push(this.#buckets[i].val);\n            }\n        }\n        return arr;\n    }\n\n    /* Print the hash table */\n    print() {\n        let pairSet = this.entries();\n        for (const pair of pairSet) {\n            console.info(`${pair.key} -> ${pair.val}`);\n        }\n    }\n}\n
    array_hash_map.ts
    /* Key-value pair: Number -> String */\nclass Pair {\n    public key: number;\n    public val: string;\n\n    constructor(key: number, val: string) {\n        this.key = key;\n        this.val = val;\n    }\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    private readonly buckets: (Pair | null)[];\n\n    constructor() {\n        // Initialize an array containing 100 buckets\n        this.buckets = new Array(100).fill(null);\n    }\n\n    /* Hash function */\n    private hashFunc(key: number): number {\n        return key % 100;\n    }\n\n    /* Lookup operation */\n    public get(key: number): string | null {\n        let index = this.hashFunc(key);\n        let pair = this.buckets[index];\n        if (pair === null) return null;\n        return pair.val;\n    }\n\n    /* Insert operation */\n    public set(key: number, val: string) {\n        let index = this.hashFunc(key);\n        this.buckets[index] = new Pair(key, val);\n    }\n\n    /* Remove operation */\n    public delete(key: number) {\n        let index = this.hashFunc(key);\n        // Set the bucket to null to mark it as deleted\n        this.buckets[index] = null;\n    }\n\n    /* Get all key-value pairs */\n    public entries(): (Pair | null)[] {\n        let arr: (Pair | null)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i]);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all keys */\n    public keys(): (number | undefined)[] {\n        let arr: (number | undefined)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i].key);\n            }\n        }\n        return arr;\n    }\n\n    /* Get all values */\n    public values(): (string | undefined)[] {\n        let arr: (string | undefined)[] = [];\n        for (let i = 0; i < this.buckets.length; i++) {\n            if (this.buckets[i]) {\n                arr.push(this.buckets[i].val);\n            }\n        }\n        return arr;\n    }\n\n    /* Print the hash table */\n    public print() {\n        let pairSet = this.entries();\n        for (const pair of pairSet) {\n            console.info(`${pair.key} -> ${pair.val}`);\n        }\n    }\n}\n
    array_hash_map.dart
    /* Key-value pair */\nclass Pair {\n  int key;\n  String val;\n  Pair(this.key, this.val);\n}\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n  late List<Pair?> _buckets;\n\n  ArrayHashMap() {\n    // Initialize an array containing 100 buckets\n    _buckets = List.filled(100, null);\n  }\n\n  /* Hash function */\n  int _hashFunc(int key) {\n    final int index = key % 100;\n    return index;\n  }\n\n  /* Lookup operation */\n  String? get(int key) {\n    final int index = _hashFunc(key);\n    final Pair? pair = _buckets[index];\n    if (pair == null) {\n      return null;\n    }\n    return pair.val;\n  }\n\n  /* Insert operation */\n  void put(int key, String val) {\n    final Pair pair = Pair(key, val);\n    final int index = _hashFunc(key);\n    _buckets[index] = pair;\n  }\n\n  /* Remove operation */\n  void remove(int key) {\n    final int index = _hashFunc(key);\n    _buckets[index] = null;\n  }\n\n  /* Get all key-value pairs */\n  List<Pair> pairSet() {\n    List<Pair> pairSet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        pairSet.add(pair);\n      }\n    }\n    return pairSet;\n  }\n\n  /* Get all keys */\n  List<int> keySet() {\n    List<int> keySet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        keySet.add(pair.key);\n      }\n    }\n    return keySet;\n  }\n\n  /* Get all values */\n  List<String> values() {\n    List<String> valueSet = [];\n    for (final Pair? pair in _buckets) {\n      if (pair != null) {\n        valueSet.add(pair.val);\n      }\n    }\n    return valueSet;\n  }\n\n  /* Print the hash table */\n  void printHashMap() {\n    for (final Pair kv in pairSet()) {\n      print(\"${kv.key} -> ${kv.val}\");\n    }\n  }\n}\n
    array_hash_map.rs
    /* Key-value pair */\n#[derive(Debug, Clone, PartialEq)]\npub struct Pair {\n    pub key: i32,\n    pub val: String,\n}\n\n/* Hash table implemented with an array */\npub struct ArrayHashMap {\n    buckets: Vec<Option<Pair>>,\n}\n\nimpl ArrayHashMap {\n    pub fn new() -> ArrayHashMap {\n        // Initialize an array containing 100 buckets\n        Self {\n            buckets: vec![None; 100],\n        }\n    }\n\n    /* Hash function */\n    fn hash_func(&self, key: i32) -> usize {\n        key as usize % 100\n    }\n\n    /* Lookup operation */\n    pub fn get(&self, key: i32) -> Option<&String> {\n        let index = self.hash_func(key);\n        self.buckets[index].as_ref().map(|pair| &pair.val)\n    }\n\n    /* Insert operation */\n    pub fn put(&mut self, key: i32, val: &str) {\n        let index = self.hash_func(key);\n        self.buckets[index] = Some(Pair {\n            key,\n            val: val.to_string(),\n        });\n    }\n\n    /* Remove operation */\n    pub fn remove(&mut self, key: i32) {\n        let index = self.hash_func(key);\n        // Set to None to mark removal\n        self.buckets[index] = None;\n    }\n\n    /* Get all key-value pairs */\n    pub fn entry_set(&self) -> Vec<&Pair> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref())\n            .collect()\n    }\n\n    /* Get all keys */\n    pub fn key_set(&self) -> Vec<&i32> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref().map(|pair| &pair.key))\n            .collect()\n    }\n\n    /* Get all values */\n    pub fn value_set(&self) -> Vec<&String> {\n        self.buckets\n            .iter()\n            .filter_map(|pair| pair.as_ref().map(|pair| &pair.val))\n            .collect()\n    }\n\n    /* Print the hash table */\n    pub fn print(&self) {\n        for pair in self.entry_set() {\n            println!(\"{} -> {}\", pair.key, pair.val);\n        }\n    }\n}\n
    array_hash_map.c
    /* Key-value pair: int -> string */\ntypedef struct {\n    int key;\n    char *val;\n} Pair;\n\n/* Hash table implemented with an array */\ntypedef struct {\n    Pair *buckets[MAX_SIZE];\n} ArrayHashMap;\n\n/* Constructor */\nArrayHashMap *newArrayHashMap() {\n    ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap));\n    for (int i=0; i < MAX_SIZE; i++) {\n        hmap->buckets[i] = NULL;\n    }\n    return hmap;\n}\n\n/* Destructor */\nvoid delArrayHashMap(ArrayHashMap *hmap) {\n    for (int i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            free(hmap->buckets[i]->val);\n            free(hmap->buckets[i]);\n        }\n    }\n    free(hmap);\n}\n\n/* Insert operation */\nvoid put(ArrayHashMap *hmap, const int key, const char *val) {\n    Pair *Pair = malloc(sizeof(Pair));\n    Pair->key = key;\n    Pair->val = malloc(strlen(val) + 1);\n    strcpy(Pair->val, val);\n\n    int index = hashFunc(key);\n    hmap->buckets[index] = Pair;\n}\n\n/* Remove operation */\nvoid removeItem(ArrayHashMap *hmap, const int key) {\n    int index = hashFunc(key);\n    free(hmap->buckets[index]->val);\n    free(hmap->buckets[index]);\n    hmap->buckets[index] = NULL;\n}\n\n/* Get all key-value pairs */\nvoid pairSet(ArrayHashMap *hmap, MapSet *set) {\n    Pair *entries;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count stored key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    entries = malloc(sizeof(Pair) * total);\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            entries[index].key = hmap->buckets[i]->key;\n            entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1);\n            strcpy(entries[index].val, hmap->buckets[i]->val);\n            index++;\n        }\n    }\n    set->set = entries;\n    set->len = total;\n}\n\n/* Get all keys */\nvoid keySet(ArrayHashMap *hmap, MapSet *set) {\n    int *keys;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count stored key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    keys = malloc(total * sizeof(int));\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            keys[index] = hmap->buckets[i]->key;\n            index++;\n        }\n    }\n    set->set = keys;\n    set->len = total;\n}\n\n/* Get all values */\nvoid valueSet(ArrayHashMap *hmap, MapSet *set) {\n    char **vals;\n    int i = 0, index = 0;\n    int total = 0;\n    /* Count stored key-value pairs */\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            total++;\n        }\n    }\n    vals = malloc(total * sizeof(char *));\n    for (i = 0; i < MAX_SIZE; i++) {\n        if (hmap->buckets[i] != NULL) {\n            vals[index] = hmap->buckets[i]->val;\n            index++;\n        }\n    }\n    set->set = vals;\n    set->len = total;\n}\n\n/* Print the hash table */\nvoid print(ArrayHashMap *hmap) {\n    int i;\n    MapSet set;\n    pairSet(hmap, &set);\n    Pair *entries = (Pair *)set.set;\n    for (i = 0; i < set.len; i++) {\n        printf(\"%d -> %s\\n\", entries[i].key, entries[i].val);\n    }\n    free(set.set);\n}\n
    array_hash_map.kt
    /* Key-value pair */\nclass Pair(\n    var key: Int,\n    var _val: String\n)\n\n/* Hash table implemented with an array */\nclass ArrayHashMap {\n    // Initialize an array containing 100 buckets\n    private val buckets = arrayOfNulls<Pair>(100)\n\n    /* Hash function */\n    fun hashFunc(key: Int): Int {\n        val index = key % 100\n        return index\n    }\n\n    /* Lookup operation */\n    fun get(key: Int): String? {\n        val index = hashFunc(key)\n        val pair = buckets[index] ?: return null\n        return pair._val\n    }\n\n    /* Insert operation */\n    fun put(key: Int, _val: String) {\n        val pair = Pair(key, _val)\n        val index = hashFunc(key)\n        buckets[index] = pair\n    }\n\n    /* Remove operation */\n    fun remove(key: Int) {\n        val index = hashFunc(key)\n        // Set the bucket to null to mark it as removed\n        buckets[index] = null\n    }\n\n    /* Get all key-value pairs */\n    fun pairSet(): MutableList<Pair> {\n        val pairSet = mutableListOf<Pair>()\n        for (pair in buckets) {\n            if (pair != null)\n                pairSet.add(pair)\n        }\n        return pairSet\n    }\n\n    /* Get all keys */\n    fun keySet(): MutableList<Int> {\n        val keySet = mutableListOf<Int>()\n        for (pair in buckets) {\n            if (pair != null)\n                keySet.add(pair.key)\n        }\n        return keySet\n    }\n\n    /* Get all values */\n    fun valueSet(): MutableList<String> {\n        val valueSet = mutableListOf<String>()\n        for (pair in buckets) {\n            if (pair != null)\n                valueSet.add(pair._val)\n        }\n        return valueSet\n    }\n\n    /* Print the hash table */\n    fun print() {\n        for (kv in pairSet()) {\n            val key = kv.key\n            val _val = kv._val\n            println(\"$key -> $_val\")\n        }\n    }\n}\n
    array_hash_map.rb
    ### Key-value pair ###\nclass Pair\n  attr_accessor :key, :val\n\n  def initialize(key, val)\n    @key = key\n    @val = val\n  end\nend\n\n### Hash table implemented with an array ###\nclass ArrayHashMap\n  ### Constructor ###\n  def initialize\n    # Initialize an array containing 100 buckets\n    @buckets = Array.new(100)\n  end\n\n  ### Hash function ###\n  def hash_func(key)\n    index = key % 100\n  end\n\n  ### Lookup operation ###\n  def get(key)\n    index = hash_func(key)\n    pair = @buckets[index]\n\n    return if pair.nil?\n    pair.val\n  end\n\n  ### Insert operation ###\n  def put(key, val)\n    pair = Pair.new(key, val)\n    index = hash_func(key)\n    @buckets[index] = pair\n  end\n\n  ### Remove operation ###\n  def remove(key)\n    index = hash_func(key)\n    # Set the bucket to nil to indicate removal\n    @buckets[index] = nil\n  end\n\n  ### Get all key-value pairs ###\n  def entry_set\n    result = []\n    @buckets.each { |pair| result << pair unless pair.nil? }\n    result\n  end\n\n  ### Get all keys ###\n  def key_set\n    result = []\n    @buckets.each { |pair| result << pair.key unless pair.nil? }\n    result\n  end\n\n  ### Get all values ###\n  def value_set\n    result = []\n    @buckets.each { |pair| result << pair.val unless pair.nil? }\n    result\n  end\n\n  ### Print the hash table ###\n  def print\n    @buckets.each { |pair| puts \"#{pair.key} -> #{pair.val}\" unless pair.nil? }\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 6.   Hash tables","6.1   Hash tables"],"tags":[]},{"location":"chapter_hashing/hash_map/#613-collisions-and-expansion","level":2,"title":"6.1.3   Collisions and expansion","text":"

    A hash function maps the input space of all possible keys into the output space of array indices. The input space is usually far larger, so multiple inputs must theoretically map to the same output.

    For the function above, keys with the same last two digits produce the same output. Looking up student IDs 12836 and 20336 gives:

    12836 % 100 = 36\n20336 % 100 = 36\n

    As shown below, both IDs point to the same name, which is clearly incorrect. This situation, where different inputs produce the same output, is called a hash collision.

    Figure 6-3   An example of a hash collision

    A larger table capacity \\(n\\) makes it less likely that several keys share a bucket, reducing collisions. Expanding the hash table can therefore reduce hash collisions.

    In the example below, (136, A) and (236, D) collide before expansion but no longer collide afterward.

    Figure 6-4   Expanding a hash table

    As with array expansion, expanding a hash table requires moving every pair into a new table, which is time-consuming. Because capacity changes, the hash function must also recalculate every pair's position, adding further computation. Programming languages therefore usually reserve enough capacity to avoid frequent expansion.

    The load factor is the number of elements divided by the number of buckets. It measures how crowded the table is and often serves as the trigger for expansion. For example, in Java, a load factor above \\(0.75\\) causes the capacity to grow to \\(2\\) times its previous size.

    ","path":["Chapter 6.   Hash tables","6.1   Hash tables"],"tags":[]},{"location":"chapter_hashing/summary/","level":1,"title":"6.4   Summary","text":"","path":["Chapter 6.   Hash tables","6.4   Summary"],"tags":[]},{"location":"chapter_hashing/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Given a key, a hash table can retrieve its value in \\(O(1)\\) time, making lookup highly efficient.
    • Common operations include lookup, adding and deleting key-value pairs, and traversing the table.
    • A hash function maps a key to an array index, allowing access to the corresponding bucket and its value.
    • Two different keys may map to the same array index and cause incorrect lookup results. This is called a hash collision.
    • A larger capacity lowers the likelihood of collisions, so expanding a hash table can alleviate them. Like array expansion, hash-table expansion is expensive.
    • The load factor is the number of elements divided by the number of buckets. It indicates how crowded the table is and is commonly used to trigger expansion.
    • Separate chaining replaces a single entry with a linked list and stores all colliding entries in that list. Long lists slow lookup; converting them to red-black trees can improve efficiency.
    • Open addressing resolves collisions through repeated probing. Linear probing uses a fixed step size, is prone to clustering, and does not allow straightforward deletion. Multiple hashing uses several hash functions, reducing clustering but adding computational cost.
    • Programming languages use different implementations: Java's HashMap uses separate chaining, while Python's dict uses open addressing.
    • For hash tables, we want deterministic, efficient hashing with uniformly distributed outputs. Cryptographic hashing also requires collision resistance and an avalanche effect.
    • Hash algorithms often use a large prime modulus to promote uniform hash values and reduce collisions.
    • Common hash algorithms include MD5, SHA-1, SHA-2, and SHA-3. MD5 is often used for file-integrity checks, while SHA-2 is widely used in security applications and protocols.
    • Programming languages usually provide built-in hashing for data types to calculate bucket indices. In general, only immutable objects are hashable.
    ","path":["Chapter 6.   Hash tables","6.4   Summary"],"tags":[]},{"location":"chapter_hashing/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: When does a hash table have \\(O(n)\\) time complexity?

    When collisions are severe, its time complexity degrades to \\(O(n)\\). With a well-designed hash function, suitable capacity, and evenly distributed collisions, it is \\(O(1)\\). We usually assume \\(O(1)\\) time when using a language's built-in hash table.

    Q: Why not use the hash function \\(f(x) = x\\)? Wouldn't that avoid collisions?

    With \\(f(x) = x\\), each element has a unique bucket index, making the structure equivalent to an array. But the input space is usually far larger than the output space, the array's length, so the final hashing step usually takes a remainder modulo that length. A hash table maps a larger state space into a smaller one while providing \\(O(1)\\) lookup.

    Q: Hash tables are implemented with arrays, linked lists, and binary trees. How can they be faster than those structures?

    First, greater time efficiency comes at the cost of space efficiency: a significant portion of a hash table's memory is unused.

    Second, the improvement applies only to particular operations. If an array or linked list can perform an operation with the same time complexity, it is usually faster because computing a hash adds overhead and increases the constant factor.

    Finally, hash-table performance can degrade. For example, separate chaining searches within linked lists or red-black trees, and there is still a risk of degeneration to \\(O(n)\\) time.

    Q: Does multiple hashing also prevent direct deletion? Can a deleted slot be reused?

    Multiple hashing is a form of open addressing, and all open-addressing methods require deletion markers rather than simply emptying a slot. A marked slot can be reused. When inserting a new element, if the hash functions locate such a slot, the new element can occupy it. This preserves the probe sequence while making effective use of space.

    Q: Why can a collision occur during a lookup with linear probing?

    The hash function leads to a bucket and a key-value pair, but the stored key may not match the requested one. That indicates a collision. Linear probing then continues with its predefined step size until it finds the correct pair or determines that none exists.

    Q: Why does expanding a hash table reduce collisions?

    The final hashing step often takes the remainder modulo the array length \\(n\\) to produce a valid index. After expansion, \\(n\\) changes, so the index of a given key may change too. Keys that previously shared a bucket may be redistributed among several buckets, reducing collisions.

    Q: If fast access is the goal, why not just use an array?

    When keys are consecutive integers in a small range, an array is simple and efficient. But for other key types, such as strings, we need a hash function to map keys to array indices and a bucket array to store entries. That structure is a hash table.

    ","path":["Chapter 6.   Hash tables","6.4   Summary"],"tags":[]},{"location":"chapter_heap/","level":1,"title":"Chapter 8.   Heaps","text":"

    Abstract

    Heaps resemble mountain ranges, rising in layers of varied shapes.

    The peaks stand at different heights, yet the highest always catches the eye first.

    ","path":["Chapter 8.   Heaps"],"tags":[]},{"location":"chapter_heap/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 8.1   Heaps
    • 8.2   Building a heap
    • 8.3   The top-k problem
    • 8.4   Summary
    • 8.5   Exercises
    ","path":["Chapter 8.   Heaps"],"tags":[]},{"location":"chapter_heap/build_heap/","level":1,"title":"8.2   Building a heap","text":"

    Sometimes we want to construct a heap from all the elements of a list. This process is called “building a heap.”

    ","path":["Chapter 8.   Heaps","8.2   Building a heap"],"tags":[]},{"location":"chapter_heap/build_heap/#821-building-through-insertion","level":2,"title":"8.2.1   Building through insertion","text":"

    First create an empty heap, then traverse the list and insert each element: append it to the end of the heap, then sift it upward.

    Each insertion increases the heap's length by one. Since nodes are added to the binary tree from the top down, this constructs the heap “top down.”

    For \\(n\\) elements, each insertion takes \\(O(\\log{n})\\) time, so this method takes \\(O(n \\log n)\\) overall.

    ","path":["Chapter 8.   Heaps","8.2   Building a heap"],"tags":[]},{"location":"chapter_heap/build_heap/#822-building-by-sifting-nodes-down","level":2,"title":"8.2.2   Building by sifting nodes down","text":"

    We can build a heap more efficiently in two steps.

    1. Copy all list elements into the heap without changing their order. The heap property may not yet hold.
    2. Traverse the heap in reverse level order, sifting down each non-leaf node.

    After a node is sifted down, the subtree rooted at that node is a valid heap. Since the traversal is reversed, the heap is built “bottom up.”

    Reverse traversal ensures that the subtrees below the current node are already valid heaps, which is necessary for sifting down the current node to work correctly.

    Leaf nodes have no children and are already valid heaps, so they need no sifting. As the code below shows, the last non-leaf node is the parent of the last node. Start there and traverse backward, sifting each node down:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def __init__(self, nums: list[int]):\n    \"\"\"Constructor: build a heap from the input list\"\"\"\n    # Place the list elements in the heap without changing their order\n    self.max_heap = nums\n    # Sift down from every non-leaf node\n    for i in range(self.parent(self.size() - 1), -1, -1):\n        self.sift_down(i)\n
    my_heap.cpp
    /* Constructor: build a heap from the input list */\nMaxHeap(vector<int> nums) {\n    // Place the list elements in the heap without changing their order\n    maxHeap = nums;\n    // Sift down from every non-leaf node\n    for (int i = parent(size() - 1); i >= 0; i--) {\n        siftDown(i);\n    }\n}\n
    my_heap.java
    /* Constructor: build a heap from the input list */\nMaxHeap(List<Integer> nums) {\n    // Place the list elements in the heap without changing their order\n    maxHeap = new ArrayList<>(nums);\n    // Sift down from every non-leaf node\n    for (int i = parent(size() - 1); i >= 0; i--) {\n        siftDown(i);\n    }\n}\n
    my_heap.cs
    /* Constructor: build a heap from an input list */\nMaxHeap(IEnumerable<int> nums) {\n    // Place the list elements in the heap without changing their order\n    maxHeap = new List<int>(nums);\n    // Sift down from every non-leaf node\n    var size = Parent(this.Size() - 1);\n    for (int i = size; i >= 0; i--) {\n        SiftDown(i);\n    }\n}\n
    my_heap.go
    /* Constructor: build a heap from a slice */\nfunc newMaxHeap(nums []any) *maxHeap {\n    // Place the list elements in the heap without changing their order\n    h := &maxHeap{data: nums}\n    for i := h.parent(len(h.data) - 1); i >= 0; i-- {\n        // Sift down from every non-leaf node\n        h.siftDown(i)\n    }\n    return h\n}\n
    my_heap.swift
    /* Constructor: build a heap from the input list */\ninit(nums: [Int]) {\n    // Place the list elements in the heap without changing their order\n    maxHeap = nums\n    // Sift down from every non-leaf node\n    for i in (0 ... parent(i: size() - 1)).reversed() {\n        siftDown(i: i)\n    }\n}\n
    my_heap.js
    /* Constructor: create an empty heap or build a heap from the input list */\nconstructor(nums) {\n    // Place the list elements in the heap without changing their order\n    this.#maxHeap = nums === undefined ? [] : [...nums];\n    // Sift down from every non-leaf node\n    for (let i = this.#parent(this.size() - 1); i >= 0; i--) {\n        this.#siftDown(i);\n    }\n}\n
    my_heap.ts
    /* Constructor: create an empty heap or build a heap from the input list */\nconstructor(nums?: number[]) {\n    // Place the list elements in the heap without changing their order\n    this.maxHeap = nums === undefined ? [] : [...nums];\n    // Sift down from every non-leaf node\n    for (let i = this.parent(this.size() - 1); i >= 0; i--) {\n        this.siftDown(i);\n    }\n}\n
    my_heap.dart
    /* Constructor: build a heap from the input list */\nMaxHeap(List<int> nums) {\n  // Place the list elements in the heap without changing their order\n  _maxHeap = nums;\n  // Sift down from every non-leaf node\n  for (int i = _parent(size() - 1); i >= 0; i--) {\n    siftDown(i);\n  }\n}\n
    my_heap.rs
    /* Constructor: build a heap from the input list */\nfn new(nums: Vec<i32>) -> Self {\n    // Place the list elements in the heap without changing their order\n    let mut heap = MaxHeap { max_heap: nums };\n    // Sift down from every non-leaf node\n    for i in (0..=Self::parent(heap.size() - 1)).rev() {\n        heap.sift_down(i);\n    }\n    heap\n}\n
    my_heap.c
    /* Constructor: build a heap from the input array */\nMaxHeap *newMaxHeap(int nums[], int size) {\n    // Place all elements in the heap\n    MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap));\n    maxHeap->size = size;\n    memcpy(maxHeap->data, nums, size * sizeof(int));\n    for (int i = parent(maxHeap, size - 1); i >= 0; i--) {\n        // Sift down from every non-leaf node\n        siftDown(maxHeap, i);\n    }\n    return maxHeap;\n}\n
    my_heap.kt
    /* Max heap */\nclass MaxHeap(nums: MutableList<Int>?) {\n    // Use a resizable list instead of an array to avoid managing capacity growth\n    private val maxHeap = mutableListOf<Int>()\n\n    /* Constructor: build a heap from the input list */\n    init {\n        // Place the list elements in the heap without changing their order\n        maxHeap.addAll(nums!!)\n        // Sift down from every non-leaf node\n        for (i in parent(size() - 1) downTo 0) {\n            siftDown(i)\n        }\n    }\n\n    /* Get the left child index */\n    private fun left(i: Int): Int {\n        return 2 * i + 1\n    }\n\n    /* Get the right child index */\n    private fun right(i: Int): Int {\n        return 2 * i + 2\n    }\n\n    /* Get the parent index */\n    private fun parent(i: Int): Int {\n        return (i - 1) / 2 // Integer division (truncates toward zero in Kotlin)\n    }\n\n    /* Swap elements */\n    private fun swap(i: Int, j: Int) {\n        val temp = maxHeap[i]\n        maxHeap[i] = maxHeap[j]\n        maxHeap[j] = temp\n    }\n\n    /* Get the heap size */\n    fun size(): Int {\n        return maxHeap.size\n    }\n\n    /* Check whether the heap is empty */\n    fun isEmpty(): Boolean {\n        /* Check whether the heap is empty */\n        return size() == 0\n    }\n\n    /* Access the top element of the heap */\n    fun peek(): Int {\n        return maxHeap[0]\n    }\n\n    /* Push an element onto the heap */\n    fun push(_val: Int) {\n        // Add a node\n        maxHeap.add(_val)\n        // Sift up\n        siftUp(size() - 1)\n    }\n\n    /* Sift up starting from node i */\n    private fun siftUp(it: Int) {\n        // Kotlin function parameters are immutable, so create a temporary variable\n        var i = it\n        while (true) {\n            // Get the parent of node i\n            val p = parent(i)\n            // Stop sifting when the root has been passed or the node needs no repair\n            if (p < 0 || maxHeap[i] <= maxHeap[p]) break\n            // Swap the two nodes\n            swap(i, p)\n            // Continue sifting up\n            i = p\n        }\n    }\n\n    /* Pop an element from the heap */\n    fun pop(): Int {\n        // Handle an empty heap\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n        swap(0, size() - 1)\n        // Remove a node\n        val _val = maxHeap.removeAt(size() - 1)\n        // Sift down\n        siftDown(0)\n        // Return the top element of the heap\n        return _val\n    }\n\n    /* Sift down starting from node i */\n    private fun siftDown(it: Int) {\n        // Kotlin function parameters are immutable, so create a temporary variable\n        var i = it\n        while (true) {\n            // Find the node with the largest value among i, l, and r, and store its index in ma\n            val l = left(i)\n            val r = right(i)\n            var ma = i\n            if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l\n            if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r\n            // Stop if node i is largest or both child indices l and r are out of bounds\n            if (ma == i) break\n            // Swap the two nodes\n            swap(i, ma)\n            // Continue sifting down\n            i = ma\n        }\n    }\n\n    /* Print the heap (binary tree) */\n    fun print() {\n        val queue = PriorityQueue { a: Int, b: Int -> b - a }\n        queue.addAll(maxHeap)\n        printHeap(queue)\n    }\n}\n
    my_heap.rb
    ### Constructor: build a heap from the input list ###\ndef initialize(nums)\n  # Place the list elements in the heap without changing their order\n  @max_heap = nums\n  # Sift down from every non-leaf node\n  parent(size - 1).downto(0) do |i|\n    sift_down(i)\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.2   Building a heap"],"tags":[]},{"location":"chapter_heap/build_heap/#823-complexity-analysis","level":2,"title":"8.2.3   Complexity analysis","text":"

    Let us derive the time complexity of the second method.

    • A complete binary tree with \\(n\\) nodes has \\((n + 1) / 2\\) leaves, where \\(/\\) denotes integer division rounded down. Therefore, \\(n / 2\\) nodes need sifting.
    • A node can move at most as far as a leaf during sifting, so the maximum number of iterations is the tree height, \\(\\log n\\).

    Multiplying these gives a time complexity of \\(O(n \\log n)\\). However, this estimate is not tight, because it ignores the fact that there are far more nodes near the bottom of the tree than near the top.

    For a more accurate calculation, assume a perfect binary tree with \\(n\\) nodes and height \\(h\\). This simplifies the calculation without affecting the validity of the result.

    Figure 8-5   Number of nodes at each level of a perfect binary tree

    As shown above, the maximum number of sift-down iterations for a node equals its distance to a leaf, which is its height. Summing “number of nodes \\(\\times\\) node height” over all levels therefore gives the total number of sifting iterations across all nodes.

    \\[ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{(h-1)}\\times1 \\]

    To simplify this expression, use a standard series technique. First multiply \\(T(h)\\) by \\(2\\):

    \\[ \\begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{h-1}\\times1 \\newline 2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \\dots + 2^{h}\\times1 \\newline \\end{aligned} \\]

    Subtract the first expression, \\(T(h)\\), from the shifted expression, \\(2 T(h)\\):

    \\[ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \\dots + 2^{h-1} + 2^h \\]

    The powers of two in \\(T(h)\\) form a geometric series. Applying the sum formula gives:

    \\[ \\begin{aligned} T(h) & = 2 \\frac{1 - 2^h}{1 - 2} - h \\newline & = 2^{h+1} - h - 2 \\newline & = O(2^h) \\end{aligned} \\]

    A perfect binary tree of height \\(h\\) has \\(n = 2^{h+1} - 1\\) nodes, so \\(O(2^h) = O(n)\\). This shows that building a heap from an input list takes \\(O(n)\\) time and is highly efficient.

    ","path":["Chapter 8.   Heaps","8.2   Building a heap"],"tags":[]},{"location":"chapter_heap/exercises/","level":1,"title":"8.5   Exercises","text":"","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#851-check-your-understanding","level":2,"title":"8.5.1   Check your understanding","text":"","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#1-restoring-the-heap-after-inserting-10","level":3,"title":"1.   Restoring the heap after inserting 10","text":"

    The array [9, 7, 8, 3, 5] represents a max heap. Insert the number 10.

    1. After appending 10 to the array, what is its parent's value?
    2. Sift up from the new node. Write the array after each swap.
    3. What is the final root element? How many swaps were made?
    Reference answer
    1. The appended 10 has index 5, so its parent's index is \\(\\lfloor(5-1)/2\\rfloor=2\\), and its parent's value is 8.

    2. Since 10 is greater than 8, the first swap produces [9, 7, 10, 3, 5, 8]. Since 10 is also greater than its new parent, 9, the second swap produces [10, 7, 9, 3, 5, 8]. The value 10 has now reached the root, so sifting stops.

    3. The final root is 10, and there were two swaps.

    ","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#2-checking-parent-child-relationships-in-a-min-heap","level":3,"title":"2.   Checking parent-child relationships in a min heap","text":"

    The array [1, 4, 3, 7, 6, 2] represents a complete binary tree. A min heap requires each parent to be no greater than its children. For index \\(i\\), the left and right child indices are \\(2i+1\\) and \\(2i+2\\), respectively.

    1. What are the indices and values of the children of index 2?
    2. The node at index 2 has value 3. Does its relationship with its children violate the min-heap property? If so, which two elements should be swapped?
    3. Based on your answer to question 2, write the array after the swap if one is needed, or explain why no swap is necessary. Finally, check the other parent-child relationships.
    Reference answer
    1. The left child of index 2 is at index 5 and has value 2. Its right child would be at index 6, but the array has length 6, so that child does not exist.

    2. The parent value 3 exceeds the child value 2, violating the min-heap property. Swap the elements at indices 2 and 5.

    3. The resulting array is [1, 4, 2, 7, 6, 3]. Checking each relationship gives: 1 ≤ 4, 1 ≤ 2; 4 ≤ 7, 4 ≤ 6; 2 ≤ 3. Every parent is no greater than its children, so the min-heap property now holds.

    ","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#3-keeping-the-three-largest-numbers-with-a-min-heap","level":3,"title":"3.   Keeping the three largest numbers with a min heap","text":"

    To keep the three largest numbers from the stream [4, 1, 7, 3, 8], maintain a min heap of at most three elements.

    Insert the first three numbers in order. Once the heap is full, for each new number: if it is greater than the root, remove the root and insert the new number; otherwise, leave the heap unchanged.

    After reading each number, write the numbers retained in the heap and the root value. You need only give the retained numbers as a set, not their arrangement in the heap array.

    Reference answer

    The results after each number are:

    Number read Retained numbers Root 4 {4} 4 1 {1, 4} 1 7 {1, 4, 7} 1 3 {3, 4, 7} 3 8 {4, 7, 8} 4

    Once the heap is full, its root is the smallest retained number. Replace the root with the new number only when the new number is larger. The final set {4, 7, 8} contains exactly the three largest numbers.

    ","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#852-programming-exercises","level":2,"title":"8.5.2   Programming exercises","text":"","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/exercises/#1-finding-the-kth-largest-element-in-an-array","level":3,"title":"1.   Finding the kth largest element in an array","text":"

    Given an integer array nums and an integer \\(k\\), where \\(1 \\le k \\le n\\) and \\(n\\) is the array length, return the element in position \\(k\\) when the array is ordered from largest to smallest.

    Count duplicate elements separately. For example, the second largest element of [5, 5, 2] is still 5. Use a min heap of at most \\(k\\) elements.

    Hints
    1. The kth largest element is the smallest of the k largest numbers.
    2. Insert each number into the min heap; if its size exceeds k, remove the minimum.
    3. When the traversal ends, the heap retains the k largest numbers, and its root is the answer.

    LeetCode

    ","path":["Chapter 8.   Heaps","8.5   Exercises"],"tags":[]},{"location":"chapter_heap/heap/","level":1,"title":"8.1   Heaps","text":"

    A heap is a complete binary tree that satisfies a particular ordering condition. There are two main types, shown below.

    • Min heap: every node's value is \\(\\leq\\) the values of its children.
    • Max heap: every node's value is \\(\\geq\\) the values of its children.

    Figure 8-1   A min heap and a max heap

    As a special kind of complete binary tree, a heap has the following properties.

    • The lowest level is filled from left to right, and all other levels are full.
    • The root is the “top” of the heap, while the rightmost node on the lowest level is its “last” node.
    • In a max heap or min heap, the root holds the largest or smallest value, respectively.
    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#811-common-heap-operations","level":2,"title":"8.1.1   Common heap operations","text":"

    Many programming languages provide a priority queue, an abstract data structure whose elements are removed according to priority.

    Heaps are commonly used to implement priority queues. A max heap corresponds to a priority queue that removes elements from largest to smallest. From the user's perspective, the two can be treated as equivalent. This book therefore does not distinguish them strictly and uses the term “heap” for both.

    Table 8-1 lists common operations. Method names depend on the programming language.

    Table 8-1   Efficiency of heap operations

    Method Description Time complexity push() Insert an element \\(O(\\log n)\\) pop() Remove the root element \\(O(\\log n)\\) peek() Access the root: the maximum in a max heap or minimum in a min heap \\(O(1)\\) size() Get the number of elements \\(O(1)\\) isEmpty() Check whether the heap is empty \\(O(1)\\)

    In practice, use the heap or priority-queue class provided by the language.

    Just as a sorting algorithm can order values in ascending or descending order, we can switch between min heaps and max heaps by setting a flag or changing the Comparator:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap.py
    # Initialize a min heap\nmin_heap, flag = [], 1\n# Initialize a max heap\nmax_heap, flag = [], -1\n\n# Python's heapq module implements a min heap by default\n# Negate elements before insertion to reverse their order and implement a max heap\n# Here, flag = 1 gives a min heap and flag = -1 gives a max heap\n\n# Insert elements into the heap\nheapq.heappush(max_heap, flag * 1)\nheapq.heappush(max_heap, flag * 3)\nheapq.heappush(max_heap, flag * 2)\nheapq.heappush(max_heap, flag * 5)\nheapq.heappush(max_heap, flag * 4)\n\n# Access the root element\npeek: int = flag * max_heap[0] # 5\n\n# Remove root elements\n# Removed elements form a descending sequence\nval = flag * heapq.heappop(max_heap) # 5\nval = flag * heapq.heappop(max_heap) # 4\nval = flag * heapq.heappop(max_heap) # 3\nval = flag * heapq.heappop(max_heap) # 2\nval = flag * heapq.heappop(max_heap) # 1\n\n# Get the heap size\nsize: int = len(max_heap)\n\n# Check whether the heap is empty\nis_empty: bool = not max_heap\n\n# Build a heap from a list\nmin_heap: list[int] = [1, 3, 2, 5, 4]\nheapq.heapify(min_heap)\n
    heap.cpp
    /* Initialize a heap */\n// Initialize a min heap\npriority_queue<int, vector<int>, greater<int>> minHeap;\n// Initialize a max heap\npriority_queue<int, vector<int>, less<int>> maxHeap;\n\n/* Insert elements into the heap */\nmaxHeap.push(1);\nmaxHeap.push(3);\nmaxHeap.push(2);\nmaxHeap.push(5);\nmaxHeap.push(4);\n\n/* Access the root element */\nint peek = maxHeap.top(); // 5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\nmaxHeap.pop(); // 5\nmaxHeap.pop(); // 4\nmaxHeap.pop(); // 3\nmaxHeap.pop(); // 2\nmaxHeap.pop(); // 1\n\n/* Get the heap size */\nint size = maxHeap.size();\n\n/* Check whether the heap is empty */\nbool isEmpty = maxHeap.empty();\n\n/* Build a heap from a list */\nvector<int> input{1, 3, 2, 5, 4};\npriority_queue<int, vector<int>, greater<int>> minHeap(input.begin(), input.end());\n
    heap.java
    /* Initialize a heap */\n// Initialize a min heap\nQueue<Integer> minHeap = new PriorityQueue<>();\n// Initialize a max heap by changing the Comparator with a lambda expression\nQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);\n\n/* Insert elements into the heap */\nmaxHeap.offer(1);\nmaxHeap.offer(3);\nmaxHeap.offer(2);\nmaxHeap.offer(5);\nmaxHeap.offer(4);\n\n/* Access the root element */\nint peek = maxHeap.peek(); // 5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\npeek = maxHeap.poll(); // 5\npeek = maxHeap.poll(); // 4\npeek = maxHeap.poll(); // 3\npeek = maxHeap.poll(); // 2\npeek = maxHeap.poll(); // 1\n\n/* Get the heap size */\nint size = maxHeap.size();\n\n/* Check whether the heap is empty */\nboolean isEmpty = maxHeap.isEmpty();\n\n/* Build a heap from a list */\nminHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));\n
    heap.cs
    /* Initialize a heap */\n// Initialize a min heap\nPriorityQueue<int, int> minHeap = new();\n// Initialize a max heap by changing the Comparer with a lambda expression\nPriorityQueue<int, int> maxHeap = new(Comparer<int>.Create((x, y) => y.CompareTo(x)));\n\n/* Insert elements into the heap */\nmaxHeap.Enqueue(1, 1);\nmaxHeap.Enqueue(3, 3);\nmaxHeap.Enqueue(2, 2);\nmaxHeap.Enqueue(5, 5);\nmaxHeap.Enqueue(4, 4);\n\n/* Access the root element */\nint peek = maxHeap.Peek();//5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\npeek = maxHeap.Dequeue();  // 5\npeek = maxHeap.Dequeue();  // 4\npeek = maxHeap.Dequeue();  // 3\npeek = maxHeap.Dequeue();  // 2\npeek = maxHeap.Dequeue();  // 1\n\n/* Get the heap size */\nint size = maxHeap.Count;\n\n/* Check whether the heap is empty */\nbool isEmpty = maxHeap.Count == 0;\n\n/* Build a heap from a list */\nminHeap = new PriorityQueue<int, int>([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]);\n
    heap.go
    // In Go, implement heap.Interface to build an integer max heap\n// Implementing heap.Interface also requires implementing sort.Interface\ntype intHeap []any\n\n// Push implements the heap.Interface method for inserting an element\nfunc (h *intHeap) Push(x any) {\n    // Push and Pop use pointer receivers\n    // because they change the slice length as well as its contents.\n    *h = append(*h, x.(int))\n}\n\n// Pop implements the heap.Interface method used to remove the root element\nfunc (h *intHeap) Pop() any {\n    // The element to remove has been placed at the end\n    last := (*h)[len(*h)-1]\n    *h = (*h)[:len(*h)-1]\n    return last\n}\n\n// Len implements a sort.Interface method\nfunc (h *intHeap) Len() int {\n    return len(*h)\n}\n\n// Less implements a sort.Interface method\nfunc (h *intHeap) Less(i, j int) bool {\n    // For a min heap, change this to a less-than comparison\n    return (*h)[i].(int) > (*h)[j].(int)\n}\n\n// Swap implements a sort.Interface method\nfunc (h *intHeap) Swap(i, j int) {\n    (*h)[i], (*h)[j] = (*h)[j], (*h)[i]\n}\n\n// Top returns the root element\nfunc (h *intHeap) Top() any {\n    return (*h)[0]\n}\n\n/* Driver Code */\nfunc TestHeap(t *testing.T) {\n    /* Initialize a heap */\n    // Initialize a max heap\n    maxHeap := &intHeap{}\n    heap.Init(maxHeap)\n    /* Insert elements into the heap */\n    // Call the heap.Interface methods to add elements\n    heap.Push(maxHeap, 1)\n    heap.Push(maxHeap, 3)\n    heap.Push(maxHeap, 2)\n    heap.Push(maxHeap, 4)\n    heap.Push(maxHeap, 5)\n\n    /* Access the root element */\n    top := maxHeap.Top()\n    fmt.Printf(\"The root element is %d\\n\", top)\n\n    /* Remove root elements */\n    // Call the heap.Interface methods to remove elements\n    heap.Pop(maxHeap) // 5\n    heap.Pop(maxHeap) // 4\n    heap.Pop(maxHeap) // 3\n    heap.Pop(maxHeap) // 2\n    heap.Pop(maxHeap) // 1\n\n    /* Get the heap size */\n    size := len(*maxHeap)\n    fmt.Printf(\"The heap contains %d elements\\n\", size)\n\n    /* Check whether the heap is empty */\n    isEmpty := len(*maxHeap) == 0\n    fmt.Printf(\"Is the heap empty: %t\\n\", isEmpty)\n}\n
    heap.swift
    /* Initialize a heap */\n// Swift's Heap type supports both minimum and maximum operations and requires swift-collections\nvar heap = Heap<Int>()\n\n/* Insert elements into the heap */\nheap.insert(1)\nheap.insert(3)\nheap.insert(2)\nheap.insert(5)\nheap.insert(4)\n\n/* Access the root element */\nvar peek = heap.max()!\n\n/* Remove root elements */\npeek = heap.removeMax() // 5\npeek = heap.removeMax() // 4\npeek = heap.removeMax() // 3\npeek = heap.removeMax() // 2\npeek = heap.removeMax() // 1\n\n/* Get the heap size */\nlet size = heap.count\n\n/* Check whether the heap is empty */\nlet isEmpty = heap.isEmpty\n\n/* Build a heap from a list */\nlet heap2 = Heap([1, 3, 2, 5, 4])\n
    heap.js
    // JavaScript provides no built-in Heap class\n
    heap.ts
    // TypeScript provides no built-in Heap class\n
    heap.dart
    // Dart provides no built-in Heap class\n
    heap.rs
    use std::collections::BinaryHeap;\nuse std::cmp::Reverse;\n\n/* Initialize a heap */\n// Initialize a min heap\nlet mut min_heap = BinaryHeap::<Reverse<i32>>::new();\n// Initialize a max heap\nlet mut max_heap = BinaryHeap::new();\n\n/* Insert elements into the heap */\nmax_heap.push(1);\nmax_heap.push(3);\nmax_heap.push(2);\nmax_heap.push(5);\nmax_heap.push(4);\n\n/* Access the root element */\nlet peek = max_heap.peek().unwrap();  // 5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\nlet peek = max_heap.pop().unwrap();   // 5\nlet peek = max_heap.pop().unwrap();   // 4\nlet peek = max_heap.pop().unwrap();   // 3\nlet peek = max_heap.pop().unwrap();   // 2\nlet peek = max_heap.pop().unwrap();   // 1\n\n/* Get the heap size */\nlet size = max_heap.len();\n\n/* Check whether the heap is empty */\nlet is_empty = max_heap.is_empty();\n\n/* Build a heap from a list */\nlet min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]);\n
    heap.c
    // C provides no built-in Heap class\n
    heap.kt
    /* Initialize a heap */\n// Initialize a min heap\nvar minHeap = PriorityQueue<Int>()\n// Initialize a max heap by changing the Comparator with a lambda expression\nval maxHeap = PriorityQueue { a: Int, b: Int -> b - a }\n\n/* Insert elements into the heap */\nmaxHeap.offer(1)\nmaxHeap.offer(3)\nmaxHeap.offer(2)\nmaxHeap.offer(5)\nmaxHeap.offer(4)\n\n/* Access the root element */\nvar peek = maxHeap.peek() // 5\n\n/* Remove root elements */\n// Removed elements form a descending sequence\npeek = maxHeap.poll() // 5\npeek = maxHeap.poll() // 4\npeek = maxHeap.poll() // 3\npeek = maxHeap.poll() // 2\npeek = maxHeap.poll() // 1\n\n/* Get the heap size */\nval size = maxHeap.size\n\n/* Check whether the heap is empty */\nval isEmpty = maxHeap.isEmpty()\n\n/* Build a heap from a list */\nminHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))\n
    heap.rb
    # Ruby provides no built-in Heap class\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#812-implementing-a-heap","level":2,"title":"8.1.2   Implementing a heap","text":"

    The following implementation is a max heap. To turn it into a min heap, reverse every ordering comparison, for example replacing \\(\\geq\\) with \\(\\leq\\). Interested readers can try this themselves.

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#1-storage-and-representation","level":3,"title":"1.   Storage and representation","text":"

    The binary-tree chapter showed that complete binary trees are well suited to array representation. Since a heap is a complete binary tree, we will store it in an array.

    In an array representation, elements store node values and indices identify positions in the tree. Index-mapping formulas take the place of node pointers.

    As shown below, a node at index \\(i\\) has its left child at \\(2i + 1\\), its right child at \\(2i + 2\\), and its parent at \\((i - 1) / 2\\), rounded down. An out-of-bounds index represents a missing node.

    Figure 8-2   Representing and storing a heap

    Wrap the index formulas in functions for convenient use:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def left(self, i: int) -> int:\n    \"\"\"Get the left child index\"\"\"\n    return 2 * i + 1\n\ndef right(self, i: int) -> int:\n    \"\"\"Get the right child index\"\"\"\n    return 2 * i + 2\n\ndef parent(self, i: int) -> int:\n    \"\"\"Get the parent index\"\"\"\n    return (i - 1) // 2  # Floor division\n
    my_heap.cpp
    /* Get the left child index */\nint left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nint right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nint parent(int i) {\n    return (i - 1) / 2; // Integer division truncates toward zero\n}\n
    my_heap.java
    /* Get the left child index */\nint left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nint right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nint parent(int i) {\n    return (i - 1) / 2; // Integer division (truncates toward zero in Java)\n}\n
    my_heap.cs
    /* Get the left child index */\nint Left(int i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nint Right(int i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nint Parent(int i) {\n    return (i - 1) / 2; // Integer division (truncates toward zero in C#)\n}\n
    my_heap.go
    /* Get the left child index */\nfunc (h *maxHeap) left(i int) int {\n    return 2*i + 1\n}\n\n/* Get the right child index */\nfunc (h *maxHeap) right(i int) int {\n    return 2*i + 2\n}\n\n/* Get the parent index */\nfunc (h *maxHeap) parent(i int) int {\n    // Integer division (truncates toward zero in Go)\n    return (i - 1) / 2\n}\n
    my_heap.swift
    /* Get the left child index */\nfunc left(i: Int) -> Int {\n    2 * i + 1\n}\n\n/* Get the right child index */\nfunc right(i: Int) -> Int {\n    2 * i + 2\n}\n\n/* Get the parent index */\nfunc parent(i: Int) -> Int {\n    (i - 1) / 2 // Integer division truncates toward zero\n}\n
    my_heap.js
    /* Get the left child index */\n#left(i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\n#right(i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\n#parent(i) {\n    return Math.floor((i - 1) / 2); // Floor division\n}\n
    my_heap.ts
    /* Get the left child index */\nleft(i: number): number {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nright(i: number): number {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nparent(i: number): number {\n    return Math.floor((i - 1) / 2); // Floor division\n}\n
    my_heap.dart
    /* Get the left child index */\nint _left(int i) {\n  return 2 * i + 1;\n}\n\n/* Get the right child index */\nint _right(int i) {\n  return 2 * i + 2;\n}\n\n/* Get the parent index */\nint _parent(int i) {\n  return (i - 1) ~/ 2; // Integer division\n}\n
    my_heap.rs
    /* Get the left child index */\nfn left(i: usize) -> usize {\n    2 * i + 1\n}\n\n/* Get the right child index */\nfn right(i: usize) -> usize {\n    2 * i + 2\n}\n\n/* Get the parent index */\nfn parent(i: usize) -> usize {\n    (i - 1) / 2 // Floor division\n}\n
    my_heap.c
    /* Get the left child index */\nint left(MaxHeap *maxHeap, int i) {\n    return 2 * i + 1;\n}\n\n/* Get the right child index */\nint right(MaxHeap *maxHeap, int i) {\n    return 2 * i + 2;\n}\n\n/* Get the parent index */\nint parent(MaxHeap *maxHeap, int i) {\n    return (i - 1) / 2; // Integer division truncates toward zero\n}\n
    my_heap.kt
    /* Get the left child index */\nfun left(i: Int): Int {\n    return 2 * i + 1\n}\n\n/* Get the right child index */\nfun right(i: Int): Int {\n    return 2 * i + 2\n}\n\n/* Get the parent index */\nfun parent(i: Int): Int {\n    return (i - 1) / 2 // Integer division (truncates toward zero in Kotlin)\n}\n
    my_heap.rb
    ### Get the left child index ###\ndef left(i)\n  2 * i + 1\nend\n\n### Get the right child index ###\ndef right(i)\n  2 * i + 2\nend\n\n### Get the parent index ###\ndef parent(i)\n  (i - 1) / 2     # Floor division\nend\n
    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#2-accessing-the-root","level":3,"title":"2.   Accessing the root","text":"

    The heap's root is the first element of the list:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def peek(self) -> int:\n    \"\"\"Access the top element of the heap\"\"\"\n    return self.max_heap[0]\n
    my_heap.cpp
    /* Access the top element of the heap */\nint peek() {\n    return maxHeap[0];\n}\n
    my_heap.java
    /* Access the top element of the heap */\nint peek() {\n    return maxHeap.get(0);\n}\n
    my_heap.cs
    /* Access the top element of the heap */\nint Peek() {\n    return maxHeap[0];\n}\n
    my_heap.go
    /* Access the top element of the heap */\nfunc (h *maxHeap) peek() any {\n    return h.data[0]\n}\n
    my_heap.swift
    /* Access the top element of the heap */\nfunc peek() -> Int {\n    maxHeap[0]\n}\n
    my_heap.js
    /* Access the top element of the heap */\npeek() {\n    return this.#maxHeap[0];\n}\n
    my_heap.ts
    /* Access the top element of the heap */\npeek(): number {\n    return this.maxHeap[0];\n}\n
    my_heap.dart
    /* Access the top element of the heap */\nint peek() {\n  return _maxHeap[0];\n}\n
    my_heap.rs
    /* Access the top element of the heap */\nfn peek(&self) -> Option<i32> {\n    self.max_heap.first().copied()\n}\n
    my_heap.c
    /* Access the top element of the heap */\nint peek(MaxHeap *maxHeap) {\n    return maxHeap->data[0];\n}\n
    my_heap.kt
    /* Access the top element of the heap */\nfun peek(): Int {\n    return maxHeap[0]\n}\n
    my_heap.rb
    ### Access the top element of the heap ###\ndef peek\n  @max_heap[0]\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#3-inserting-an-element","level":3,"title":"3.   Inserting an element","text":"

    To insert val, first append it at the end of the heap. Since it may be larger than other elements, this can violate the heap property. We must restore the property along the path from the inserted node to the root, an operation called heapifying.

    Start at the inserted node and sift upward. As shown below, compare it with its parent and swap them if the inserted value is larger. Repeat upward until the root is reached or no swap is needed.

    <1><2><3><4><5><6><7><8><9>

    Figure 8-3   Steps for inserting into a heap

    With \\(n\\) nodes, the tree height is \\(O(\\log n)\\), so sifting takes at most \\(O(\\log n)\\) iterations. Insertion therefore takes \\(O(\\log n)\\) time. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def push(self, val: int):\n    \"\"\"Push an element onto the heap\"\"\"\n    # Add a node\n    self.max_heap.append(val)\n    # Sift up\n    self.sift_up(self.size() - 1)\n\ndef sift_up(self, i: int):\n    \"\"\"Sift up starting from node i\"\"\"\n    while True:\n        # Get the parent of node i\n        p = self.parent(i)\n        # Stop sifting when the root has been passed or the node needs no repair\n        if p < 0 or self.max_heap[i] <= self.max_heap[p]:\n            break\n        # Swap the two nodes\n        self.swap(i, p)\n        # Continue sifting up\n        i = p\n
    my_heap.cpp
    /* Push an element onto the heap */\nvoid push(int val) {\n    // Add a node\n    maxHeap.push_back(val);\n    // Sift up\n    siftUp(size() - 1);\n}\n\n/* Sift up starting from node i */\nvoid siftUp(int i) {\n    while (true) {\n        // Get the parent of node i\n        int p = parent(i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || maxHeap[i] <= maxHeap[p])\n            break;\n        // Swap the two nodes\n        swap(maxHeap[i], maxHeap[p]);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.java
    /* Push an element onto the heap */\nvoid push(int val) {\n    // Add a node\n    maxHeap.add(val);\n    // Sift up\n    siftUp(size() - 1);\n}\n\n/* Sift up starting from node i */\nvoid siftUp(int i) {\n    while (true) {\n        // Get the parent of node i\n        int p = parent(i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || maxHeap.get(i) <= maxHeap.get(p))\n            break;\n        // Swap the two nodes\n        swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.cs
    /* Push an element onto the heap */\nvoid Push(int val) {\n    // Add a node\n    maxHeap.Add(val);\n    // Sift up\n    SiftUp(Size() - 1);\n}\n\n/* Sift up starting from node i */\nvoid SiftUp(int i) {\n    while (true) {\n        // Get the parent of node i\n        int p = Parent(i);\n        // Stop sifting if the root has been passed or the node needs no adjustment\n        if (p < 0 || maxHeap[i] <= maxHeap[p])\n            break;\n        // Swap the two nodes\n        Swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.go
    /* Push an element onto the heap */\nfunc (h *maxHeap) push(val any) {\n    // Add a node\n    h.data = append(h.data, val)\n    // Sift up\n    h.siftUp(len(h.data) - 1)\n}\n\n/* Sift up starting from node i */\nfunc (h *maxHeap) siftUp(i int) {\n    for true {\n        // Get the parent of node i\n        p := h.parent(i)\n        // Stop sifting when the root has been passed or the node needs no repair\n        if p < 0 || h.data[i].(int) <= h.data[p].(int) {\n            break\n        }\n        // Swap the two nodes\n        h.swap(i, p)\n        // Continue sifting up\n        i = p\n    }\n}\n
    my_heap.swift
    /* Push an element onto the heap */\nfunc push(val: Int) {\n    // Add a node\n    maxHeap.append(val)\n    // Sift up\n    siftUp(i: size() - 1)\n}\n\n/* Sift up starting from node i */\nfunc siftUp(i: Int) {\n    var i = i\n    while true {\n        // Get the parent of node i\n        let p = parent(i: i)\n        // Stop when the parent index is invalid or the node already satisfies the heap order\n        if p < 0 || maxHeap[i] <= maxHeap[p] {\n            break\n        }\n        // Swap the two nodes\n        swap(i: i, j: p)\n        // Continue sifting up\n        i = p\n    }\n}\n
    my_heap.js
    /* Push an element onto the heap */\npush(val) {\n    // Add a node\n    this.#maxHeap.push(val);\n    // Sift up\n    this.#siftUp(this.size() - 1);\n}\n\n/* Sift up starting from node i */\n#siftUp(i) {\n    while (true) {\n        // Get the parent of node i\n        const p = this.#parent(i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break;\n        // Swap the two nodes\n        this.#swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.ts
    /* Push an element onto the heap */\npush(val: number): void {\n    // Add a node\n    this.maxHeap.push(val);\n    // Sift up\n    this.siftUp(this.size() - 1);\n}\n\n/* Sift up starting from node i */\nsiftUp(i: number): void {\n    while (true) {\n        // Get the parent of node i\n        const p = this.parent(i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break;\n        // Swap the two nodes\n        this.swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.dart
    /* Push an element onto the heap */\nvoid push(int val) {\n  // Add a node\n  _maxHeap.add(val);\n  // Sift up\n  siftUp(size() - 1);\n}\n\n/* Sift up starting from node i */\nvoid siftUp(int i) {\n  while (true) {\n    // Get the parent of node i\n    int p = _parent(i);\n    // Stop sifting when the root has been passed or the node needs no repair\n    if (p < 0 || _maxHeap[i] <= _maxHeap[p]) {\n      break;\n    }\n    // Swap the two nodes\n    _swap(i, p);\n    // Continue sifting up\n    i = p;\n  }\n}\n
    my_heap.rs
    /* Push an element onto the heap */\nfn push(&mut self, val: i32) {\n    // Add a node\n    self.max_heap.push(val);\n    // Sift up\n    self.sift_up(self.size() - 1);\n}\n\n/* Sift up starting from node i */\nfn sift_up(&mut self, mut i: usize) {\n    loop {\n        // Stop sifting up if node i is already the root\n        if i == 0 {\n            break;\n        }\n        // Get the parent of node i\n        let p = Self::parent(i);\n        // Stop sifting up when the node already satisfies the heap property\n        if self.max_heap[i] <= self.max_heap[p] {\n            break;\n        }\n        // Swap the two nodes\n        self.swap(i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.c
    /* Push an element onto the heap */\nvoid push(MaxHeap *maxHeap, int val) {\n    // Normally, the number of nodes should remain below this limit\n    if (maxHeap->size == MAX_SIZE) {\n        printf(\"Heap is full!\");\n        return;\n    }\n    // Add a node\n    maxHeap->data[maxHeap->size] = val;\n    maxHeap->size++;\n\n    // Sift up\n    siftUp(maxHeap, maxHeap->size - 1);\n}\n\n/* Sift up starting from node i */\nvoid siftUp(MaxHeap *maxHeap, int i) {\n    while (true) {\n        // Get the parent of node i\n        int p = parent(maxHeap, i);\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) {\n            break;\n        }\n        // Swap the two nodes\n        swap(maxHeap, i, p);\n        // Continue sifting up\n        i = p;\n    }\n}\n
    my_heap.kt
    /* Push an element onto the heap */\nfun push(_val: Int) {\n    // Add a node\n    maxHeap.add(_val)\n    // Sift up\n    siftUp(size() - 1)\n}\n\n/* Sift up starting from node i */\nfun siftUp(it: Int) {\n    // Kotlin function parameters are immutable, so create a temporary variable\n    var i = it\n    while (true) {\n        // Get the parent of node i\n        val p = parent(i)\n        // Stop sifting when the root has been passed or the node needs no repair\n        if (p < 0 || maxHeap[i] <= maxHeap[p]) break\n        // Swap the two nodes\n        swap(i, p)\n        // Continue sifting up\n        i = p\n    }\n}\n
    my_heap.rb
    ### Push an element onto the heap ###\ndef push(val)\n  # Add a node\n  @max_heap << val\n  # Sift up\n  sift_up(size - 1)\nend\n\n### Sift up starting from node i ###\ndef sift_up(i)\n  loop do\n    # Get the parent of node i\n    p = parent(i)\n    # Stop sifting when the root has been passed or the node needs no repair\n    break if p < 0 || @max_heap[i] <= @max_heap[p]\n    # Swap the two nodes\n    swap(i, p)\n    # Continue sifting up\n    i = p\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#4-removing-the-root","level":3,"title":"4.   Removing the root","text":"

    The root is the first element of the list. Removing it directly would change every remaining index, making it difficult to restore the heap property. To minimize index changes, use the following steps.

    1. Swap the root with the last element, the rightmost leaf.
    2. Remove the last element from the list. Because of the swap, this is the original root.
    3. Starting at the root, sift downward.

    As shown below, sifting down proceeds in the opposite direction from sifting up. Compare the root with its two children and swap it with the larger child when needed. Continue downward until a leaf is reached or no swap is necessary.

    <1><2><3><4><5><6><7><8><9><10>

    Figure 8-4   Steps for removing the root of a heap

    Like insertion, removing the root takes \\(O(\\log n)\\) time. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py
    def pop(self) -> int:\n    \"\"\"Pop an element from the heap\"\"\"\n    # Handle an empty heap\n    if self.is_empty():\n        raise IndexError(\"Heap is empty\")\n    # Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    self.swap(0, self.size() - 1)\n    # Remove a node\n    val = self.max_heap.pop()\n    # Sift down\n    self.sift_down(0)\n    # Return the top element of the heap\n    return val\n\ndef sift_down(self, i: int):\n    \"\"\"Sift down starting from node i\"\"\"\n    while True:\n        # Find the node with the largest value among i, l, and r, and store its index in ma\n        l, r, ma = self.left(i), self.right(i), i\n        if l < self.size() and self.max_heap[l] > self.max_heap[ma]:\n            ma = l\n        if r < self.size() and self.max_heap[r] > self.max_heap[ma]:\n            ma = r\n        # Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i:\n            break\n        # Swap the two nodes\n        self.swap(i, ma)\n        # Continue sifting down\n        i = ma\n
    my_heap.cpp
    /* Pop an element from the heap */\nvoid pop() {\n    // Handle an empty heap\n    if (isEmpty()) {\n        throw out_of_range(\"Heap is empty\");\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(maxHeap[0], maxHeap[size() - 1]);\n    // Remove a node\n    maxHeap.pop_back();\n    // Sift down\n    siftDown(0);\n}\n\n/* Sift down starting from node i */\nvoid siftDown(int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = left(i), r = right(i), ma = i;\n        if (l < size() && maxHeap[l] > maxHeap[ma])\n            ma = l;\n        if (r < size() && maxHeap[r] > maxHeap[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i)\n            break;\n        swap(maxHeap[i], maxHeap[ma]);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.java
    /* Pop an element from the heap */\nint pop() {\n    // Handle an empty heap\n    if (isEmpty())\n        throw new IndexOutOfBoundsException();\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(0, size() - 1);\n    // Remove a node\n    int val = maxHeap.remove(size() - 1);\n    // Sift down\n    siftDown(0);\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\nvoid siftDown(int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = left(i), r = right(i), ma = i;\n        if (l < size() && maxHeap.get(l) > maxHeap.get(ma))\n            ma = l;\n        if (r < size() && maxHeap.get(r) > maxHeap.get(ma))\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i)\n            break;\n        // Swap the two nodes\n        swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.cs
    /* Pop an element from the heap */\nint Pop() {\n    // Handle an empty heap\n    if (IsEmpty())\n        throw new IndexOutOfRangeException();\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    Swap(0, Size() - 1);\n    // Remove a node\n    int val = maxHeap.Last();\n    maxHeap.RemoveAt(Size() - 1);\n    // Sift down\n    SiftDown(0);\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\nvoid SiftDown(int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = Left(i), r = Right(i), ma = i;\n        if (l < Size() && maxHeap[l] > maxHeap[ma])\n            ma = l;\n        if (r < Size() && maxHeap[r] > maxHeap[ma])\n            ma = r;\n        // Stop sifting if node i has the largest value or has no children\n        if (ma == i) break;\n        // Swap the two nodes\n        Swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.go
    /* Pop an element from the heap */\nfunc (h *maxHeap) pop() any {\n    // Handle an empty heap\n    if h.isEmpty() {\n        fmt.Println(\"error\")\n        return nil\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    h.swap(0, h.size()-1)\n    // Remove a node\n    val := h.data[len(h.data)-1]\n    h.data = h.data[:len(h.data)-1]\n    // Sift down\n    h.siftDown(0)\n\n    // Return the top element of the heap\n    return val\n}\n\n/* Sift down starting from node i */\nfunc (h *maxHeap) siftDown(i int) {\n    for true {\n        // Find the node with the largest value among i, l, and r, and store its index in max\n        l, r, max := h.left(i), h.right(i), i\n        if l < h.size() && h.data[l].(int) > h.data[max].(int) {\n            max = l\n        }\n        if r < h.size() && h.data[r].(int) > h.data[max].(int) {\n            max = r\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if max == i {\n            break\n        }\n        // Swap the two nodes\n        h.swap(i, max)\n        // Continue sifting down\n        i = max\n    }\n}\n
    my_heap.swift
    /* Pop an element from the heap */\nfunc pop() -> Int {\n    // Handle an empty heap\n    if isEmpty() {\n        fatalError(\"Heap is empty\")\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(i: 0, j: size() - 1)\n    // Remove a node\n    let val = maxHeap.remove(at: size() - 1)\n    // Sift down\n    siftDown(i: 0)\n    // Return the top element of the heap\n    return val\n}\n\n/* Sift down starting from node i */\nfunc siftDown(i: Int) {\n    var i = i\n    while true {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = left(i: i)\n        let r = right(i: i)\n        var ma = i\n        if l < size(), maxHeap[l] > maxHeap[ma] {\n            ma = l\n        }\n        if r < size(), maxHeap[r] > maxHeap[ma] {\n            ma = r\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break\n        }\n        // Swap the two nodes\n        swap(i: i, j: ma)\n        // Continue sifting down\n        i = ma\n    }\n}\n
    my_heap.js
    /* Pop an element from the heap */\npop() {\n    // Handle an empty heap\n    if (this.isEmpty()) throw new Error('Heap is empty');\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    this.#swap(0, this.size() - 1);\n    // Remove a node\n    const val = this.#maxHeap.pop();\n    // Sift down\n    this.#siftDown(0);\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\n#siftDown(i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        const l = this.#left(i),\n            r = this.#right(i);\n        let ma = i;\n        if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l;\n        if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma === i) break;\n        // Swap the two nodes\n        this.#swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.ts
    /* Pop an element from the heap */\npop(): number {\n    // Handle an empty heap\n    if (this.isEmpty()) throw new RangeError('Heap is empty');\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    this.swap(0, this.size() - 1);\n    // Remove a node\n    const val = this.maxHeap.pop();\n    // Sift down\n    this.siftDown(0);\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\nsiftDown(i: number): void {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        const l = this.left(i),\n            r = this.right(i);\n        let ma = i;\n        if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l;\n        if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma === i) break;\n        // Swap the two nodes\n        this.swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.dart
    /* Pop an element from the heap */\nint pop() {\n  // Handle an empty heap\n  if (isEmpty()) throw Exception('Heap is empty');\n  // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n  _swap(0, size() - 1);\n  // Remove a node\n  int val = _maxHeap.removeLast();\n  // Sift down\n  siftDown(0);\n  // Return the top element of the heap\n  return val;\n}\n\n/* Sift down starting from node i */\nvoid siftDown(int i) {\n  while (true) {\n    // Find the node with the largest value among i, l, and r, and store its index in ma\n    int l = _left(i);\n    int r = _right(i);\n    int ma = i;\n    if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l;\n    if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r;\n    // Stop if node i is largest or both child indices l and r are out of bounds\n    if (ma == i) break;\n    // Swap the two nodes\n    _swap(i, ma);\n    // Continue sifting down\n    i = ma;\n  }\n}\n
    my_heap.rs
    /* Pop an element from the heap */\nfn pop(&mut self) -> i32 {\n    // Handle an empty heap\n    if self.is_empty() {\n        panic!(\"Index out of bounds\");\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    self.swap(0, self.size() - 1);\n    // Remove a node\n    let val = self.max_heap.pop().unwrap();\n    // Sift down\n    self.sift_down(0);\n    // Return the top element of the heap\n    val\n}\n\n/* Sift down starting from node i */\nfn sift_down(&mut self, mut i: usize) {\n    loop {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let (l, r, mut ma) = (Self::left(i), Self::right(i), i);\n        if l < self.size() && self.max_heap[l] > self.max_heap[ma] {\n            ma = l;\n        }\n        if r < self.size() && self.max_heap[r] > self.max_heap[ma] {\n            ma = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break;\n        }\n        // Swap the two nodes\n        self.swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n
    my_heap.c
    /* Pop an element from the heap */\nint pop(MaxHeap *maxHeap) {\n    // Handle an empty heap\n    if (isEmpty(maxHeap)) {\n        printf(\"Heap is empty!\");\n        return INT_MAX;\n    }\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(maxHeap, 0, size(maxHeap) - 1);\n    // Remove a node\n    int val = maxHeap->data[maxHeap->size - 1];\n    maxHeap->size--;\n    // Sift down\n    siftDown(maxHeap, 0);\n\n    // Return the top element of the heap\n    return val;\n}\n\n/* Sift down starting from node i */\nvoid siftDown(MaxHeap *maxHeap, int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in max\n        int l = left(maxHeap, i);\n        int r = right(maxHeap, i);\n        int max = i;\n        if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) {\n            max = l;\n        }\n        if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) {\n            max = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (max == i) {\n            break;\n        }\n        // Swap the two nodes\n        swap(maxHeap, i, max);\n        // Continue sifting down\n        i = max;\n    }\n}\n
    my_heap.kt
    /* Pop an element from the heap */\nfun pop(): Int {\n    // Handle an empty heap\n    if (isEmpty()) throw IndexOutOfBoundsException()\n    // Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n    swap(0, size() - 1)\n    // Remove a node\n    val _val = maxHeap.removeAt(size() - 1)\n    // Sift down\n    siftDown(0)\n    // Return the top element of the heap\n    return _val\n}\n\n/* Sift down starting from node i */\nfun siftDown(it: Int) {\n    // Kotlin function parameters are immutable, so create a temporary variable\n    var i = it\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        val l = left(i)\n        val r = right(i)\n        var ma = i\n        if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l\n        if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i) break\n        // Swap the two nodes\n        swap(i, ma)\n        // Continue sifting down\n        i = ma\n    }\n}\n
    my_heap.rb
    ### Pop an element from the heap ###\ndef pop\n  # Handle an empty heap\n  raise IndexError, \"Heap is empty\" if is_empty?\n  # Swap the root with the rightmost leaf on the last level (swap the first and last elements)\n  swap(0, size - 1)\n  # Remove a node\n  val = @max_heap.pop\n  # Sift down\n  sift_down(0)\n  # Return the top element of the heap\n  val\nend\n\n### Sift down starting from node i ###\ndef sift_down(i)\n  loop do\n    # Find the node with the largest value among i, l, and r, and store its index in ma\n    l, r, ma = left(i), right(i), i\n    ma = l if l < size && @max_heap[l] > @max_heap[ma]\n    ma = r if r < size && @max_heap[r] > @max_heap[ma]\n\n    # Stop if node i is largest or both child indices l and r are out of bounds\n    break if ma == i\n\n    # Swap the two nodes\n    swap(i, ma)\n    # Continue sifting down\n    i = ma\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/heap/#813-common-applications","level":2,"title":"8.1.3   Common applications","text":"
    • Priority queues: Heaps are usually the first choice for implementing priority queues. Enqueueing and dequeueing both take \\(O(\\log n)\\) time, and building a heap takes \\(O(n)\\).
    • Heap sort: Build a heap from a dataset, then repeatedly remove its root to obtain sorted data. In practice, a more elegant implementation is usually used; see the heap-sort section.
    • Finding the largest \\(k\\) elements: This is both a classic algorithm problem and a common application, such as selecting the 10 hottest news stories for Weibo's trending list or the 10 best-selling products.
    ","path":["Chapter 8.   Heaps","8.1   Heaps"],"tags":[]},{"location":"chapter_heap/summary/","level":1,"title":"8.4   Summary","text":"","path":["Chapter 8.   Heaps","8.4   Summary"],"tags":[]},{"location":"chapter_heap/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • A heap is a complete binary tree. Depending on its ordering condition, it is either a max heap or a min heap. The root holds the largest or smallest element, respectively.
    • A priority queue removes elements according to their priority and is usually implemented with a heap.
    • Common heap operations and their time complexities include insertion in \\(O(\\log n)\\), removing the root in \\(O(\\log n)\\), and accessing the root in \\(O(1)\\).
    • Complete binary trees are well suited to array representation, so heaps are usually stored in arrays.
    • Sifting restores the heap property and is used during both insertion and removal.
    • Building a heap from \\(n\\) input elements can be optimized to \\(O(n)\\) time.
    • Top-k is a classic algorithm problem that a heap can solve efficiently in \\(O(n \\log k)\\) time.
    ","path":["Chapter 8.   Heaps","8.4   Summary"],"tags":[]},{"location":"chapter_heap/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Is the heap data structure the same concept as the heap in memory management?

    No; they simply share a name. In computer memory, the heap is used for dynamic memory allocation, allowing a running program to store data. A program can request heap memory for structures such as objects and arrays. When that data is no longer needed, the memory must be released to prevent leaks. Compared with stack memory, heap memory requires more careful management; misuse can lead to memory leaks, dangling pointers, and other problems.

    ","path":["Chapter 8.   Heaps","8.4   Summary"],"tags":[]},{"location":"chapter_heap/top_k/","level":1,"title":"8.3   The top-k problem","text":"

    Question

    Given an unsorted array nums of length \\(n\\), return its largest \\(k\\) elements.

    We first introduce two straightforward solutions, then a more efficient solution using a heap.

    ","path":["Chapter 8.   Heaps","8.3   The top-k problem"],"tags":[]},{"location":"chapter_heap/top_k/#831-method-1-repeated-selection","level":2,"title":"8.3.1   Method 1: Repeated selection","text":"

    As shown below, scan the array \\(k\\) times, selecting the \\(1\\)st, \\(2\\)nd, \\(\\dots\\), and \\(k\\)th largest elements in successive rounds. This takes \\(O(nk)\\) time.

    This method is suitable only when \\(k \\ll n\\). As \\(k\\) approaches \\(n\\), its time complexity approaches \\(O(n^2)\\), making it very slow.

    Figure 8-6   Finding the largest k elements by repeated scans

    Tip

    When \\(k = n\\), this produces a fully sorted sequence and is equivalent to selection sort.

    ","path":["Chapter 8.   Heaps","8.3   The top-k problem"],"tags":[]},{"location":"chapter_heap/top_k/#832-method-2-sorting","level":2,"title":"8.3.2   Method 2: Sorting","text":"

    As shown below, sort nums and return its rightmost \\(k\\) elements. This takes \\(O(n \\log n)\\) time.

    This method does more work than necessary: we need only the largest \\(k\\) elements, not a sorted order for all the others.

    Figure 8-7   Finding the largest k elements by sorting

    ","path":["Chapter 8.   Heaps","8.3   The top-k problem"],"tags":[]},{"location":"chapter_heap/top_k/#833-method-3-a-heap","level":2,"title":"8.3.3   Method 3: A heap","text":"

    A heap solves the top-k problem more efficiently, following the steps below.

    1. Initialize a min heap, whose root holds its smallest element.
    2. Insert the first \\(k\\) array elements into the heap one by one.
    3. Starting with element \\(k + 1\\), if the current element is larger than the root, remove the root and insert the current element.
    4. After the traversal, the heap contains the largest \\(k\\) elements.
    <1><2><3><4><5><6><7><8><9>

    Figure 8-8   Finding the largest k elements with a heap

    An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby top_k.py
    def top_k_heap(nums: list[int], k: int) -> list[int]:\n    \"\"\"Use a heap to find the k largest elements in an array\"\"\"\n    # Initialize a min heap\n    heap = []\n    # Push the first k array elements onto the heap\n    for i in range(k):\n        heapq.heappush(heap, nums[i])\n    # Starting with element k+1, keep the heap size at k\n    for i in range(k, len(nums)):\n        # If the current element is larger than the heap top, pop the top element and push the current element\n        if nums[i] > heap[0]:\n            heapq.heappop(heap)\n            heapq.heappush(heap, nums[i])\n    return heap\n
    top_k.cpp
    /* Use a heap to find the k largest elements in an array */\npriority_queue<int, vector<int>, greater<int>> topKHeap(vector<int> &nums, int k) {\n    // Initialize a min heap\n    priority_queue<int, vector<int>, greater<int>> heap;\n    // Push the first k array elements onto the heap\n    for (int i = 0; i < k; i++) {\n        heap.push(nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (int i = k; i < nums.size(); i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > heap.top()) {\n            heap.pop();\n            heap.push(nums[i]);\n        }\n    }\n    return heap;\n}\n
    top_k.java
    /* Use a heap to find the k largest elements in an array */\nQueue<Integer> topKHeap(int[] nums, int k) {\n    // Initialize a min heap\n    Queue<Integer> heap = new PriorityQueue<Integer>();\n    // Push the first k array elements onto the heap\n    for (int i = 0; i < k; i++) {\n        heap.offer(nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (int i = k; i < nums.length; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > heap.peek()) {\n            heap.poll();\n            heap.offer(nums[i]);\n        }\n    }\n    return heap;\n}\n
    top_k.cs
    /* Use a heap to find the k largest elements in an array */\nPriorityQueue<int, int> TopKHeap(int[] nums, int k) {\n    // Initialize a min heap\n    PriorityQueue<int, int> heap = new();\n    // Push the first k array elements onto the heap\n    for (int i = 0; i < k; i++) {\n        heap.Enqueue(nums[i], nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (int i = k; i < nums.Length; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > heap.Peek()) {\n            heap.Dequeue();\n            heap.Enqueue(nums[i], nums[i]);\n        }\n    }\n    return heap;\n}\n
    top_k.go
    /* Use a heap to find the k largest elements in an array */\nfunc topKHeap(nums []int, k int) *minHeap {\n    // Initialize a min heap\n    h := &minHeap{}\n    heap.Init(h)\n    // Push the first k array elements onto the heap\n    for i := 0; i < k; i++ {\n        heap.Push(h, nums[i])\n    }\n    // Starting with element k+1, keep the heap size at k\n    for i := k; i < len(nums); i++ {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if nums[i] > h.Top().(int) {\n            heap.Pop(h)\n            heap.Push(h, nums[i])\n        }\n    }\n    return h\n}\n
    top_k.swift
    /* Use a heap to find the k largest elements in an array */\nfunc topKHeap(nums: [Int], k: Int) -> [Int] {\n    // Build a heap from the first k elements and use it as a min heap\n    var heap = Heap(nums.prefix(k))\n    // Starting with element k+1, keep the heap size at k\n    for i in nums.indices.dropFirst(k) {\n        // If the current element exceeds the heap minimum, remove the minimum and insert the current element\n        if nums[i] > heap.min()! {\n            _ = heap.removeMin()\n            heap.insert(nums[i])\n        }\n    }\n    return heap.unordered\n}\n
    top_k.js
    /* Push an element onto the heap */\nfunction pushMinHeap(maxHeap, val) {\n    // Negate the value\n    maxHeap.push(-val);\n}\n\n/* Pop an element from the heap */\nfunction popMinHeap(maxHeap) {\n    // Negate the value\n    return -maxHeap.pop();\n}\n\n/* Access the top element of the heap */\nfunction peekMinHeap(maxHeap) {\n    // Negate the value\n    return -maxHeap.peek();\n}\n\n/* Get the elements in the heap */\nfunction getMinHeap(maxHeap) {\n    // Negate the value\n    return maxHeap.getMaxHeap().map((num) => -num);\n}\n\n/* Use a heap to find the k largest elements in an array */\nfunction topKHeap(nums, k) {\n    // Initialize a min heap\n    // Note: negate all values in the heap to simulate a min heap with a max heap\n    const maxHeap = new MaxHeap([]);\n    // Push the first k array elements onto the heap\n    for (let i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (let i = k; i < nums.length; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    // Return the elements in the heap\n    return getMinHeap(maxHeap);\n}\n
    top_k.ts
    /* Push an element onto the heap */\nfunction pushMinHeap(maxHeap: MaxHeap, val: number): void {\n    // Negate the value\n    maxHeap.push(-val);\n}\n\n/* Pop an element from the heap */\nfunction popMinHeap(maxHeap: MaxHeap): number {\n    // Negate the value\n    return -maxHeap.pop();\n}\n\n/* Access the top element of the heap */\nfunction peekMinHeap(maxHeap: MaxHeap): number {\n    // Negate the value\n    return -maxHeap.peek();\n}\n\n/* Get the elements in the heap */\nfunction getMinHeap(maxHeap: MaxHeap): number[] {\n    // Negate the value\n    return maxHeap.getMaxHeap().map((num: number) => -num);\n}\n\n/* Use a heap to find the k largest elements in an array */\nfunction topKHeap(nums: number[], k: number): number[] {\n    // Initialize a min heap\n    // Note: negate all values in the heap to simulate a min heap with a max heap\n    const maxHeap = new MaxHeap([]);\n    // Push the first k array elements onto the heap\n    for (let i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (let i = k; i < nums.length; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    // Return the elements in the heap\n    return getMinHeap(maxHeap);\n}\n
    top_k.dart
    /* Use a heap to find the k largest elements in an array */\nMinHeap topKHeap(List<int> nums, int k) {\n  // Initialize a min heap with the first k elements of the array\n  MinHeap heap = MinHeap(nums.sublist(0, k));\n  // Starting with element k+1, keep the heap size at k\n  for (int i = k; i < nums.length; i++) {\n    // If the current element is larger than the heap top, pop the top element and push the current element\n    if (nums[i] > heap.peek()) {\n      heap.pop();\n      heap.push(nums[i]);\n    }\n  }\n  return heap;\n}\n
    top_k.rs
    /* Use a heap to find the k largest elements in an array */\nfn top_k_heap(nums: Vec<i32>, k: usize) -> BinaryHeap<Reverse<i32>> {\n    // BinaryHeap is a max heap; Reverse reverses the comparison order to implement a min heap\n    let mut heap = BinaryHeap::<Reverse<i32>>::new();\n    // Push the first k array elements onto the heap\n    for &num in nums.iter().take(k) {\n        heap.push(Reverse(num));\n    }\n    // Starting with element k+1, keep the heap size at k\n    for &num in nums.iter().skip(k) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if num > heap.peek().unwrap().0 {\n            heap.pop();\n            heap.push(Reverse(num));\n        }\n    }\n    heap\n}\n
    top_k.c
    /* Push an element onto the heap */\nvoid pushMinHeap(MaxHeap *maxHeap, int val) {\n    // Negate the element\n    push(maxHeap, -val);\n}\n\n/* Pop an element from the heap */\nint popMinHeap(MaxHeap *maxHeap) {\n    // Negate the element\n    return -pop(maxHeap);\n}\n\n/* Access the top element of the heap */\nint peekMinHeap(MaxHeap *maxHeap) {\n    // Negate the element\n    return -peek(maxHeap);\n}\n\n/* Get the elements in the heap */\nint *getMinHeap(MaxHeap *maxHeap) {\n    // Negate every element in the heap and store the results in array res\n    int *res = (int *)malloc(maxHeap->size * sizeof(int));\n    for (int i = 0; i < maxHeap->size; i++) {\n        res[i] = -maxHeap->data[i];\n    }\n    return res;\n}\n\n/* Get the elements in the heap */\nint *getMinHeap(MaxHeap *maxHeap) {\n    // Negate every element in the heap and store the results in array res\n    int *res = (int *)malloc(maxHeap->size * sizeof(int));\n    for (int i = 0; i < maxHeap->size; i++) {\n        res[i] = -maxHeap->data[i];\n    }\n    return res;\n}\n\n// Use a heap to find the k largest elements in an array\nint *topKHeap(int *nums, int sizeNums, int k) {\n    // Initialize a min heap\n    // Note: negate every element to simulate a min heap with a max heap\n    int *empty = (int *)malloc(0);\n    MaxHeap *maxHeap = newMaxHeap(empty, 0);\n    // Push the first k array elements onto the heap\n    for (int i = 0; i < k; i++) {\n        pushMinHeap(maxHeap, nums[i]);\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (int i = k; i < sizeNums; i++) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > peekMinHeap(maxHeap)) {\n            popMinHeap(maxHeap);\n            pushMinHeap(maxHeap, nums[i]);\n        }\n    }\n    int *res = getMinHeap(maxHeap);\n    // Free memory\n    delMaxHeap(maxHeap);\n    return res;\n}\n
    top_k.kt
    /* Use a heap to find the k largest elements in an array */\nfun topKHeap(nums: IntArray, k: Int): Queue<Int> {\n    // Initialize a min heap\n    val heap = PriorityQueue<Int>()\n    // Push the first k array elements onto the heap\n    for (i in 0..<k) {\n        heap.offer(nums[i])\n    }\n    // Starting with element k+1, keep the heap size at k\n    for (i in k..<nums.size) {\n        // If the current element is larger than the heap top, pop the top element and push the current element\n        if (nums[i] > heap.peek()) {\n            heap.poll()\n            heap.offer(nums[i])\n        }\n    }\n    return heap\n}\n
    top_k.rb
    ### Use a heap to find the k largest elements in an array ###\ndef top_k_heap(nums, k)\n  # Initialize a min heap\n  # Note: negate all values in the heap to simulate a min heap with a max heap\n  max_heap = MaxHeap.new([])\n\n  # Push the first k array elements onto the heap\n  for i in 0...k\n    push_min_heap(max_heap, nums[i])\n  end\n\n  # Starting with element k+1, keep the heap size at k\n  for i in k...nums.length\n    # If the current element is larger than the heap top, pop the top element and push the current element\n    if nums[i] > peek_min_heap(max_heap)\n      pop_min_heap(max_heap)\n      push_min_heap(max_heap, nums[i])\n    end\n  end\n\n  get_min_heap(max_heap)\nend\n
    Visualize execution

    View full screen >

    There are \\(n\\) rounds of heap insertion and removal to consider, and the heap has at most \\(k\\) elements, so the time complexity is \\(O(n \\log k)\\). This is highly efficient: when \\(k\\) is small, the complexity approaches \\(O(n)\\); when \\(k\\) is large, it does not exceed \\(O(n \\log n)\\).

    This approach also suits dynamic data streams. As new data arrives, we can maintain the heap to update the largest \\(k\\) elements continuously.

    ","path":["Chapter 8.   Heaps","8.3   The top-k problem"],"tags":[]},{"location":"chapter_hello_algo/","level":1,"title":"Foreword","text":"

    A few years ago, I shared a series of solutions to the “Coding Interviews” problems on LeetCode and received encouragement and support from many readers. In our conversations, one question came up more than any other: “How do I get started with algorithms?” Over time, I became deeply interested in this question.

    Jumping into practice problems with little preparation seems to be the most popular approach: simple, direct, and effective. Yet solving problems is rather like playing Minesweeper. Strong independent learners can clear the mines one by one, while those with a weak foundation may run into one setback after another and gradually retreat in frustration. Reading a textbook from cover to cover is another common approach. For job seekers, however, theses, applications, written tests, and interview preparation already consume most of their energy, making a thick textbook a formidable challenge.

    If you face similar difficulties, I am glad this book has found you. It is my answer to that question—perhaps not the optimal solution, but at least a constructive attempt. Although this book alone will not secure a job offer, it will guide you through the “knowledge map” of data structures and algorithms. It will show you the shapes, sizes, and locations of different “mines” and teach you ways to clear them. With these skills, I believe you can approach practice problems and technical reading more confidently and gradually build a complete body of knowledge.

    I wholeheartedly agree with Professor Feynman's words: “Knowledge isn't free. You have to pay attention.” In this sense, the book is not entirely “free.” To honor the precious attention you invest in it, I will devote as much of my own attention as I can to writing it.

    I am aware of the limits of my knowledge. Although the content has undergone considerable refinement, many errors surely remain. I sincerely welcome corrections and suggestions from teachers and fellow learners.

    Hello, algorithms!

    Computers have transformed the world. Their speed and remarkable programmability make them an ideal medium for executing algorithms and processing data. From the lifelike graphics of video games and the intelligent decisions of self-driving cars to AlphaGo's brilliant games and ChatGPT's natural interactions, these applications are all compelling expressions of algorithms running on computers.

    In fact, algorithms and data structures existed throughout the world long before computers. Early algorithms were relatively simple, such as ancient counting methods and steps for making tools. As civilization advanced, algorithms became more refined and complex. From exquisite craftsmanship and industrial products that boosted productivity to the scientific laws governing the universe, ingenious algorithmic ideas lie behind almost everything, whether ordinary or astonishing.

    Data structures are equally widespread. Systems ranging from social networks to subway routes can be modeled as “graphs.” Social organizations, from nations to families, often have the characteristics of “trees.” Winter clothing behaves like a “stack”: what goes on first comes off last. A tube of shuttlecocks is like a “queue,” with items inserted at one end and removed at the other. A dictionary resembles a “hash table,” allowing quick lookup of a desired entry.

    Through clear animated illustrations and runnable code examples, this book aims to help readers understand the core concepts of algorithms and data structures and implement them through programming. Building on that foundation, it seeks to reveal how algorithms take shape in our complex world and to show their beauty. I hope you find it helpful!

    ","path":["Foreword"],"tags":[]},{"location":"chapter_introduction/","level":1,"title":"Chapter 1.   Getting to know algorithms","text":"

    Abstract

    A young woman dances gracefully among the data, the melody of algorithms rippling through her skirt.

    She invites you to join her. Follow her steps into a world of algorithms filled with logic and beauty.

    ","path":["Chapter 1.   Getting to know algorithms"],"tags":[]},{"location":"chapter_introduction/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 1.1   Algorithms are everywhere
    • 1.2   What is an algorithm?
    • 1.3   Summary
    ","path":["Chapter 1.   Getting to know algorithms"],"tags":[]},{"location":"chapter_introduction/algorithms_are_everywhere/","level":1,"title":"1.1   Algorithms are everywhere","text":"

    When we hear the word “algorithm,” we naturally think of mathematics. In fact, many algorithms do not involve complex mathematics. Instead, they rely on basic logic that we encounter throughout everyday life.

    Before we formally explore algorithms, here is an interesting fact: you have already learned many algorithms without realizing it, and you routinely use them in everyday life. The following examples illustrate this point.

    Example 1: Looking up a word in a dictionary. In a Chinese dictionary, each character has a corresponding pinyin pronunciation, and entries are arranged alphabetically by pinyin. Suppose we want to find a character whose pinyin begins with \\(r\\). We would usually follow the process shown in Figure 1-1.

    1. Open the dictionary roughly halfway through and check the initial letter on that page. Suppose it is \\(m\\).
    2. Since \\(r\\) comes after \\(m\\) in the pinyin alphabet, rule out the first half of the dictionary and narrow the search to the second half.
    3. Repeat steps 1. and 2. until you find the pages whose pinyin entries begin with \\(r\\).
    <1><2><3><4><5>

    Figure 1-1   Steps for looking up a word in a dictionary

    This dictionary skill, learned in elementary school, is actually the well-known “binary search” algorithm. From a data structure perspective, we can view the dictionary as a sorted “array”; from an algorithm perspective, the sequence of lookup operations above is “binary search.”

    Example 2: Arranging playing cards. When playing cards, we need to arrange our hand from smallest to largest each round. The process is shown in Figure 1-2.

    1. Divide the cards into “sorted” and “unsorted” sections, initially treating the single leftmost card as sorted.
    2. Take a card from the unsorted section and insert it into the correct position in the sorted section. The two leftmost cards are now sorted.
    3. Repeat step 2., inserting one card from the unsorted section into the sorted section each round, until all the cards are sorted.

    Figure 1-2   Steps for sorting playing cards

    This method of arranging playing cards is essentially “insertion sort,” an algorithm that is very efficient for small datasets. Insertion sort appears in the library sorting functions of many programming languages.

    Example 3: Making change. Suppose we buy goods costing \\(69\\) yuan at a supermarket and pay the cashier \\(100\\) yuan. The cashier needs to give us \\(31\\) yuan in change and naturally reasons through the process shown in Figure 1-3.

    1. The available denominations smaller than \\(31\\) yuan are \\(1\\), \\(5\\), \\(10\\), and \\(20\\) yuan.
    2. Take the largest available denomination, \\(20\\) yuan, leaving \\(31 - 20 = 11\\) yuan.
    3. Take the largest denomination that fits the remaining amount, \\(10\\) yuan, leaving \\(11 - 10 = 1\\) yuan.
    4. Take the largest denomination that fits the remaining amount, \\(1\\) yuan, leaving \\(1 - 1 = 0\\) yuan.
    5. The change is complete: \\(20 + 10 + 1 = 31\\) yuan.

    Figure 1-3   The process of making change

    At each step above, we make the choice that looks best at the moment—using the largest possible denomination—and eventually obtain a valid way to make change. In terms of data structures and algorithms, this is a “greedy” algorithm.

    From cooking a meal to traveling between stars, solving almost any problem involves algorithms. Computers allow us to store data structures in memory through programming and write code that calls on CPUs and GPUs to execute algorithms. We can therefore bring real-world problems to computers and solve complex problems more efficiently.

    Tip

    If concepts such as data structures, algorithms, arrays, and binary search still seem unclear, keep reading. This book will guide you into the world of data structures and algorithms.

    ","path":["Chapter 1.   Getting to know algorithms","1.1   Algorithms are everywhere"],"tags":[]},{"location":"chapter_introduction/summary/","level":1,"title":"1.3   Summary","text":"","path":["Chapter 1.   Getting to know algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Algorithms are everywhere in daily life; they are not remote, inaccessible knowledge. In fact, we have already learned many algorithms without realizing it and use them to solve problems large and small.
    • Looking up a word in a dictionary follows the same principle as binary search. Binary search illustrates the important algorithmic idea of divide and conquer.
    • Arranging playing cards closely resembles insertion sort. Insertion sort is well suited to small datasets.
    • Making change is essentially a greedy algorithm: at each step, make the choice that looks best at the moment.
    • An algorithm is a set of instructions or steps that solves a specific problem within a finite time. A data structure is a way of organizing and storing data in a computer.
    • Data structures and algorithms are closely connected. Data structures provide the foundation for algorithms, while algorithms bring data structures to life.
    • We can compare data structures and algorithms to assembling building blocks: the blocks represent data; their shapes and connections represent data structures; and the assembly steps represent algorithms.
    ","path":["Chapter 1.   Getting to know algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: As a programmer, I have never used algorithms to solve problems in my daily work. Common algorithms are already built into programming languages, so I can simply use them. Does this mean the problems we encounter at work are not difficult enough to require algorithms?

    If specific job skills are like the techniques of a martial art, foundational subjects are more like the underlying strength that makes those techniques effective.

    I believe the purpose of learning algorithms—and other foundational subjects—is not to implement them from scratch at work. It is to use what we have learned to respond and make judgments professionally when solving problems, improving the overall quality of our work. For a simple example, every programming language has a built-in sorting function:

    • Without studying data structures and algorithms, we might pass any dataset to this sorting function. It runs smoothly and performs reasonably well, so nothing seems wrong.
    • After studying algorithms, however, we know that the built-in sorting function has a time complexity of \\(O(n \\log n)\\). If the data consists of integers with a fixed number of digits, such as student IDs, we can use the more efficient “radix sort” to reduce the time complexity to \\(O(nk)\\), where \\(k\\) is the number of digits. For large datasets, the time saved can create considerable value through lower costs, a better user experience, and so on.

    In engineering, optimal solutions are difficult to reach for many problems, and many others are solved only “well enough.” How difficult a problem appears depends both on its nature and on the knowledge of the person examining it. The more knowledge and experience we have, the more deeply we can analyze a problem and the more elegantly we can solve it.

    Q: Now that AI agents write almost all our code, do we still need to learn algorithms?

    To quote Mu Li: “An agent is an ability multiplier: it multiplies your ability by 10. It is not an adder that adds 10 to your ability.”

    If AI multiplies our ability, then that starting ability matters too. I think two kinds of ability are especially worth developing. The first is communication, particularly collaboration with AI: state the goal clearly, supply the necessary context, and correct the course promptly. Explaining a problem to AI also helps us clarify it for ourselves. The second is foundational knowledge. AI can fill in the details, but we need to understand the basic concepts ourselves to ask relevant questions, judge what is correct, and turn its output into useful results.

    One distinctive feature of AI is that, unlike earlier tools, it has no clear boundaries to its functionality. It is a little like water, taking different shapes in different people's hands. The stronger a person's abilities and the better their taste, the more pronounced AI's multiplying effect becomes.

    ","path":["Chapter 1.   Getting to know algorithms","1.3   Summary"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/","level":1,"title":"1.2   What is an algorithm?","text":"","path":["Chapter 1.   Getting to know algorithms","1.2   What is an algorithm?"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#121-definition-of-an-algorithm","level":2,"title":"1.2.1   Definition of an algorithm","text":"

    An algorithm is a set of instructions or steps that solves a specific problem within a finite time. It has the following properties.

    • The problem is well defined, with clear definitions of its inputs and outputs.
    • The algorithm is feasible: it can finish within a finite number of steps, using finite time and memory.
    • Every step has a definite meaning. Given the same inputs and execution conditions, the output is always the same.
    ","path":["Chapter 1.   Getting to know algorithms","1.2   What is an algorithm?"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#122-definition-of-a-data-structure","level":2,"title":"1.2.2   Definition of a data structure","text":"

    A data structure is a way of organizing and storing data. It encompasses the data itself, the relationships between data items, and the methods used to operate on them. Its design aims to achieve the following goals.

    • Use as little space as possible to conserve computer memory.
    • Make data operations as fast as possible, including access, insertion, deletion, and updating.
    • Provide a concise representation of data and its logical relationships so that algorithms can run efficiently.

    Designing a data structure involves tradeoffs. Improving one aspect often requires compromising another. Here are two examples.

    • Compared with arrays, linked lists make insertion and deletion easier, at the expense of data access speed.
    • Compared with linked lists, graphs provide richer information about logical relationships but require more memory.
    ","path":["Chapter 1.   Getting to know algorithms","1.2   What is an algorithm?"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#123-the-relationship-between-data-structures-and-algorithms","level":2,"title":"1.2.3   The relationship between data structures and algorithms","text":"

    As Figure 1-4 shows, data structures and algorithms are closely intertwined in three ways.

    • Data structures are the foundation of algorithms. They provide algorithms with data in a structured form and methods for operating on that data.
    • Algorithms bring data structures to life. A data structure by itself only stores information; it must be combined with an algorithm to solve a specific problem.
    • An algorithm can often be implemented using different data structures, but its efficiency may vary greatly. Choosing the right data structure is crucial.

    Figure 1-4   The relationship between data structures and algorithms

    Data structures and algorithms are like assembling the building blocks shown in Figure 1-5. A set of building blocks includes not only many pieces but also detailed assembly instructions. By following those instructions step by step, we can build an intricate model.

    Figure 1-5   Assembling building blocks

    Table 1-1 describes this analogy in detail.

    Table 1-1   Comparing data structures and algorithms to building blocks

    Data structures and algorithms Building blocks Input data Unassembled blocks Data structure How the blocks are organized, including their shapes, sizes, and connections Algorithm A sequence of steps for assembling the blocks into the desired form Output data The finished model

    Data structures and algorithms are independent of programming languages. This is why the book can provide implementations in multiple languages.

    A common shorthand

    In everyday discussions, we often shorten “data structures and algorithms” to “algorithms.” For example, the familiar algorithm problems on LeetCode actually test knowledge of both data structures and algorithms.

    ","path":["Chapter 1.   Getting to know algorithms","1.2   What is an algorithm?"],"tags":[]},{"location":"chapter_preface/","level":1,"title":"Chapter 0.   Preface","text":"

    Abstract

    Algorithms are like a beautiful symphony, with each line of code flowing to its rhythm.

    May this book play softly in your mind, leaving a melody that is both distinctive and lasting.

    ","path":["Chapter 0.   Preface"],"tags":[]},{"location":"chapter_preface/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 0.1   About this book
    • 0.2   How to use this book
    • 0.3   Summary
    ","path":["Chapter 0.   Preface"],"tags":[]},{"location":"chapter_preface/about_the_book/","level":1,"title":"0.1   About this book","text":"

    This project aims to create an open-source, free, beginner-friendly introduction to data structures and algorithms.

    • Animated illustrations throughout the book make concepts clear and accessible, with a gentle learning curve that guides beginners through the landscape of data structures and algorithms.
    • Source code runs with one click, helping readers improve their programming skills through practice and understand how algorithms work and how data structures are implemented.
    • We encourage readers to learn together. Ask questions and share insights in the comments, and make progress through discussion.
    ","path":["Chapter 0.   Preface","0.1   About this book"],"tags":[]},{"location":"chapter_preface/about_the_book/#011-who-this-book-is-for","level":2,"title":"0.1.1   Who this book is for","text":"

    If you are new to algorithms, have never studied them, or have some experience with practice problems but only a hazy understanding of data structures and algorithms—sometimes feeling you understand, sometimes not—this book is written for you!

    If you have already solved a substantial number of problems and know most common problem types, this book can help you review and organize your knowledge. The repository's source code can serve as a “problem-solving toolkit” or an “algorithm dictionary.”

    If you are an expert in algorithms, we welcome your valuable suggestions and invite you to contribute to the book.

    Prerequisites

    You should have basic programming skills in at least one language and be able to read and write simple code.

    ","path":["Chapter 0.   Preface","0.1   About this book"],"tags":[]},{"location":"chapter_preface/about_the_book/#012-organization","level":2,"title":"0.1.2   Organization","text":"

    Figure 0-1 shows the book's main topics.

    • Complexity analysis: Criteria and methods for evaluating data structures and algorithms, including how to derive time and space complexity, common complexity classes, and examples.
    • Data structures: Basic data types and ways to classify data structures; definitions, strengths, weaknesses, common operations, variants, typical applications, and implementations of arrays, linked lists, stacks, queues, hash tables, trees, heaps, graphs, and more.
    • Algorithms: Definitions, strengths, weaknesses, efficiency, applications, solution steps, and example problems for searching, sorting, divide and conquer, backtracking, dynamic programming, greedy algorithms, and more.

    Figure 0-1   The book's main topics

    ","path":["Chapter 0.   Preface","0.1   About this book"],"tags":[]},{"location":"chapter_preface/about_the_book/#013-acknowledgments","level":2,"title":"0.1.3   Acknowledgments","text":"

    This book continues to improve through the collective efforts of many contributors in the open-source community. Thank you to everyone who has invested time and energy in writing it. In the order automatically generated by GitHub, they are: krahets, coderonion, Gonglja, nuomi1, Reanon, justin-tse, hpstory, danielsss, curtishd, night-cruise, S-N-O-R-L-A-X, rongyi, msk397, gvenusleo, khoaxuantu, rivertwilight, K3v123, gyt95, zhuoqinyue, yuelinxin, Zuoxun, mingXta, Phoenix0415, FangYuan33, GN-Yu, longsizhuo, pengchzn, QiLOL, Cathay-Chen, guowei-gong, xBLACKICEx, IsChristina, JoseHung, qualifier1024, hello-ikun, magentaqin, Guanngxu, thomasq0, sunshinesDL, L-Super, Transmigration-zhou, WSL0809, Slone123c, lhxsm, yuan0221, what-is-me, theNefelibatas, Shyam-Chen, sangxiaai, longranger2, codeberg-user, xiongsp, JeffersonHuang, prinpal, seven1240, Wonderdch, malone6, xiaomiusa87, gaofer, bluebean-cloud, a16su, SamJin98, hongyun-robot, nanlei, XiaChuerwu, yd-j, iron-irax, mgisr, steventimes, junminhong, heshuyue, danny900714, Nigh, Dr-XYZ, MolDuM, XC-Zero, reeswell, PXG-XPG, NI-SW, Horbin-Magician, Enlightenus, YangXuanyi, xjr7670, beatrix-chan, DullSword, qq909244296, iStig, boloboloda, hts0000, gledfish, fbigm, echo1937, jiaxianhua, wenjianmin, keshida, kilikilikid, lclc6, lwbaptx, linyejoe2, liuxjerry, szu17dmy, dshlstarr, Yucao-cy, coderlef, czruby, bongbongbakudan, beintentional, ZongYangL, ZhongYuuu, ZhongGuanbin, hezhizhen, linzeyan, ZJKung, JTCPOWI, KawaiiAsh, luluxia, xb534, ztkuaikuai, yw-1021, ElaBosak233, baagod, zhouLion, yishangzhang, yi427, yanedie, yabo083, weibk, wangwang105, th1nk3r-ing, tao363, 4yDX3906, syd168, sslmj2020, smilelsb, siqyka, selear, sdshaoda, Xi-Row, popozhu, nuquist19, noobcodemaker, XiaoK29, chadyi, lyl625760, lucaswangdev, llql1211, 0130w, shanghai-Jerry, EJackYang, Javesun99, eltociear, lipusheng, KNChiu, BlindTerran, ShiMaRing, lovelock, FreddieLi, FloranceYeh, fanchenggang, gltianwen, goerll, nedchu, curly210102, CuB3y0nd, KraHsu, CarrotDLaw, youshaoXG, bubble9um, Asashishi, Asa0oo0o0o, fanenr, eagleanurag, akshiterate, 52coder, foursevenlove, KorsChen, hopkings2008, yang-le, realwujing, Evilrabbit520, Umer-Jahangir, Turing-1024-Lee, Suremotoo, paoxiaomooo, Chieko-Seren, Senrian, Allen-Scai, 19santosh99, ymmmas, Risuntsy, Richard-Zhang1019, RafaelCaso, qingpeng9802, primexiao, Urbaner3, codetypess, nidhoggfgg, MwumLi, CreatorMetaSky, martinx, ZnYang2018, hugtyftg, logan-qiu, psychelzh, Kunchen-Luo, Keynman and KeiichiKasai.

    The code was reviewed by coderonion, curtishd, Gonglja, gvenusleo, hpstory, justin-tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon, and rongyi, listed alphabetically. Thank you for the time and effort you devoted to ensuring consistent, well-written code across languages.

    The English edition was reviewed by yuelinxin, K3v123, magentaqin, QiLOL, Phoenix0415, SamJin98, yanedie, RafaelCaso, pengchzn, and thomasq0; the Japanese edition by eltociear; the Russian edition by И. А. Шевкун and Yuyan Huang; and the Traditional Chinese edition by Shyam-Chen and Dr-XYZ. Their contributions have made the book accessible to a wider readership. Thank you all.

    The tool for generating the book's ePub edition was developed by zhongfq. Thank you for giving readers a more flexible way to read.

    Many people helped me while I was writing this book.

    • Thank you to my mentor at work, Dr. Xi Li. During a long conversation, your encouragement to “get started” strengthened my resolve to write this book;
    • Thank you to my girlfriend Paopao, the book's first reader. Your many suggestions from a beginner's perspective have made it more accessible to newcomers;
    • Thank you to Tengbao, Qibao, and Feibao for giving the book a creative name that brings back fond memories of writing our first line of code, “Hello World!”;
    • Thank you to Xiaoquan for your professional help with intellectual property, which played an important role in improving this open-source book;
    • Thank you to Sutong for designing the beautiful cover and logo, and for patiently revising them many times to satisfy my perfectionism;
    • Thank you to @squidfunk for your typography advice and for developing the open-source documentation theme Material-for-MkDocs.

    While writing, I read many textbooks and articles on data structures and algorithms. These works provided excellent models and helped ensure the accuracy and quality of this book. My thanks go to all the teachers and predecessors whose outstanding contributions made this possible!

    The book advocates learning through both thought and hands-on practice, an approach deeply inspired by Dive into Deep Learning. I warmly recommend this excellent book to every reader.

    My heartfelt thanks to my parents. Your constant support and encouragement have given me the opportunity to do this wonderfully engaging work.

    ","path":["Chapter 0.   Preface","0.1   About this book"],"tags":[]},{"location":"chapter_preface/suggestions/","level":1,"title":"0.2   How to use this book","text":"

    Tip

    For the best reading experience, we recommend reading this section in full.

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#021-writing-conventions","level":2,"title":"0.2.1   Writing conventions","text":"
    • Sections with * after the title are optional and cover more difficult material. If your time is limited, you can skip them at first.
    • Technical terms appear in bold in the print and PDF editions or are underlined in the web edition, such as array. Remembering these terms will help you read technical literature.
    • Key points and summary statements appear in bold and deserve particular attention.
    • Words and phrases used in a specific sense appear in “quotation marks” to avoid ambiguity.
    • When terminology differs between programming languages, this book follows Python; for example, it uses None to represent a null value.
    • For a more compact layout, this book sometimes departs from a language's usual commenting conventions. Comments fall into three main categories: heading comments, explanatory comments, and multiline comments.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    \"\"\"Heading comment identifying a function, class, test case, and so on\"\"\"\n\n# Explanatory comment describing the code in detail\n\n\"\"\"\nMultiline\ncomment\n\"\"\"\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n// Multiline\n// comment\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    /* Heading comment identifying a function, class, test case, and so on */\n\n// Explanatory comment describing the code in detail\n\n/**\n * Multiline\n * comment\n */\n
    ### Heading comment identifying a function, class, test case, and so on ###\n\n# Explanatory comment describing the code in detail\n\n# Multiline\n# comment\n
    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#022-learn-efficiently-with-animated-illustrations","level":2,"title":"0.2.2   Learn efficiently with animated illustrations","text":"

    Compared with text, videos and images convey information in a denser, more structured form that is easier to understand. In this book, key and difficult concepts are presented mainly through animated illustrations, with text providing explanations and additional detail.

    When a passage includes an animated illustration like the one below, focus on the illustration and use the text to support it, drawing on both to understand the material.

    Figure 0-2   An example of an animated illustration

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#023-deepen-your-understanding-through-coding","level":2,"title":"0.2.3   Deepen your understanding through coding","text":"

    The accompanying code is hosted in the GitHub repository. As Figure 0-3 shows, the source code includes test cases and can be run with one click.

    If time permits, we recommend typing out the code yourself while referring to the examples. If your study time is limited, at least read and run all the code.

    Writing code often teaches us more than reading it. Hands-on practice is where real learning happens.

    Figure 0-3   An example of running code

    Getting ready to run the code involves three main steps.

    Step 1: Set up a local programming environment. Follow the tutorial in the appendix. If your environment is already set up, skip this step.

    Step 2: Clone or download the repository. Visit the GitHub repository. If you have Git installed, clone the repository with the following command:

    git clone https://github.com/krahets/hello-algo.git\n

    Alternatively, click “Download ZIP” in the location shown below to download the code as an archive, then extract it on your computer.

    Figure 0-4   Cloning the repository and downloading the code

    Step 3: Run the source code. As shown below, when a code block has a filename at the top, you can find the corresponding source file in the repository's en/codes folder. These files can be run with one click, saving unnecessary debugging time so you can focus on learning.

    Figure 0-5   A code block labeled with its source filename

    In addition to running code locally, the web edition supports visual execution of Python code through pythontutor. Click “Visualize execution” beneath a code block to expand the view and watch the algorithm run. You can also click “View full screen” for a larger display, as shown in Figure 0-6.

    Figure 0-6   Python code visualized in PythonTutor

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#024-learn-together-through-questions-and-discussion","level":2,"title":"0.2.4   Learn together through questions and discussion","text":"

    As you read, do not simply skip concepts you have not understood. You are welcome to ask questions in the comments. My fellow contributors and I will do our best to answer, usually within two days.

    As shown below, every section in the web edition has a comments area at the bottom. I hope you will read it regularly. Other readers' questions can reveal gaps in your own understanding and encourage deeper thought. I also hope you will generously answer their questions, share your insights, and help them make progress.

    Figure 0-7   An example of the comments area

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/suggestions/#025-a-learning-path-for-algorithms","level":2,"title":"0.2.5   A learning path for algorithms","text":"

    Broadly speaking, learning data structures and algorithms can be divided into three stages.

    1. Stage 1: Learn the fundamentals. Become familiar with the properties and uses of data structures, and study how different algorithms work, their steps, applications, and efficiency.
    2. Stage 2: Solve practice problems. We recommend starting with popular problems and solving at least 100 to become familiar with common algorithmic problems. When you first begin, forgetting what you have learned may be a challenge, but this is normal. Review problems according to the “Ebbinghaus forgetting curve”; after 3–5 rounds of repetition, they will usually be firmly in memory. See this GitHub repository for recommended problem lists and practice plans.
    3. Stage 3: Build a systematic understanding. Read algorithm articles, problem-solving frameworks, and textbooks to keep expanding your knowledge. In your practice, try more advanced strategies: group problems by topic, solve one problem in several ways, or apply one solution to several problems. You can find advice on these strategies in various communities.

    As Figure 0-8 shows, this book mainly covers “Stage 1,” helping you approach Stages 2 and 3 more efficiently.

    Figure 0-8   A learning path for algorithms

    ","path":["Chapter 0.   Preface","0.2   How to use this book"],"tags":[]},{"location":"chapter_preface/summary/","level":1,"title":"0.3   Summary","text":"","path":["Chapter 0.   Preface","0.3   Summary"],"tags":[]},{"location":"chapter_preface/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • This book is primarily for beginners in algorithms. If you already have some background, it can help you review your knowledge systematically, and its source code can serve as a toolkit for solving practice problems.
    • The book has three main parts—complexity analysis, data structures, and algorithms—covering most topics in the field.
    • For beginners, reading an introductory book early on is essential and can help avoid many detours.
    • The animated illustrations usually explain key or challenging concepts. Pay particular attention to them as you read.
    • Practice is the best way to learn programming. We strongly recommend running the source code and writing it out yourself.
    • Every section in the web edition has a comments area. You are welcome to share your questions and insights at any time.
    ","path":["Chapter 0.   Preface","0.3   Summary"],"tags":[]},{"location":"chapter_reference/","level":1,"title":"References","text":"

    [1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition).

    [2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition).

    [3] Robert Sedgewick, et al. Algorithms (4th Edition).

    [4] Weimin Yan. Data Structures (C Edition).

    [5] Junhui Deng. Data Structures (C++ Edition, 3rd Edition).

    [6] Mark Allen Weiss; translated by Yue Chen. Data Structures and Algorithm Analysis in Java (3rd Edition).

    [7] Jie Cheng. A Friendly Guide to Data Structures.

    [8] Zheng Wang. The Beauty of Data Structures and Algorithms.

    [9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).

    [10] Aston Zhang, et al. Dive into Deep Learning.

    ","path":["References"],"tags":[]},{"location":"chapter_searching/","level":1,"title":"Chapter 10.   Searching","text":"

    Abstract

    Searching is an adventure into the unknown. We may need to explore every corner of a mysterious space, or we may be able to pinpoint our target quickly.

    On this journey, every exploration may reveal an unexpected answer.

    ","path":["Chapter 10.   Searching"],"tags":[]},{"location":"chapter_searching/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 10.1   Binary search
    • 10.2   Finding an insertion point with binary search
    • 10.3   Finding boundaries with binary search
    • 10.4   Optimizing with hashing
    • 10.5   Search algorithms revisited
    • 10.6   Summary
    • 10.7   Exercises
    ","path":["Chapter 10.   Searching"],"tags":[]},{"location":"chapter_searching/binary_search/","level":1,"title":"10.1   Binary search","text":"

    Binary search is an efficient search algorithm based on divide and conquer. It uses the sorted order of the data to halve the search range each round, continuing until it finds the target element or the search interval becomes empty.

    Question

    Given an array nums of length \\(n\\), with distinct elements arranged in ascending order, find and return the index of target. If the array does not contain this element, return \\(-1\\). An example is shown below.

    Figure 10-1   Example data for binary search

    As shown below, initialize pointers \\(i = 0\\) and \\(j = n - 1\\) to the first and last elements, respectively. They represent the search interval \\([0, n - 1]\\). Square brackets denote a closed interval, which includes its endpoints.

    Next, repeat the following two steps.

    1. Calculate the midpoint index \\(m = \\lfloor {(i + j) / 2} \\rfloor\\), where \\(\\lfloor \\: \\rfloor\\) denotes rounding down.
    2. Compare nums[m] with target. There are three cases.
      1. If nums[m] < target, then target lies in \\([m + 1, j]\\), so set \\(i = m + 1\\).
      2. If nums[m] > target, then target lies in \\([i, m - 1]\\), so set \\(j = m - 1\\).
      3. If nums[m] = target, we have found target, so return index \\(m\\).

    If the array does not contain the target, the search interval eventually becomes empty. Return \\(-1\\) in this case.

    <1><2><3><4><5><6><7>

    Figure 10-2   The binary search process

    Because \\(i\\) and \\(j\\) are both of type int, \\(i + j\\) may exceed the range of int. To avoid integer overflow, we usually calculate the midpoint using \\(m = \\lfloor {i + (j - i) / 2} \\rfloor\\).

    The implementation is shown below:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py
    def binary_search(nums: list[int], target: int) -> int:\n    \"\"\"Binary search (closed interval)\"\"\"\n    # Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    i, j = 0, len(nums) - 1\n    # Loop until the search interval is empty (when i > j)\n    while i <= j:\n        # Python integers can grow arbitrarily large, subject to available memory, so integer overflow is not a concern\n        m = (i + j) // 2  # Calculate the midpoint index m\n        if nums[m] < target:\n            i = m + 1  # In this case, target must be in [m+1, j] if it exists\n        elif nums[m] > target:\n            j = m - 1  # In this case, target must be in [i, m-1] if it exists\n        else:\n            return m  # The target element was found; return its index\n    return -1  # The target element was not found; return -1\n
    binary_search.cpp
    /* Binary search (closed interval) */\nint binarySearch(vector<int> &nums, int target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    int i = 0, j = nums.size() - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.java
    /* Binary search (closed interval) */\nint binarySearch(int[] nums, int target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    int i = 0, j = nums.length - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.cs
    /* Binary search (closed interval) */\nint BinarySearch(int[] nums, int target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    int i = 0, j = nums.Length - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2;   // Calculate the midpoint index m\n        if (nums[m] < target)      // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else                       // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.go
    /* Binary search (closed interval) */\nfunc binarySearch(nums []int, target int) int {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    i, j := 0, len(nums)-1\n    // Loop until the search interval is empty (when i > j)\n    for i <= j {\n        m := i + (j-i)/2      // Calculate the midpoint index m\n        if nums[m] < target { // In this case, target must be in [m+1, j] if it exists\n            i = m + 1\n        } else if nums[m] > target { // In this case, target must be in [i, m-1] if it exists\n            j = m - 1\n        } else { // The target element was found; return its index\n            return m\n        }\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.swift
    /* Binary search (closed interval) */\nfunc binarySearch(nums: [Int], target: Int) -> Int {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    // Loop until the search interval is empty (when i > j)\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target { // In this case, target must be in [m+1, j] if it exists\n            i = m + 1\n        } else if nums[m] > target { // In this case, target must be in [i, m-1] if it exists\n            j = m - 1\n        } else { // The target element was found; return its index\n            return m\n        }\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.js
    /* Binary search (closed interval) */\nfunction binarySearch(nums, target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    let i = 0,\n        j = nums.length - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        // Calculate midpoint index m, using parseInt() to round down\n        const m = parseInt(i + (j - i) / 2);\n        if (nums[m] < target)\n            // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target)\n            // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else return m; // The target element was found; return its index\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.ts
    /* Binary search (closed interval) */\nfunction binarySearch(nums: number[], target: number): number {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    let i = 0,\n        j = nums.length - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        // Calculate the midpoint index m\n        const m = Math.floor(i + (j - i) / 2);\n        if (nums[m] < target) {\n            // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        } else if (nums[m] > target) {\n            // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        } else {\n            // The target element was found; return its index\n            return m;\n        }\n    }\n    return -1; // The target element was not found; return -1\n}\n
    binary_search.dart
    /* Binary search (closed interval) */\nint binarySearch(List<int> nums, int target) {\n  // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n  int i = 0, j = nums.length - 1;\n  // Loop until the search interval is empty (when i > j)\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      // In this case, target must be in [m+1, j] if it exists\n      i = m + 1;\n    } else if (nums[m] > target) {\n      // In this case, target must be in [i, m-1] if it exists\n      j = m - 1;\n    } else {\n      // The target element was found; return its index\n      return m;\n    }\n  }\n  // The target element was not found; return -1\n  return -1;\n}\n
    binary_search.rs
    /* Binary search (closed interval) */\nfn binary_search(nums: &[i32], target: i32) -> i32 {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    let mut i = 0;\n    let mut j = nums.len() as i32 - 1;\n    // Loop until the search interval is empty (when i > j)\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        } else if nums[m as usize] > target {\n            // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        } else {\n            // The target element was found; return its index\n            return m;\n        }\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.c
    /* Binary search (closed interval) */\nint binarySearch(int *nums, int len, int target) {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    int i = 0, j = len - 1;\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // In this case, target must be in [m+1, j] if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.kt
    /* Binary search (closed interval) */\nfun binarySearch(nums: IntArray, target: Int): Int {\n    // Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n    var i = 0\n    var j = nums.size - 1\n    // Loop until the search interval is empty (when i > j)\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) // In this case, target must be in [m+1, j] if it exists\n            i = m + 1\n        else if (nums[m] > target) // In this case, target must be in [i, m-1] if it exists\n            j = m - 1\n        else  // The target element was found; return its index\n            return m\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.rb
    ### Binary search (closed interval) ###\ndef binary_search(nums, target)\n  # Initialize the closed interval [0, n-1], with i and j pointing to the first and last elements\n  i, j = 0, nums.length - 1\n\n  # Loop until the search interval is empty (when i > j)\n  while i <= j\n    # Ruby integers can grow arbitrarily large, limited by available memory, so integer overflow is not a concern here\n    m = (i + j) / 2   # Calculate the midpoint index m\n\n    if nums[m] < target\n      i = m + 1 # In this case, target must be in [m+1, j] if it exists\n    elsif nums[m] > target\n      j = m - 1 # In this case, target must be in [i, m-1] if it exists\n    else\n      return m  # The target element was found; return its index\n    end\n  end\n\n  -1  # The target element was not found; return -1\nend\n
    Visualize execution

    View full screen >

    Time complexity is \\(O(\\log n)\\): the search interval is halved each round, so the loop runs \\(\\log_2 n\\) times.

    Space complexity is \\(O(1)\\): pointers \\(i\\) and \\(j\\) use constant space.

    ","path":["Chapter 10.   Searching","10.1   Binary search"],"tags":[]},{"location":"chapter_searching/binary_search/#1011-ways-to-represent-the-interval","level":2,"title":"10.1.1   Ways to represent the interval","text":"

    Besides the closed interval above, another common representation is a “left-closed, right-open” interval, written \\([0, n)\\). It includes the left endpoint but excludes the right endpoint. With this representation, \\([i, j)\\) is empty when \\(i = j\\).

    We can implement an equivalent binary search algorithm using this representation:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py
    def binary_search_lcro(nums: list[int], target: int) -> int:\n    \"\"\"Binary search (left-closed, right-open interval)\"\"\"\n    # Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    i, j = 0, len(nums)\n    # Loop until the search interval is empty (when i = j)\n    while i < j:\n        m = (i + j) // 2  # Calculate the midpoint index m\n        if nums[m] < target:\n            i = m + 1  # In this case, target must be in [m+1, j) if it exists\n        elif nums[m] > target:\n            j = m  # In this case, target must be in [i, m) if it exists\n        else:\n            return m  # The target element was found; return its index\n    return -1  # The target element was not found; return -1\n
    binary_search.cpp
    /* Binary search (left-closed, right-open interval) */\nint binarySearchLCRO(vector<int> &nums, int target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    int i = 0, j = nums.size();\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.java
    /* Binary search (left-closed, right-open interval) */\nint binarySearchLCRO(int[] nums, int target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    int i = 0, j = nums.length;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.cs
    /* Binary search (left-closed, right-open interval) */\nint BinarySearchLCRO(int[] nums, int target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    int i = 0, j = nums.Length;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2;   // Calculate the midpoint index m\n        if (nums[m] < target)      // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m;\n        else                       // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.go
    /* Binary search (left-closed, right-open interval) */\nfunc binarySearchLCRO(nums []int, target int) int {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    i, j := 0, len(nums)\n    // Loop until the search interval is empty (when i = j)\n    for i < j {\n        m := i + (j-i)/2      // Calculate the midpoint index m\n        if nums[m] < target { // In this case, target must be in [m+1, j) if it exists\n            i = m + 1\n        } else if nums[m] > target { // In this case, target must be in [i, m) if it exists\n            j = m\n        } else { // The target element was found; return its index\n            return m\n        }\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.swift
    /* Binary search (left-closed, right-open interval) */\nfunc binarySearchLCRO(nums: [Int], target: Int) -> Int {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    var i = nums.startIndex\n    var j = nums.endIndex\n    // Loop until the search interval is empty (when i = j)\n    while i < j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target { // In this case, target must be in [m+1, j) if it exists\n            i = m + 1\n        } else if nums[m] > target { // In this case, target must be in [i, m) if it exists\n            j = m\n        } else { // The target element was found; return its index\n            return m\n        }\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.js
    /* Binary search (left-closed, right-open interval) */\nfunction binarySearchLCRO(nums, target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    let i = 0,\n        j = nums.length;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        // Calculate midpoint index m, using parseInt() to round down\n        const m = parseInt(i + (j - i) / 2);\n        if (nums[m] < target)\n            // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target)\n            // In this case, target must be in [i, m) if it exists\n            j = m;\n        // The target element was found; return its index\n        else return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.ts
    /* Binary search (left-closed, right-open interval) */\nfunction binarySearchLCRO(nums: number[], target: number): number {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    let i = 0,\n        j = nums.length;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        // Calculate the midpoint index m\n        const m = Math.floor(i + (j - i) / 2);\n        if (nums[m] < target) {\n            // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        } else if (nums[m] > target) {\n            // In this case, target must be in [i, m) if it exists\n            j = m;\n        } else {\n            // The target element was found; return its index\n            return m;\n        }\n    }\n    return -1; // The target element was not found; return -1\n}\n
    binary_search.dart
    /* Binary search (left-closed, right-open interval) */\nint binarySearchLCRO(List<int> nums, int target) {\n  // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n  int i = 0, j = nums.length;\n  // Loop until the search interval is empty (when i = j)\n  while (i < j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      // In this case, target must be in [m+1, j) if it exists\n      i = m + 1;\n    } else if (nums[m] > target) {\n      // In this case, target must be in [i, m) if it exists\n      j = m;\n    } else {\n      // The target element was found; return its index\n      return m;\n    }\n  }\n  // The target element was not found; return -1\n  return -1;\n}\n
    binary_search.rs
    /* Binary search (left-closed, right-open interval) */\nfn binary_search_lcro(nums: &[i32], target: i32) -> i32 {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    let mut i = 0;\n    let mut j = nums.len() as i32;\n    // Loop until the search interval is empty (when i = j)\n    while i < j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        } else if nums[m as usize] > target {\n            // In this case, target must be in [i, m) if it exists\n            j = m;\n        } else {\n            // The target element was found; return its index\n            return m;\n        }\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.c
    /* Binary search (left-closed, right-open interval) */\nint binarySearchLCRO(int *nums, int len, int target) {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    int i = 0, j = len;\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target)    // In this case, target must be in [m+1, j) if it exists\n            i = m + 1;\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m;\n        else // The target element was found; return its index\n            return m;\n    }\n    // The target element was not found; return -1\n    return -1;\n}\n
    binary_search.kt
    /* Binary search (left-closed, right-open interval) */\nfun binarySearchLCRO(nums: IntArray, target: Int): Int {\n    // Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n    var i = 0\n    var j = nums.size\n    // Loop until the search interval is empty (when i = j)\n    while (i < j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) // In this case, target must be in [m+1, j) if it exists\n            i = m + 1\n        else if (nums[m] > target) // In this case, target must be in [i, m) if it exists\n            j = m\n        else  // The target element was found; return its index\n            return m\n    }\n    // The target element was not found; return -1\n    return -1\n}\n
    binary_search.rb
    ### Binary search (left-closed, right-open interval) ###\ndef binary_search_lcro(nums, target)\n  # Initialize the left-closed, right-open interval [0, n), with i at the first element and j one past the last\n  i, j = 0, nums.length\n\n  # Loop until the search interval is empty (when i = j)\n  while i < j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # In this case, target must be in [m+1, j) if it exists\n    elsif nums[m] > target\n      j = m - 1 # In this case, target must be in [i, m) if it exists\n    else\n      return m  # The target element was found; return its index\n    end\n  end\n\n  -1  # The target element was not found; return -1\nend\n
    Visualize execution

    View full screen >

    As shown below, the two interval representations require different initialization, loop conditions, and operations for narrowing the interval.

    Since a closed interval includes both endpoints, the operations that narrow the interval through pointers \\(i\\) and \\(j\\) are symmetric. This makes mistakes less likely, so we generally recommend the closed-interval version.

    Figure 10-3   Two ways to define the interval

    ","path":["Chapter 10.   Searching","10.1   Binary search"],"tags":[]},{"location":"chapter_searching/binary_search/#1012-strengths-and-limitations","level":2,"title":"10.1.2   Strengths and limitations","text":"

    Binary search performs well in both time and space.

    • Binary search is time-efficient. Its logarithmic time complexity offers a significant advantage for large datasets. For example, when \\(n = 2^{20}\\), linear search requires \\(2^{20} = 1048576\\) iterations, whereas binary search needs only \\(\\log_2 2^{20} = 20\\).
    • Binary search requires no extra storage. It uses less space than search algorithms that rely on additional storage, such as hash lookup.

    However, binary search is not suitable in every situation, mainly for the following reasons.

    • Binary search requires sorted data. Sorting unsorted input solely to perform a binary search is not worthwhile, because sorting usually takes \\(O(n \\log n)\\) time, more than either linear or binary search. When insertions are frequent, preserving the array's order requires inserting each element at a specific position, an expensive \\(O(n)\\) operation.
    • Binary search is suitable only for arrays. It needs to jump between nonadjacent elements, which is inefficient in a linked list. It is therefore unsuitable for linked lists and data structures based on them.
    • Linear search performs better on small datasets. Each iteration of linear search needs only one comparison. Binary search needs one addition, one division, 1–3 comparisons, and one more addition or subtraction, totaling 4–6 elementary operations. When \\(n\\) is small, linear search can therefore be faster.
    ","path":["Chapter 10.   Searching","10.1   Binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/","level":1,"title":"10.3   Finding boundaries with binary search","text":"","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1031-finding-the-left-boundary","level":2,"title":"10.3.1   Finding the left boundary","text":"

    Question

    Given a sorted array nums of length \\(n\\) that may contain duplicate elements, return the index of the leftmost occurrence of target. If the array does not contain this element, return \\(-1\\).

    Recall the method for finding an insertion point with binary search: when the search finishes, \\(i\\) points to the leftmost target. Finding the insertion point therefore amounts to finding the index of the leftmost target.

    We can use the insertion-point function to find the left boundary. However, target may be absent from the array, leading to either of the following outcomes.

    • The insertion index \\(i\\) is out of bounds.
    • nums[i] is not equal to target.

    In either case, simply return \\(-1\\). The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py
    def binary_search_left_edge(nums: list[int], target: int) -> int:\n    \"\"\"Find the leftmost occurrence of target using binary search\"\"\"\n    # This is equivalent to finding the insertion point of target\n    i = binary_search_insertion(nums, target)\n    # If target was not found, return -1\n    if i == len(nums) or nums[i] != target:\n        return -1\n    # Target was found; return index i\n    return i\n
    binary_search_edge.cpp
    /* Find the leftmost occurrence of target using binary search */\nint binarySearchLeftEdge(vector<int> &nums, int target) {\n    // This is equivalent to finding the insertion point of target\n    int i = binarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i == nums.size() || nums[i] != target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.java
    /* Find the leftmost occurrence of target using binary search */\nint binarySearchLeftEdge(int[] nums, int target) {\n    // This is equivalent to finding the insertion point of target\n    int i = binary_search_insertion.binarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i == nums.length || nums[i] != target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.cs
    /* Find the leftmost occurrence of target using binary search */\nint BinarySearchLeftEdge(int[] nums, int target) {\n    // This is equivalent to finding the insertion point of target\n    int i = binary_search_insertion.BinarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i == nums.Length || nums[i] != target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.go
    /* Find the leftmost occurrence of target using binary search */\nfunc binarySearchLeftEdge(nums []int, target int) int {\n    // This is equivalent to finding the insertion point of target\n    i := binarySearchInsertion(nums, target)\n    // If target was not found, return -1\n    if i == len(nums) || nums[i] != target {\n        return -1\n    }\n    // Target was found; return index i\n    return i\n}\n
    binary_search_edge.swift
    /* Find the leftmost occurrence of target using binary search */\nfunc binarySearchLeftEdge(nums: [Int], target: Int) -> Int {\n    // This is equivalent to finding the insertion point of target\n    let i = binarySearchInsertion(nums: nums, target: target)\n    // If target was not found, return -1\n    if i == nums.endIndex || nums[i] != target {\n        return -1\n    }\n    // Target was found; return index i\n    return i\n}\n
    binary_search_edge.js
    /* Find the leftmost occurrence of target using binary search */\nfunction binarySearchLeftEdge(nums, target) {\n    // This is equivalent to finding the insertion point of target\n    const i = binarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i === nums.length || nums[i] !== target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.ts
    /* Find the leftmost occurrence of target using binary search */\nfunction binarySearchLeftEdge(nums: Array<number>, target: number): number {\n    // This is equivalent to finding the insertion point of target\n    const i = binarySearchInsertion(nums, target);\n    // If target was not found, return -1\n    if (i === nums.length || nums[i] !== target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.dart
    /* Find the leftmost occurrence of target using binary search */\nint binarySearchLeftEdge(List<int> nums, int target) {\n  // This is equivalent to finding the insertion point of target\n  int i = binarySearchInsertion(nums, target);\n  // If target was not found, return -1\n  if (i == nums.length || nums[i] != target) {\n    return -1;\n  }\n  // Target was found; return index i\n  return i;\n}\n
    binary_search_edge.rs
    /* Find the leftmost occurrence of target using binary search */\nfn binary_search_left_edge(nums: &[i32], target: i32) -> i32 {\n    // This is equivalent to finding the insertion point of target\n    let i = binary_search_insertion(nums, target);\n    // If target was not found, return -1\n    if i == nums.len() as i32 || nums[i as usize] != target {\n        return -1;\n    }\n    // Target was found; return index i\n    i\n}\n
    binary_search_edge.c
    /* Find the leftmost occurrence of target using binary search */\nint binarySearchLeftEdge(int *nums, int numSize, int target) {\n    // This is equivalent to finding the insertion point of target\n    int i = binarySearchInsertion(nums, numSize, target);\n    // If target was not found, return -1\n    if (i == numSize || nums[i] != target) {\n        return -1;\n    }\n    // Target was found; return index i\n    return i;\n}\n
    binary_search_edge.kt
    /* Find the leftmost occurrence of target using binary search */\nfun binarySearchLeftEdge(nums: IntArray, target: Int): Int {\n    // This is equivalent to finding the insertion point of target\n    val i = binarySearchInsertion(nums, target)\n    // If target was not found, return -1\n    if (i == nums.size || nums[i] != target) {\n        return -1\n    }\n    // Target was found; return index i\n    return i\n}\n
    binary_search_edge.rb
    ### Find the leftmost occurrence of target using binary search ###\ndef binary_search_left_edge(nums, target)\n  # This is equivalent to finding the insertion point of target\n  i = binary_search_insertion(nums, target)\n\n  # If target was not found, return -1\n  return -1 if i == nums.length || nums[i] != target\n\n  i # Target was found; return index i\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1032-finding-the-right-boundary","level":2,"title":"10.3.2   Finding the right boundary","text":"

    How can we find the rightmost target? The most direct approach is to modify the pointer update for nums[m] == target. We omit the code here; interested readers can implement it themselves.

    Here are two shortcuts.

    ","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1-reusing-the-left-boundary-search","level":3,"title":"1.   Reusing the left-boundary search","text":"

    We can use the function for finding the leftmost element to find the rightmost one: turn the search for the rightmost target into a search for the leftmost target + 1.

    As shown below, when the search finishes, pointer \\(i\\) points to the leftmost target + 1, if it exists, while \\(j\\) points to the rightmost target. We therefore return \\(j\\).

    Figure 10-7   Turning a right-boundary search into a left-boundary search

    The returned insertion point is \\(i\\), so subtract \\(1\\) to obtain \\(j\\):

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py
    def binary_search_right_edge(nums: list[int], target: int) -> int:\n    \"\"\"Find the rightmost occurrence of target using binary search\"\"\"\n    # Reduce the problem to finding the leftmost insertion point of target + 1\n    i = binary_search_insertion(nums, target + 1)\n    # j is the candidate rightmost target index; i is the index of the first element greater than target, or len(nums)\n    j = i - 1\n    # If target was not found, return -1\n    if j == -1 or nums[j] != target:\n        return -1\n    # Target was found; return index j\n    return j\n
    binary_search_edge.cpp
    /* Find the rightmost occurrence of target using binary search */\nint binarySearchRightEdge(vector<int> &nums, int target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    int i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    int j = i - 1;\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.java
    /* Find the rightmost occurrence of target using binary search */\nint binarySearchRightEdge(int[] nums, int target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    int i = binary_search_insertion.binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    int j = i - 1;\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.cs
    /* Find the rightmost occurrence of target using binary search */\nint BinarySearchRightEdge(int[] nums, int target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1);\n    // j is the candidate index of the rightmost target; i is the insertion point after all elements <= target\n    int j = i - 1;\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.go
    /* Find the rightmost occurrence of target using binary search */\nfunc binarySearchRightEdge(nums []int, target int) int {\n    // For integer values, find the leftmost insertion point of target + 1 (assuming no overflow)\n    i := binarySearchInsertion(nums, target+1)\n    // i is the index of the first element greater than target (or len(nums)); j is the candidate index of the rightmost target\n    j := i - 1\n    // If target was not found, return -1\n    if j == -1 || nums[j] != target {\n        return -1\n    }\n    // Target was found; return index j\n    return j\n}\n
    binary_search_edge.swift
    /* Find the rightmost occurrence of target using binary search */\nfunc binarySearchRightEdge(nums: [Int], target: Int) -> Int {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    let i = binarySearchInsertion(nums: nums, target: target + 1)\n    // i is the first index with a value greater than target (or nums.endIndex); j is the candidate index of the rightmost target\n    let j = i - 1\n    // If target was not found, return -1\n    if j == -1 || nums[j] != target {\n        return -1\n    }\n    // Target was found; return index j\n    return j\n}\n
    binary_search_edge.js
    /* Find the rightmost occurrence of target using binary search */\nfunction binarySearchRightEdge(nums, target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    const i = binarySearchInsertion(nums, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    const j = i - 1;\n    // If target was not found, return -1\n    if (j === -1 || nums[j] !== target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.ts
    /* Find the rightmost occurrence of target using binary search */\nfunction binarySearchRightEdge(nums: Array<number>, target: number): number {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    const i = binarySearchInsertion(nums, target + 1);\n    // For integer elements, i is the first index greater than target (or the array length); j is the candidate rightmost target index\n    const j = i - 1;\n    // If target was not found, return -1\n    if (j === -1 || nums[j] !== target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.dart
    /* Find the rightmost occurrence of target using binary search */\nint binarySearchRightEdge(List<int> nums, int target) {\n  // Reduce the problem to finding the leftmost insertion point of target + 1\n  int i = binarySearchInsertion(nums, target + 1);\n  // i is the first index greater than target (or the array length); j is the candidate rightmost target index\n  int j = i - 1;\n  // If target was not found, return -1\n  if (j == -1 || nums[j] != target) {\n    return -1;\n  }\n  // Target was found; return index j\n  return j;\n}\n
    binary_search_edge.rs
    /* Find the rightmost occurrence of target using binary search */\nfn binary_search_right_edge(nums: &[i32], target: i32) -> i32 {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    let i = binary_search_insertion(nums, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    let j = i - 1;\n    // If target was not found, return -1\n    if j == -1 || nums[j as usize] != target {\n        return -1;\n    }\n    // Target was found; return index j\n    j\n}\n
    binary_search_edge.c
    /* Find the rightmost occurrence of target using binary search */\nint binarySearchRightEdge(int *nums, int numSize, int target) {\n    // Reduce the problem to finding the leftmost insertion point of target + 1\n    int i = binarySearchInsertion(nums, numSize, target + 1);\n    // j points to the rightmost target, while i points to the first element greater than target\n    int j = i - 1;\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1;\n    }\n    // Target was found; return index j\n    return j;\n}\n
    binary_search_edge.kt
    /* Find the rightmost occurrence of target using binary search */\nfun binarySearchRightEdge(nums: IntArray, target: Int): Int {\n    // For integer values, find the leftmost insertion point of target + 1 (assuming no overflow)\n    val i = binarySearchInsertion(nums, target + 1)\n    // i is the index of the first element greater than target (or the array length); j is the candidate rightmost target index\n    val j = i - 1\n    // If target was not found, return -1\n    if (j == -1 || nums[j] != target) {\n        return -1\n    }\n    // Target was found; return index j\n    return j\n}\n
    binary_search_edge.rb
    ### Find the rightmost occurrence of target using binary search ###\ndef binary_search_right_edge(nums, target)\n  # For integer elements, find the leftmost insertion point of target + 1\n  i = binary_search_insertion(nums, target + 1)\n\n  # i is the index of the first element greater than target (or the array length); j is the candidate rightmost target index\n  j = i - 1\n\n  # If target was not found, return -1\n  return -1 if j == -1 || nums[j] != target\n\n  j # Target was found; return index j\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#2-turning-a-boundary-search-into-an-element-search","level":3,"title":"2.   Turning a boundary search into an element search","text":"

    When the array does not contain target, \\(i\\) ultimately points to the first element greater than target, while \\(j\\) points to the rightmost element smaller than target.

    As shown below, we can therefore construct a value absent from the array and use it to find the left and right boundaries.

    • To find the leftmost target, search for target - 0.5 and return pointer \\(i\\).
    • To find the rightmost target, search for target + 0.5 and return pointer \\(j\\).

    Figure 10-8   Turning a boundary search into an element search

    The code is omitted here, but keep two points in mind.

    • The given array contains no fractional values, so we do not need to handle equality.
    • Since this method introduces fractional values, change the function's target variable to a floating-point type. No change is needed in Python.
    ","path":["Chapter 10.   Searching","10.3   Finding boundaries with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/","level":1,"title":"10.2   Finding an insertion point with binary search","text":"

    Binary search can do more than find a target element. It also solves many variations of the problem, such as finding where to insert a target.

    ","path":["Chapter 10.   Searching","10.2   Finding an insertion point with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1021-when-there-are-no-duplicates","level":2,"title":"10.2.1   When there are no duplicates","text":"

    Question

    Given a sorted array nums of length \\(n\\) with no duplicate elements and a value target, insert target into nums while preserving its order. If target is already present, insert the new value to its left. Return the index of the newly inserted target, as illustrated below.

    Figure 10-4   Example data for finding an insertion point

    To reuse the binary search code from the previous section, we need to answer two questions.

    Question 1: If the array contains target, is the insertion index the index of that element?

    The problem asks us to insert target to the left of the equal element, so the new target takes the original one's position. Thus, if the array contains target, the insertion index is the index of that target.

    Question 2: If the array does not contain target, which index is the insertion point?

    Consider the search process more closely. Pointer \\(i\\) moves when nums[m] < target, so \\(i\\) approaches elements greater than or equal to target. Similarly, pointer \\(j\\) keeps approaching elements less than or equal to target.

    When the search ends, \\(i\\) points to the first element greater than target, and \\(j\\) points to the rightmost element smaller than target. Thus, when target is absent, the insertion index is \\(i\\). The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py
    def binary_search_insertion_simple(nums: list[int], target: int) -> int:\n    \"\"\"Find an insertion point using binary search (no duplicates)\"\"\"\n    i, j = 0, len(nums) - 1  # Initialize the closed interval [0, n-1]\n    while i <= j:\n        m = (i + j) // 2  # Calculate the midpoint index m\n        if nums[m] < target:\n            i = m + 1  # Target must be in [m+1, j] if it exists\n        elif nums[m] > target:\n            j = m - 1  # Target must be in [i, m-1] if it exists\n        else:\n            return m  # Target was found; return insertion point m\n    # Target was not found; return insertion point i\n    return i\n
    binary_search_insertion.cpp
    /* Find an insertion point using binary search (no duplicates) */\nint binarySearchInsertionSimple(vector<int> &nums, int target) {\n    int i = 0, j = nums.size() - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.java
    /* Find an insertion point using binary search (no duplicates) */\nint binarySearchInsertionSimple(int[] nums, int target) {\n    int i = 0, j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.cs
    /* Find an insertion point using binary search (no duplicates) */\nint BinarySearchInsertionSimple(int[] nums, int target) {\n    int i = 0, j = nums.Length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.go
    /* Find an insertion point using binary search (no duplicates) */\nfunc binarySearchInsertionSimple(nums []int, target int) int {\n    // Initialize the closed interval [0, n-1]\n    i, j := 0, len(nums)-1\n    for i <= j {\n        // Calculate the midpoint index m\n        m := i + (j-i)/2\n        if nums[m] < target {\n            // Target must be in [m+1, j] if it exists\n            i = m + 1\n        } else if nums[m] > target {\n            // Target must be in [i, m-1] if it exists\n            j = m - 1\n        } else {\n            // Target was found; return insertion point m\n            return m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i\n}\n
    binary_search_insertion.swift
    /* Find an insertion point using binary search (no duplicates) */\nfunc binarySearchInsertionSimple(nums: [Int], target: Int) -> Int {\n    // Initialize the closed interval [0, n-1]\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target {\n            i = m + 1 // Target must be in [m+1, j] if it exists\n        } else if nums[m] > target {\n            j = m - 1 // Target must be in [i, m-1] if it exists\n        } else {\n            return m // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i\n}\n
    binary_search_insertion.js
    /* Find an insertion point using binary search (no duplicates) */\nfunction binarySearchInsertionSimple(nums, target) {\n    let i = 0,\n        j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, using Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.ts
    /* Find an insertion point using binary search (no duplicates) */\nfunction binarySearchInsertionSimple(\n    nums: Array<number>,\n    target: number\n): number {\n    let i = 0,\n        j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, using Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.dart
    /* Find an insertion point using binary search (no duplicates) */\nint binarySearchInsertionSimple(List<int> nums, int target) {\n  int i = 0, j = nums.length - 1; // Initialize the closed interval [0, n-1]\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      i = m + 1; // Target must be in [m+1, j] if it exists\n    } else if (nums[m] > target) {\n      j = m - 1; // Target must be in [i, m-1] if it exists\n    } else {\n      return m; // Target was found; return insertion point m\n    }\n  }\n  // Target was not found; return insertion point i\n  return i;\n}\n
    binary_search_insertion.rs
    /* Find an insertion point using binary search (no duplicates) */\nfn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 {\n    let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize the closed interval [0, n-1]\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if nums[m as usize] > target {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m;\n        }\n    }\n    // Target was not found; return insertion point i\n    i\n}\n
    binary_search_insertion.c
    /* Find an insertion point using binary search (no duplicates) */\nint binarySearchInsertionSimple(int *nums, int numSize, int target) {\n    int i = 0, j = numSize - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            return m; // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i;\n}\n
    binary_search_insertion.kt
    /* Find an insertion point using binary search (no duplicates) */\nfun binarySearchInsertionSimple(nums: IntArray, target: Int): Int {\n    var i = 0\n    var j = nums.size - 1 // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1 // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1 // Target must be in [i, m-1] if it exists\n        } else {\n            return m // Target was found; return insertion point m\n        }\n    }\n    // Target was not found; return insertion point i\n    return i\n}\n
    binary_search_insertion.rb
    ### Find an insertion point using binary search (no duplicates) ###\ndef binary_search_insertion_simple(nums, target)\n  # Initialize the closed interval [0, n-1]\n  i, j = 0, nums.length - 1\n\n  while i <= j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # Target must be in [m+1, j] if it exists\n    elsif nums[m] > target\n      j = m - 1 # Target must be in [i, m-1] if it exists\n    else\n      return m  # Target was found; return insertion point m\n    end\n  end\n\n  i # Target was not found; return insertion point i\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 10.   Searching","10.2   Finding an insertion point with binary search"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1022-when-duplicates-are-present","level":2,"title":"10.2.2   When duplicates are present","text":"

    Question

    Modify the previous problem to allow duplicate elements in the array. All other requirements remain the same.

    If the array contains multiple occurrences of target, ordinary binary search can return the index of one occurrence, but cannot determine how many more occurrences lie to its left or right.

    The target must be inserted as far left as possible, so we need the index of the leftmost target. One initial approach follows the steps below.

    1. Run binary search to find the index of any target, denoted by \\(k\\).
    2. Starting at index \\(k\\), scan leftward and return when the leftmost target is reached.

    Figure 10-5   Finding the insertion point among duplicates with a linear scan

    Although this works, it includes a linear search and therefore takes \\(O(n)\\) time. It is inefficient when the array contains many occurrences of target.

    Now consider extending binary search itself. As shown below, the overall process remains the same: each round calculates midpoint index \\(m\\) and compares target with nums[m]. The cases are as follows.

    • If nums[m] < target or nums[m] > target, we have not found target. Narrow the interval as in ordinary binary search, bringing pointers \\(i\\) and \\(j\\) closer to target.
    • If nums[m] == target, elements smaller than target lie in \\([i, m - 1]\\). Narrow the interval by setting \\(j = m - 1\\), bringing pointer \\(j\\) closer to elements smaller than target.

    When the loop finishes, \\(i\\) points to the leftmost target, and \\(j\\) points to the rightmost element smaller than target. Index \\(i\\) is therefore the insertion point.

    <1><2><3><4><5><6><7><8>

    Figure 10-6   Steps for finding an insertion point among duplicates

    In the code below, the branches for nums[m] > target and nums[m] == target perform the same operation, so they can be combined.

    Even so, we can leave them separate to make the logic clearer and easier to read.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py
    def binary_search_insertion(nums: list[int], target: int) -> int:\n    \"\"\"Find an insertion point using binary search (with duplicates)\"\"\"\n    i, j = 0, len(nums) - 1  # Initialize the closed interval [0, n-1]\n    while i <= j:\n        m = (i + j) // 2  # Calculate the midpoint index m\n        if nums[m] < target:\n            i = m + 1  # Target must be in [m+1, j] if it exists\n        elif nums[m] > target:\n            j = m - 1  # Target must be in [i, m-1] if it exists\n        else:\n            j = m - 1  # Continue in [i, m-1] to find the rightmost element smaller than target\n    # Return insertion point i\n    return i\n
    binary_search_insertion.cpp
    /* Find an insertion point using binary search (with duplicates) */\nint binarySearchInsertion(vector<int> &nums, int target) {\n    int i = 0, j = nums.size() - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue searching [i, m-1] for the insertion point before any equal elements\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.java
    /* Find an insertion point using binary search (with duplicates) */\nint binarySearchInsertion(int[] nums, int target) {\n    int i = 0, j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.cs
    /* Find an insertion point using binary search (with duplicates) */\nint BinarySearchInsertion(int[] nums, int target) {\n    int i = 0, j = nums.Length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.go
    /* Find an insertion point using binary search (with duplicates) */\nfunc binarySearchInsertion(nums []int, target int) int {\n    // Initialize the closed interval [0, n-1]\n    i, j := 0, len(nums)-1\n    for i <= j {\n        // Calculate the midpoint index m\n        m := i + (j-i)/2\n        if nums[m] < target {\n            // Target must be in [m+1, j] if it exists\n            i = m + 1\n        } else if nums[m] > target {\n            // Target must be in [i, m-1] if it exists\n            j = m - 1\n        } else {\n            // Continue in [i, m-1] to find the rightmost element smaller than target\n            j = m - 1\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
    binary_search_insertion.swift
    /* Find an insertion point using binary search (with duplicates) */\nfunc binarySearchInsertion(nums: [Int], target: Int) -> Int {\n    // Initialize the closed interval [0, n-1]\n    var i = nums.startIndex\n    var j = nums.endIndex - 1\n    while i <= j {\n        let m = i + (j - i) / 2 // Calculate the midpoint index m\n        if nums[m] < target {\n            i = m + 1 // Target must be in [m+1, j] if it exists\n        } else if nums[m] > target {\n            j = m - 1 // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1 // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
    binary_search_insertion.js
    /* Find an insertion point using binary search (with duplicates) */\nfunction binarySearchInsertion(nums, target) {\n    let i = 0,\n        j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, using Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.ts
    /* Find an insertion point using binary search (with duplicates) */\nfunction binarySearchInsertion(nums: Array<number>, target: number): number {\n    let i = 0,\n        j = nums.length - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        const m = Math.floor(i + (j - i) / 2); // Calculate midpoint index m, using Math.floor() to round down\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.dart
    /* Find an insertion point using binary search (with duplicates) */\nint binarySearchInsertion(List<int> nums, int target) {\n  int i = 0, j = nums.length - 1; // Initialize the closed interval [0, n-1]\n  while (i <= j) {\n    int m = i + (j - i) ~/ 2; // Calculate the midpoint index m\n    if (nums[m] < target) {\n      i = m + 1; // Target must be in [m+1, j] if it exists\n    } else if (nums[m] > target) {\n      j = m - 1; // Target must be in [i, m-1] if it exists\n    } else {\n      j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n    }\n  }\n  // Return insertion point i\n  return i;\n}\n
    binary_search_insertion.rs
    /* Find an insertion point using binary search (with duplicates) */\npub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 {\n    let (mut i, mut j) = (0, nums.len() as i32 - 1); // Initialize the closed interval [0, n-1]\n    while i <= j {\n        let m = i + (j - i) / 2; // Calculate the midpoint index m\n        if nums[m as usize] < target {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if nums[m as usize] > target {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue searching [i, m-1] for the insertion point before any equal elements\n        }\n    }\n    // Return insertion point i\n    i\n}\n
    binary_search_insertion.c
    /* Find an insertion point using binary search (with duplicates) */\nint binarySearchInsertion(int *nums, int numSize, int target) {\n    int i = 0, j = numSize - 1; // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        int m = i + (j - i) / 2; // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1; // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1; // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1; // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i;\n}\n
    binary_search_insertion.kt
    /* Find an insertion point using binary search (with duplicates) */\nfun binarySearchInsertion(nums: IntArray, target: Int): Int {\n    var i = 0\n    var j = nums.size - 1 // Initialize the closed interval [0, n-1]\n    while (i <= j) {\n        val m = i + (j - i) / 2 // Calculate the midpoint index m\n        if (nums[m] < target) {\n            i = m + 1 // Target must be in [m+1, j] if it exists\n        } else if (nums[m] > target) {\n            j = m - 1 // Target must be in [i, m-1] if it exists\n        } else {\n            j = m - 1 // Continue in [i, m-1] to find the rightmost element smaller than target\n        }\n    }\n    // Return insertion point i\n    return i\n}\n
    binary_search_insertion.rb
    ### Find an insertion point using binary search (with duplicates) ###\ndef binary_search_insertion(nums, target)\n  # Initialize the closed interval [0, n-1]\n  i, j = 0, nums.length - 1\n\n  while i <= j\n    # Calculate the midpoint index m\n    m = (i + j) / 2\n\n    if nums[m] < target\n      i = m + 1 # Target must be in [m+1, j] if it exists\n    elsif nums[m] > target\n      j = m - 1 # Target must be in [i, m-1] if it exists\n    else\n      j = m - 1 # Continue in [i, m-1] to find the rightmost element smaller than target\n    end\n  end\n\n  i # Return insertion point i\nend\n
    Visualize execution

    View full screen >

    Tip

    All the code in this section uses closed intervals. Interested readers can implement versions using left-closed, right-open intervals.

    Viewed more broadly, binary search simply assigns a search goal to each of pointers \\(i\\) and \\(j\\). The goal may be a particular element, such as target, or a range of elements, such as those smaller than target.

    As the interval is repeatedly halved, pointers \\(i\\) and \\(j\\) approach their assigned goals. Eventually they either find the answer or stop after crossing a boundary.

    ","path":["Chapter 10.   Searching","10.2   Finding an insertion point with binary search"],"tags":[]},{"location":"chapter_searching/exercises/","level":1,"title":"10.7   Exercises","text":"","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#1071-check-your-understanding","level":2,"title":"10.7.1   Check your understanding","text":"","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#1-how-binary-search-narrows-the-search-interval","level":3,"title":"1.   How binary search narrows the search interval","text":"

    Search for 16 in the sorted array [2, 5, 8, 12, 16, 23, 38]. Use a closed interval [i, j] and the midpoint \\(m=i+(j-i)/2\\), rounded down.

    For each iteration, write (i, j, m), the midpoint element, and how the interval is narrowed next, until the target is found.

    Reference answer

    The search proceeds as follows:

    Iteration (i, j, m) Midpoint element Next step 1 (0, 6, 3) 12 12 < 16, so set i = 4 2 (4, 6, 5) 23 23 > 16, so set j = 4 3 (4, 4, 4) 16 Target found; return index 4

    Since the array is sorted, when the midpoint value is below the target, we can exclude the midpoint and everything to its left. When it is above the target, we can exclude the midpoint and everything to its right.

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#2-left-and-right-boundaries-of-repeated-elements","level":3,"title":"2.   Left and right boundaries of repeated elements","text":"

    Search for 2 in the array [1, 2, 2, 2, 4, 6]. A student uses binary search, finds the target at index 2, and immediately returns, saying: “Index 2 is the left boundary of the number 2.”

    1. Is the student correct? What are the left and right boundaries of 2? Explain.
    2. When finding the left boundary, which side should we continue searching if the midpoint element equals the target?
    3. Which side should we continue searching when finding the right boundary? State only the direction; you do not need to give the full search process.
    Reference answer
    1. The student is incorrect. Returning immediately upon finding a 2 guarantees only that some occurrence has been found, not the leftmost or rightmost one. Here, the left boundary is index 1 and the right boundary is index 3.

    2. When finding the left boundary, continue searching to the left even if the midpoint element equals 2. For example, with a closed interval, set j = m - 1.

    3. When finding the right boundary, continue searching to the right after the midpoint element equals 2. For example, set i = m + 1.

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#3-choosing-a-search-method-for-different-data","level":3,"title":"3.   Choosing a search method for different data","text":"

    Choose a suitable method from linear search, binary search, and a hash table for each situation, and explain:

    1. Repeatedly searching \\(10^7\\) integers that are already sorted and will not change, without building an additional data structure.
    2. Repeatedly checking whether a key exists in a data set with frequent insertions and deletions, with no need to preserve order or perform range queries.
    3. Searching for a value only once in an unsorted array.
    Reference answer
    1. Binary search: the data is sorted and static, giving \\(O(\\log n)\\) time without an additional data structure.
    2. A hash table: when the hash function distributes keys fairly evenly among buckets, insertion, deletion, and key lookup all have average time complexity \\(O(1)\\).
    3. Scan directly from beginning to end. For a single search, sorting or building a hash table would first require processing the entire array, without reducing the total work for this task.

      The choice depends on whether the data is sorted, whether additional structures are allowed, how many queries there are, and which operations are needed.

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#1072-programming-exercises","level":2,"title":"10.7.2   Programming exercises","text":"","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#1-binary-search-in-a-sorted-array","level":3,"title":"1.   Binary search in a sorted array","text":"

    Given an integer array nums in strictly increasing order and a target value target, find target using binary search. Return its array index if it exists, or -1 otherwise.

    Hints
    1. The initial interval has left = 0 and right = n - 1; it is nonempty while left <= right.
    2. Calculate the midpoint with mid = left + (right - left) // 2.
    3. If nums[mid] is less than target, move the left boundary to mid + 1; if nums[mid] is greater than target, move the right boundary to mid - 1; return immediately if they are equal.

    LeetCode

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/exercises/#2-finding-an-insertion-position-in-a-sorted-array","level":3,"title":"2.   Finding an insertion position in a sorted array","text":"

    You are given an integer array nums in strictly increasing order and a target value target.

    If target is already in the array, return its index. Otherwise, return the position at which target can be inserted while preserving strictly increasing order. The answer may be 0 or the array length. Use binary search.

    Hints
    1. The answer may be 0 or the array length n.
    2. With a closed interval, if nums[mid] is greater than or equal to target, set right = mid - 1 to keep checking farther left; otherwise, set left = mid + 1.
    3. When the loop ends, left is the insertion position.

    LeetCode

    ","path":["Chapter 10.   Searching","10.7   Exercises"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/","level":1,"title":"10.4   Optimizing with hashing","text":"

    In algorithm problems, we often reduce time complexity by replacing linear search with hash lookup. Let us explore this strategy through a problem.

    Question

    Given an integer array nums and a target value target, find two elements whose sum equals target and return their array indices. Any valid solution is acceptable.

    ","path":["Chapter 10.   Searching","10.4   Optimizing with hashing"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1041-linear-search-trading-time-for-space","level":2,"title":"10.4.1   Linear search: trading time for space","text":"

    Consider examining every possible pair directly. As shown below, use two nested loops and check whether the two integers sum to target each time. If they do, return their indices.

    Figure 10-9   Solving two sum with linear search

    The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py
    def two_sum_brute_force(nums: list[int], target: int) -> list[int]:\n    \"\"\"Method 1: brute-force enumeration\"\"\"\n    # Two nested loops give a time complexity of O(n^2)\n    for i in range(len(nums) - 1):\n        for j in range(i + 1, len(nums)):\n            if nums[i] + nums[j] == target:\n                return [i, j]\n    return []\n
    two_sum.cpp
    /* Method 1: brute-force enumeration */\nvector<int> twoSumBruteForce(vector<int> &nums, int target) {\n    int size = nums.size();\n    // Two nested loops give a time complexity of O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return {i, j};\n        }\n    }\n    return {};\n}\n
    two_sum.java
    /* Method 1: brute-force enumeration */\nint[] twoSumBruteForce(int[] nums, int target) {\n    int size = nums.length;\n    // Two nested loops give a time complexity of O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return new int[] { i, j };\n        }\n    }\n    return new int[0];\n}\n
    two_sum.cs
    /* Method 1: brute-force enumeration */\nint[] TwoSumBruteForce(int[] nums, int target) {\n    int size = nums.Length;\n    // Two nested loops give a time complexity of O(n^2)\n    for (int i = 0; i < size - 1; i++) {\n        for (int j = i + 1; j < size; j++) {\n            if (nums[i] + nums[j] == target)\n                return [i, j];\n        }\n    }\n    return [];\n}\n
    two_sum.go
    /* Method 1: brute-force enumeration */\nfunc twoSumBruteForce(nums []int, target int) []int {\n    size := len(nums)\n    // Two nested loops give a time complexity of O(n^2)\n    for i := 0; i < size-1; i++ {\n        for j := i + 1; j < size; j++ {\n            if nums[i]+nums[j] == target {\n                return []int{i, j}\n            }\n        }\n    }\n    return nil\n}\n
    two_sum.swift
    /* Method 1: brute-force enumeration */\nfunc twoSumBruteForce(nums: [Int], target: Int) -> [Int] {\n    // Two nested loops give a time complexity of O(n^2)\n    for i in nums.indices.dropLast() {\n        for j in nums.indices.dropFirst(i + 1) {\n            if nums[i] + nums[j] == target {\n                return [i, j]\n            }\n        }\n    }\n    return [0]\n}\n
    two_sum.js
    /* Method 1: brute-force enumeration */\nfunction twoSumBruteForce(nums, target) {\n    const n = nums.length;\n    // Two nested loops give a time complexity of O(n^2)\n    for (let i = 0; i < n; i++) {\n        for (let j = i + 1; j < n; j++) {\n            if (nums[i] + nums[j] === target) {\n                return [i, j];\n            }\n        }\n    }\n    return [];\n}\n
    two_sum.ts
    /* Method 1: brute-force enumeration */\nfunction twoSumBruteForce(nums: number[], target: number): number[] {\n    const n = nums.length;\n    // Two nested loops give a time complexity of O(n^2)\n    for (let i = 0; i < n; i++) {\n        for (let j = i + 1; j < n; j++) {\n            if (nums[i] + nums[j] === target) {\n                return [i, j];\n            }\n        }\n    }\n    return [];\n}\n
    two_sum.dart
    /* Method 1: brute-force enumeration */\nList<int> twoSumBruteForce(List<int> nums, int target) {\n  int size = nums.length;\n  // Two nested loops give a time complexity of O(n^2)\n  for (var i = 0; i < size - 1; i++) {\n    for (var j = i + 1; j < size; j++) {\n      if (nums[i] + nums[j] == target) return [i, j];\n    }\n  }\n  return [0];\n}\n
    two_sum.rs
    /* Method 1: brute-force enumeration */\npub fn two_sum_brute_force(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {\n    let size = nums.len();\n    // Two nested loops give a time complexity of O(n^2)\n    for i in 0..size - 1 {\n        for j in i + 1..size {\n            if nums[i] + nums[j] == target {\n                return Some(vec![i as i32, j as i32]);\n            }\n        }\n    }\n    None\n}\n
    two_sum.c
    /* Method 1: brute-force enumeration */\nint *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) {\n    for (int i = 0; i < numsSize; ++i) {\n        for (int j = i + 1; j < numsSize; ++j) {\n            if (nums[i] + nums[j] == target) {\n                int *res = malloc(sizeof(int) * 2);\n                res[0] = i, res[1] = j;\n                *returnSize = 2;\n                return res;\n            }\n        }\n    }\n    *returnSize = 0;\n    return NULL;\n}\n
    two_sum.kt
    /* Method 1: brute-force enumeration */\nfun twoSumBruteForce(nums: IntArray, target: Int): IntArray {\n    val size = nums.size\n    // Two nested loops give a time complexity of O(n^2)\n    for (i in 0..<size - 1) {\n        for (j in i + 1..<size) {\n            if (nums[i] + nums[j] == target) return intArrayOf(i, j)\n        }\n    }\n    return IntArray(0)\n}\n
    two_sum.rb
    ### Method 1: brute-force enumeration ###\ndef two_sum_brute_force(nums, target)\n  # Two nested loops give a time complexity of O(n^2)\n  for i in 0...(nums.length - 1)\n    for j in (i + 1)...nums.length\n      return [i, j] if nums[i] + nums[j] == target\n    end\n  end\n\n  []\nend\n
    Visualize execution

    View full screen >

    This approach takes \\(O(n^2)\\) time and \\(O(1)\\) space, making it very slow for large datasets.

    ","path":["Chapter 10.   Searching","10.4   Optimizing with hashing"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1042-hash-lookup-trading-space-for-time","level":2,"title":"10.4.2   Hash lookup: trading space for time","text":"

    Now use a hash table whose keys are array elements and whose values are their indices. Iterate through the array, performing the steps below each round.

    1. Check whether target - nums[i] is in the hash table. If it is, return the indices of the two elements.
    2. Add the key-value pair consisting of nums[i] and index i to the hash table.
    <1><2><3>

    Figure 10-10   Solving two sum with an auxiliary hash table

    The implementation below requires only one loop:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py
    def two_sum_hash_table(nums: list[int], target: int) -> list[int]:\n    \"\"\"Method 2: auxiliary hash table\"\"\"\n    # The auxiliary hash table uses O(n) space\n    dic = {}\n    # A single loop with average O(1) hash operations gives an average time complexity of O(n)\n    for i in range(len(nums)):\n        if target - nums[i] in dic:\n            return [dic[target - nums[i]], i]\n        dic[nums[i]] = i\n    return []\n
    two_sum.cpp
    /* Method 2: auxiliary hash table */\nvector<int> twoSumHashTable(vector<int> &nums, int target) {\n    int size = nums.size();\n    // The auxiliary hash table uses O(n) space\n    unordered_map<int, int> dic;\n    // A single loop gives a time complexity of O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.find(target - nums[i]) != dic.end()) {\n            return {dic[target - nums[i]], i};\n        }\n        dic.emplace(nums[i], i);\n    }\n    return {};\n}\n
    two_sum.java
    /* Method 2: auxiliary hash table */\nint[] twoSumHashTable(int[] nums, int target) {\n    int size = nums.length;\n    // The auxiliary hash table uses O(n) space\n    Map<Integer, Integer> dic = new HashMap<>();\n    // A single loop gives a time complexity of O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.containsKey(target - nums[i])) {\n            return new int[] { dic.get(target - nums[i]), i };\n        }\n        dic.put(nums[i], i);\n    }\n    return new int[0];\n}\n
    two_sum.cs
    /* Method 2: auxiliary hash table */\nint[] TwoSumHashTable(int[] nums, int target) {\n    int size = nums.Length;\n    // The auxiliary hash table uses O(n) space\n    Dictionary<int, int> dic = [];\n    // A single loop gives a time complexity of O(n)\n    for (int i = 0; i < size; i++) {\n        if (dic.ContainsKey(target - nums[i])) {\n            return [dic[target - nums[i]], i];\n        }\n        dic.Add(nums[i], i);\n    }\n    return [];\n}\n
    two_sum.go
    /* Method 2: auxiliary hash table */\nfunc twoSumHashTable(nums []int, target int) []int {\n    // The auxiliary hash table uses O(n) space\n    hashTable := map[int]int{}\n    // A single loop gives a time complexity of O(n)\n    for idx, val := range nums {\n        if preIdx, ok := hashTable[target-val]; ok {\n            return []int{preIdx, idx}\n        }\n        hashTable[val] = idx\n    }\n    return nil\n}\n
    two_sum.swift
    /* Method 2: auxiliary hash table */\nfunc twoSumHashTable(nums: [Int], target: Int) -> [Int] {\n    // The auxiliary hash table uses O(n) space\n    var dic: [Int: Int] = [:]\n    // A single loop gives a time complexity of O(n)\n    for i in nums.indices {\n        if let j = dic[target - nums[i]] {\n            return [j, i]\n        }\n        dic[nums[i]] = i\n    }\n    return [0]\n}\n
    two_sum.js
    /* Method 2: auxiliary hash table */\nfunction twoSumHashTable(nums, target) {\n    // The auxiliary hash table uses O(n) space\n    let m = {};\n    // A single loop gives a time complexity of O(n)\n    for (let i = 0; i < nums.length; i++) {\n        if (m[target - nums[i]] !== undefined) {\n            return [m[target - nums[i]], i];\n        } else {\n            m[nums[i]] = i;\n        }\n    }\n    return [];\n}\n
    two_sum.ts
    /* Method 2: auxiliary hash table */\nfunction twoSumHashTable(nums: number[], target: number): number[] {\n    // The auxiliary hash table uses O(n) space\n    let m: Map<number, number> = new Map();\n    // A single loop gives a time complexity of O(n)\n    for (let i = 0; i < nums.length; i++) {\n        let index = m.get(target - nums[i]);\n        if (index !== undefined) {\n            return [index, i];\n        } else {\n            m.set(nums[i], i);\n        }\n    }\n    return [];\n}\n
    two_sum.dart
    /* Method 2: auxiliary hash table */\nList<int> twoSumHashTable(List<int> nums, int target) {\n  int size = nums.length;\n  // The auxiliary hash table uses O(n) space\n  Map<int, int> dic = HashMap();\n  // A single loop gives a time complexity of O(n)\n  for (var i = 0; i < size; i++) {\n    if (dic.containsKey(target - nums[i])) {\n      return [dic[target - nums[i]]!, i];\n    }\n    dic.putIfAbsent(nums[i], () => i);\n  }\n  return [0];\n}\n
    two_sum.rs
    /* Method 2: auxiliary hash table */\npub fn two_sum_hash_table(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {\n    // The auxiliary hash table uses O(n) space\n    let mut dic = HashMap::new();\n    // A single loop gives a time complexity of O(n)\n    for (i, num) in nums.iter().enumerate() {\n        match dic.get(&(target - num)) {\n            Some(v) => return Some(vec![*v as i32, i as i32]),\n            None => dic.insert(num, i as i32),\n        };\n    }\n    None\n}\n
    two_sum.c
    /* Hash table */\ntypedef struct {\n    int key;\n    int val;\n    UT_hash_handle hh; // Implemented using uthash.h\n} HashTable;\n\n/* Look up a key in the hash table */\nHashTable *find(HashTable *h, int key) {\n    HashTable *tmp;\n    HASH_FIND_INT(h, &key, tmp);\n    return tmp;\n}\n\n/* Insert an entry into the hash table */\nvoid insert(HashTable **h, int key, int val) {\n    HashTable *t = find(*h, key);\n    if (t == NULL) {\n        HashTable *tmp = malloc(sizeof(HashTable));\n        tmp->key = key, tmp->val = val;\n        HASH_ADD_INT(*h, key, tmp);\n    } else {\n        t->val = val;\n    }\n}\n\n/* Method 2: auxiliary hash table */\nint *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) {\n    HashTable *hashtable = NULL;\n    for (int i = 0; i < numsSize; i++) {\n        HashTable *t = find(hashtable, target - nums[i]);\n        if (t != NULL) {\n            int *res = malloc(sizeof(int) * 2);\n            res[0] = t->val, res[1] = i;\n            *returnSize = 2;\n            return res;\n        }\n        insert(&hashtable, nums[i], i);\n    }\n    *returnSize = 0;\n    return NULL;\n}\n
    two_sum.kt
    /* Method 2: auxiliary hash table */\nfun twoSumHashTable(nums: IntArray, target: Int): IntArray {\n    val size = nums.size\n    // The auxiliary hash table uses O(n) space\n    val dic = HashMap<Int, Int>()\n    // A single loop gives a time complexity of O(n)\n    for (i in 0..<size) {\n        if (dic.containsKey(target - nums[i])) {\n            return intArrayOf(dic[target - nums[i]]!!, i)\n        }\n        dic[nums[i]] = i\n    }\n    return IntArray(0)\n}\n
    two_sum.rb
    ### Method 2: auxiliary hash table ###\ndef two_sum_hash_table(nums, target)\n  # The auxiliary hash table uses O(n) space\n  dic = {}\n  # A single loop gives a time complexity of O(n)\n  for i in 0...nums.length\n    return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i])\n\n    dic[nums[i]] = i\n  end\n\n  []\nend\n
    Visualize execution

    View full screen >

    Hash lookup reduces the time complexity from \\(O(n^2)\\) to \\(O(n)\\), greatly improving efficiency.

    Maintaining an additional hash table requires \\(O(n)\\) space. Even so, this approach offers a better balance of time and space efficiency, making it the optimal solution to this problem.

    ","path":["Chapter 10.   Searching","10.4   Optimizing with hashing"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/","level":1,"title":"10.5   Search algorithms revisited","text":"

    A search algorithm finds one or more elements that satisfy specified conditions in a data structure, such as an array, linked list, tree, or graph.

    Search algorithms fall into two categories according to their approach.

    • Locate the target by traversing the data structure, as in traversals of arrays, linked lists, trees, and graphs.
    • Find elements efficiently by using the organization of the data or prior information about it, as in binary search, hash lookup, and binary search tree lookup.

    These ideas have all appeared in earlier chapters, so search algorithms are already familiar to us. In this section, we revisit them from a more systematic perspective.

    ","path":["Chapter 10.   Searching","10.5   Search algorithms revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1051-brute-force-search","level":2,"title":"10.5.1   Brute-force search","text":"

    Brute-force search locates a target by visiting the elements of a data structure one by one.

    • “Linear search” applies to linear data structures such as arrays and linked lists. Starting at one end, it visits elements in sequence until it finds the target or reaches the other end without finding it.
    • “Breadth-first search” and “depth-first search” are two strategies for traversing graphs and trees. Breadth-first search starts at an initial node and searches level by level, visiting nearby nodes before more distant ones. Depth-first search follows a path as far as possible, then backtracks and tries other paths until the whole structure has been traversed.

    Brute-force search is simple and broadly applicable. It requires neither preprocessing the data nor building an additional data structure.

    However, these algorithms take \\(O(n)\\) time, where \\(n\\) is the number of elements, so they perform poorly on large datasets.

    ","path":["Chapter 10.   Searching","10.5   Search algorithms revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1052-adaptive-search","level":2,"title":"10.5.2   Adaptive search","text":"

    Adaptive search uses properties of the data, such as its ordering, to optimize the search process and locate the target more efficiently.

    • “Binary search” uses sorted data to search efficiently and applies only to arrays.
    • “Hash lookup” uses a hash table to map lookup keys to target data as key-value pairs.
    • “Tree search” uses comparisons of node values in a particular tree structure, such as a binary search tree, to rule out nodes quickly and locate the target.

    These algorithms are efficient, achieving time complexities of \\(O(\\log n)\\) or even \\(O(1)\\).

    However, they often require preprocessing the data. Binary search requires sorting the array in advance; hash lookup and tree search require additional data structures, whose maintenance also costs time and space.

    Tip

    Adaptive search algorithms are often called lookup algorithms. Their main purpose is to retrieve target elements quickly from specific data structures.

    ","path":["Chapter 10.   Searching","10.5   Search algorithms revisited"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1053-choosing-a-search-method","level":2,"title":"10.5.3   Choosing a search method","text":"

    Given a dataset of size \\(n\\), we can find a target using linear search, binary search, tree search, hash lookup, and other methods. Figure 10-11 illustrates how these methods work.

    Figure 10-11   Different search strategies

    Table 10-1 compares their efficiency and properties.

    Table 10-1   Efficiency of lookup algorithms

    Linear search Binary search Tree search Hash lookup Find an element \\(O(n)\\) \\(O(\\log n)\\) \\(O(\\log n)\\) \\(O(1)\\) Insert an element \\(O(1)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) Delete an element \\(O(n)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) Extra space \\(O(1)\\) \\(O(1)\\) \\(O(n)\\) \\(O(n)\\) Preprocessing / Sort \\(O(n \\log n)\\) Build a tree \\(O(n \\log n)\\) Build a hash table \\(O(n)\\) Data ordering Unsorted Sorted Sorted Unsorted

    The choice also depends on dataset size, required search performance, and how frequently the data is queried and updated.

    Linear search

    • It is broadly applicable and requires no preprocessing. If we need only one lookup, preprocessing for the other three methods takes longer than a linear search itself.
    • It suits small datasets, where time complexity has less influence on actual performance.
    • It suits frequently updated data because no additional data structure needs to be maintained.

    Binary search

    • It suits large datasets, with predictable performance and a worst-case time complexity of \\(O(\\log n)\\).
    • The dataset cannot be excessively large, because an array requires contiguous memory.
    • It is unsuitable for frequent insertions and deletions, since maintaining a sorted array is expensive.

    Hash lookup

    • It suits situations demanding very fast queries, with an average time complexity of \\(O(1)\\).
    • It is unsuitable when sorted data or range queries are needed, because a hash table does not preserve order.
    • It depends heavily on the hash function and collision-resolution strategy, creating a greater risk of performance degradation.
    • It is unsuitable for excessively large datasets, because a hash table needs extra space to minimize collisions and maintain good query performance.

    Tree search

    • It suits very large datasets, because tree nodes are stored at separate locations in memory.
    • It suits situations requiring sorted data or range queries.
    • Repeated insertions and deletions can make a binary search tree skewed, degrading its time complexity to \\(O(n)\\).
    • An AVL tree or red-black tree keeps operations at \\(O(\\log n)\\), but maintaining balance adds overhead.
    ","path":["Chapter 10.   Searching","10.5   Search algorithms revisited"],"tags":[]},{"location":"chapter_searching/summary/","level":1,"title":"10.6   Summary","text":"","path":["Chapter 10.   Searching","10.6   Summary"],"tags":[]},{"location":"chapter_searching/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Binary search relies on sorted data and repeatedly halves the search interval. It requires ordered input and applies only to arrays or data structures based on arrays.
    • Brute-force search locates data by traversing a data structure. Linear search suits arrays and linked lists; breadth-first and depth-first search suit graphs and trees. These algorithms are broadly applicable and require no preprocessing, but their \\(O(n)\\) time complexity is relatively high.
    • Hash lookup, tree search, and binary search efficiently locate targets in particular data structures. Their time complexities can be \\(O(\\log n)\\) or even \\(O(1)\\), but they usually rely on additional data structures.
    • In practice, choose a search method by considering dataset size, required search performance, query frequency, update frequency, and related factors.
    • Linear search suits small or frequently updated datasets; binary search suits large, sorted datasets; hash lookup suits fast queries without range-query requirements; tree search suits large, dynamic datasets that need ordering and range queries.
    • Replacing linear search with hash lookup is a common optimization that reduces lookup time from \\(O(n)\\) to \\(O(1)\\).
    ","path":["Chapter 10.   Searching","10.6   Summary"],"tags":[]},{"location":"chapter_sorting/","level":1,"title":"Chapter 11.   Sorting","text":"

    Abstract

    Sorting is like a magic key that turns chaos into order, helping us understand and process data more efficiently.

    From a simple ascending sequence to a complex arrangement by category, sorting reveals the harmonious beauty of data.

    ","path":["Chapter 11.   Sorting"],"tags":[]},{"location":"chapter_sorting/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 11.1   Sorting algorithms
    • 11.2   Selection sort
    • 11.3   Bubble sort
    • 11.4   Insertion sort
    • 11.5   Quick sort
    • 11.6   Merge sort
    • 11.7   Heap sort
    • 11.8   Bucket sort
    • 11.9   Counting sort
    • 11.10   Radix sort
    • 11.11   Summary
    • 11.12   Exercises
    ","path":["Chapter 11.   Sorting"],"tags":[]},{"location":"chapter_sorting/bubble_sort/","level":1,"title":"11.3   Bubble sort","text":"

    Bubble sort sorts by repeatedly comparing and swapping adjacent elements. The process resembles bubbles rising from the bottom to the top, which gives the algorithm its name.

    As shown below, we can simulate bubbling through swaps. Scan from the left end of the array to the right, comparing each pair of adjacent elements. If the left element is greater than the right, swap them. After the scan, the largest element has moved to the far right.

    <1><2><3><4><5><6><7>

    Figure 11-4   Simulating bubbling through element swaps

    ","path":["Chapter 11.   Sorting","11.3   Bubble sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1131-algorithm-steps","level":2,"title":"11.3.1   Algorithm steps","text":"

    For an array of length \\(n\\), bubble sort proceeds as shown below.

    1. First, perform a bubbling pass over all \\(n\\) elements, moving the largest element to its correct position.
    2. Next, perform a pass over the remaining \\(n - 1\\) elements, moving the second-largest element to its correct position.
    3. Continue in this way. After \\(n - 1\\) passes, the largest \\(n - 1\\) elements are all in their correct positions.
    4. The one remaining element must be the smallest and needs no further sorting. The array is now sorted.

    Figure 11-5   The bubble sort process

    An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py
    def bubble_sort(nums: list[int]):\n    \"\"\"Bubble sort\"\"\"\n    n = len(nums)\n    # Outer loop: the unsorted range is [0, i]\n    for i in range(n - 1, 0, -1):\n        # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                nums[j], nums[j + 1] = nums[j + 1], nums[j]\n
    bubble_sort.cpp
    /* Bubble sort */\nvoid bubbleSort(vector<int> &nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                // Use the std::swap() function here\n                swap(nums[j], nums[j + 1]);\n            }\n        }\n    }\n}\n
    bubble_sort.java
    /* Bubble sort */\nvoid bubbleSort(int[] nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
    bubble_sort.cs
    /* Bubble sort */\nvoid BubbleSort(int[] nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n            }\n        }\n    }\n}\n
    bubble_sort.go
    /* Bubble sort */\nfunc bubbleSort(nums []int) {\n    // Outer loop: the unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                nums[j], nums[j+1] = nums[j+1], nums[j]\n            }\n        }\n    }\n}\n
    bubble_sort.swift
    /* Bubble sort */\nfunc bubbleSort(nums: inout [Int]) {\n    // Outer loop: the unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swapAt(j, j + 1)\n            }\n        }\n    }\n}\n
    bubble_sort.js
    /* Bubble sort */\nfunction bubbleSort(nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
    bubble_sort.ts
    /* Bubble sort */\nfunction bubbleSort(nums: number[]): void {\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n            }\n        }\n    }\n}\n
    bubble_sort.dart
    /* Bubble sort */\nvoid bubbleSort(List<int> nums) {\n  // Outer loop: the unsorted range is [0, i]\n  for (int i = nums.length - 1; i > 0; i--) {\n    // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for (int j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n      }\n    }\n  }\n}\n
    bubble_sort.rs
    /* Bubble sort */\nfn bubble_sort(nums: &mut [i32]) {\n    // Outer loop: the unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swap(j, j + 1);\n            }\n        }\n    }\n}\n
    bubble_sort.c
    /* Bubble sort */\nvoid bubbleSort(int nums[], int size) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = size - 1; i > 0; i--) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                int temp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = temp;\n            }\n        }\n    }\n}\n
    bubble_sort.kt
    /* Bubble sort */\nfun bubbleSort(nums: IntArray) {\n    // Outer loop: the unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n            }\n        }\n    }\n}\n
    bubble_sort.rb
    ### Bubble sort ###\ndef bubble_sort(nums)\n  n = nums.length\n  # Outer loop: the unsorted range is [0, i]\n  for i in (n - 1).downto(1)\n    # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        nums[j], nums[j + 1] = nums[j + 1], nums[j]\n      end\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.3   Bubble sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1132-improving-efficiency","level":2,"title":"11.3.2   Improving efficiency","text":"

    If a bubbling pass performs no swaps, the array is already sorted and we can return immediately. We can therefore add a variable flag to detect this situation and stop as soon as it occurs.

    With this optimization, the worst-case and average time complexities remain \\(O(n^2)\\). However, a fully sorted input achieves the best-case time complexity of \\(O(n)\\).

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py
    def bubble_sort_with_flag(nums: list[int]):\n    \"\"\"Bubble sort (flag optimization)\"\"\"\n    n = len(nums)\n    # Outer loop: the unsorted range is [0, i]\n    for i in range(n - 1, 0, -1):\n        flag = False  # Initialize the flag\n        # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in range(i):\n            if nums[j] > nums[j + 1]:\n                # Swap nums[j] and nums[j + 1]\n                nums[j], nums[j + 1] = nums[j + 1], nums[j]\n                flag = True  # Record that a swap occurred\n        if not flag:\n            break  # Exit immediately if this pass made no swaps\n
    bubble_sort.cpp
    /* Bubble sort (flag optimization)*/\nvoid bubbleSortWithFlag(vector<int> &nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.size() - 1; i > 0; i--) {\n        bool flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                // Use the std::swap() function here\n                swap(nums[j], nums[j + 1]);\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if (!flag)\n            break; // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.java
    /* Bubble sort (flag optimization) */\nvoid bubbleSortWithFlag(int[] nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.length - 1; i > 0; i--) {\n        boolean flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                int tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if (!flag)\n            break; // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.cs
    /* Bubble sort (flag optimization)*/\nvoid BubbleSortWithFlag(int[] nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = nums.Length - 1; i > 0; i--) {\n        bool flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n                flag = true;  // Record that a swap occurred\n            }\n        }\n        if (!flag) break;     // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.go
    /* Bubble sort (flag optimization)*/\nfunc bubbleSortWithFlag(nums []int) {\n    // Outer loop: the unsorted range is [0, i]\n    for i := len(nums) - 1; i > 0; i-- {\n        flag := false // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j := 0; j < i; j++ {\n            if nums[j] > nums[j+1] {\n                // Swap nums[j] and nums[j + 1]\n                nums[j], nums[j+1] = nums[j+1], nums[j]\n                flag = true // Record that a swap occurred\n            }\n        }\n        if flag == false { // Exit immediately if this pass made no swaps\n            break\n        }\n    }\n}\n
    bubble_sort.swift
    /* Bubble sort (flag optimization)*/\nfunc bubbleSortWithFlag(nums: inout [Int]) {\n    // Outer loop: the unsorted range is [0, i]\n    for i in nums.indices.dropFirst().reversed() {\n        var flag = false // Initialize the flag\n        for j in 0 ..< i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swapAt(j, j + 1)\n                flag = true // Record that a swap occurred\n            }\n        }\n        if !flag { // Exit immediately if this pass made no swaps\n            break\n        }\n    }\n}\n
    bubble_sort.js
    /* Bubble sort (flag optimization)*/\nfunction bubbleSortWithFlag(nums) {\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        let flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if (!flag) break; // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.ts
    /* Bubble sort (flag optimization)*/\nfunction bubbleSortWithFlag(nums: number[]): void {\n    // Outer loop: the unsorted range is [0, i]\n    for (let i = nums.length - 1; i > 0; i--) {\n        let flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (let j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                let tmp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = tmp;\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if (!flag) break; // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.dart
    /* Bubble sort (flag optimization)*/\nvoid bubbleSortWithFlag(List<int> nums) {\n  // Outer loop: the unsorted range is [0, i]\n  for (int i = nums.length - 1; i > 0; i--) {\n    bool flag = false; // Initialize the flag\n    // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for (int j = 0; j < i; j++) {\n      if (nums[j] > nums[j + 1]) {\n        // Swap nums[j] and nums[j + 1]\n        int tmp = nums[j];\n        nums[j] = nums[j + 1];\n        nums[j + 1] = tmp;\n        flag = true; // Record that a swap occurred\n      }\n    }\n    if (!flag) break; // Exit immediately if this pass made no swaps\n  }\n}\n
    bubble_sort.rs
    /* Bubble sort (flag optimization) */\nfn bubble_sort_with_flag(nums: &mut [i32]) {\n    // Outer loop: the unsorted range is [0, i]\n    for i in (1..nums.len()).rev() {\n        let mut flag = false; // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for j in 0..i {\n            if nums[j] > nums[j + 1] {\n                // Swap nums[j] and nums[j + 1]\n                nums.swap(j, j + 1);\n                flag = true; // Record that a swap occurred\n            }\n        }\n        if !flag {\n            break; // Exit immediately if this pass made no swaps\n        };\n    }\n}\n
    bubble_sort.c
    /* Bubble sort (flag optimization)*/\nvoid bubbleSortWithFlag(int nums[], int size) {\n    // Outer loop: the unsorted range is [0, i]\n    for (int i = size - 1; i > 0; i--) {\n        bool flag = false;\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (int j = 0; j < i; j++) {\n            if (nums[j] > nums[j + 1]) {\n                int temp = nums[j];\n                nums[j] = nums[j + 1];\n                nums[j + 1] = temp;\n                flag = true;\n            }\n        }\n        if (!flag)\n            break;\n    }\n}\n
    bubble_sort.kt
    /* Bubble sort (flag optimization) */\nfun bubbleSortWithFlag(nums: IntArray) {\n    // Outer loop: the unsorted range is [0, i]\n    for (i in nums.size - 1 downTo 1) {\n        var flag = false // Initialize the flag\n        // Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n        for (j in 0..<i) {\n            if (nums[j] > nums[j + 1]) {\n                // Swap nums[j] and nums[j + 1]\n                val temp = nums[j]\n                nums[j] = nums[j + 1]\n                nums[j + 1] = temp\n                flag = true // Record that a swap occurred\n            }\n        }\n        if (!flag) break // Exit immediately if this pass made no swaps\n    }\n}\n
    bubble_sort.rb
    ### Bubble sort (flag optimization)###\ndef bubble_sort_with_flag(nums)\n  n = nums.length\n  # Outer loop: the unsorted range is [0, i]\n  for i in (n - 1).downto(1)\n    flag = false # Initialize the flag\n\n    # Inner loop: move the largest element in the unsorted range [0, i] to its right end through swaps\n    for j in 0...i\n      if nums[j] > nums[j + 1]\n        # Swap nums[j] and nums[j + 1]\n        nums[j], nums[j + 1] = nums[j + 1], nums[j]\n        flag = true # Record that a swap occurred\n      end\n    end\n\n    break unless flag # Exit immediately if this pass made no swaps\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.3   Bubble sort"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1133-algorithm-properties","level":2,"title":"11.3.3   Algorithm properties","text":"
    • Time complexity: \\(O(n^2)\\); adaptive. Successive passes traverse \\(n - 1\\), \\(n - 2\\), \\(\\dots\\), \\(2\\), and \\(1\\) positions, totaling \\((n - 1) n / 2\\). With the flag optimization, the best-case time complexity is \\(O(n)\\).
    • Space complexity: \\(O(1)\\); in-place. Pointers \\(i\\) and \\(j\\) use constant extra space.
    • Stable. Equal elements are not swapped during a bubbling pass.
    ","path":["Chapter 11.   Sorting","11.3   Bubble sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/","level":1,"title":"11.8   Bucket sort","text":"

    The sorting algorithms discussed so far are “comparison-based”: they sort by comparing elements. Their worst-case time complexity has a lower bound of \\(\\Omega(n \\log n)\\). We now turn to several “non-comparison” sorting algorithms, which can achieve linear time complexity.

    Bucket sort is a classic application of divide and conquer. It creates ordered buckets, each corresponding to a range of values, and distributes the data evenly among them. It then sorts each bucket separately and combines the results in bucket order.

    ","path":["Chapter 11.   Sorting","11.8   Bucket sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1181-algorithm-steps","level":2,"title":"11.8.1   Algorithm steps","text":"

    Consider an array of length \\(n\\) containing floating-point values in \\([0, 1)\\). Bucket sort proceeds as shown below.

    1. Initialize \\(k\\) buckets and distribute the \\(n\\) elements among the \\(k\\) buckets.
    2. Sort each bucket separately, using the programming language's built-in sorting function here.
    3. Combine the results in ascending bucket order.

    Figure 11-13   The bucket sort process

    The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bucket_sort.py
    def bucket_sort(nums: list[float]):\n    \"\"\"Bucket sort\"\"\"\n    # Initialize k = n/2 buckets, expecting 2 elements per bucket\n    k = len(nums) // 2\n    buckets = [[] for _ in range(k)]\n    # 1. Distribute the array elements among the buckets\n    for num in nums:\n        # The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        i = int(num * k)\n        # Add num to bucket i\n        buckets[i].append(num)\n    # 2. Sort each bucket\n    for bucket in buckets:\n        # Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort()\n    # 3. Traverse the buckets to combine the results\n    i = 0\n    for bucket in buckets:\n        for num in bucket:\n            nums[i] = num\n            i += 1\n
    bucket_sort.cpp
    /* Bucket sort */\nvoid bucketSort(vector<float> &nums) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    int k = nums.size() / 2;\n    vector<vector<float>> buckets(k);\n    // 1. Distribute the array elements among the buckets\n    for (float num : nums) {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        int i = num * k;\n        // Add num to bucket i\n        buckets[i].push_back(num);\n    }\n    // 2. Sort each bucket\n    for (vector<float> &bucket : buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        sort(bucket.begin(), bucket.end());\n    }\n    // 3. Traverse the buckets to combine the results\n    int i = 0;\n    for (vector<float> &bucket : buckets) {\n        for (float num : bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
    bucket_sort.java
    /* Bucket sort */\nvoid bucketSort(float[] nums) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    int k = nums.length / 2;\n    List<List<Float>> buckets = new ArrayList<>();\n    for (int i = 0; i < k; i++) {\n        buckets.add(new ArrayList<>());\n    }\n    // 1. Distribute the array elements among the buckets\n    for (float num : nums) {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        int i = (int) (num * k);\n        // Add num to bucket i\n        buckets.get(i).add(num);\n    }\n    // 2. Sort each bucket\n    for (List<Float> bucket : buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        Collections.sort(bucket);\n    }\n    // 3. Traverse the buckets to combine the results\n    int i = 0;\n    for (List<Float> bucket : buckets) {\n        for (float num : bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
    bucket_sort.cs
    /* Bucket sort */\nvoid BucketSort(float[] nums) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    int k = nums.Length / 2;\n    List<List<float>> buckets = [];\n    for (int i = 0; i < k; i++) {\n        buckets.Add([]);\n    }\n    // 1. Distribute the array elements among the buckets\n    foreach (float num in nums) {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        int i = (int)(num * k);\n        // Add num to bucket i\n        buckets[i].Add(num);\n    }\n    // 2. Sort each bucket\n    foreach (List<float> bucket in buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.Sort();\n    }\n    // 3. Traverse the buckets to combine the results\n    int j = 0;\n    foreach (List<float> bucket in buckets) {\n        foreach (float num in bucket) {\n            nums[j++] = num;\n        }\n    }\n}\n
    bucket_sort.go
    /* Bucket sort */\nfunc bucketSort(nums []float64) {\n    // Initialize k = floor(n/2) buckets, expecting about 2 elements per bucket for uniformly distributed input\n    k := len(nums) / 2\n    buckets := make([][]float64, k)\n    for i := 0; i < k; i++ {\n        buckets[i] = make([]float64, 0)\n    }\n    // 1. Distribute the array elements among the buckets\n    for _, num := range nums {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        i := int(num * float64(k))\n        // Add num to bucket i\n        buckets[i] = append(buckets[i], num)\n    }\n    // 2. Sort each bucket\n    for i := 0; i < k; i++ {\n        // Use the standard slice-sorting function, or replace it with another sorting algorithm\n        sort.Float64s(buckets[i])\n    }\n    // 3. Traverse the buckets to combine the results\n    i := 0\n    for _, bucket := range buckets {\n        for _, num := range bucket {\n            nums[i] = num\n            i++\n        }\n    }\n}\n
    bucket_sort.swift
    /* Bucket sort */\nfunc bucketSort(nums: inout [Double]) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    let k = nums.count / 2\n    var buckets = (0 ..< k).map { _ in [Double]() }\n    // 1. Distribute the array elements among the buckets\n    for num in nums {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        let i = Int(num * Double(k))\n        // Add num to bucket i\n        buckets[i].append(num)\n    }\n    // 2. Sort each bucket\n    for i in buckets.indices {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        buckets[i].sort()\n    }\n    // 3. Traverse the buckets to combine the results\n    var i = nums.startIndex\n    for bucket in buckets {\n        for num in bucket {\n            nums[i] = num\n            i += 1\n        }\n    }\n}\n
    bucket_sort.js
    /* Bucket sort */\nfunction bucketSort(nums) {\n    // Initialize ceil(n/2) buckets, aiming for about 2 elements per bucket\n    const k = nums.length / 2;\n    const buckets = [];\n    for (let i = 0; i < k; i++) {\n        buckets.push([]);\n    }\n    // 1. Distribute the array elements among the buckets\n    for (const num of nums) {\n        // The input range is [0, 1); floor(num * k) maps values to indices from 0 to ceil(k)-1\n        const i = Math.floor(num * k);\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for (const bucket of buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort((a, b) => a - b);\n    }\n    // 3. Traverse the buckets to combine the results\n    let i = 0;\n    for (const bucket of buckets) {\n        for (const num of bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
    bucket_sort.ts
    /* Bucket sort */\nfunction bucketSort(nums: number[]): void {\n    // Set k = n/2 and create ceil(k) buckets, aiming for about 2 elements per bucket\n    const k = nums.length / 2;\n    const buckets: number[][] = [];\n    for (let i = 0; i < k; i++) {\n        buckets.push([]);\n    }\n    // 1. Distribute the array elements among the buckets\n    for (const num of nums) {\n        // The input range is [0, 1); floor(num * k) maps each value to an index in [0, ceil(k)-1]\n        const i = Math.floor(num * k);\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for (const bucket of buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort((a, b) => a - b);\n    }\n    // 3. Traverse the buckets to combine the results\n    let i = 0;\n    for (const bucket of buckets) {\n        for (const num of bucket) {\n            nums[i++] = num;\n        }\n    }\n}\n
    bucket_sort.dart
    /* Bucket sort */\nvoid bucketSort(List<double> nums) {\n  // Initialize k = floor(n/2) buckets, aiming for about 2 elements per bucket\n  int k = nums.length ~/ 2;\n  List<List<double>> buckets = List.generate(k, (index) => []);\n\n  // 1. Distribute the array elements among the buckets\n  for (double _num in nums) {\n    // The input range is [0, 1); use _num * k to map each value to an index in [0, k-1]\n    int i = (_num * k).toInt();\n    // Add _num to bucket i\n    buckets[i].add(_num);\n  }\n  // 2. Sort each bucket\n  for (List<double> bucket in buckets) {\n    bucket.sort();\n  }\n  // 3. Traverse the buckets to combine the results\n  int i = 0;\n  for (List<double> bucket in buckets) {\n    for (double _num in bucket) {\n      nums[i++] = _num;\n    }\n  }\n}\n
    bucket_sort.rs
    /* Bucket sort */\nfn bucket_sort(nums: &mut [f64]) {\n    // Initialize k = n/2 buckets, expecting 2 elements per bucket\n    let k = nums.len() / 2;\n    let mut buckets = vec![vec![]; k];\n    // 1. Distribute the array elements among the buckets\n    for &num in nums.iter() {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        let i = (num * k as f64) as usize;\n        // Add num to bucket i\n        buckets[i].push(num);\n    }\n    // 2. Sort each bucket\n    for bucket in &mut buckets {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort_by(|a, b| a.partial_cmp(b).unwrap());\n    }\n    // 3. Traverse the buckets to combine the results\n    let mut i = 0;\n    for bucket in buckets.iter() {\n        for &num in bucket.iter() {\n            nums[i] = num;\n            i += 1;\n        }\n    }\n}\n
    bucket_sort.c
    /* Bucket sort */\nvoid bucketSort(float nums[], int n) {\n    int k = n / 2;                                 // Initialize k = n/2 buckets\n    int *sizes = malloc(k * sizeof(int));          // Record the size of each bucket\n    float **buckets = malloc(k * sizeof(float *)); // Array of dynamic arrays (buckets)\n    // Preallocate enough space for each bucket\n    for (int i = 0; i < k; ++i) {\n        buckets[i] = (float *)malloc(n * sizeof(float));\n        sizes[i] = 0;\n    }\n    // 1. Distribute the array elements among the buckets\n    for (int i = 0; i < n; ++i) {\n        int idx = (int)(nums[i] * k);\n        buckets[idx][sizes[idx]++] = nums[i];\n    }\n    // 2. Sort each bucket\n    for (int i = 0; i < k; ++i) {\n        qsort(buckets[i], sizes[i], sizeof(float), compare);\n    }\n    // 3. Merge the sorted buckets\n    int idx = 0;\n    for (int i = 0; i < k; ++i) {\n        for (int j = 0; j < sizes[i]; ++j) {\n            nums[idx++] = buckets[i][j];\n        }\n        // Free memory\n        free(buckets[i]);\n    }\n}\n
    bucket_sort.kt
    /* Bucket sort */\nfun bucketSort(nums: FloatArray) {\n    // Initialize k = floor(n/2) buckets, expecting about 2 elements per bucket\n    val k = nums.size / 2\n    val buckets = mutableListOf<MutableList<Float>>()\n    for (i in 0..<k) {\n        buckets.add(mutableListOf())\n    }\n    // 1. Distribute the array elements among the buckets\n    for (num in nums) {\n        // The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n        val i = (num * k).toInt()\n        // Add num to bucket i\n        buckets[i].add(num)\n    }\n    // 2. Sort each bucket\n    for (bucket in buckets) {\n        // Use the built-in sorting function, or replace it with another sorting algorithm\n        bucket.sort()\n    }\n    // 3. Traverse the buckets to combine the results\n    var i = 0\n    for (bucket in buckets) {\n        for (num in bucket) {\n            nums[i++] = num\n        }\n    }\n}\n
    bucket_sort.rb
    ### Bucket sort ###\ndef bucket_sort(nums)\n  # Initialize k = floor(n/2) buckets, aiming for about 2 elements per bucket\n  k = nums.length / 2\n  buckets = Array.new(k) { [] }\n\n  # 1. Distribute the array elements among the buckets\n  nums.each do |num|\n    # The input range is [0, 1); use num * k to map each value to an index in [0, k-1]\n    i = (num * k).to_i\n    # Add num to bucket i\n    buckets[i] << num\n  end\n\n  # 2. Sort each bucket\n  buckets.each do |bucket|\n    # Use the built-in sorting function, or replace it with another sorting algorithm\n    bucket.sort!\n  end\n\n  # 3. Traverse the buckets to combine the results\n  i = 0\n  buckets.each do |bucket|\n    bucket.each do |num|\n      nums[i] = num\n      i += 1\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.8   Bucket sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1182-algorithm-properties","level":2,"title":"11.8.2   Algorithm properties","text":"

    Bucket sort is useful for very large datasets. For example, suppose the input contains one million elements and memory limitations prevent loading all of them at once. We can divide them into 1,000 buckets, sort the buckets separately, and combine the results.

    • Time complexity: \\(O(n + k)\\). If the elements are evenly distributed, each bucket contains \\(\\frac{n}{k}\\) elements. If sorting one bucket takes \\(O(\\frac{n}{k} \\log\\frac{n}{k})\\) time, sorting all buckets takes \\(O(n \\log\\frac{n}{k})\\). When the number of buckets \\(k\\) is relatively large, this approaches \\(O(n)\\). Combining the results visits all buckets and elements, taking \\(O(n + k)\\) time. In the worst case, all data falls into one bucket, and sorting it takes \\(O(n^2)\\) time.
    • Space complexity: \\(O(n + k)\\); not in-place. Extra space is needed for \\(k\\) buckets and a total of \\(n\\) elements.
    • Whether bucket sort is stable depends on whether the algorithm used to sort within each bucket is stable.
    ","path":["Chapter 11.   Sorting","11.8   Bucket sort"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1183-distributing-elements-evenly","level":2,"title":"11.8.3   Distributing elements evenly","text":"

    Bucket sort can theoretically achieve \\(O(n)\\) time. The key is distributing elements evenly among the buckets, since real data is often unevenly distributed. Suppose we want to divide all products on Taobao evenly into 10 buckets by price. There are many products below 100 yuan but few above 1,000 yuan. Splitting the price range into 10 equal intervals would leave very different numbers of products in the buckets.

    To obtain an even distribution, we can first choose rough boundaries and divide the data into 3 buckets. Then split the more crowded buckets into 3 smaller buckets, repeating until all buckets contain roughly equal numbers of elements.

    As shown below, this effectively creates a recursion tree whose leaf values should be as balanced as possible. We need not use 3 buckets at every step; the subdivision can be chosen to suit the data.

    Figure 11-14   Recursively subdividing buckets

    If we know the probability distribution of product prices in advance, we can choose bucket boundaries according to that distribution. We do not necessarily need to measure the distribution explicitly; we can approximate it with a probability model suited to the data.

    In Figure 11-15, we assume prices follow a normal distribution. This lets us choose suitable price intervals and distribute products evenly among the buckets.

    Figure 11-15   Defining buckets from a probability distribution

    ","path":["Chapter 11.   Sorting","11.8   Bucket sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/","level":1,"title":"11.9   Counting sort","text":"

    Counting sort sorts by counting how often each element occurs. It is typically used for integer arrays.

    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1191-a-simple-implementation","level":2,"title":"11.9.1   A simple implementation","text":"

    Start with a simple example: an array nums of length \\(n\\) containing nonnegative integers. The overall process is shown below.

    1. Traverse the array to find its maximum value, denoted by \\(m\\), then create an auxiliary array counter of length \\(m + 1\\).
    2. Use counter to count occurrences of each number in nums. Here, counter[num] is the number of occurrences of num. Simply traverse nums and increment counter[num] by \\(1\\) for each current value num.
    3. Because the indices of counter are inherently ordered, the numbers are effectively already sorted. Traverse counter and write each number into nums in ascending order, repeating it according to its count.

    Figure 11-16   The counting sort process

    The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py
    def counting_sort_naive(nums: list[int]):\n    \"\"\"Counting sort\"\"\"\n    # Simple implementation; cannot be used to sort objects\n    # 1. Find the maximum array element m\n    m = max(nums)\n    # 2. Count the occurrences of each number\n    # counter[num] is the number of occurrences of num\n    counter = [0] * (m + 1)\n    for num in nums:\n        counter[num] += 1\n    # 3. Traverse counter and fill the original array nums with the elements\n    i = 0\n    for num in range(m + 1):\n        for _ in range(counter[num]):\n            nums[i] = num\n            i += 1\n
    counting_sort.cpp
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid countingSortNaive(vector<int> &nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int num : nums) {\n        m = max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    vector<int> counter(m + 1, 0);\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.java
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid countingSortNaive(int[] nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int num : nums) {\n        m = Math.max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int[] counter = new int[m + 1];\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.cs
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid CountingSortNaive(int[] nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    foreach (int num in nums) {\n        m = Math.Max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int[] counter = new int[m + 1];\n    foreach (int num in nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.go
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfunc countingSortNaive(nums []int) {\n    // 1. Find the maximum array element m\n    m := 0\n    for _, num := range nums {\n        if num > m {\n            m = num\n        }\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    counter := make([]int, m+1)\n    for _, num := range nums {\n        counter[num]++\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    for i, num := 0, 0; num < m+1; num++ {\n        for j := 0; j < counter[num]; j++ {\n            nums[i] = num\n            i++\n        }\n    }\n}\n
    counting_sort.swift
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfunc countingSortNaive(nums: inout [Int]) {\n    // 1. Find the maximum array element m\n    let m = nums.max()!\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    var counter = Array(repeating: 0, count: m + 1)\n    for num in nums {\n        counter[num] += 1\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    var i = 0\n    for num in 0 ..< m + 1 {\n        for _ in 0 ..< counter[num] {\n            nums[i] = num\n            i += 1\n        }\n    }\n}\n
    counting_sort.js
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfunction countingSortNaive(nums) {\n    // 1. Find the maximum array element m\n    let m = Math.max(...nums);\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    const counter = new Array(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    let i = 0;\n    for (let num = 0; num < m + 1; num++) {\n        for (let j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.ts
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfunction countingSortNaive(nums: number[]): void {\n    // 1. Find the maximum array element m\n    let m: number = Math.max(...nums);\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    const counter: number[] = new Array<number>(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    let i = 0;\n    for (let num = 0; num < m + 1; num++) {\n        for (let j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n}\n
    counting_sort.dart
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid countingSortNaive(List<int> nums) {\n  // 1. Find the maximum array element m\n  int m = 0;\n  for (int _num in nums) {\n    m = max(m, _num);\n  }\n  // 2. Count the occurrences of each number\n  // counter[_num] is the number of occurrences of _num\n  List<int> counter = List.filled(m + 1, 0);\n  for (int _num in nums) {\n    counter[_num]++;\n  }\n  // 3. Traverse counter and fill the original array nums with the elements\n  int i = 0;\n  for (int _num = 0; _num < m + 1; _num++) {\n    for (int j = 0; j < counter[_num]; j++, i++) {\n      nums[i] = _num;\n    }\n  }\n}\n
    counting_sort.rs
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfn counting_sort_naive(nums: &mut [i32]) {\n    // 1. Find the maximum array element m\n    let m = *nums.iter().max().unwrap();\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    let mut counter = vec![0; m as usize + 1];\n    for &num in nums.iter() {\n        counter[num as usize] += 1;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    let mut i = 0;\n    for num in 0..m + 1 {\n        for _ in 0..counter[num as usize] {\n            nums[i] = num;\n            i += 1;\n        }\n    }\n}\n
    counting_sort.c
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nvoid countingSortNaive(int nums[], int size) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > m) {\n            m = nums[i];\n        }\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int *counter = calloc(m + 1, sizeof(int));\n    for (int i = 0; i < size; i++) {\n        counter[nums[i]]++;\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    int i = 0;\n    for (int num = 0; num < m + 1; num++) {\n        for (int j = 0; j < counter[num]; j++, i++) {\n            nums[i] = num;\n        }\n    }\n    // 4. Free memory\n    free(counter);\n}\n
    counting_sort.kt
    /* Counting sort */\n// Simple implementation; cannot be used to sort objects\nfun countingSortNaive(nums: IntArray) {\n    // 1. Find the maximum array element m\n    var m = 0\n    for (num in nums) {\n        m = max(m, num)\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    val counter = IntArray(m + 1)\n    for (num in nums) {\n        counter[num]++\n    }\n    // 3. Traverse counter and fill the original array nums with the elements\n    var i = 0\n    for (num in 0..<m + 1) {\n        var j = 0\n        while (j < counter[num]) {\n            nums[i] = num\n            j++\n            i++\n        }\n    }\n}\n
    counting_sort.rb
    ### Counting sort ###\ndef counting_sort_naive(nums)\n  # Simple implementation; cannot be used to sort objects\n  # 1. Find the maximum array element m\n  m = 0\n  nums.each { |num| m = [m, num].max }\n  # 2. Count the occurrences of each number\n  # counter[num] is the number of occurrences of num\n  counter = Array.new(m + 1, 0)\n  nums.each { |num| counter[num] += 1 }\n  # 3. Traverse counter and fill the original array nums with the elements\n  i = 0\n  for num in 0...(m + 1)\n    (0...counter[num]).each do\n      nums[i] = num\n      i += 1\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    How counting sort relates to bucket sort

    From a bucket-sort perspective, each index of counter is a bucket, and counting occurrences is equivalent to distributing elements into their corresponding buckets. Counting sort is essentially a special case of bucket sort for integer data.

    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1192-a-complete-implementation","level":2,"title":"11.9.2   A complete implementation","text":"

    You may have noticed that step 3. above fails when the input consists of objects. Suppose the inputs are product objects and we want to sort them by price, a member variable of the class. The algorithm above can produce only the sorted prices.

    How can we obtain the sorted original data? First, calculate the “prefix sums” of counter. The prefix sum prefix[i] is the sum of the array entries up to index i:

    \\[ \\text{prefix}[i] = \\sum_{j=0}^i \\text{counter[j]} \\]

    The prefix sum has a precise meaning: prefix[num] - 1 is the index of the last occurrence of num in the result array res. This tells us where each element belongs. Next, traverse the original array nums in reverse and perform two steps for each element num.

    1. Place num at index prefix[num] - 1 in res.
    2. Decrease prefix[num] by \\(1\\) to obtain the index for placing the next occurrence of num.

    After the traversal, res contains the sorted result. Finally, copy res over the original array nums. Figure 11-17 shows the complete process.

    <1><2><3><4><5><6><7><8>

    Figure 11-17   Steps of counting sort

    The implementation is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py
    def counting_sort(nums: list[int]):\n    \"\"\"Counting sort\"\"\"\n    # Full implementation; stable and adaptable to sorting objects\n    # 1. Find the maximum array element m\n    m = max(nums)\n    # 2. Count the occurrences of each number\n    # counter[num] is the number of occurrences of num\n    counter = [0] * (m + 1)\n    for num in nums:\n        counter[num] += 1\n    # 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    # That is, counter[num]-1 is the index of the last occurrence of num in res\n    for i in range(m):\n        counter[i + 1] += counter[i]\n    # 4. Traverse nums in reverse and place each element in the result array res\n    # Initialize array res to store the result\n    n = len(nums)\n    res = [0] * n\n    for i in range(n - 1, -1, -1):\n        num = nums[i]\n        res[counter[num] - 1] = num  # Place num at the corresponding index\n        counter[num] -= 1  # Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    # Overwrite the original array nums with the result array res\n    for i in range(n):\n        nums[i] = res[i]\n
    counting_sort.cpp
    /* Counting sort */\n// Stable implementation that can be adapted to sort objects\nvoid countingSort(vector<int> &nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int num : nums) {\n        m = max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    vector<int> counter(m + 1, 0);\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    int n = nums.size();\n    vector<int> res(n);\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--;              // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    nums = res;\n}\n
    counting_sort.java
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nvoid countingSort(int[] nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int num : nums) {\n        m = Math.max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int[] counter = new int[m + 1];\n    for (int num : nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    int n = nums.length;\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
    counting_sort.cs
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nvoid CountingSort(int[] nums) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    foreach (int num in nums) {\n        m = Math.Max(m, num);\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int[] counter = new int[m + 1];\n    foreach (int num in nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    int n = nums.Length;\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
    counting_sort.go
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nfunc countingSort(nums []int) {\n    // 1. Find the maximum array element m\n    m := 0\n    for _, num := range nums {\n        if num > m {\n            m = num\n        }\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    counter := make([]int, m+1)\n    for _, num := range nums {\n        counter[num]++\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for i := 0; i < m; i++ {\n        counter[i+1] += counter[i]\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    n := len(nums)\n    res := make([]int, n)\n    for i := n - 1; i >= 0; i-- {\n        num := nums[i]\n        // Place num at the corresponding index\n        res[counter[num]-1] = num\n        // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n        counter[num]--\n    }\n    // Overwrite the original array nums with the result array res\n    copy(nums, res)\n}\n
    counting_sort.swift
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nfunc countingSort(nums: inout [Int]) {\n    // 1. Find the maximum array element m\n    let m = nums.max()!\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    var counter = Array(repeating: 0, count: m + 1)\n    for num in nums {\n        counter[num] += 1\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for i in 0 ..< m {\n        counter[i + 1] += counter[i]\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    var res = Array(repeating: 0, count: nums.count)\n    for i in nums.indices.reversed() {\n        let num = nums[i]\n        res[counter[num] - 1] = num // Place num at the corresponding index\n        counter[num] -= 1 // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for i in nums.indices {\n        nums[i] = res[i]\n    }\n}\n
    counting_sort.js
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nfunction countingSort(nums) {\n    // 1. Find the maximum array element m\n    let m = Math.max(...nums);\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    const counter = new Array(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (let i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    const n = nums.length;\n    const res = new Array(n);\n    for (let i = n - 1; i >= 0; i--) {\n        const num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
    counting_sort.ts
    /* Counting sort */\n// Full stable implementation; this approach can be adapted to sort objects\nfunction countingSort(nums: number[]): void {\n    // 1. Find the maximum array element m\n    let m: number = Math.max(...nums);\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    const counter: number[] = new Array<number>(m + 1).fill(0);\n    for (const num of nums) {\n        counter[num]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (let i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    const n = nums.length;\n    const res: number[] = new Array<number>(n);\n    for (let i = n - 1; i >= 0; i--) {\n        const num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n
    counting_sort.dart
    /* Counting sort */\n// Full stable implementation; this approach can be adapted to sort objects\nvoid countingSort(List<int> nums) {\n  // 1. Find the maximum array element m\n  int m = 0;\n  for (int _num in nums) {\n    m = max(m, _num);\n  }\n  // 2. Count the occurrences of each number\n  // counter[_num] is the number of occurrences of _num\n  List<int> counter = List.filled(m + 1, 0);\n  for (int _num in nums) {\n    counter[_num]++;\n  }\n  // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n  // That is, counter[_num]-1 is the index of the last occurrence of _num in res\n  for (int i = 0; i < m; i++) {\n    counter[i + 1] += counter[i];\n  }\n  // 4. Traverse nums in reverse and place each element in the result array res\n  // Initialize array res to store the result\n  int n = nums.length;\n  List<int> res = List.filled(n, 0);\n  for (int i = n - 1; i >= 0; i--) {\n    int _num = nums[i];\n    res[counter[_num] - 1] = _num; // Place _num at the corresponding index\n    counter[_num]--; // Decrement the prefix sum by 1 to determine where to place the next occurrence of _num\n  }\n  // Overwrite the original array nums with the result array res\n  nums.setAll(0, res);\n}\n
    counting_sort.rs
    /* Counting sort */\n// Stable implementation that can be adapted to sort objects\nfn counting_sort(nums: &mut [i32]) {\n    // 1. Find the maximum array element m\n    let m = *nums.iter().max().unwrap() as usize;\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    let mut counter = vec![0; m + 1];\n    for &num in nums.iter() {\n        counter[num as usize] += 1;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for i in 0..m {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    let n = nums.len();\n    let mut res = vec![0; n];\n    for i in (0..n).rev() {\n        let num = nums[i];\n        res[counter[num as usize] - 1] = num; // Place num at the corresponding index\n        counter[num as usize] -= 1; // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    nums.copy_from_slice(&res)\n}\n
    counting_sort.c
    /* Counting sort */\n// Full, stable implementation; can be adapted to sort objects\nvoid countingSort(int nums[], int size) {\n    // 1. Find the maximum array element m\n    int m = 0;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > m) {\n            m = nums[i];\n        }\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    int *counter = calloc(m, sizeof(int));\n    for (int i = 0; i < size; i++) {\n        counter[nums[i]]++;\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (int i = 0; i < m; i++) {\n        counter[i + 1] += counter[i];\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    int *res = malloc(sizeof(int) * size);\n    for (int i = size - 1; i >= 0; i--) {\n        int num = nums[i];\n        res[counter[num] - 1] = num; // Place num at the corresponding index\n        counter[num]--;              // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    memcpy(nums, res, size * sizeof(int));\n    // 5. Free memory\n    free(res);\n    free(counter);\n}\n
    counting_sort.kt
    /* Counting sort */\n// Full implementation; stable and adaptable to sorting objects\nfun countingSort(nums: IntArray) {\n    // 1. Find the maximum array element m\n    var m = 0\n    for (num in nums) {\n        m = max(m, num)\n    }\n    // 2. Count the occurrences of each number\n    // counter[num] is the number of occurrences of num\n    val counter = IntArray(m + 1)\n    for (num in nums) {\n        counter[num]++\n    }\n    // 3. Compute prefix sums of counter to convert occurrence counts into end positions\n    // That is, counter[num]-1 is the index of the last occurrence of num in res\n    for (i in 0..<m) {\n        counter[i + 1] += counter[i]\n    }\n    // 4. Traverse nums in reverse and place each element in the result array res\n    // Initialize array res to store the result\n    val n = nums.size\n    val res = IntArray(n)\n    for (i in n - 1 downTo 0) {\n        val num = nums[i]\n        res[counter[num] - 1] = num // Place num at the corresponding index\n        counter[num]-- // Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n    }\n    // Overwrite the original array nums with the result array res\n    for (i in 0..<n) {\n        nums[i] = res[i]\n    }\n}\n
    counting_sort.rb
    ### Counting sort ###\ndef counting_sort(nums)\n  # Full, stable implementation; the approach can be adapted to sort objects\n  # 1. Find the maximum array element m\n  m = nums.max\n  # 2. Count the occurrences of each number\n  # counter[num] is the number of occurrences of num\n  counter = Array.new(m + 1, 0)\n  nums.each { |num| counter[num] += 1 }\n  # 3. Compute prefix sums of counter to convert occurrence counts into end positions\n  # That is, counter[num]-1 is the index of the last occurrence of num in res\n  (0...m).each { |i| counter[i + 1] += counter[i] }\n  # 4. Traverse nums in reverse and place each element in the result array res\n  # Initialize array res to store the result\n  n = nums.length\n  res = Array.new(n, 0)\n  (n - 1).downto(0).each do |i|\n    num = nums[i]\n    res[counter[num] - 1] = num # Place num at the corresponding index\n    counter[num] -= 1 # Decrement the prefix sum by 1 to determine where to place the next occurrence of num\n  end\n  # Overwrite the original array nums with the result array res\n  (0...n).each { |i| nums[i] = res[i] }\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1193-algorithm-properties","level":2,"title":"11.9.3   Algorithm properties","text":"
    • Time complexity: \\(O(n + m)\\); not adaptive. Traversing nums and counter each takes linear time. Usually \\(n \\gg m\\), so the complexity approaches \\(O(n)\\).
    • Space complexity: \\(O(n + m)\\); not in-place. The algorithm uses arrays res and counter of lengths \\(n\\) and \\(m\\), respectively.
    • Stable. Since elements are placed into res from right to left, traversing nums in reverse preserves the relative order of equal elements. A forward traversal also produces a correctly sorted result, but it is not stable.
    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1194-limitations","level":2,"title":"11.9.4   Limitations","text":"

    Counting sort may seem ingenious: it sorts efficiently simply by counting occurrences. However, its prerequisites are relatively restrictive.

    Counting sort applies only to nonnegative integers. For other data types, the values must be convertible to nonnegative integers without changing their relative order. For example, if an integer array contains negative numbers, add a constant to every element to make them all positive, sort, and then reverse the transformation.

    Counting sort suits large datasets with a relatively small value range. In the example above, \\(m\\) must not be too large, or the algorithm will consume excessive space. When \\(n \\ll m\\), counting sort takes \\(O(m)\\) time and may be slower than an \\(O(n \\log n)\\) sorting algorithm.

    ","path":["Chapter 11.   Sorting","11.9   Counting sort"],"tags":[]},{"location":"chapter_sorting/exercises/","level":1,"title":"11.12   Exercises","text":"","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#11121-check-your-understanding","level":2,"title":"11.12.1   Check your understanding","text":"","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#1-the-first-passes-of-selection-sort-and-bubble-sort","level":3,"title":"1.   The first passes of selection sort and bubble sort","text":"

    Given the array [4, 2, 5, 1, 3], sort in ascending order throughout this exercise.

    1. Simulate the first two passes of selection sort. Write the array after each pass and identify the positions whose final values are now fixed.
    2. Simulate the first pass of bubble sort. Write the array and the number of swaps, and identify the position whose final value is now fixed.
    Reference answer
    1. The first two passes are:

      Pass Array Explanation 1 [1, 2, 5, 4, 3] Swap the minimum element, 1, with the first element 2 [1, 2, 5, 4, 3] The value 2 is already at index 1, so no swap is needed

      The first two positions are now fixed. Subsequent passes need only continue selecting the minimum from [5, 4, 3].

    2. Compare adjacent elements in order: swap 4 and 2, leave 4 and 5 unchanged, swap 5 and 1, then swap 5 and 3. The result is [2, 4, 1, 3, 5], with three swaps. The maximum element, 5, has moved to the end, fixing the last position.

    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#2-does-the-order-of-equal-elements-change","level":3,"title":"2.   Does the order of equal elements change?","text":"

    In the array \\([2_a, 2_b, 1]\\), \\(2_a\\) and \\(2_b\\) have equal values; the subscripts mark their original order.

    1. Write the array after the first pass of selection sort. Has the relative order of \\(2_a\\) and \\(2_b\\) changed?
    2. Write the array after the first pass of bubble sort. Has the relative order of \\(2_a\\) and \\(2_b\\) changed?
    3. Based on your answers, explain how the two algorithms differ in preserving the original order of equal elements.
    Reference answer
    1. The first pass of selection sort selects the minimum element, 1, and swaps it with the first element, \\(2_a\\), producing \\([1, 2_b, 2_a]\\). The swap moves \\(2_a\\) after \\(2_b\\), changing their relative order.

    2. Bubble sort first compares \\(2_a\\) with \\(2_b\\) and does not swap them because they are equal. It then compares and swaps \\(2_b\\) and 1. After the first pass, the array is \\([2_a, 1, 2_b]\\). Since \\(2_a\\) still precedes \\(2_b\\), their relative order is unchanged.

    3. In this example, selection sort changes the original order of equal elements. Bubble sort swaps adjacent elements only when the left element is greater than the right one. Equal elements are never swapped with each other, so their original order is preserved.

    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#3-comparing-counting-sort-and-radix-sort","level":3,"title":"3.   Comparing counting sort and radix sort","text":"

    A school needs to sort many student IDs, each exactly eight digits long. Answer the following:

    1. How many passes does radix sort need when starting with the least significant digit?
    2. If counting sort treats each student ID directly as an integer, why would many counting entries go unused?
    3. Based on your answers, which would you choose for sorting many eight-digit student IDs: counting sort or radix sort? Why?
    Reference answer
    1. Each ID has eight digits, so there are eight passes, from the least to the most significant digit. Each pass groups digits only by values 0 through 9.

    2. Direct counting reserves an entry for every possible eight-digit value, but actual students use only a small fraction of them. Most counts would remain 0.

    3. Choose radix sort. It uses the fixed number of digits and the ten possible values per digit, requiring only eight stable grouping passes. Counting sort using each entire eight-digit ID as an integer index would reserve entries for many values that never occur.

    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#11122-programming-exercises","level":2,"title":"11.12.2   Programming exercises","text":"","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#1-sorting-an-array-with-merge-sort","level":3,"title":"1.   Sorting an array with merge sort","text":"

    Given an integer array nums, implement merge sort yourself to arrange its elements in nondecreasing order and return the result. Do not call the language's built-in sorting function.

    Hints
    1. An interval of length at most 1 is already sorted.
    2. Split the interval in half at the midpoint and recursively sort each half.
    3. Use two pointers to merge the sorted halves, then write the result back to the original array.

    LeetCode

    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/exercises/#2-sorting-an-integer-array-with-counting-sort","level":3,"title":"2.   Sorting an integer array with counting sort","text":"

    You are given an integer array nums and a nonnegative integer \\(K\\). Every element lies between \\(0\\) and \\(K\\).

    Implement counting sort, write the elements back into nums in nondecreasing order, and return nums. Do not determine the order by comparing elements with one another, and do not use the language's built-in sorting function.

    Hints
    1. Since every element is between 0 and K, its value can directly index the counting array.
    2. First scan nums, incrementing the count at the corresponding position by 1.
    3. Then scan the counting array from 0 through K. If x occurs a given number of times, write that many consecutive copies of x into nums.
    ","path":["Chapter 11.   Sorting","11.12   Exercises"],"tags":[]},{"location":"chapter_sorting/heap_sort/","level":1,"title":"11.7   Heap sort","text":"

    Tip

    Before reading this section, make sure you have completed the chapter on heaps.

    Heap sort is an efficient sorting algorithm built on the heap data structure. We can implement it with the heap-building and removal operations we have already learned.

    1. Build a min heap from the input array. The smallest element is now at the root.
    2. Repeatedly remove the root and record the removed elements to obtain an ascending sequence.

    Although this works, it requires an extra array to store the removed elements, wasting space. In practice, we usually use a more elegant implementation.

    ","path":["Chapter 11.   Sorting","11.7   Heap sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1171-algorithm-steps","level":2,"title":"11.7.1   Algorithm steps","text":"

    For an array of length \\(n\\), heap sort proceeds as shown below.

    1. Build a max heap from the input array. The largest element is now at the root.
    2. Swap the root, the first element, with the last element of the heap. Reduce the heap's length by \\(1\\) and increase the number of sorted elements by \\(1\\).
    3. Starting at the root, sift down to restore the heap property.
    4. Repeat steps 2. and 3.. After \\(n - 1\\) rounds, the array is sorted.

    Tip

    In fact, removing the root of a heap also includes steps 2. and 3., with the additional step of removing an element from the array.

    <1><2><3><4><5><6><7><8><9><10><11><12>

    Figure 11-12   Steps of heap sort

    The implementation uses the same sift_down() function introduced in the heap chapter. Because the heap shrinks as maximum elements are extracted, add a length parameter \\(n\\) to specify its current valid length. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap_sort.py
    def sift_down(nums: list[int], n: int, i: int):\n    \"\"\"Sift down from node i in a heap of size n\"\"\"\n    while True:\n        # Find the node with the largest value among i, l, and r, and store its index in ma\n        l = 2 * i + 1\n        r = 2 * i + 2\n        ma = i\n        if l < n and nums[l] > nums[ma]:\n            ma = l\n        if r < n and nums[r] > nums[ma]:\n            ma = r\n        # Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i:\n            break\n        # Swap the two nodes\n        nums[i], nums[ma] = nums[ma], nums[i]\n        # Continue sifting down\n        i = ma\n\ndef heap_sort(nums: list[int]):\n    \"\"\"Heap sort\"\"\"\n    # Build the heap by sifting down from every non-leaf node\n    for i in range(len(nums) // 2 - 1, -1, -1):\n        sift_down(nums, len(nums), i)\n    # Extract the largest element from the heap, repeating for n-1 rounds\n    for i in range(len(nums) - 1, 0, -1):\n        # Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        nums[0], nums[i] = nums[i], nums[0]\n        # Sift down starting at the root\n        sift_down(nums, i, 0)\n
    heap_sort.cpp
    /* Sift down from node i in a heap of size n */\nvoid siftDown(vector<int> &nums, int n, int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i) {\n            break;\n        }\n        // Swap the two nodes\n        swap(nums[i], nums[ma]);\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(vector<int> &nums) {\n    // Build the heap by sifting down from every non-leaf node\n    for (int i = nums.size() / 2 - 1; i >= 0; --i) {\n        siftDown(nums, nums.size(), i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (int i = nums.size() - 1; i > 0; --i) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        swap(nums[0], nums[i]);\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.java
    /* Sift down from node i in a heap of size n */\nvoid siftDown(int[] nums, int n, int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i)\n            break;\n        // Swap the two nodes\n        int temp = nums[i];\n        nums[i] = nums[ma];\n        nums[ma] = temp;\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(int[] nums) {\n    // Build the heap by sifting down from every non-leaf node\n    for (int i = nums.length / 2 - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (int i = nums.length - 1; i > 0; i--) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        int tmp = nums[0];\n        nums[0] = nums[i];\n        nums[i] = tmp;\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.cs
    /* Sift down from node i in a heap of size n */\nvoid SiftDown(int[] nums, int n, int i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i)\n            break;\n        // Swap the two nodes\n        (nums[ma], nums[i]) = (nums[i], nums[ma]);\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid HeapSort(int[] nums) {\n    // Build the heap by sifting down from every non-leaf node\n    for (int i = nums.Length / 2 - 1; i >= 0; i--) {\n        SiftDown(nums, nums.Length, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (int i = nums.Length - 1; i > 0; i--) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        (nums[i], nums[0]) = (nums[0], nums[i]);\n        // Sift down starting at the root\n        SiftDown(nums, i, 0);\n    }\n}\n
    heap_sort.go
    /* Sift down from node i in a heap of size n */\nfunc siftDown(nums *[]int, n, i int) {\n    for true {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        l := 2*i + 1\n        r := 2*i + 2\n        ma := i\n        if l < n && (*nums)[l] > (*nums)[ma] {\n            ma = l\n        }\n        if r < n && (*nums)[r] > (*nums)[ma] {\n            ma = r\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break\n        }\n        // Swap the two nodes\n        (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i]\n        // Continue sifting down\n        i = ma\n    }\n}\n\n/* Heap sort */\nfunc heapSort(nums *[]int) {\n    // Build the heap by sifting down from every non-leaf node\n    for i := len(*nums)/2 - 1; i >= 0; i-- {\n        siftDown(nums, len(*nums), i)\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for i := len(*nums) - 1; i > 0; i-- {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0]\n        // Sift down starting at the root\n        siftDown(nums, i, 0)\n    }\n}\n
    heap_sort.swift
    /* Sift down from node i in a heap of size n */\nfunc siftDown(nums: inout [Int], n: Int, i: Int) {\n    var i = i\n    while true {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = 2 * i + 1\n        let r = 2 * i + 2\n        var ma = i\n        if l < n, nums[l] > nums[ma] {\n            ma = l\n        }\n        if r < n, nums[r] > nums[ma] {\n            ma = r\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break\n        }\n        // Swap the two nodes\n        nums.swapAt(i, ma)\n        // Continue sifting down\n        i = ma\n    }\n}\n\n/* Heap sort */\nfunc heapSort(nums: inout [Int]) {\n    // Build the heap by sifting down from every non-leaf node\n    for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) {\n        siftDown(nums: &nums, n: nums.count, i: i)\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for i in nums.indices.dropFirst().reversed() {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        nums.swapAt(0, i)\n        // Sift down starting at the root\n        siftDown(nums: &nums, n: i, i: 0)\n    }\n}\n
    heap_sort.js
    /* Sift down from node i in a heap of size n */\nfunction siftDown(nums, n, i) {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let ma = i;\n        if (l < n && nums[l] > nums[ma]) {\n            ma = l;\n        }\n        if (r < n && nums[r] > nums[ma]) {\n            ma = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma === i) {\n            break;\n        }\n        // Swap the two nodes\n        [nums[i], nums[ma]] = [nums[ma], nums[i]];\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfunction heapSort(nums) {\n    // Build the heap by sifting down from every non-leaf node\n    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        [nums[0], nums[i]] = [nums[i], nums[0]];\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.ts
    /* Sift down from node i in a heap of size n */\nfunction siftDown(nums: number[], n: number, i: number): void {\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let ma = i;\n        if (l < n && nums[l] > nums[ma]) {\n            ma = l;\n        }\n        if (r < n && nums[r] > nums[ma]) {\n            ma = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma === i) {\n            break;\n        }\n        // Swap the two nodes\n        [nums[i], nums[ma]] = [nums[ma], nums[i]];\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfunction heapSort(nums: number[]): void {\n    // Build the heap by sifting down from every non-leaf node\n    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {\n        siftDown(nums, nums.length, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (let i = nums.length - 1; i > 0; i--) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        [nums[0], nums[i]] = [nums[i], nums[0]];\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.dart
    /* Sift down from node i in a heap of size n */\nvoid siftDown(List<int> nums, int n, int i) {\n  while (true) {\n    // Find the node with the largest value among i, l, and r, and store its index in ma\n    int l = 2 * i + 1;\n    int r = 2 * i + 2;\n    int ma = i;\n    if (l < n && nums[l] > nums[ma]) ma = l;\n    if (r < n && nums[r] > nums[ma]) ma = r;\n    // Stop if node i is largest or both child indices l and r are out of bounds\n    if (ma == i) break;\n    // Swap the two nodes\n    int temp = nums[i];\n    nums[i] = nums[ma];\n    nums[ma] = temp;\n    // Continue sifting down\n    i = ma;\n  }\n}\n\n/* Heap sort */\nvoid heapSort(List<int> nums) {\n  // Build the heap by sifting down from every non-leaf node\n  for (int i = nums.length ~/ 2 - 1; i >= 0; i--) {\n    siftDown(nums, nums.length, i);\n  }\n  // Extract the largest element from the heap, repeating for n-1 rounds\n  for (int i = nums.length - 1; i > 0; i--) {\n    // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n    int tmp = nums[0];\n    nums[0] = nums[i];\n    nums[i] = tmp;\n    // Sift down starting at the root\n    siftDown(nums, i, 0);\n  }\n}\n
    heap_sort.rs
    /* Sift down from node i in a heap of size n */\nfn sift_down(nums: &mut [i32], n: usize, mut i: usize) {\n    loop {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        let l = 2 * i + 1;\n        let r = 2 * i + 2;\n        let mut ma = i;\n        if l < n && nums[l] > nums[ma] {\n            ma = l;\n        }\n        if r < n && nums[r] > nums[ma] {\n            ma = r;\n        }\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if ma == i {\n            break;\n        }\n        // Swap the two nodes\n        nums.swap(i, ma);\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nfn heap_sort(nums: &mut [i32]) {\n    // Build the heap by sifting down from every non-leaf node\n    for i in (0..nums.len() / 2).rev() {\n        sift_down(nums, nums.len(), i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for i in (1..nums.len()).rev() {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        nums.swap(0, i);\n        // Sift down starting at the root\n        sift_down(nums, i, 0);\n    }\n}\n
    heap_sort.c
    /* Sift down from node i in a heap of size n */\nvoid siftDown(int nums[], int n, int i) {\n    while (1) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        int l = 2 * i + 1;\n        int r = 2 * i + 2;\n        int ma = i;\n        if (l < n && nums[l] > nums[ma])\n            ma = l;\n        if (r < n && nums[r] > nums[ma])\n            ma = r;\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i) {\n            break;\n        }\n        // Swap the two nodes\n        int temp = nums[i];\n        nums[i] = nums[ma];\n        nums[ma] = temp;\n        // Continue sifting down\n        i = ma;\n    }\n}\n\n/* Heap sort */\nvoid heapSort(int nums[], int n) {\n    // Build the heap by sifting down from every non-leaf node\n    for (int i = n / 2 - 1; i >= 0; --i) {\n        siftDown(nums, n, i);\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (int i = n - 1; i > 0; --i) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        int tmp = nums[0];\n        nums[0] = nums[i];\n        nums[i] = tmp;\n        // Sift down starting at the root\n        siftDown(nums, i, 0);\n    }\n}\n
    heap_sort.kt
    /* Sift down from node i in a heap of size n */\nfun siftDown(nums: IntArray, n: Int, li: Int) {\n    var i = li\n    while (true) {\n        // Find the node with the largest value among i, l, and r, and store its index in ma\n        val l = 2 * i + 1\n        val r = 2 * i + 2\n        var ma = i\n        if (l < n && nums[l] > nums[ma]) \n            ma = l\n        if (r < n && nums[r] > nums[ma]) \n            ma = r\n        // Stop if node i is largest or both child indices l and r are out of bounds\n        if (ma == i) \n            break\n        // Swap the two nodes\n        val temp = nums[i]\n        nums[i] = nums[ma]\n        nums[ma] = temp\n        // Continue sifting down\n        i = ma\n    }\n}\n\n/* Heap sort */\nfun heapSort(nums: IntArray) {\n    // Build the heap by sifting down from every non-leaf node\n    for (i in nums.size / 2 - 1 downTo 0) {\n        siftDown(nums, nums.size, i)\n    }\n    // Extract the largest element from the heap, repeating for n-1 rounds\n    for (i in nums.size - 1 downTo 1) {\n        // Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n        val temp = nums[0]\n        nums[0] = nums[i]\n        nums[i] = temp\n        // Sift down starting at the root\n        siftDown(nums, i, 0)\n    }\n}\n
    heap_sort.rb
    ### Sift down from node i in a heap of size n ###\ndef sift_down(nums, n, i)\n  while true\n    # Find the node with the largest value among i, l, and r, and store its index in ma\n    l = 2 * i + 1\n    r = 2 * i + 2\n    ma = i\n    ma = l if l < n && nums[l] > nums[ma]\n    ma = r if r < n && nums[r] > nums[ma]\n    # Stop if node i is largest or both child indices l and r are out of bounds\n    break if ma == i\n    # Swap the two nodes\n    nums[i], nums[ma] = nums[ma], nums[i]\n    # Continue sifting down\n    i = ma\n  end\nend\n\n### Heap sort ###\ndef heap_sort(nums)\n  # Build the heap by sifting down from every non-leaf node\n  (nums.length / 2 - 1).downto(0) do |i|\n    sift_down(nums, nums.length, i)\n  end\n  # Extract the largest element from the heap, repeating for n-1 rounds\n  (nums.length - 1).downto(1) do |i|\n    # Swap the root with the rightmost leaf on the last level (the first and last elements of the current heap)\n    nums[0], nums[i] = nums[i], nums[0]\n    # Sift down starting at the root\n    sift_down(nums, i, 0)\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.7   Heap sort"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1172-algorithm-properties","level":2,"title":"11.7.2   Algorithm properties","text":"
    • Time complexity: \\(O(n \\log n)\\); not adaptive. Building the heap takes \\(O(n)\\) time. Extracting its maximum takes \\(O(\\log n)\\) time and is repeated \\(n - 1\\) times.
    • Space complexity: \\(O(1)\\); in-place. A few pointer variables use \\(O(1)\\) space. Swapping and sifting take place within the original array.
    • Not stable. Swapping the root with the last element of the heap may change the relative order of equal elements.
    ","path":["Chapter 11.   Sorting","11.7   Heap sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/","level":1,"title":"11.4   Insertion sort","text":"

    Insertion sort is a simple sorting algorithm that works much like arranging a hand of playing cards.

    Choose a base element from the unsorted section, compare it with the elements in the sorted section to its left, and insert it into the correct position.

    Figure 11-6 shows how to insert an element into an array. Let the base element be base. Shift all elements between the target index and base one position to the right, then assign base to the target index.

    Figure 11-6   A single insertion operation

    ","path":["Chapter 11.   Sorting","11.4   Insertion sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1141-algorithm-steps","level":2,"title":"11.4.1   Algorithm steps","text":"

    The overall insertion sort process is shown below.

    1. Initially, the first element of the array is treated as sorted.
    2. Select the second element as base and insert it into the correct position. The first two elements are now sorted.
    3. Select the third element as base and insert it into the correct position. The first three elements are now sorted.
    4. Continue in this way. In the final round, select the last element as base and insert it into the correct position. All elements are now sorted.

    Figure 11-7   The insertion sort process

    An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby insertion_sort.py
    def insertion_sort(nums: list[int]):\n    \"\"\"Insertion sort\"\"\"\n    # Outer loop: the sorted range is [0, i-1]\n    for i in range(1, len(nums)):\n        base = nums[i]\n        j = i - 1\n        # Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while j >= 0 and nums[j] > base:\n            nums[j + 1] = nums[j]  # Shift nums[j] one position to the right\n            j -= 1\n        nums[j + 1] = base  # Place base at the correct position\n
    insertion_sort.cpp
    /* Insertion sort */\nvoid insertionSort(vector<int> &nums) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (int i = 1; i < nums.size(); i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = base; // Place base at the correct position\n    }\n}\n
    insertion_sort.java
    /* Insertion sort */\nvoid insertionSort(int[] nums) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (int i = 1; i < nums.length; i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = base;        // Place base at the correct position\n    }\n}\n
    insertion_sort.cs
    /* Insertion sort */\nvoid InsertionSort(int[] nums) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (int i = 1; i < nums.Length; i++) {\n        int bas = nums[i], j = i - 1;\n        // Inner loop: insert bas at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > bas) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = bas;         // Place bas at the correct position\n    }\n}\n
    insertion_sort.go
    /* Insertion sort */\nfunc insertionSort(nums []int) {\n    // Outer loop: the sorted range is [0, i-1]\n    for i := 1; i < len(nums); i++ {\n        base := nums[i]\n        j := i - 1\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        for j >= 0 && nums[j] > base {\n            nums[j+1] = nums[j] // Shift nums[j] one position to the right\n            j--\n        }\n        nums[j+1] = base // Place base at the correct position\n    }\n}\n
    insertion_sort.swift
    /* Insertion sort */\nfunc insertionSort(nums: inout [Int]) {\n    // Outer loop: the sorted range is [0, i-1]\n    for i in nums.indices.dropFirst() {\n        let base = nums[i]\n        var j = i - 1\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while j >= 0, nums[j] > base {\n            nums[j + 1] = nums[j] // Shift nums[j] one position to the right\n            j -= 1\n        }\n        nums[j + 1] = base // Place base at the correct position\n    }\n}\n
    insertion_sort.js
    /* Insertion sort */\nfunction insertionSort(nums) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (let i = 1; i < nums.length; i++) {\n        let base = nums[i],\n            j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = base; // Place base at the correct position\n    }\n}\n
    insertion_sort.ts
    /* Insertion sort */\nfunction insertionSort(nums: number[]): void {\n    // Outer loop: the sorted range is [0, i-1]\n    for (let i = 1; i < nums.length; i++) {\n        const base = nums[i];\n        let j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n            j--;\n        }\n        nums[j + 1] = base; // Place base at the correct position\n    }\n}\n
    insertion_sort.dart
    /* Insertion sort */\nvoid insertionSort(List<int> nums) {\n  // Outer loop: the sorted range is [0, i-1]\n  for (int i = 1; i < nums.length; i++) {\n    int base = nums[i], j = i - 1;\n    // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n    while (j >= 0 && nums[j] > base) {\n      nums[j + 1] = nums[j]; // Shift nums[j] one position to the right\n      j--;\n    }\n    nums[j + 1] = base; // Place base at the correct position\n  }\n}\n
    insertion_sort.rs
    /* Insertion sort */\nfn insertion_sort(nums: &mut [i32]) {\n    // Outer loop: the sorted range is [0, i-1]\n    for i in 1..nums.len() {\n        let (base, mut j) = (nums[i], (i - 1) as i32);\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while j >= 0 && nums[j as usize] > base {\n            nums[(j + 1) as usize] = nums[j as usize]; // Shift nums[j] one position to the right\n            j -= 1;\n        }\n        nums[(j + 1) as usize] = base; // Place base at the correct position\n    }\n}\n
    insertion_sort.c
    /* Insertion sort */\nvoid insertionSort(int nums[], int size) {\n    // Outer loop: the sorted range is [0, i-1]\n    for (int i = 1; i < size; i++) {\n        int base = nums[i], j = i - 1;\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            // Shift nums[j] one position to the right\n            nums[j + 1] = nums[j];\n            j--;\n        }\n        // Place base at the correct position\n        nums[j + 1] = base;\n    }\n}\n
    insertion_sort.kt
    /* Insertion sort */\nfun insertionSort(nums: IntArray) {\n    //Outer loop: the sorted prefix grows to 1, 2, ..., n elements\n    for (i in nums.indices) {\n        val base = nums[i]\n        var j = i - 1\n        // Inner loop: insert base at the correct position in the sorted range [0, i-1]\n        while (j >= 0 && nums[j] > base) {\n            nums[j + 1] = nums[j] // Shift nums[j] one position to the right\n            j--\n        }\n        nums[j + 1] = base        // Place base at the correct position\n    }\n}\n
    insertion_sort.rb
    ### Insertion sort ###\ndef insertion_sort(nums)\n  n = nums.length\n  # Outer loop: the sorted range is [0, i-1]\n  for i in 1...n\n    base = nums[i]\n    j = i - 1\n    # Inner loop: insert base at the correct position in the sorted range [0, i-1]\n    while j >= 0 && nums[j] > base\n      nums[j + 1] = nums[j] # Shift nums[j] one position to the right\n      j -= 1\n    end\n    nums[j + 1] = base # Place base at the correct position\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.4   Insertion sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1142-algorithm-properties","level":2,"title":"11.4.2   Algorithm properties","text":"
    • Time complexity: \\(O(n^2)\\); adaptive. In the worst case, the insertion operations require \\(n - 1\\), \\(n-2\\), \\(\\dots\\), \\(2\\), and \\(1\\) iterations, totaling \\((n - 1) n / 2\\), so the time complexity is \\(O(n^2)\\). For ordered data, an insertion can stop early. A fully sorted input achieves the best-case time complexity of \\(O(n)\\).
    • Space complexity: \\(O(1)\\); in-place. Pointers \\(i\\) and \\(j\\) use constant extra space.
    • Stable. Each element is inserted to the right of equal elements, preserving their order.
    ","path":["Chapter 11.   Sorting","11.4   Insertion sort"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1143-why-use-insertion-sort","level":2,"title":"11.4.3   Why use insertion sort?","text":"

    Insertion sort takes \\(O(n^2)\\) time, while quick sort, which we will study shortly, takes \\(O(n \\log n)\\). Despite its higher time complexity, insertion sort is usually faster for small datasets.

    This resembles the comparison between linear and binary search. Algorithms such as quick sort, with \\(O(n \\log n)\\) time complexity, use divide and conquer and tend to involve more elementary operations. When the dataset is small, \\(n^2\\) and \\(n \\log n\\) are relatively close, so asymptotic complexity is not the dominant factor; the number of elementary operations per round matters more.

    In fact, the built-in sorting functions of many programming languages, such as Java, use insertion sort. The general approach is to use a divide-and-conquer algorithm such as quick sort for long arrays and insertion sort directly for short ones.

    Although bubble sort, selection sort, and insertion sort all take \\(O(n^2)\\) time, insertion sort is used much more often in practice, mainly for the following reasons.

    • Bubble sort swaps elements, using a temporary variable and three elementary operations. Insertion sort shifts elements by assignment, requiring just one operation. Bubble sort therefore usually has higher computational overhead.
    • Selection sort takes \\(O(n^2)\\) time in every case. For partially sorted data, insertion sort is usually more efficient than selection sort.
    • Selection sort is not stable and therefore cannot be used for multilevel sorting.
    ","path":["Chapter 11.   Sorting","11.4   Insertion sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/","level":1,"title":"11.6   Merge sort","text":"

    Merge sort is a divide-and-conquer sorting algorithm with the “division” and “merging” phases shown below.

    1. Division: recursively split the array at its midpoint, turning the problem of sorting a long array into problems of sorting shorter arrays.
    2. Merging: stop dividing when a subarray has length 1, then repeatedly merge two shorter sorted arrays into one longer sorted array until the process is complete.

    Figure 11-10   The division and merging phases of merge sort

    ","path":["Chapter 11.   Sorting","11.6   Merge sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1161-algorithm-steps","level":2,"title":"11.6.1   Algorithm steps","text":"

    As shown below, the division phase works from the top down, recursively splitting each array into two subarrays at its midpoint.

    1. Calculate the midpoint mid, then recursively divide the left subarray, [left, mid], and the right subarray, [mid + 1, right].
    2. Repeat step 1. recursively until each subarray has length 1.

    The merging phase works from the bottom up, combining the left and right subarrays into a sorted array. Merging begins with subarrays of length 1, so every subarray involved in this phase is already sorted.

    <1><2><3><4><5><6><7><8><9><10>

    Figure 11-11   Steps of merge sort

    Merge sort follows the same recursive order as a postorder traversal of a binary tree.

    • Postorder traversal: recurse into the left subtree, then the right subtree, and finally process the root.
    • Merge sort: recurse into the left subarray, then the right subarray, and finally merge them.

    The implementation follows. The interval to merge in nums is [left, right], while its corresponding interval in tmp is [0, right - left].

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby merge_sort.py
    def merge(nums: list[int], left: int, mid: int, right: int):\n    \"\"\"Merge the left and right subarrays\"\"\"\n    # The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    # Create a temporary array tmp to store the merged result\n    tmp = [0] * (right - left + 1)\n    # Initialize the starting indices of the left and right subarrays\n    i, j, k = left, mid + 1, 0\n    # While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while i <= mid and j <= right:\n        if nums[i] <= nums[j]:\n            tmp[k] = nums[i]\n            i += 1\n        else:\n            tmp[k] = nums[j]\n            j += 1\n        k += 1\n    # Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid:\n        tmp[k] = nums[i]\n        i += 1\n        k += 1\n    while j <= right:\n        tmp[k] = nums[j]\n        j += 1\n        k += 1\n    # Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for k in range(0, len(tmp)):\n        nums[left + k] = tmp[k]\n\ndef merge_sort(nums: list[int], left: int, right: int):\n    \"\"\"Merge sort\"\"\"\n    # Base case\n    if left >= right:\n        return  # Stop recursing when the subarray has at most one element\n    # Divide phase\n    mid = (left + right) // 2 # Calculate the midpoint\n    merge_sort(nums, left, mid)  # Recurse on the left subarray\n    merge_sort(nums, mid + 1, right)  # Recurse on the right subarray\n    # Merge phase\n    merge(nums, left, mid, right)\n
    merge_sort.cpp
    /* Merge the left and right subarrays */\nvoid merge(vector<int> &nums, int left, int mid, int right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    vector<int> tmp(right - left + 1);\n    // Initialize the starting indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.size(); k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid mergeSort(vector<int> &nums, int left, int right) {\n    // Base case\n    if (left >= right)\n        return; // Stop recursing when the subarray has at most one element\n    // Divide phase\n    int mid = left + (right - left) / 2;    // Calculate the midpoint\n    mergeSort(nums, left, mid);      // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.java
    /* Merge the left and right subarrays */\nvoid merge(int[] nums, int left, int mid, int right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    int[] tmp = new int[right - left + 1];\n    // Initialize the starting indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid mergeSort(int[] nums, int left, int right) {\n    // Base case\n    if (left >= right)\n        return; // Stop recursing when the subarray has at most one element\n    // Divide phase\n    int mid = left + (right - left) / 2; // Calculate the midpoint\n    mergeSort(nums, left, mid); // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.cs
    /* Merge the left and right subarrays */\nvoid Merge(int[] nums, int left, int mid, int right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    int[] tmp = new int[right - left + 1];\n    // Initialize the starting indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++];\n        else\n            tmp[k++] = nums[j++];\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.Length; ++k) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nvoid MergeSort(int[] nums, int left, int right) {\n    // Base case\n    if (left >= right) return;       // Stop recursing when the subarray has at most one element\n    // Divide phase\n    int mid = left + (right - left) / 2;    // Calculate the midpoint\n    MergeSort(nums, left, mid);      // Recurse on the left subarray\n    MergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    Merge(nums, left, mid, right);\n}\n
    merge_sort.go
    /* Merge the left and right subarrays */\nfunc merge(nums []int, left, mid, right int) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    tmp := make([]int, right-left+1)\n    // Initialize the starting indices of the left and right subarrays\n    i, j, k := left, mid+1, 0\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    for i <= mid && j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i]\n            i++\n        } else {\n            tmp[k] = nums[j]\n            j++\n        }\n        k++\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    for i <= mid {\n        tmp[k] = nums[i]\n        i++\n        k++\n    }\n    for j <= right {\n        tmp[k] = nums[j]\n        j++\n        k++\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for k := 0; k < len(tmp); k++ {\n        nums[left+k] = tmp[k]\n    }\n}\n\n/* Merge sort */\nfunc mergeSort(nums []int, left, right int) {\n    // Base case\n    if left >= right {\n        return\n    }\n    // Divide phase\n    mid := left + (right - left) / 2\n    mergeSort(nums, left, mid)\n    mergeSort(nums, mid+1, right)\n    // Merge phase\n    merge(nums, left, mid, right)\n}\n
    merge_sort.swift
    /* Merge the left and right subarrays */\nfunc merge(nums: inout [Int], left: Int, mid: Int, right: Int) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    var tmp = Array(repeating: 0, count: right - left + 1)\n    // Initialize the starting indices of the left and right subarrays\n    var i = left, j = mid + 1, k = 0\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while i <= mid, j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i]\n            i += 1\n        } else {\n            tmp[k] = nums[j]\n            j += 1\n        }\n        k += 1\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid {\n        tmp[k] = nums[i]\n        i += 1\n        k += 1\n    }\n    while j <= right {\n        tmp[k] = nums[j]\n        j += 1\n        k += 1\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for k in tmp.indices {\n        nums[left + k] = tmp[k]\n    }\n}\n\n/* Merge sort */\nfunc mergeSort(nums: inout [Int], left: Int, right: Int) {\n    // Base case\n    if left >= right { // Stop recursing when the subarray has at most one element\n        return\n    }\n    // Divide phase\n    let mid = left + (right - left) / 2 // Calculate the midpoint\n    mergeSort(nums: &nums, left: left, right: mid) // Recurse on the left subarray\n    mergeSort(nums: &nums, left: mid + 1, right: right) // Recurse on the right subarray\n    // Merge phase\n    merge(nums: &nums, left: left, mid: mid, right: right)\n}\n
    merge_sort.js
    /* Merge the left and right subarrays */\nfunction merge(nums, left, mid, right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    const tmp = new Array(right - left + 1);\n    // Initialize the starting indices of the left and right subarrays\n    let i = left,\n        j = mid + 1,\n        k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfunction mergeSort(nums, left, right) {\n    // Base case\n    if (left >= right) return; // Stop recursing when the subarray has at most one element\n    // Divide phase\n    let mid = Math.floor(left + (right - left) / 2); // Calculate the midpoint\n    mergeSort(nums, left, mid); // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.ts
    /* Merge the left and right subarrays */\nfunction merge(nums: number[], left: number, mid: number, right: number): void {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    const tmp = new Array(right - left + 1);\n    // Initialize the starting indices of the left and right subarrays\n    let i = left,\n        j = mid + 1,\n        k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmp.length; k++) {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfunction mergeSort(nums: number[], left: number, right: number): void {\n    // Base case\n    if (left >= right) return; // Stop recursing when the subarray has zero or one element\n    // Divide phase\n    let mid = Math.floor(left + (right - left) / 2); // Calculate the midpoint\n    mergeSort(nums, left, mid); // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.dart
    /* Merge the left and right subarrays */\nvoid merge(List<int> nums, int left, int mid, int right) {\n  // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n  // Create a temporary array tmp to store the merged result\n  List<int> tmp = List.filled(right - left + 1, 0);\n  // Initialize the starting indices of the left and right subarrays\n  int i = left, j = mid + 1, k = 0;\n  // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n  while (i <= mid && j <= right) {\n    if (nums[i] <= nums[j])\n      tmp[k++] = nums[i++];\n    else\n      tmp[k++] = nums[j++];\n  }\n  // Copy the remaining elements of the left and right subarrays into the temporary array\n  while (i <= mid) {\n    tmp[k++] = nums[i++];\n  }\n  while (j <= right) {\n    tmp[k++] = nums[j++];\n  }\n  // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n  for (k = 0; k < tmp.length; k++) {\n    nums[left + k] = tmp[k];\n  }\n}\n\n/* Merge sort */\nvoid mergeSort(List<int> nums, int left, int right) {\n  // Base case\n  if (left >= right) return; // Stop recursing when the subarray has at most one element\n  // Divide phase\n  int mid = left + (right - left) ~/ 2; // Calculate the midpoint\n  mergeSort(nums, left, mid); // Recurse on the left subarray\n  mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n  // Merge phase\n  merge(nums, left, mid, right);\n}\n
    merge_sort.rs
    /* Merge the left and right subarrays */\nfn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    let tmp_size = right - left + 1;\n    let mut tmp = vec![0; tmp_size];\n    // Initialize the starting indices of the left and right subarrays\n    let (mut i, mut j, mut k) = (left, mid + 1, 0);\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while i <= mid && j <= right {\n        if nums[i] <= nums[j] {\n            tmp[k] = nums[i];\n            i += 1;\n        } else {\n            tmp[k] = nums[j];\n            j += 1;\n        }\n        k += 1;\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while i <= mid {\n        tmp[k] = nums[i];\n        k += 1;\n        i += 1;\n    }\n    while j <= right {\n        tmp[k] = nums[j];\n        k += 1;\n        j += 1;\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for k in 0..tmp_size {\n        nums[left + k] = tmp[k];\n    }\n}\n\n/* Merge sort */\nfn merge_sort(nums: &mut [i32], left: usize, right: usize) {\n    // Base case\n    if left >= right {\n        return; // Stop recursing when the subarray has at most one element\n    }\n\n    // Divide phase\n    let mid = left + (right - left) / 2; // Calculate the midpoint\n    merge_sort(nums, left, mid); // Recurse on the left subarray\n    merge_sort(nums, mid + 1, right); // Recurse on the right subarray\n\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.c
    /* Merge the left and right subarrays */\nvoid merge(int *nums, int left, int mid, int right) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    int tmpSize = right - left + 1;\n    int *tmp = (int *)malloc(tmpSize * sizeof(int));\n    // Initialize the starting indices of the left and right subarrays\n    int i = left, j = mid + 1, k = 0;\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j]) {\n            tmp[k++] = nums[i++];\n        } else {\n            tmp[k++] = nums[j++];\n        }\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++];\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++];\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (k = 0; k < tmpSize; ++k) {\n        nums[left + k] = tmp[k];\n    }\n    // Free memory\n    free(tmp);\n}\n\n/* Merge sort */\nvoid mergeSort(int *nums, int left, int right) {\n    // Base case\n    if (left >= right)\n        return; // Stop recursing when the subarray has at most one element\n    // Divide phase\n    int mid = left + (right - left) / 2;    // Calculate the midpoint\n    mergeSort(nums, left, mid);      // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right); // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right);\n}\n
    merge_sort.kt
    /* Merge the left and right subarrays */\nfun merge(nums: IntArray, left: Int, mid: Int, right: Int) {\n    // The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n    // Create a temporary array tmp to store the merged result\n    val tmp = IntArray(right - left + 1)\n    // Initialize the starting indices of the left and right subarrays\n    var i = left\n    var j = mid + 1\n    var k = 0\n    // While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n    while (i <= mid && j <= right) {\n        if (nums[i] <= nums[j])\n            tmp[k++] = nums[i++]\n        else\n            tmp[k++] = nums[j++]\n    }\n    // Copy the remaining elements of the left and right subarrays into the temporary array\n    while (i <= mid) {\n        tmp[k++] = nums[i++]\n    }\n    while (j <= right) {\n        tmp[k++] = nums[j++]\n    }\n    // Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n    for (l in tmp.indices) {\n        nums[left + l] = tmp[l]\n    }\n}\n\n/* Merge sort */\nfun mergeSort(nums: IntArray, left: Int, right: Int) {\n    // Base case\n    if (left >= right) return  // Stop recursing when the subarray has at most one element\n    // Divide phase\n    val mid = left + (right - left) / 2 // Calculate the midpoint\n    mergeSort(nums, left, mid) // Recurse on the left subarray\n    mergeSort(nums, mid + 1, right) // Recurse on the right subarray\n    // Merge phase\n    merge(nums, left, mid, right)\n}\n
    merge_sort.rb
    ### Merge the left and right subarrays ###\ndef merge(nums, left, mid, right)\n  # The left subarray spans [left, mid], and the right subarray spans [mid+1, right]\n  # Create a temporary array tmp to store the merged result\n  tmp = Array.new(right - left + 1, 0)\n  # Initialize the starting indices of the left and right subarrays\n  i, j, k = left, mid + 1, 0\n  # While both subarrays have elements remaining, compare them and copy the smaller element into the temporary array\n  while i <= mid && j <= right\n    if nums[i] <= nums[j]\n      tmp[k] = nums[i]\n      i += 1\n    else\n      tmp[k] = nums[j]\n      j += 1\n    end\n    k += 1\n  end\n  # Copy the remaining elements of the left and right subarrays into the temporary array\n  while i <= mid\n    tmp[k] = nums[i]\n    i += 1\n    k += 1\n  end\n  while j <= right\n    tmp[k] = nums[j]\n    j += 1\n    k += 1\n  end\n  # Copy the elements of temporary array tmp back into the corresponding range of the original array nums\n  (0...tmp.length).each do |k|\n    nums[left + k] = tmp[k]\n  end\nend\n\n### Merge sort ###\ndef merge_sort(nums, left, right)\n  # Base case\n  # Stop recursing when the subarray contains at most one element\n  return if left >= right\n  # Divide phase\n  mid = left + (right - left) / 2 # Calculate the midpoint\n  merge_sort(nums, left, mid) # Recurse on the left subarray\n  merge_sort(nums, mid + 1, right) # Recurse on the right subarray\n  # Merge phase\n  merge(nums, left, mid, right)\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.6   Merge sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1162-algorithm-properties","level":2,"title":"11.6.2   Algorithm properties","text":"
    • Time complexity: \\(O(n \\log n)\\); not adaptive. Division produces a recursion tree of height \\(\\log n\\). Merging performs a total of \\(n\\) operations at each level, giving \\(O(n \\log n)\\) overall.
    • Space complexity: \\(O(n)\\); not in-place. The recursion depth is \\(\\log n\\), requiring \\(O(\\log n)\\) space for stack frames. Merging uses an auxiliary array requiring \\(O(n)\\) extra space.
    • Stable. Merging preserves the order of equal elements.
    ","path":["Chapter 11.   Sorting","11.6   Merge sort"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1163-sorting-linked-lists","level":2,"title":"11.6.3   Sorting linked lists","text":"

    Merge sort has a significant advantage over other sorting algorithms for linked lists: it can reduce the space complexity of sorting a linked list to \\(O(1)\\).

    • Division: use iteration instead of recursion to divide the list, eliminating the space needed for recursive stack frames.
    • Merging: inserting and deleting linked-list nodes requires only changing references or pointers. Merging two short sorted lists into a longer one therefore needs no additional list.

    The implementation is fairly involved. Interested readers can consult further resources for details.

    ","path":["Chapter 11.   Sorting","11.6   Merge sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/","level":1,"title":"11.5   Quick sort","text":"

    Quick sort is an efficient, widely used sorting algorithm based on divide and conquer.

    Its core operation is “partitioning”: choose an array element as the “pivot,” move all smaller elements to its left, and move all larger elements to its right. The partitioning process is shown below.

    1. Choose the leftmost element as the pivot and initialize pointers i and j at the two ends of the array.
    2. In a loop, use i to find the first element greater than the pivot and j to find the first element smaller than it, then swap those elements.
    3. Repeat step 2. until i and j meet. Finally, swap the pivot into the boundary between the two subarrays.
    <1><2><3><4><5><6><7><8><9>

    Figure 11-8   Steps of partitioning

    After partitioning, the original array has three parts: the left subarray, the pivot, and the right subarray. Every element in the left subarray is \\(\\leq\\) the pivot, which is \\(\\leq\\) every element in the right subarray. We now need only sort the two subarrays.

    Quick sort's divide-and-conquer strategy

    Partitioning reduces the problem of sorting one long array to sorting two shorter arrays.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
    def partition(self, nums: list[int], left: int, right: int) -> int:\n    \"\"\"Two-pointer partitioning\"\"\"\n    # Use nums[left] as the pivot\n    i, j = left, right\n    while i < j:\n        while i < j and nums[j] >= nums[left]:\n            j -= 1  # Scan from right to left for the first element smaller than the pivot\n        while i < j and nums[i] <= nums[left]:\n            i += 1  # Scan from left to right for the first element larger than the pivot\n        # Swap the elements\n        nums[i], nums[j] = nums[j], nums[i]\n    # Swap the pivot into the boundary position between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i  # Return the pivot index\n
    quick_sort.cpp
    /* Two-pointer partitioning */\nint partition(vector<int> &nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;                // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;                // Scan from left to right for the first element larger than the pivot\n        swap(nums[i], nums[j]); // Swap these two elements\n    }\n    swap(nums[i], nums[left]);  // Swap the pivot into the boundary position between the two subarrays\n    return i;                   // Return the pivot index\n}\n
    quick_sort.java
    /* Swap the elements */\nvoid swap(int[] nums, int i, int j) {\n    int tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\nint partition(int[] nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left);  // Swap the pivot into the boundary position between the two subarrays\n    return i;             // Return the pivot index\n}\n
    quick_sort.cs
    /* Swap the elements */\nvoid Swap(int[] nums, int i, int j) {\n    (nums[j], nums[i]) = (nums[i], nums[j]);\n}\n\n/* Two-pointer partitioning */\nint Partition(int[] nums, int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        Swap(nums, i, j); // Swap these two elements\n    }\n    Swap(nums, i, left);  // Swap the pivot into the boundary position between the two subarrays\n    return i;             // Return the pivot index\n}\n
    quick_sort.go
    /* Two-pointer partitioning */\nfunc (q *quickSort) partition(nums []int, left, right int) int {\n    // Use nums[left] as the pivot\n    i, j := left, right\n    for i < j {\n        for i < j && nums[j] >= nums[left] {\n            j-- // Scan from right to left for the first element smaller than the pivot\n        }\n        for i < j && nums[i] <= nums[left] {\n            i++ // Scan from left to right for the first element larger than the pivot\n        }\n        // Swap the elements\n        nums[i], nums[j] = nums[j], nums[i]\n    }\n    // Swap the pivot into the boundary position between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i // Return the pivot index\n}\n
    quick_sort.swift
    /* Two-pointer partitioning */\nfunc partition(nums: inout [Int], left: Int, right: Int) -> Int {\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while i < j {\n        while i < j, nums[j] >= nums[left] {\n            j -= 1 // Scan from right to left for the first element smaller than the pivot\n        }\n        while i < j, nums[i] <= nums[left] {\n            i += 1 // Scan from left to right for the first element larger than the pivot\n        }\n        nums.swapAt(i, j) // Swap these two elements\n    }\n    nums.swapAt(i, left) // Swap the pivot into the boundary position between the two subarrays\n    return i // Return the pivot index\n}\n
    quick_sort.js
    /* Swap the elements */\nswap(nums, i, j) {\n    let tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\npartition(nums, left, right) {\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j -= 1; // Scan from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i += 1; // Scan from left to right for the first element larger than the pivot\n        }\n        // Swap the elements\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i; // Return the pivot index\n}\n
    quick_sort.ts
    /* Swap the elements */\nswap(nums: number[], i: number, j: number): void {\n    let tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\npartition(nums: number[], left: number, right: number): number {\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j -= 1; // Scan from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i += 1; // Scan from left to right for the first element larger than the pivot\n        }\n        // Swap the elements\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i; // Return the pivot index\n}\n
    quick_sort.dart
    /* Swap the elements */\nvoid _swap(List<int> nums, int i, int j) {\n  int tmp = nums[i];\n  nums[i] = nums[j];\n  nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\nint _partition(List<int> nums, int left, int right) {\n  // Use nums[left] as the pivot\n  int i = left, j = right;\n  while (i < j) {\n    while (i < j && nums[j] >= nums[left]) j--; // Scan from right to left for the first element smaller than the pivot\n    while (i < j && nums[i] <= nums[left]) i++; // Scan from left to right for the first element larger than the pivot\n    _swap(nums, i, j); // Swap these two elements\n  }\n  _swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n  return i; // Return the pivot index\n}\n
    quick_sort.rs
    /* Two-pointer partitioning */\nfn partition(nums: &mut [i32], left: usize, right: usize) -> usize {\n    // Use nums[left] as the pivot\n    let (mut i, mut j) = (left, right);\n    while i < j {\n        while i < j && nums[j] >= nums[left] {\n            j -= 1; // Scan from right to left for the first element smaller than the pivot\n        }\n        while i < j && nums[i] <= nums[left] {\n            i += 1; // Scan from left to right for the first element larger than the pivot\n        }\n        nums.swap(i, j); // Swap these two elements\n    }\n    nums.swap(i, left); // Swap the pivot into the boundary position between the two subarrays\n    i // Return the pivot index\n}\n
    quick_sort.c
    /* Swap the elements */\nvoid swap(int nums[], int i, int j) {\n    int tmp = nums[i];\n    nums[i] = nums[j];\n    nums[j] = tmp;\n}\n\n/* Two-pointer partitioning */\nint partition(int nums[], int left, int right) {\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j--; // Scan from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i++; // Scan from left to right for the first element larger than the pivot\n        }\n        // Swap these two elements\n        swap(nums, i, j);\n    }\n    // Swap the pivot into the boundary position between the two subarrays\n    swap(nums, i, left);\n    // Return the pivot index\n    return i;\n}\n
    quick_sort.kt
    /* Swap the elements */\nfun swap(nums: IntArray, i: Int, j: Int) {\n    val temp = nums[i]\n    nums[i] = nums[j]\n    nums[j] = temp\n}\n\n/* Two-pointer partitioning */\nfun partition(nums: IntArray, left: Int, right: Int): Int {\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--           // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++           // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j)  // Swap these two elements\n    }\n    swap(nums, i, left)   // Swap the pivot into the boundary position between the two subarrays\n    return i              // Return the pivot index\n}\n
    quick_sort.rb
    ### Two-pointer partitioning ###\ndef partition(nums, left, right)\n  # Use nums[left] as the pivot\n  i, j = left, right\n  while i < j\n    while i < j && nums[j] >= nums[left]\n      j -= 1 # Scan from right to left for the first element smaller than the pivot\n    end\n    while i < j && nums[i] <= nums[left]\n      i += 1 # Scan from left to right for the first element larger than the pivot\n    end\n    # Swap the elements\n    nums[i], nums[j] = nums[j], nums[i]\n  end\n  # Swap the pivot into the boundary position between the two subarrays\n  nums[i], nums[left] = nums[left], nums[i]\n  i # Return the pivot index\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1151-algorithm-steps","level":2,"title":"11.5.1   Algorithm steps","text":"

    The overall quick sort process is shown below.

    1. Partition the original array to obtain unsorted left and right subarrays.
    2. Recursively partition the left and right subarrays.
    3. Continue until each subarray has length 1, completing the sort.

    Figure 11-9   The quick sort process

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
    def quick_sort(self, nums: list[int], left: int, right: int):\n    \"\"\"Quick sort\"\"\"\n    # Stop recursing when the subarray has at most one element\n    if left >= right:\n        return\n    # Two-pointer partitioning\n    pivot = self.partition(nums, left, right)\n    # Recurse on the left and right subarrays\n    self.quick_sort(nums, left, pivot - 1)\n    self.quick_sort(nums, pivot + 1, right)\n
    quick_sort.cpp
    /* Quick sort */\nvoid quickSort(vector<int> &nums, int left, int right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right)\n        return;\n    // Two-pointer partitioning\n    int pivot = partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.java
    /* Quick sort */\nvoid quickSort(int[] nums, int left, int right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right)\n        return;\n    // Two-pointer partitioning\n    int pivot = partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.cs
    /* Quick sort */\nvoid QuickSort(int[] nums, int left, int right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right)\n        return;\n    // Two-pointer partitioning\n    int pivot = Partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    QuickSort(nums, left, pivot - 1);\n    QuickSort(nums, pivot + 1, right);\n}\n
    quick_sort.go
    /* Quick sort */\nfunc (q *quickSort) quickSort(nums []int, left, right int) {\n    // Stop recursing when the subarray has at most one element\n    if left >= right {\n        return\n    }\n    // Two-pointer partitioning\n    pivot := q.partition(nums, left, right)\n    // Recurse on the left and right subarrays\n    q.quickSort(nums, left, pivot-1)\n    q.quickSort(nums, pivot+1, right)\n}\n
    quick_sort.swift
    /* Quick sort */\nfunc quickSort(nums: inout [Int], left: Int, right: Int) {\n    // Stop recursing when the subarray has at most one element\n    if left >= right {\n        return\n    }\n    // Two-pointer partitioning\n    let pivot = partition(nums: &nums, left: left, right: right)\n    // Recurse on the left and right subarrays\n    quickSort(nums: &nums, left: left, right: pivot - 1)\n    quickSort(nums: &nums, left: pivot + 1, right: right)\n}\n
    quick_sort.js
    /* Quick sort */\nquickSort(nums, left, right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right) return;\n    // Two-pointer partitioning\n    const pivot = this.partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    this.quickSort(nums, left, pivot - 1);\n    this.quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.ts
    /* Quick sort */\nquickSort(nums: number[], left: number, right: number): void {\n    // Stop recursing when the subarray has zero or one element\n    if (left >= right) {\n        return;\n    }\n    // Two-pointer partitioning\n    const pivot = this.partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    this.quickSort(nums, left, pivot - 1);\n    this.quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.dart
    /* Quick sort */\nvoid quickSort(List<int> nums, int left, int right) {\n  // Stop recursing when the subarray has at most one element\n  if (left >= right) return;\n  // Two-pointer partitioning\n  int pivot = _partition(nums, left, right);\n  // Recurse on the left and right subarrays\n  quickSort(nums, left, pivot - 1);\n  quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.rs
    /* Quick sort */\npub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) {\n    // Stop recursing when the subarray has at most one element\n    if left >= right {\n        return;\n    }\n    // Two-pointer partitioning\n    let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n    // Recurse on the left and right subarrays\n    Self::quick_sort(left, pivot - 1, nums);\n    Self::quick_sort(pivot + 1, right, nums);\n}\n
    quick_sort.c
    /* Quick sort */\nvoid quickSort(int nums[], int left, int right) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right) {\n        return;\n    }\n    // Two-pointer partitioning\n    int pivot = partition(nums, left, right);\n    // Recurse on the left and right subarrays\n    quickSort(nums, left, pivot - 1);\n    quickSort(nums, pivot + 1, right);\n}\n
    quick_sort.kt
    /* Quick sort */\nfun quickSort(nums: IntArray, left: Int, right: Int) {\n    // Stop recursing when the subarray has at most one element\n    if (left >= right) return\n    // Two-pointer partitioning\n    val pivot = partition(nums, left, right)\n    // Recurse on the left and right subarrays\n    quickSort(nums, left, pivot - 1)\n    quickSort(nums, pivot + 1, right)\n}\n
    quick_sort.rb
    ### Quick sort ###\ndef quick_sort(nums, left, right)\n  # Continue while the subarray contains more than one element\n  if left < right\n    # Two-pointer partitioning\n    pivot = partition(nums, left, right)\n    # Recurse on the left and right subarrays\n    quick_sort(nums, left, pivot - 1)\n    quick_sort(nums, pivot + 1, right)\n  end\n  nums\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1152-algorithm-properties","level":2,"title":"11.5.2   Algorithm properties","text":"
    • Time complexity: \\(O(n \\log n)\\); not adaptive. On average, partitioning produces \\(\\log n\\) recursive levels, with \\(n\\) loop iterations in total at each level, giving \\(O(n \\log n)\\) time. In the worst case, each partition splits an array of length \\(n\\) into subarrays of lengths \\(0\\) and \\(n - 1\\). The recursion reaches \\(n\\) levels, each involving up to \\(n\\) iterations, for \\(O(n^2)\\) time overall.
    • Space complexity: \\(O(n)\\); in-place. A fully reverse-sorted input reaches the worst-case recursion depth of \\(n\\), requiring \\(O(n)\\) space for stack frames. Sorting takes place in the original array without an auxiliary array.
    • Not stable. In the final partitioning step, the pivot may be swapped to the right of an equal element.
    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1153-why-is-quick-sort-quick","level":2,"title":"11.5.3   Why is quick sort quick?","text":"

    As its name suggests, quick sort has an efficiency advantage. Although its average time complexity matches that of merge sort and heap sort, it is usually faster, mainly for the following reasons.

    • The worst case is unlikely. Quick sort's worst-case time complexity is \\(O(n^2)\\), making its performance less predictable than merge sort's, but it runs in \\(O(n \\log n)\\) time in the vast majority of cases.
    • Efficient cache use. During partitioning, the system can load the whole subarray into cache, making element access efficient. Algorithms such as heap sort jump between elements and do not share this advantage.
    • Small constant factors. Of these three algorithms, quick sort performs the fewest comparisons, assignments, swaps, and similar operations overall. This resembles the reason insertion sort is faster than bubble sort.
    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1154-improving-pivot-selection","level":2,"title":"11.5.4   Improving pivot selection","text":"

    Quick sort can become slower on certain inputs. Consider a fully reverse-sorted array. Because the leftmost element is chosen as the pivot, partitioning moves it to the far right, leaving subarrays of lengths \\(n - 1\\) and \\(0\\). Continuing recursively produces a subarray of length \\(0\\) after every partition. Divide and conquer becomes ineffective, and quick sort degenerates into a process resembling bubble sort.

    To reduce this risk, we can improve how the pivot is selected. For example, choose a random element as the pivot. With bad luck, however, every pivot may still be poor, leading to disappointing performance.

    Programming languages generally produce pseudorandom numbers. A test case constructed specifically for a pseudorandom sequence can still degrade quick sort's performance.

    For further improvement, choose three candidate elements, usually the first, last, and middle elements, and use their median as the pivot. This greatly increases the chance that the pivot is neither too small nor too large. We can use more candidates to improve robustness further. This approach greatly reduces the likelihood of degradation to \\(O(n^2)\\).

    An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
    def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int:\n    \"\"\"Select the median of three candidate elements\"\"\"\n    l, m, r = nums[left], nums[mid], nums[right]\n    if (l <= m <= r) or (r <= m <= l):\n        return mid  # m lies between l and r\n    if (m <= l <= r) or (r <= l <= m):\n        return left  # l lies between m and r\n    return right\n\ndef partition(self, nums: list[int], left: int, right: int) -> int:\n    \"\"\"Two-pointer partitioning (median of three)\"\"\"\n    # Select the median of the leftmost, middle, and rightmost elements as the pivot\n    med = self.median_three(nums, left, (left + right) // 2, right)\n    # Swap the median into the leftmost position of the current subarray\n    nums[left], nums[med] = nums[med], nums[left]\n    # Use nums[left] as the pivot\n    i, j = left, right\n    while i < j:\n        while i < j and nums[j] >= nums[left]:\n            j -= 1  # Scan from right to left for the first element smaller than the pivot\n        while i < j and nums[i] <= nums[left]:\n            i += 1  # Scan from left to right for the first element larger than the pivot\n        # Swap the elements\n        nums[i], nums[j] = nums[j], nums[i]\n    # Swap the pivot into the boundary position between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i  # Return the pivot index\n
    quick_sort.cpp
    /* Select the median of three candidate elements */\nint medianThree(vector<int> &nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m lies between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l lies between m and r\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint partition(vector<int> &nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the current subarray\n    swap(nums[left], nums[med]);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;                // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;                // Scan from left to right for the first element larger than the pivot\n        swap(nums[i], nums[j]); // Swap these two elements\n    }\n    swap(nums[i], nums[left]);  // Swap the pivot into the boundary position between the two subarrays\n    return i;                   // Return the pivot index\n}\n
    quick_sort.java
    /* Select the median of three candidate elements */\nint medianThree(int[] nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m lies between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l lies between m and r\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint partition(int[] nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the current subarray\n    swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left);  // Swap the pivot into the boundary position between the two subarrays\n    return i;             // Return the pivot index\n}\n
    quick_sort.cs
    /* Select the median of three candidate elements */\nint MedianThree(int[] nums, int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m lies between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l lies between m and r\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint Partition(int[] nums, int left, int right) {\n    // Select the median of three candidate elements\n    int med = MedianThree(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the current subarray\n    Swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--;          // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        Swap(nums, i, j); // Swap these two elements\n    }\n    Swap(nums, i, left);  // Swap the pivot into the boundary position between the two subarrays\n    return i;             // Return the pivot index\n}\n
    quick_sort.go
    /* Select the median of three candidate elements */\nfunc (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int {\n    l, m, r := nums[left], nums[mid], nums[right]\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid // m lies between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left // l lies between m and r\n    }\n    return right\n}\n\n/* Two-pointer partitioning (median of three)*/\nfunc (q *quickSortMedian) partition(nums []int, left, right int) int {\n    // Select the median of the left, middle, and right elements as the pivot\n    med := q.medianThree(nums, left, (left+right)/2, right)\n    // Swap the median into the leftmost position of the current subarray\n    nums[left], nums[med] = nums[med], nums[left]\n    // Use nums[left] as the pivot\n    i, j := left, right\n    for i < j {\n        for i < j && nums[j] >= nums[left] {\n            j-- //Scan from right to left for the first element smaller than the pivot\n        }\n        for i < j && nums[i] <= nums[left] {\n            i++ //Scan from left to right for the first element larger than the pivot\n        }\n        //Swap the elements\n        nums[i], nums[j] = nums[j], nums[i]\n    }\n    //Swap the pivot into the boundary position between the two subarrays\n    nums[i], nums[left] = nums[left], nums[i]\n    return i //Return the pivot index\n}\n
    quick_sort.swift
    /* Select the median of three candidate elements */\nfunc medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int {\n    let l = nums[left]\n    let m = nums[mid]\n    let r = nums[right]\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid // m lies between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left // l lies between m and r\n    }\n    return right\n}\n\n/* Two-pointer partitioning (median of three) */\nfunc partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int {\n    // Select the median of three candidate elements\n    let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right)\n    // Swap the median into the leftmost position of the current subarray\n    nums.swapAt(left, med)\n    return partition(nums: &nums, left: left, right: right)\n}\n
    quick_sort.js
    /* Select the median of three candidate elements */\nmedianThree(nums, left, mid, right) {\n    let l = nums[left],\n        m = nums[mid],\n        r = nums[right];\n    // m lies between l and r\n    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;\n    // l lies between m and r\n    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\npartition(nums, left, right) {\n    // Select the median of three candidate elements\n    let med = this.medianThree(\n        nums,\n        left,\n        Math.floor((left + right) / 2),\n        right\n    );\n    // Swap the median into the leftmost position of the current subarray\n    this.swap(nums, left, med);\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) j--; // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left]) i++; // Scan from left to right for the first element larger than the pivot\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i; // Return the pivot index\n}\n
    quick_sort.ts
    /* Select the median of three candidate elements */\nmedianThree(\n    nums: number[],\n    left: number,\n    mid: number,\n    right: number\n): number {\n    let l = nums[left],\n        m = nums[mid],\n        r = nums[right];\n    // m lies between l and r\n    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;\n    // l lies between m and r\n    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\npartition(nums: number[], left: number, right: number): number {\n    // Select the median of three candidate elements\n    let med = this.medianThree(\n        nums,\n        left,\n        Math.floor((left + right) / 2),\n        right\n    );\n    // Swap the median into the leftmost position of the current subarray\n    this.swap(nums, left, med);\n    // Use nums[left] as the pivot\n    let i = left,\n        j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left]) {\n            j--; // Scan from right to left for the first element smaller than the pivot\n        }\n        while (i < j && nums[i] <= nums[left]) {\n            i++; // Scan from left to right for the first element larger than the pivot\n        }\n        this.swap(nums, i, j); // Swap these two elements\n    }\n    this.swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i; // Return the pivot index\n}\n
    quick_sort.dart
    /* Select the median of three candidate elements */\nint _medianThree(List<int> nums, int left, int mid, int right) {\n  int l = nums[left], m = nums[mid], r = nums[right];\n  if ((l <= m && m <= r) || (r <= m && m <= l))\n    return mid; // m lies between l and r\n  if ((m <= l && l <= r) || (r <= l && l <= m))\n    return left; // l lies between m and r\n  return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint _partition(List<int> nums, int left, int right) {\n  // Select the median of three candidate elements\n  int med = _medianThree(nums, left, (left + right) ~/ 2, right);\n  // Swap the median into the leftmost position of the current subarray\n  _swap(nums, left, med);\n  // Use nums[left] as the pivot\n  int i = left, j = right;\n  while (i < j) {\n    while (i < j && nums[j] >= nums[left]) j--; // Scan from right to left for the first element smaller than the pivot\n    while (i < j && nums[i] <= nums[left]) i++; // Scan from left to right for the first element larger than the pivot\n    _swap(nums, i, j); // Swap these two elements\n  }\n  _swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n  return i; // Return the pivot index\n}\n
    quick_sort.rs
    /* Select the median of three candidate elements */\nfn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize {\n    let (l, m, r) = (nums[left], nums[mid], nums[right]);\n    if (l <= m && m <= r) || (r <= m && m <= l) {\n        return mid; // m lies between l and r\n    }\n    if (m <= l && l <= r) || (r <= l && l <= m) {\n        return left; // l lies between m and r\n    }\n    right\n}\n\n/* Two-pointer partitioning (median of three) */\nfn partition(nums: &mut [i32], left: usize, right: usize) -> usize {\n    // Select the median of three candidate elements\n    let med = Self::median_three(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the current subarray\n    nums.swap(left, med);\n    // Use nums[left] as the pivot\n    let (mut i, mut j) = (left, right);\n    while i < j {\n        while i < j && nums[j] >= nums[left] {\n            j -= 1; // Scan from right to left for the first element smaller than the pivot\n        }\n        while i < j && nums[i] <= nums[left] {\n            i += 1; // Scan from left to right for the first element larger than the pivot\n        }\n        nums.swap(i, j); // Swap these two elements\n    }\n    nums.swap(i, left); // Swap the pivot into the boundary position between the two subarrays\n    i // Return the pivot index\n}\n
    quick_sort.c
    /* Select the median of three candidate elements */\nint medianThree(int nums[], int left, int mid, int right) {\n    int l = nums[left], m = nums[mid], r = nums[right];\n    if ((l <= m && m <= r) || (r <= m && m <= l))\n        return mid; // m lies between l and r\n    if ((m <= l && l <= r) || (r <= l && l <= m))\n        return left; // l lies between m and r\n    return right;\n}\n\n/* Two-pointer partitioning (median of three) */\nint partitionMedian(int nums[], int left, int right) {\n    // Select the median of three candidate elements\n    int med = medianThree(nums, left, (left + right) / 2, right);\n    // Swap the median into the leftmost position of the array\n    swap(nums, left, med);\n    // Use nums[left] as the pivot\n    int i = left, j = right;\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--; // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++;          // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j); // Swap these two elements\n    }\n    swap(nums, i, left); // Swap the pivot into the boundary position between the two subarrays\n    return i;            // Return the pivot index\n}\n
    quick_sort.kt
    /* Select the median of three candidate elements */\nfun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int {\n    val l = nums[left]\n    val m = nums[mid]\n    val r = nums[right]\n    if ((m in l..r) || (m in r..l))\n        return mid  // m lies between l and r\n    if ((l in m..r) || (l in r..m))\n        return left // l lies between m and r\n    return right\n}\n\n/* Two-pointer partitioning (median of three) */\nfun partitionMedian(nums: IntArray, left: Int, right: Int): Int {\n    // Select the median of three candidate elements\n    val med = medianThree(nums, left, (left + right) / 2, right)\n    // Swap the median into the leftmost position of the current subarray\n    swap(nums, left, med)\n    // Use nums[left] as the pivot\n    var i = left\n    var j = right\n    while (i < j) {\n        while (i < j && nums[j] >= nums[left])\n            j--                      // Scan from right to left for the first element smaller than the pivot\n        while (i < j && nums[i] <= nums[left])\n            i++                      // Scan from left to right for the first element larger than the pivot\n        swap(nums, i, j)             // Swap these two elements\n    }\n    swap(nums, i, left)              // Swap the pivot into the boundary position between the two subarrays\n    return i                         // Return the pivot index\n}\n
    quick_sort.rb
    ### Select the median of three candidate elements ###\ndef median_three(nums, left, mid, right)\n  # Select the median of three candidate elements\n  _l, _m, _r = nums[left], nums[mid], nums[right]\n  # _m lies between _l and _r\n  return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l)\n  # _l lies between _m and _r\n  return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m)\n  return right\nend\n\n### Two-pointer partitioning (median of three)###\ndef partition(nums, left, right)\n  ### Use nums[left] as the pivot\n  med = median_three(nums, left, (left + right) / 2, right)\n  # Swap the median into the leftmost position of the current subarray\n  nums[left], nums[med] = nums[med], nums[left]\n  i, j = left, right\n  while i < j\n    while i < j && nums[j] >= nums[left]\n      j -= 1 # Scan from right to left for the first element smaller than the pivot\n    end\n    while i < j && nums[i] <= nums[left]\n      i += 1 # Scan from left to right for the first element larger than the pivot\n    end\n    # Swap the elements\n    nums[i], nums[j] = nums[j], nums[i]\n  end\n  # Swap the pivot into the boundary position between the two subarrays\n  nums[i], nums[left] = nums[left], nums[i]\n  i # Return the pivot index\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1155-reducing-recursion-depth","level":2,"title":"11.5.5   Reducing recursion depth","text":"

    Quick sort may use considerable space on certain inputs. For a fully sorted input, let the current subarray have length \\(m\\). Each partition produces a left subarray of length \\(0\\) and a right subarray of length \\(m - 1\\). Each recursive call therefore reduces the problem size by only one element, producing a recursion tree of height \\(n - 1\\) and requiring \\(O(n)\\) space for stack frames.

    To prevent stack frames from accumulating, compare the two subarray lengths after each partition and recurse only on the shorter one. Since that subarray has length at most \\(n / 2\\), the recursion depth is at most \\(\\log n\\), reducing the worst-case space complexity to \\(O(\\log n)\\). The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py
    def quick_sort(self, nums: list[int], left: int, right: int):\n    \"\"\"Quick sort (recursion depth optimization)\"\"\"\n    # Stop when the subarray has at most one element\n    while left < right:\n        # Perform two-pointer partitioning\n        pivot = self.partition(nums, left, right)\n        # Apply quick sort to the shorter of the two subarrays\n        if pivot - left < right - pivot:\n            self.quick_sort(nums, left, pivot - 1)  # Recursively sort the left subarray\n            left = pivot + 1  # The remaining unsorted range is [pivot + 1, right]\n        else:\n            self.quick_sort(nums, pivot + 1, right)  # Recursively sort the right subarray\n            right = pivot - 1  # The remaining unsorted range is [left, pivot - 1]\n
    quick_sort.cpp
    /* Quick sort (recursion depth optimization) */\nvoid quickSort(vector<int> &nums, int left, int right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        int pivot = partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1;                 // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1;                 // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.java
    /* Quick sort (recursion depth optimization) */\nvoid quickSort(int[] nums, int left, int right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        int pivot = partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.cs
    /* Quick sort (recursion depth optimization) */\nvoid QuickSort(int[] nums, int left, int right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        int pivot = Partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            QuickSort(nums, left, pivot - 1);  // Recursively sort the left subarray\n            left = pivot + 1;  // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            QuickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.go
    /* Quick sort (recursion depth optimization)*/\nfunc (q *quickSortTailCall) quickSort(nums []int, left, right int) {\n    // Stop when the subarray has at most one element\n    for left < right {\n        // Perform two-pointer partitioning\n        pivot := q.partition(nums, left, right)\n        // Apply quick sort to the shorter of the two subarrays\n        if pivot-left < right-pivot {\n            q.quickSort(nums, left, pivot-1) // Recursively sort the left subarray\n            left = pivot + 1                 // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            q.quickSort(nums, pivot+1, right) // Recursively sort the right subarray\n            right = pivot - 1                 // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.swift
    /* Quick sort (recursion depth optimization) */\nfunc quickSortTailCall(nums: inout [Int], left: Int, right: Int) {\n    var left = left\n    var right = right\n    // Stop when the subarray has at most one element\n    while left < right {\n        // Perform two-pointer partitioning\n        let pivot = partition(nums: &nums, left: left, right: right)\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left) < (right - pivot) {\n            quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // Recursively sort the left subarray\n            left = pivot + 1 // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // Recursively sort the right subarray\n            right = pivot - 1 // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.js
    /* Quick sort (recursion depth optimization) */\nquickSort(nums, left, right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        let pivot = this.partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.ts
    /* Quick sort (recursion depth optimization) */\nquickSort(nums: number[], left: number, right: number): void {\n    // Stop when the subarray has zero or one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        let pivot = this.partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            this.quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n            left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            this.quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.dart
    /* Quick sort (recursion depth optimization) */\nvoid quickSort(List<int> nums, int left, int right) {\n  // Stop when the subarray has at most one element\n  while (left < right) {\n    // Perform two-pointer partitioning\n    int pivot = _partition(nums, left, right);\n    // Apply quick sort to the shorter of the two subarrays\n    if (pivot - left < right - pivot) {\n      quickSort(nums, left, pivot - 1); // Recursively sort the left subarray\n      left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n    } else {\n      quickSort(nums, pivot + 1, right); // Recursively sort the right subarray\n      right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n    }\n  }\n}\n
    quick_sort.rs
    /* Quick sort (recursion depth optimization) */\npub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) {\n    // Stop when the subarray has at most one element\n    while left < right {\n        // Perform two-pointer partitioning\n        let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n        // Apply quick sort to the shorter of the two subarrays\n        if pivot - left < right - pivot {\n            Self::quick_sort(left, pivot - 1, nums); // Recursively sort the left subarray\n            left = pivot + 1; // The remaining unsorted range is [pivot + 1, right]\n        } else {\n            Self::quick_sort(pivot + 1, right, nums); // Recursively sort the right subarray\n            right = pivot - 1; // The remaining unsorted range is [left, pivot - 1]\n        }\n    }\n}\n
    quick_sort.c
    /* Quick sort (recursion depth optimization) */\nvoid quickSortTailCall(int nums[], int left, int right) {\n    // Stop when the subarray has at most one element\n    while (left < right) {\n        // Perform two-pointer partitioning\n        int pivot = partition(nums, left, right);\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - left < right - pivot) {\n            // Recursively sort the left subarray\n            quickSortTailCall(nums, left, pivot - 1);\n            // The remaining unsorted range is [pivot + 1, right]\n            left = pivot + 1;\n        } else {\n            // Recursively sort the right subarray\n            quickSortTailCall(nums, pivot + 1, right);\n            // The remaining unsorted range is [left, pivot - 1]\n            right = pivot - 1;\n        }\n    }\n}\n
    quick_sort.kt
    /* Quick sort (recursion depth optimization) */\nfun quickSortTailCall(nums: IntArray, left: Int, right: Int) {\n    // Stop when the subarray has at most one element\n    var l = left\n    var r = right\n    while (l < r) {\n        // Perform two-pointer partitioning\n        val pivot = partition(nums, l, r)\n        // Apply quick sort to the shorter of the two subarrays\n        if (pivot - l < r - pivot) {\n            quickSort(nums, l, pivot - 1) // Recursively sort the left subarray\n            l = pivot + 1 // The remaining unsorted range is [pivot + 1, r]\n        } else {\n            quickSort(nums, pivot + 1, r) // Recursively sort the right subarray\n            r = pivot - 1 // The remaining unsorted range is [l, pivot - 1]\n        }\n    }\n}\n
    quick_sort.rb
    ### Quick sort (recursion depth optimization)###\ndef quick_sort(nums, left, right)\n  # Continue while the subarray contains more than one element\n  while left < right\n    # Two-pointer partitioning\n    pivot = partition(nums, left, right)\n    # Apply quick sort to the shorter of the two subarrays\n    if pivot - left < right - pivot\n      quick_sort(nums, left, pivot - 1)\n      left = pivot + 1 # The remaining unsorted range is [pivot + 1, right]\n    else\n      quick_sort(nums, pivot + 1, right)\n      right = pivot - 1 # The remaining unsorted range is [left, pivot - 1]\n    end\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.5   Quick sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/","level":1,"title":"11.10   Radix sort","text":"

    The previous section introduced counting sort, which suits a large number of elements \\(n\\) with a small value range \\(m\\). Suppose we need to sort \\(n = 10^6\\) student IDs, each an \\(8\\)-digit number. The value range \\(m = 10^8\\) is very large, so counting sort would require substantial memory. Radix sort avoids this problem.

    Radix sort shares counting sort's core idea of sorting by counting occurrences. It also uses the relative significance of digit positions, sorting one digit at a time to obtain the final order.

    ","path":["Chapter 11.   Sorting","11.10   Radix sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11101-algorithm-steps","level":2,"title":"11.10.1   Algorithm steps","text":"

    For the student IDs, number the least significant digit as \\(1\\) and the most significant as \\(8\\). Radix sort proceeds as shown below.

    1. Initialize the digit position \\(k = 1\\).
    2. Apply counting sort to the \\(k\\)th digit of each student ID. The data is now sorted in ascending order by its \\(k\\)th digit.
    3. Increase the digit position \\(k\\) by \\(1\\) and return to step 2.. Continue until all digit positions have been processed.

    Figure 11-18   The radix sort process

    Now consider the implementation. For a number \\(x\\) in base \\(d\\), its \\(k\\)th digit \\(x_k\\) can be obtained with the following formula:

    \\[ x_k = \\lfloor\\frac{x}{d^{k-1}}\\rfloor \\bmod d \\]

    Here, \\(\\lfloor a \\rfloor\\) rounds the floating-point value \\(a\\) down, and \\(\\bmod \\: d\\) takes the remainder modulo \\(d\\). For the student IDs, \\(d = 10\\) and \\(k \\in [1, 8]\\).

    We also need to modify counting sort slightly so that it sorts by the \\(k\\)th digit:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby radix_sort.py
    def digit(num: int, exp: int) -> int:\n    \"\"\"Get the k-th digit of num, where exp = 10^(k-1)\"\"\"\n    # Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num // exp) % 10\n\ndef counting_sort_digit(nums: list[int], exp: int):\n    \"\"\"Counting sort (sort nums by the k-th digit)\"\"\"\n    # Decimal digits range from 0 to 9, so the bucket array needs length 10\n    counter = [0] * 10\n    n = len(nums)\n    # Count the occurrences of each digit from 0 to 9\n    for i in range(n):\n        d = digit(nums[i], exp)  # Get the k-th digit of nums[i] and store it in d\n        counter[d] += 1  # Count the occurrences of digit d\n    # Compute prefix sums to convert occurrence counts into array positions\n    for i in range(1, 10):\n        counter[i] += counter[i - 1]\n    # Traverse in reverse and place each element into res according to the bucket counts\n    res = [0] * n\n    for i in range(n - 1, -1, -1):\n        d = digit(nums[i], exp)\n        j = counter[d] - 1  # Get the destination index j for digit d in the array\n        res[j] = nums[i]  # Place the current element at index j\n        counter[d] -= 1  # Decrease the count for d by 1\n    # Overwrite the original array nums with the result\n    for i in range(n):\n        nums[i] = res[i]\n\ndef radix_sort(nums: list[int]):\n    \"\"\"Radix sort\"\"\"\n    # Get the largest array element to determine the maximum number of digits\n    m = max(nums)\n    # Process digits from least significant to most significant\n    exp = 1\n    while exp <= m:\n        # Apply counting sort to the k-th digit of the array elements\n        # k = 1 -> exp = 1\n        # k = 2 -> exp = 10\n        # That is, exp = 10^(k-1)\n        counting_sort_digit(nums, exp)\n        exp *= 10\n
    radix_sort.cpp
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid countingSortDigit(vector<int> &nums, int exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    vector<int> counter(10, 0);\n    int n = nums.size();\n    // Count the occurrences of each digit from 0 to 9\n    for (int i = 0; i < n; i++) {\n        int d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++;                // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    vector<int> res(n, 0);\n    for (int i = n - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (int i = 0; i < n; i++)\n        nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(vector<int> &nums) {\n    // Get the largest array element to determine the maximum number of digits\n    int m = *max_element(nums.begin(), nums.end());\n    // Process digits from least significant to most significant\n    for (int exp = 1; exp <= m; exp *= 10)\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n}\n
    radix_sort.java
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid countingSortDigit(int[] nums, int exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    int[] counter = new int[10];\n    int n = nums.length;\n    // Count the occurrences of each digit from 0 to 9\n    for (int i = 0; i < n; i++) {\n        int d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++;                // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (int i = 0; i < n; i++)\n        nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(int[] nums) {\n    // Get the largest array element to determine the maximum number of digits\n    int m = Integer.MIN_VALUE;\n    for (int num : nums)\n        if (num > m)\n            m = num;\n    // Process digits from least significant to most significant\n    for (int exp = 1; exp <= m; exp *= 10) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
    radix_sort.cs
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nint Digit(int num, int exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid CountingSortDigit(int[] nums, int exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    int[] counter = new int[10];\n    int n = nums.Length;\n    // Count the occurrences of each digit from 0 to 9\n    for (int i = 0; i < n; i++) {\n        int d = Digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++;                // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    int[] res = new int[n];\n    for (int i = n - 1; i >= 0; i--) {\n        int d = Digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (int i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nvoid RadixSort(int[] nums) {\n    // Get the largest array element to determine the maximum number of digits\n    int m = int.MinValue;\n    foreach (int num in nums) {\n        if (num > m) m = num;\n    }\n    // Process digits from least significant to most significant\n    for (int exp = 1; exp <= m; exp *= 10) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        CountingSortDigit(nums, exp);\n    }\n}\n
    radix_sort.go
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfunc digit(num, exp int) int {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfunc countingSortDigit(nums []int, exp int) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    counter := make([]int, 10)\n    n := len(nums)\n    // Count the occurrences of each digit from 0 to 9\n    for i := 0; i < n; i++ {\n        d := digit(nums[i], exp) // Get the k-th digit of nums[i] and store it in d\n        counter[d]++             // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for i := 1; i < 10; i++ {\n        counter[i] += counter[i-1]\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    res := make([]int, n)\n    for i := n - 1; i >= 0; i-- {\n        d := digit(nums[i], exp)\n        j := counter[d] - 1 // Get the destination index j for digit d in the array\n        res[j] = nums[i]    // Place the current element at index j\n        counter[d]--        // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for i := 0; i < n; i++ {\n        nums[i] = res[i]\n    }\n}\n\n/* Radix sort */\nfunc radixSort(nums []int) {\n    // Get the largest array element to determine the maximum number of digits\n    max := math.MinInt\n    for _, num := range nums {\n        if num > max {\n            max = num\n        }\n    }\n    // Process digits from least significant to most significant\n    for exp := 1; max >= exp; exp *= 10 {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp)\n    }\n}\n
    radix_sort.swift
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfunc digit(num: Int, exp: Int) -> Int {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    (num / exp) % 10\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfunc countingSortDigit(nums: inout [Int], exp: Int) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    var counter = Array(repeating: 0, count: 10)\n    // Count the occurrences of each digit from 0 to 9\n    for i in nums.indices {\n        let d = digit(num: nums[i], exp: exp) // Get the k-th digit of nums[i] and store it in d\n        counter[d] += 1 // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for i in 1 ..< 10 {\n        counter[i] += counter[i - 1]\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    var res = Array(repeating: 0, count: nums.count)\n    for i in nums.indices.reversed() {\n        let d = digit(num: nums[i], exp: exp)\n        let j = counter[d] - 1 // Get the destination index j for digit d in the array\n        res[j] = nums[i] // Place the current element at index j\n        counter[d] -= 1 // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for i in nums.indices {\n        nums[i] = res[i]\n    }\n}\n\n/* Radix sort */\nfunc radixSort(nums: inout [Int]) {\n    // Get the largest array element to determine the maximum number of digits\n    var m = Int.min\n    for num in nums {\n        if num > m {\n            m = num\n        }\n    }\n    // Process digits from least significant to most significant\n    for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums: &nums, exp: exp)\n    }\n}\n
    radix_sort.js
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfunction digit(num, exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return Math.floor(num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfunction countingSortDigit(nums, exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    const counter = new Array(10).fill(0);\n    const n = nums.length;\n    // Count the occurrences of each digit from 0 to 9\n    for (let i = 0; i < n; i++) {\n        const d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++; // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (let i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    const res = new Array(n).fill(0);\n    for (let i = n - 1; i >= 0; i--) {\n        const d = digit(nums[i], exp);\n        const j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d]--; // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nfunction radixSort(nums) {\n    // Get the largest array element to determine the maximum number of digits\n    let m = Math.max(... nums);\n    // Process digits from least significant to most significant\n    for (let exp = 1; exp <= m; exp *= 10) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
    radix_sort.ts
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfunction digit(num: number, exp: number): number {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return Math.floor(num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfunction countingSortDigit(nums: number[], exp: number): void {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    const counter = new Array(10).fill(0);\n    const n = nums.length;\n    // Count the occurrences of each digit from 0 to 9\n    for (let i = 0; i < n; i++) {\n        const d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d]++; // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (let i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    const res = new Array(n).fill(0);\n    for (let i = n - 1; i >= 0; i--) {\n        const d = digit(nums[i], exp);\n        const j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d]--; // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (let i = 0; i < n; i++) {\n        nums[i] = res[i];\n    }\n}\n\n/* Radix sort */\nfunction radixSort(nums: number[]): void {\n    // Get the largest array element to determine the maximum number of digits\n    let m: number = Math.max(... nums);\n    // Process digits from least significant to most significant\n    for (let exp = 1; exp <= m; exp *= 10) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp);\n    }\n}\n
    radix_sort.dart
    /* Get the k-th digit of _num, where exp = 10^(k-1) */\nint digit(int _num, int exp) {\n  // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n  return (_num ~/ exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid countingSortDigit(List<int> nums, int exp) {\n  // Decimal digits range from 0 to 9, so the bucket array needs length 10\n  List<int> counter = List<int>.filled(10, 0);\n  int n = nums.length;\n  // Count the occurrences of each digit from 0 to 9\n  for (int i = 0; i < n; i++) {\n    int d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n    counter[d]++; // Count the occurrences of digit d\n  }\n  // Compute prefix sums to convert occurrence counts into array positions\n  for (int i = 1; i < 10; i++) {\n    counter[i] += counter[i - 1];\n  }\n  // Traverse in reverse and place each element into res according to the bucket counts\n  List<int> res = List<int>.filled(n, 0);\n  for (int i = n - 1; i >= 0; i--) {\n    int d = digit(nums[i], exp);\n    int j = counter[d] - 1; // Get the destination index j for digit d in the array\n    res[j] = nums[i]; // Place the current element at index j\n    counter[d]--; // Decrease the count for d by 1\n  }\n  // Overwrite the original array nums with the result\n  for (int i = 0; i < n; i++) nums[i] = res[i];\n}\n\n/* Radix sort */\nvoid radixSort(List<int> nums) {\n  // Get the largest array element to determine the maximum number of digits\n  // On native platforms, Dart int values are 64-bit integers\n  int m = -1 << 63;\n  for (int _num in nums) if (_num > m) m = _num;\n  // Process digits from least significant to most significant\n  for (int exp = 1; exp <= m; exp *= 10)\n    // Apply counting sort to the k-th digit of the array elements\n    // k = 1 -> exp = 1\n    // k = 2 -> exp = 10\n    // That is, exp = 10^(k-1)\n    countingSortDigit(nums, exp);\n}\n
    radix_sort.rs
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfn digit(num: i32, exp: i32) -> usize {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return ((num / exp) % 10) as usize;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfn counting_sort_digit(nums: &mut [i32], exp: i32) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    let mut counter = [0; 10];\n    let n = nums.len();\n    // Count the occurrences of each digit from 0 to 9\n    for i in 0..n {\n        let d = digit(nums[i], exp); // Get the k-th digit of nums[i] and store it in d\n        counter[d] += 1; // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for i in 1..10 {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    let mut res = vec![0; n];\n    for i in (0..n).rev() {\n        let d = digit(nums[i], exp);\n        let j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i]; // Place the current element at index j\n        counter[d] -= 1; // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    nums.copy_from_slice(&res);\n}\n\n/* Radix sort */\nfn radix_sort(nums: &mut [i32]) {\n    // Get the largest array element to determine the maximum number of digits\n    let m = *nums.into_iter().max().unwrap();\n    // Process digits from least significant to most significant\n    let mut exp = 1;\n    while exp <= m {\n        counting_sort_digit(nums, exp);\n        exp *= 10;\n    }\n}\n
    radix_sort.c
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nint digit(int num, int exp) {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10;\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nvoid countingSortDigit(int nums[], int size, int exp) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    int *counter = (int *)malloc((sizeof(int) * 10));\n    memset(counter, 0, sizeof(int) * 10); // Initialize the counts to 0\n    // Count the occurrences of each digit from 0 to 9\n    for (int i = 0; i < size; i++) {\n        // Get the k-th digit of nums[i] and store it in d\n        int d = digit(nums[i], exp);\n        // Count the occurrences of digit d\n        counter[d]++;\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (int i = 1; i < 10; i++) {\n        counter[i] += counter[i - 1];\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    int *res = (int *)malloc(sizeof(int) * size);\n    for (int i = size - 1; i >= 0; i--) {\n        int d = digit(nums[i], exp);\n        int j = counter[d] - 1; // Get the destination index j for digit d in the array\n        res[j] = nums[i];       // Place the current element at index j\n        counter[d]--;           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (int i = 0; i < size; i++) {\n        nums[i] = res[i];\n    }\n    // Free memory\n    free(res);\n    free(counter);\n}\n\n/* Radix sort */\nvoid radixSort(int nums[], int size) {\n    // Get the largest array element to determine the maximum number of digits\n    int max = INT32_MIN;\n    for (int i = 0; i < size; i++) {\n        if (nums[i] > max) {\n            max = nums[i];\n        }\n    }\n    // Process digits from least significant to most significant\n    for (int exp = 1; max >= exp; exp *= 10)\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, size, exp);\n}\n
    radix_sort.kt
    /* Get the k-th digit of num, where exp = 10^(k-1) */\nfun digit(num: Int, exp: Int): Int {\n    // Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n    return (num / exp) % 10\n}\n\n/* Counting sort (sort nums by the k-th digit) */\nfun countingSortDigit(nums: IntArray, exp: Int) {\n    // Decimal digits range from 0 to 9, so the bucket array needs length 10\n    val counter = IntArray(10)\n    val n = nums.size\n    // Count the occurrences of each digit from 0 to 9\n    for (i in 0..<n) {\n        val d = digit(nums[i], exp) // Get the k-th digit of nums[i] and store it in d\n        counter[d]++                // Count the occurrences of digit d\n    }\n    // Compute prefix sums to convert occurrence counts into array positions\n    for (i in 1..9) {\n        counter[i] += counter[i - 1]\n    }\n    // Traverse in reverse and place each element into res according to the bucket counts\n    val res = IntArray(n)\n    for (i in n - 1 downTo 0) {\n        val d = digit(nums[i], exp)\n        val j = counter[d] - 1 // Get the destination index j for digit d in the array\n        res[j] = nums[i]       // Place the current element at index j\n        counter[d]--           // Decrease the count for d by 1\n    }\n    // Overwrite the original array nums with the result\n    for (i in 0..<n)\n        nums[i] = res[i]\n}\n\n/* Radix sort */\nfun radixSort(nums: IntArray) {\n    // Get the largest array element to determine the maximum number of digits\n    var m = Int.MIN_VALUE\n    for (num in nums) if (num > m) m = num\n    var exp = 1\n    // Process digits from least significant to most significant\n    while (exp <= m) {\n        // Apply counting sort to the k-th digit of the array elements\n        // k = 1 -> exp = 1\n        // k = 2 -> exp = 10\n        // That is, exp = 10^(k-1)\n        countingSortDigit(nums, exp)\n        exp *= 10\n    }\n}\n
    radix_sort.rb
    ### Get the k-th digit of num, where exp = 10^(k-1) ###\ndef digit(num, exp)\n  # Passing exp instead of k avoids repeatedly performing expensive exponentiation here\n  (num / exp) % 10\nend\n\n### Counting sort (sort nums by the k-th digit)###\ndef counting_sort_digit(nums, exp)\n  # Decimal digits range from 0 to 9, so the bucket array needs length 10\n  counter = Array.new(10, 0)\n  n = nums.length\n  # Count the occurrences of each digit from 0 to 9\n  for i in 0...n\n    d = digit(nums[i], exp) # Get the k-th digit of nums[i] and store it in d\n    counter[d] += 1 # Count the occurrences of digit d\n  end\n  # Compute prefix sums to convert occurrence counts into array positions\n  (1...10).each { |i| counter[i] += counter[i - 1] }\n  # Traverse in reverse and place each element into res according to the bucket counts\n  res = Array.new(n, 0)\n  for i in (n - 1).downto(0)\n    d = digit(nums[i], exp)\n    j = counter[d] - 1 # Get the destination index j for digit d in the array\n    res[j] = nums[i] # Place the current element at index j\n    counter[d] -= 1 # Decrease the count for d by 1\n  end\n  # Overwrite the original array nums with the result\n  (0...n).each { |i| nums[i] = res[i] }\nend\n\n### Radix sort ###\ndef radix_sort(nums)\n  # Get the largest array element to determine the maximum number of digits\n  m = nums.max\n  # Process digits from least significant to most significant\n  exp = 1\n  while exp <= m\n    # Apply counting sort to the k-th digit of the array elements\n    # k = 1 -> exp = 1\n    # k = 2 -> exp = 10\n    # That is, exp = 10^(k-1)\n    counting_sort_digit(nums, exp)\n    exp *= 10\n  end\nend\n
    Visualize execution

    View full screen >

    Why start with the least significant digit?

    In successive rounds, a later sort takes precedence over an earlier one. For example, if the first round orders two elements as \\(a < b\\) but the second orders them as \\(a > b\\), the second result replaces the first. Higher digits have greater priority, so lower digits must be sorted first.

    ","path":["Chapter 11.   Sorting","11.10   Radix sort"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11102-algorithm-properties","level":2,"title":"11.10.2   Algorithm properties","text":"

    Compared with counting sort, radix sort handles a larger range of values, provided the data can be represented with a fixed number of digits and that number is not too large. For example, floating-point numbers are unsuitable because their digit count \\(k\\) may be so large that \\(O(nk) \\gg O(n^2)\\).

    • Time complexity: \\(O(nk)\\); not adaptive. For \\(n\\) elements represented in base \\(d\\) with at most \\(k\\) digits, counting sort on one digit takes \\(O(n + d)\\) time. Sorting all \\(k\\) digits takes \\(O((n + d)k)\\). Usually \\(d\\) and \\(k\\) are relatively small, so the complexity approaches \\(O(n)\\).
    • Space complexity: \\(O(n + d)\\); not in-place. Like counting sort, radix sort requires arrays res and counter of lengths \\(n\\) and \\(d\\).
    • Stable. Radix sort is stable when the counting sort used within it is stable. If counting sort is not stable, radix sort cannot guarantee a correctly sorted result.
    ","path":["Chapter 11.   Sorting","11.10   Radix sort"],"tags":[]},{"location":"chapter_sorting/selection_sort/","level":1,"title":"11.2   Selection sort","text":"

    Selection sort works very simply: in each round, select the smallest element from the unsorted section and place it at the end of the sorted section.

    For an array of length \\(n\\), the algorithm proceeds as shown below.

    1. Initially, all elements are unsorted, so the unsorted index interval is \\([0, n-1]\\).
    2. Select the smallest element in \\([0, n-1]\\) and swap it with the element at index \\(0\\). The first element is now sorted.
    3. Select the smallest element in \\([1, n-1]\\) and swap it with the element at index \\(1\\). The first two elements are now sorted.
    4. Continue in this way. After \\(n - 1\\) rounds of selection and swapping, the first \\(n - 1\\) elements are sorted.
    5. The remaining element must be the largest and needs no sorting. The array is now sorted.
    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 11-2   Steps of selection sort

    In the code, \\(k\\) tracks the smallest element in the unsorted section:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby selection_sort.py
    def selection_sort(nums: list[int]):\n    \"\"\"Selection sort\"\"\"\n    n = len(nums)\n    # Outer loop: the unsorted range is [i, n-1]\n    for i in range(n - 1):\n        # Inner loop: find the smallest element in the unsorted range\n        k = i\n        for j in range(i + 1, n):\n            if nums[j] < nums[k]:\n                k = j  # Record the index of the smallest element\n        # Swap the smallest element with the first element of the unsorted range\n        nums[i], nums[k] = nums[k], nums[i]\n
    selection_sort.cpp
    /* Selection sort */\nvoid selectionSort(vector<int> &nums) {\n    int n = nums.size();\n    // Outer loop: the unsorted range is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        swap(nums[i], nums[k]);\n    }\n}\n
    selection_sort.java
    /* Selection sort */\nvoid selectionSort(int[] nums) {\n    int n = nums.length;\n    // Outer loop: the unsorted range is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        int temp = nums[i];\n        nums[i] = nums[k];\n        nums[k] = temp;\n    }\n}\n
    selection_sort.cs
    /* Selection sort */\nvoid SelectionSort(int[] nums) {\n    int n = nums.Length;\n    // Outer loop: the unsorted range is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        (nums[k], nums[i]) = (nums[i], nums[k]);\n    }\n}\n
    selection_sort.go
    /* Selection sort */\nfunc selectionSort(nums []int) {\n    n := len(nums)\n    // Outer loop: the unsorted range is [i, n-1]\n    for i := 0; i < n-1; i++ {\n        // Inner loop: find the smallest element in the unsorted range\n        k := i\n        for j := i + 1; j < n; j++ {\n            if nums[j] < nums[k] {\n                // Record the index of the smallest element\n                k = j\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        nums[i], nums[k] = nums[k], nums[i]\n\n    }\n}\n
    selection_sort.swift
    /* Selection sort */\nfunc selectionSort(nums: inout [Int]) {\n    // Outer loop: the unsorted range is [i, n-1]\n    for i in nums.indices.dropLast() {\n        // Inner loop: find the smallest element in the unsorted range\n        var k = i\n        for j in nums.indices.dropFirst(i + 1) {\n            if nums[j] < nums[k] {\n                k = j // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        nums.swapAt(i, k)\n    }\n}\n
    selection_sort.js
    /* Selection sort */\nfunction selectionSort(nums) {\n    let n = nums.length;\n    // Outer loop: the unsorted range is [i, n-1]\n    for (let i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        let k = i;\n        for (let j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k]) {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        [nums[i], nums[k]] = [nums[k], nums[i]];\n    }\n}\n
    selection_sort.ts
    /* Selection sort */\nfunction selectionSort(nums: number[]): void {\n    let n = nums.length;\n    // Outer loop: the unsorted range is [i, n-1]\n    for (let i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        let k = i;\n        for (let j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k]) {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        [nums[i], nums[k]] = [nums[k], nums[i]];\n    }\n}\n
    selection_sort.dart
    /* Selection sort */\nvoid selectionSort(List<int> nums) {\n  int n = nums.length;\n  // Outer loop: the unsorted range is [i, n-1]\n  for (int i = 0; i < n - 1; i++) {\n    // Inner loop: find the smallest element in the unsorted range\n    int k = i;\n    for (int j = i + 1; j < n; j++) {\n      if (nums[j] < nums[k]) k = j; // Record the index of the smallest element\n    }\n    // Swap the smallest element with the first element of the unsorted range\n    int temp = nums[i];\n    nums[i] = nums[k];\n    nums[k] = temp;\n  }\n}\n
    selection_sort.rs
    /* Selection sort */\nfn selection_sort(nums: &mut [i32]) {\n    if nums.is_empty() {\n        return;\n    }\n    let n = nums.len();\n    // Outer loop: the unsorted range is [i, n-1]\n    for i in 0..n - 1 {\n        // Inner loop: find the smallest element in the unsorted range\n        let mut k = i;\n        for j in i + 1..n {\n            if nums[j] < nums[k] {\n                k = j; // Record the index of the smallest element\n            }\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        nums.swap(i, k);\n    }\n}\n
    selection_sort.c
    /* Selection sort */\nvoid selectionSort(int nums[], int n) {\n    // Outer loop: the unsorted range is [i, n-1]\n    for (int i = 0; i < n - 1; i++) {\n        // Inner loop: find the smallest element in the unsorted range\n        int k = i;\n        for (int j = i + 1; j < n; j++) {\n            if (nums[j] < nums[k])\n                k = j; // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        int temp = nums[i];\n        nums[i] = nums[k];\n        nums[k] = temp;\n    }\n}\n
    selection_sort.kt
    /* Selection sort */\nfun selectionSort(nums: IntArray) {\n    val n = nums.size\n    // Outer loop: the unsorted range is [i, n-1]\n    for (i in 0..<n - 1) {\n        var k = i\n        // Inner loop: find the smallest element in the unsorted range\n        for (j in i + 1..<n) {\n            if (nums[j] < nums[k])\n                k = j // Record the index of the smallest element\n        }\n        // Swap the smallest element with the first element of the unsorted range\n        val temp = nums[i]\n        nums[i] = nums[k]\n        nums[k] = temp\n    }\n}\n
    selection_sort.rb
    ### Selection sort ###\ndef selection_sort(nums)\n  n = nums.length\n  # Outer loop: the unsorted range is [i, n-1]\n  for i in 0...(n - 1)\n    # Inner loop: find the smallest element in the unsorted range\n    k = i\n    for j in (i + 1)...n\n      if nums[j] < nums[k]\n        k = j # Record the index of the smallest element\n      end\n    end\n    # Swap the smallest element with the first element of the unsorted range\n    nums[i], nums[k] = nums[k], nums[i]\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 11.   Sorting","11.2   Selection sort"],"tags":[]},{"location":"chapter_sorting/selection_sort/#1121-algorithm-properties","level":2,"title":"11.2.1   Algorithm properties","text":"
    • Time complexity: \\(O(n^2)\\); not adaptive. The outer loop runs \\(n - 1\\) times. The inner loop runs \\(n - 1\\) times in the first round and \\(1\\) time in the last, giving successive counts of \\(n - 1\\), \\(n - 2\\), \\(\\dots\\), \\(2\\), and \\(1\\), which sum to \\(\\frac{n(n - 1)}{2}\\).
    • Space complexity: \\(O(1)\\); in-place. Pointers \\(i\\) and \\(j\\) use constant extra space.
    • Not stable. As shown below, nums[i] may be swapped to the right of an equal element, changing their relative order.

    Figure 11-3   An example showing that selection sort is not stable

    ","path":["Chapter 11.   Sorting","11.2   Selection sort"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/","level":1,"title":"11.1   Sorting algorithms","text":"

    A sorting algorithm arranges a dataset in a specified order. Sorting is widely used because ordered data is often easier to search, analyze, and process efficiently.

    As shown below, the data may consist of integers, floating-point numbers, characters, strings, and other types. The ordering rule can be chosen as needed: numerical magnitude, ASCII character order, or a custom rule.

    Figure 11-1   Examples of data types and ordering rules

    ","path":["Chapter 11.   Sorting","11.1   Sorting algorithms"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1111-evaluation-criteria","level":2,"title":"11.1.1   Evaluation criteria","text":"

    Efficiency: We want low time complexity and few operations overall, corresponding to smaller constant factors in the complexity. Efficiency is especially important for large datasets.

    In-place operation: An in-place sort works directly on the original array without an auxiliary array, saving memory. It usually moves data fewer times and runs faster as well.

    Stability: A stable sort preserves the relative order of equal elements in the array.

    Stable sorting is necessary for multilevel sorting. Suppose a table stores student names in its first column and ages in its second. An unstable sort may destroy the input's existing order:

    # The input is already sorted by name\n# (name, age)\n  ('A', 19)\n  ('B', 18)\n  ('C', 21)\n  ('D', 19)\n  ('E', 23)\n\n# Suppose an unstable algorithm sorts the list by age.\n# The relative positions of ('D', 19) and ('A', 19) change,\n# so the original ordering by name is lost\n  ('B', 18)\n  ('D', 19)\n  ('A', 19)\n  ('C', 21)\n  ('E', 23)\n

    Adaptiveness: An adaptive sort uses ordering already present in the input to reduce computation and improve efficiency. Its best-case time complexity is usually better than its average complexity.

    Comparison-based or not: A comparison-based sort uses comparison operators (\\(<\\), \\(=\\), \\(>\\)) to determine the relative order of elements and sort the array. Its worst-case time complexity has a lower bound of \\(\\Omega(n \\log n)\\). A non-comparison sort does not use comparison operators and can achieve \\(O(n)\\) time, but is less broadly applicable.

    ","path":["Chapter 11.   Sorting","11.1   Sorting algorithms"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1112-the-ideal-sorting-algorithm","level":2,"title":"11.1.2   The ideal sorting algorithm","text":"

    Fast, in-place, stable, adaptive, and broadly applicable. No sorting algorithm with all these properties has yet been found. We must therefore choose according to the characteristics of the data and the requirements of the problem.

    Next, we will study different sorting algorithms and analyze their strengths and weaknesses using these criteria.

    ","path":["Chapter 11.   Sorting","11.1   Sorting algorithms"],"tags":[]},{"location":"chapter_sorting/summary/","level":1,"title":"11.11   Summary","text":"","path":["Chapter 11.   Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_sorting/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • Bubble sort sorts by swapping adjacent elements. Adding a flag to stop early improves its best-case time complexity to \\(O(n)\\).
    • Insertion sort inserts one element from the unsorted section into its correct position in the sorted section each round. Although its time complexity is \\(O(n^2)\\), its low operation count makes it popular for small datasets.
    • Quick sort sorts through partitioning. Repeatedly choosing the worst pivot can degrade its time complexity to \\(O(n^2)\\). Median or random pivot selection reduces this risk. Recursing on the shorter subarray reduces recursion depth and improves space complexity to \\(O(\\log n)\\).
    • Merge sort has division and merging phases, exemplifying divide and conquer. Sorting an array requires an auxiliary array and \\(O(n)\\) space, while sorting a linked list can be optimized to use \\(O(1)\\) space.
    • Bucket sort has three steps: distribute data into buckets, sort within each bucket, and combine the results. It also uses divide and conquer and suits very large datasets. The key is distributing data evenly.
    • Counting sort is a special case of bucket sort that sorts by counting occurrences. It suits large datasets with a limited value range and requires data that can be converted to positive integers.
    • Radix sort sorts one digit at a time and requires data that can be represented as numbers with a fixed number of digits.
    • Ideally, a sorting algorithm would be efficient, stable, in-place, and adaptive. As with other data structures and algorithms, however, no single sorting algorithm meets all these requirements. In practice, choose one according to the data's properties.
    • Figure 11-19 compares common sorting algorithms in efficiency, stability, in-place operation, and adaptiveness.

    Figure 11-19   Comparison of sorting algorithms

    ","path":["Chapter 11.   Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_sorting/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: When is stability necessary in a sorting algorithm?

    We often sort objects by one of their attributes. For example, students have names and heights, and we want a multilevel sort. First sort by name to obtain (A, 180) (B, 185) (C, 170) (D, 170), then sort by height. An unstable algorithm may produce (D, 170) (C, 170) (A, 180) (B, 185).

    Students D and C have switched positions, breaking the ordering by name. This is not what we want.

    Q: Can the right-to-left and left-to-right scans in partitioning be reversed?

    No. When the leftmost element is the pivot, the right-to-left scan must come first. This may seem counterintuitive, so let us examine why.

    The final step of partition() swaps nums[left] and nums[i]. Afterward, every element to the left of the pivot must be <= the pivot, so nums[left] >= nums[i] must hold before that swap. If we scan left to right first and fail to find a larger element, the loop stops at i == j, where it is possible that nums[j] == nums[i] > nums[left]. The final swap would then move an element larger than the pivot to the far left, causing partitioning to fail.

    For example, given [0, 0, 0, 0, 1], scanning left to right first produces [1, 0, 0, 0, 0] after partitioning, which is incorrect.

    Conversely, if we choose nums[right] as the pivot, we must scan left to right first.

    Q: In the recursion-depth optimization for quick sort, why does choosing the shorter subarray keep the depth at most \\(\\log n\\)?

    Recursion depth is the number of recursive calls that have not yet returned. Each partition divides the array into two subarrays. After the optimization, the subarray chosen for recursion is at most half the original length. Even in the worst case, where it is always exactly half, the depth is \\(\\log n\\).

    In the original quick sort, we may repeatedly recurse on the larger subarray, with lengths \\(n\\), \\(n - 1\\), \\(\\dots\\), \\(2\\), and \\(1\\) in the worst case, giving depth \\(n\\). The optimization prevents this.

    Q: If all array elements are equal, does quick sort take \\(O(n^2)\\) time? How can we handle this degeneration?

    Yes. In this case, consider partitioning into three sections: elements smaller than, equal to, and greater than the pivot. Recurse only on the smaller and greater sections. If all elements are equal, a single partition then completes the sort.

    Q: Why is bucket sort's worst-case time complexity \\(O(n^2)\\)?

    In the worst case, all elements fall into one bucket. If an \\(O(n^2)\\) algorithm is used to sort that bucket, the overall time complexity is \\(O(n^2)\\).

    ","path":["Chapter 11.   Sorting","11.11   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/","level":1,"title":"Chapter 5.   Stacks and queues","text":"

    Abstract

    A stack is like cats piled on top of one another; a queue is like cats standing in line.

    They represent last-in, first-out and first-in, first-out order, respectively.

    ","path":["Chapter 5.   Stacks and queues"],"tags":[]},{"location":"chapter_stack_and_queue/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 5.1   Stacks
    • 5.2   Queues
    • 5.3   Deques
    • 5.4   Summary
    • 5.5   Exercises
    ","path":["Chapter 5.   Stacks and queues"],"tags":[]},{"location":"chapter_stack_and_queue/deque/","level":1,"title":"5.3   Deques","text":"

    A queue allows removal only at the front and insertion only at the rear. As shown below, a double-ended queue, or deque, is more flexible: elements can be added or removed at either end.

    Figure 5-7   Deque operations

    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#531-common-deque-operations","level":2,"title":"5.3.1   Common deque operations","text":"

    Table 5-3 lists common operations. Actual method names depend on the programming language.

    Table 5-3   Efficiency of deque operations

    Method Description Time complexity push_first() Add an element at the front \\(O(1)\\) push_last() Add an element at the rear \\(O(1)\\) pop_first() Remove the front element \\(O(1)\\) pop_last() Remove the rear element \\(O(1)\\) peek_first() Access the front element \\(O(1)\\) peek_last() Access the rear element \\(O(1)\\)

    Again, we can use a deque class already provided by the programming language:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby deque.py
    from collections import deque\n\n# Initialize a deque\ndeq: deque[int] = deque()\n\n# Enqueue elements\ndeq.append(2)      # Add at the rear\ndeq.append(5)\ndeq.append(4)\ndeq.appendleft(3)  # Add at the front\ndeq.appendleft(1)\n\n# Access elements\nfront: int = deq[0]  # Front element\nrear: int = deq[-1]  # Rear element\n\n# Dequeue elements\npop_front: int = deq.popleft()  # Remove the front element\npop_rear: int = deq.pop()       # Remove the rear element\n\n# Get the deque size\nsize: int = len(deq)\n\n# Check whether the deque is empty\nis_empty: bool = len(deq) == 0\n
    deque.cpp
    /* Initialize a deque */\ndeque<int> deque;\n\n/* Enqueue elements */\ndeque.push_back(2);   // Add at the rear\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3);  // Add at the front\ndeque.push_front(1);\n\n/* Access elements */\nint front = deque.front(); // Front element\nint back = deque.back();   // Rear element\n\n/* Dequeue elements */\ndeque.pop_front();  // Remove the front element\ndeque.pop_back();   // Remove the rear element\n\n/* Get the deque size */\nint size = deque.size();\n\n/* Check whether the deque is empty */\nbool empty = deque.empty();\n
    deque.java
    /* Initialize a deque */\nDeque<Integer> deque = new LinkedList<>();\n\n/* Enqueue elements */\ndeque.offerLast(2);   // Add at the rear\ndeque.offerLast(5);\ndeque.offerLast(4);\ndeque.offerFirst(3);  // Add at the front\ndeque.offerFirst(1);\n\n/* Access elements */\nint peekFirst = deque.peekFirst();  // Front element\nint peekLast = deque.peekLast();    // Rear element\n\n/* Dequeue elements */\nint popFirst = deque.pollFirst();  // Remove the front element\nint popLast = deque.pollLast();    // Remove the rear element\n\n/* Get the deque size */\nint size = deque.size();\n\n/* Check whether the deque is empty */\nboolean isEmpty = deque.isEmpty();\n
    deque.cs
    /* Initialize a deque */\n// In C#, use a LinkedList as a deque\nLinkedList<int> deque = new();\n\n/* Enqueue elements */\ndeque.AddLast(2);   // Add at the rear\ndeque.AddLast(5);\ndeque.AddLast(4);\ndeque.AddFirst(3);  // Add at the front\ndeque.AddFirst(1);\n\n/* Access elements */\nint peekFirst = deque.First.Value;  // Front element\nint peekLast = deque.Last.Value;    // Rear element\n\n/* Dequeue elements */\ndeque.RemoveFirst();  // Remove the front element\ndeque.RemoveLast();   // Remove the rear element\n\n/* Get the deque size */\nint size = deque.Count;\n\n/* Check whether the deque is empty */\nbool isEmpty = deque.Count == 0;\n
    deque_test.go
    /* Initialize a deque */\n// In Go, use a list as a deque\ndeque := list.New()\n\n/* Enqueue elements */\ndeque.PushBack(2)      // Add at the rear\ndeque.PushBack(5)\ndeque.PushBack(4)\ndeque.PushFront(3)     // Add at the front\ndeque.PushFront(1)\n\n/* Access elements */\nfront := deque.Front() // Front element\nrear := deque.Back()   // Rear element\n\n/* Dequeue elements */\ndeque.Remove(front)    // Remove the front element\ndeque.Remove(rear)     // Remove the rear element\n\n/* Get the deque size */\nsize := deque.Len()\n\n/* Check whether the deque is empty */\nisEmpty := deque.Len() == 0\n
    deque.swift
    /* Initialize a deque */\n// Swift has no built-in deque class; use an Array as a deque\nvar deque: [Int] = []\n\n/* Enqueue elements */\ndeque.append(2) // Add at the rear\ndeque.append(5)\ndeque.append(4)\ndeque.insert(3, at: 0) // Add at the front\ndeque.insert(1, at: 0)\n\n/* Access elements */\nlet peekFirst = deque.first! // Front element\nlet peekLast = deque.last! // Rear element\n\n/* Dequeue elements */\n// When using an Array, popFirst takes O(n) time\nlet popFirst = deque.removeFirst() // Remove the front element\nlet popLast = deque.removeLast() // Remove the rear element\n\n/* Get the deque size */\nlet size = deque.count\n\n/* Check whether the deque is empty */\nlet isEmpty = deque.isEmpty\n
    deque.js
    /* Initialize a deque */\n// JavaScript has no built-in deque; use an Array instead\nconst deque = [];\n\n/* Enqueue elements */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// Note that this is an array, so unshift() takes O(n) time\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* Access elements */\nconst peekFirst = deque[0];\nconst peekLast = deque[deque.length - 1];\n\n/* Dequeue elements */\n// Note that this is an array, so shift() takes O(n) time\nconst popFront = deque.shift();\nconst popBack = deque.pop();\n\n/* Get the deque size */\nconst size = deque.length;\n\n/* Check whether the deque is empty */\nconst isEmpty = size === 0;\n
    deque.ts
    /* Initialize a deque */\n// TypeScript has no built-in deque; use an Array instead\nconst deque: number[] = [];\n\n/* Enqueue elements */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// Note that this is an array, so unshift() takes O(n) time\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* Access elements */\nconst peekFirst: number = deque[0];\nconst peekLast: number = deque[deque.length - 1];\n\n/* Dequeue elements */\n// Note that this is an array, so shift() takes O(n) time\nconst popFront: number = deque.shift() as number;\nconst popBack: number = deque.pop() as number;\n\n/* Get the deque size */\nconst size: number = deque.length;\n\n/* Check whether the deque is empty */\nconst isEmpty: boolean = size === 0;\n
    deque.dart
    /* Initialize a deque */\n// In Dart, Queue is defined as a deque\nQueue<int> deque = Queue<int>();\n\n/* Enqueue elements */\ndeque.addLast(2);  // Add at the rear\ndeque.addLast(5);\ndeque.addLast(4);\ndeque.addFirst(3); // Add at the front\ndeque.addFirst(1);\n\n/* Access elements */\nint peekFirst = deque.first; // Front element\nint peekLast = deque.last;   // Rear element\n\n/* Dequeue elements */\nint popFirst = deque.removeFirst(); // Remove the front element\nint popLast = deque.removeLast();   // Remove the rear element\n\n/* Get the deque size */\nint size = deque.length;\n\n/* Check whether the deque is empty */\nbool isEmpty = deque.isEmpty;\n
    deque.rs
    /* Initialize a deque */\nlet mut deque: VecDeque<u32> = VecDeque::new();\n\n/* Enqueue elements */\ndeque.push_back(2);  // Add at the rear\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3); // Add at the front\ndeque.push_front(1);\n\n/* Access elements */\nif let Some(front) = deque.front() { // Front element\n}\nif let Some(rear) = deque.back() {   // Rear element\n}\n\n/* Dequeue elements */\nif let Some(pop_front) = deque.pop_front() { // Remove the front element\n}\nif let Some(pop_rear) = deque.pop_back() {   // Remove the rear element\n}\n\n/* Get the deque size */\nlet size = deque.len();\n\n/* Check whether the deque is empty */\nlet is_empty = deque.is_empty();\n
    deque.c
    // C provides no built-in deque\n
    deque.kt
    /* Initialize a deque */\nval deque = LinkedList<Int>()\n\n/* Enqueue elements */\ndeque.offerLast(2)  // Add at the rear\ndeque.offerLast(5)\ndeque.offerLast(4)\ndeque.offerFirst(3) // Add at the front\ndeque.offerFirst(1)\n\n/* Access elements */\nval peekFirst = deque.peekFirst() // Front element\nval peekLast = deque.peekLast()   // Rear element\n\n/* Dequeue elements */\nval popFirst = deque.pollFirst() // Remove the front element\nval popLast = deque.pollLast()   // Remove the rear element\n\n/* Get the deque size */\nval size = deque.size\n\n/* Check whether the deque is empty */\nval isEmpty = deque.isEmpty()\n
    deque.rb
    # Initialize a deque\n# Ruby has no built-in deque; use an Array instead\ndeque = []\n\n# Enqueue elements\ndeque << 2\ndeque << 5\ndeque << 4\n# Note that this is an array, so Array#unshift takes O(n) time\ndeque.unshift(3)\ndeque.unshift(1)\n\n# Access elements\npeek_first = deque.first\npeek_last = deque.last\n\n# Dequeue elements\n# Note that this is an array, so Array#shift takes O(n) time\npop_front = deque.shift\npop_back = deque.pop\n\n# Get the deque size\nsize = deque.length\n\n# Check whether the deque is empty\nis_empty = size.zero?\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#532-implementing-a-deque","level":2,"title":"5.3.2   Implementing a deque *","text":"

    A deque is implemented much like a queue, using either a linked list or an array as the underlying structure.

    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#1-implementation-with-a-doubly-linked-list","level":3,"title":"1.   Implementation with a doubly linked list","text":"

    In the previous section, we used a singly linked list for a queue because it easily supports removing the head for dequeueing and adding after the tail for enqueueing.

    A deque supports both insertion and removal at both ends. It therefore needs the symmetric operations in the other direction as well. For this reason, we use a doubly linked list.

    As shown below, treat the list's head and tail as the deque's front and rear, and support adding and removing nodes at both ends.

    <1><2><3><4><5>

    Figure 5-8   Insertion and removal in a linked-list deque

    The implementation is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_deque.py
    class ListNode:\n    \"\"\"Doubly linked list node\"\"\"\n\n    def __init__(self, val: int):\n        \"\"\"Constructor\"\"\"\n        self.val: int = val\n        self.next: ListNode | None = None  # Reference to the next node\n        self.prev: ListNode | None = None  # Reference to the previous node\n\nclass LinkedListDeque:\n    \"\"\"Deque implemented with a doubly linked list\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._front: ListNode | None = None  # Head node front\n        self._rear: ListNode | None = None  # Tail node rear\n        self._size: int = 0  # Deque size\n\n    def size(self) -> int:\n        \"\"\"Get the deque size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the deque is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int, is_front: bool):\n        \"\"\"Enqueue operation\"\"\"\n        node = ListNode(num)\n        # If the linked list is empty, make both front and rear point to node\n        if self.is_empty():\n            self._front = self._rear = node\n        # Enqueue at the front\n        elif is_front:\n            # Add node to the head of the linked list\n            self._front.prev = node\n            node.next = self._front\n            self._front = node  # Update the head node\n        # Enqueue at the rear\n        else:\n            # Add node to the tail of the linked list\n            self._rear.next = node\n            node.prev = self._rear\n            self._rear = node  # Update the tail node\n        self._size += 1  # Update the deque size\n\n    def push_first(self, num: int):\n        \"\"\"Enqueue at the front\"\"\"\n        self.push(num, True)\n\n    def push_last(self, num: int):\n        \"\"\"Enqueue at the rear\"\"\"\n        self.push(num, False)\n\n    def pop(self, is_front: bool) -> int:\n        \"\"\"Dequeue operation\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        # When the deque size is 1, the head and tail point to the same node\n        if self._size == 1:\n            val: int = self._front.val\n            self._front = None\n            self._rear = None\n        # Dequeue from the front\n        elif is_front:\n            val: int = self._front.val  # Save the head node value\n            # Remove the head node\n            fnext: ListNode | None = self._front.next\n            fnext.prev = None\n            self._front.next = None\n            self._front = fnext  # Update the head node\n        # Dequeue from the rear\n        else:\n            val: int = self._rear.val  # Save the tail node value\n            # Remove the tail node\n            rprev: ListNode | None = self._rear.prev\n            rprev.next = None\n            self._rear.prev = None\n            self._rear = rprev  # Update the tail node\n        self._size -= 1  # Update the deque size\n        return val\n\n    def pop_first(self) -> int:\n        \"\"\"Dequeue from the front\"\"\"\n        return self.pop(True)\n\n    def pop_last(self) -> int:\n        \"\"\"Dequeue from the rear\"\"\"\n        return self.pop(False)\n\n    def peek_first(self) -> int:\n        \"\"\"Access the front element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        return self._front.val\n\n    def peek_last(self) -> int:\n        \"\"\"Access the rear element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        return self._rear.val\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return an array for printing\"\"\"\n        node = self._front\n        res = [0] * self.size()\n        for i in range(self.size()):\n            res[i] = node.val\n            node = node.next\n        return res\n
    linkedlist_deque.cpp
    /* Doubly linked list node */\nstruct DoublyListNode {\n    int val;              // Node value\n    DoublyListNode *next; // Pointer to the next node\n    DoublyListNode *prev; // Pointer to the previous node\n    DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {\n    }\n};\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n  private:\n    DoublyListNode *front, *rear; // Head node front and tail node rear\n    int queSize = 0;              // Deque size\n\n  public:\n    /* Constructor */\n    LinkedListDeque() : front(nullptr), rear(nullptr) {\n    }\n\n    /* Destructor */\n    ~LinkedListDeque() {\n        // Traverse the linked list and delete each node to free its memory\n        DoublyListNode *pre, *cur = front;\n        while (cur != nullptr) {\n            pre = cur;\n            cur = cur->next;\n            delete pre;\n        }\n    }\n\n    /* Get the deque size */\n    int size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue operation */\n    void push(int num, bool isFront) {\n        DoublyListNode *node = new DoublyListNode(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty())\n            front = rear = node;\n        // Enqueue at the front\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front->prev = node;\n            node->next = front;\n            front = node; // Update the head node\n        // Enqueue at the rear\n        } else {\n            // Add node to the tail of the linked list\n            rear->next = node;\n            node->prev = rear;\n            rear = node; // Update the tail node\n        }\n        queSize++; // Update the deque size\n    }\n\n    /* Enqueue at the front */\n    void pushFirst(int num) {\n        push(num, true);\n    }\n\n    /* Enqueue at the rear */\n    void pushLast(int num) {\n        push(num, false);\n    }\n\n    /* Dequeue operation */\n    int pop(bool isFront) {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        int val;\n        // When the deque size is 1, the head and tail point to the same node\n        if (queSize == 1) {\n            val = front->val;\n            delete front; // Both point to the same node, so free it only once\n            front = nullptr;\n            rear = nullptr;\n        }\n        // Dequeue from the front\n        else if (isFront) {\n            val = front->val; // Save the head node value\n            // Remove the head node\n            DoublyListNode *fNext = front->next;\n            fNext->prev = nullptr;\n            front->next = nullptr;\n            delete front;\n            front = fNext; // Update the head node\n        // Dequeue from the rear\n        } else {\n            val = rear->val; // Save the tail node value\n            // Remove the tail node\n            DoublyListNode *rPrev = rear->prev;\n            rPrev->next = nullptr;\n            rear->prev = nullptr;\n            delete rear;\n            rear = rPrev; // Update the tail node\n        }\n        queSize--; // Update the deque size\n        return val;\n    }\n\n    /* Dequeue from the front */\n    int popFirst() {\n        return pop(true);\n    }\n\n    /* Dequeue from the rear */\n    int popLast() {\n        return pop(false);\n    }\n\n    /* Access the front element */\n    int peekFirst() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return front->val;\n    }\n\n    /* Access the rear element */\n    int peekLast() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return rear->val;\n    }\n\n    /* Return a vector for printing */\n    vector<int> toVector() {\n        DoublyListNode *node = front;\n        vector<int> res(size());\n        for (int i = 0; i < res.size(); i++) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
    linkedlist_deque.java
    /* Doubly linked list node */\nclass ListNode {\n    int val; // Node value\n    ListNode next; // Reference to the next node\n    ListNode prev; // Reference to the previous node\n\n    ListNode(int val) {\n        this.val = val;\n        prev = next = null;\n    }\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    private ListNode front, rear; // Head node front and tail node rear\n    private int queSize = 0; // Deque size\n\n    public LinkedListDeque() {\n        front = rear = null;\n    }\n\n    /* Get the deque size */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue operation */\n    private void push(int num, boolean isFront) {\n        ListNode node = new ListNode(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty())\n            front = rear = node;\n        // Enqueue at the front\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front.prev = node;\n            node.next = front;\n            front = node; // Update the head node\n        // Enqueue at the rear\n        } else {\n            // Add node to the tail of the linked list\n            rear.next = node;\n            node.prev = rear;\n            rear = node; // Update the tail node\n        }\n        queSize++; // Update the queue size\n    }\n\n    /* Enqueue at the front */\n    public void pushFirst(int num) {\n        push(num, true);\n    }\n\n    /* Enqueue at the rear */\n    public void pushLast(int num) {\n        push(num, false);\n    }\n\n    /* Dequeue operation */\n    private int pop(boolean isFront) {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        int val;\n        // When the queue size is 1, the head and tail point to the same node\n        if (queSize == 1) {\n            val = front.val;\n            front = null;\n            rear = null;\n        }\n        // Dequeue from the front\n        else if (isFront) {\n            val = front.val; // Save the head node value\n            // Remove the head node\n            ListNode fNext = front.next;\n            fNext.prev = null;\n            front.next = null;\n            front = fNext; // Update the head node\n        // Dequeue from the rear\n        } else {\n            val = rear.val; // Save the tail node value\n            // Remove the tail node\n            ListNode rPrev = rear.prev;\n            rPrev.next = null;\n            rear.prev = null;\n            rear = rPrev; // Update the tail node\n        }\n        queSize--; // Update the queue size\n        return val;\n    }\n\n    /* Dequeue from the front */\n    public int popFirst() {\n        return pop(true);\n    }\n\n    /* Dequeue from the rear */\n    public int popLast() {\n        return pop(false);\n    }\n\n    /* Access the front element */\n    public int peekFirst() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return front.val;\n    }\n\n    /* Access the rear element */\n    public int peekLast() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return rear.val;\n    }\n\n    /* Return an array for printing */\n    public int[] toArray() {\n        ListNode node = front;\n        int[] res = new int[size()];\n        for (int i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_deque.cs
    /* Doubly linked list node */\nclass ListNode(int val) {\n    public int val = val;       // Node value\n    public ListNode? next = null; // Reference to the next node\n    public ListNode? prev = null; // Reference to the previous node\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    ListNode? front, rear; // Head node front and tail node rear\n    int queSize = 0;      // Deque size\n\n    public LinkedListDeque() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the deque size */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Enqueue operation */\n    void Push(int num, bool isFront) {\n        ListNode node = new(num);\n        // If the linked list is empty, make both front and rear point to node\n        if (IsEmpty()) {\n            front = node;\n            rear = node;\n        }\n        // Enqueue at the front\n        else if (isFront) {\n            // Add node to the head of the linked list\n            front!.prev = node;\n            node.next = front;\n            front = node; // Update the head node                           \n        }\n        // Enqueue at the rear\n        else {\n            // Add node to the tail of the linked list\n            rear!.next = node;\n            node.prev = rear;\n            rear = node;  // Update the tail node\n        }\n\n        queSize++; // Update the queue size\n    }\n\n    /* Enqueue at the front */\n    public void PushFirst(int num) {\n        Push(num, true);\n    }\n\n    /* Enqueue at the rear */\n    public void PushLast(int num) {\n        Push(num, false);\n    }\n\n    /* Dequeue operation */\n    int? Pop(bool isFront) {\n        if (IsEmpty())\n            throw new Exception();\n        int? val;\n        // When the queue size is 1, the head and tail point to the same node\n        if (queSize == 1) {\n            val = front!.val;\n            front = null;\n            rear = null;\n        }\n        // Dequeue from the front\n        else if (isFront) {\n            val = front!.val; // Save the head node value\n            // Remove the head node\n            ListNode? fNext = front?.next;\n            fNext!.prev = null;\n            front.next = null;\n            front = fNext;   // Update the head node\n        }\n        // Dequeue from the rear\n        else {\n            val = rear!.val;  // Save the tail node value\n            // Remove the tail node\n            ListNode? rPrev = rear?.prev;\n            rPrev!.next = null;\n            rear.prev = null;\n            rear = rPrev;    // Update the tail node\n        }\n\n        queSize--; // Update the queue size\n        return val;\n    }\n\n    /* Dequeue from the front */\n    public int? PopFirst() {\n        return Pop(true);\n    }\n\n    /* Dequeue from the rear */\n    public int? PopLast() {\n        return Pop(false);\n    }\n\n    /* Access the front element */\n    public int? PeekFirst() {\n        if (IsEmpty())\n            throw new Exception();\n        return front?.val;\n    }\n\n    /* Access the rear element */\n    public int? PeekLast() {\n        if (IsEmpty())\n            throw new Exception();\n        return rear?.val;\n    }\n\n    /* Return an array for printing */\n    public int?[] ToArray() {\n        ListNode? node = front;\n        int?[] res = new int?[Size()];\n        for (int i = 0; i < res.Length; i++) {\n            res[i] = node?.val;\n            node = node?.next;\n        }\n\n        return res;\n    }\n}\n
    linkedlist_deque.go
    /* Deque implemented with a doubly linked list */\ntype linkedListDeque struct {\n    // Use the standard library list package\n    data *list.List\n}\n\n/* Initialize the deque */\nfunc newLinkedListDeque() *linkedListDeque {\n    return &linkedListDeque{\n        data: list.New(),\n    }\n}\n\n/* Enqueue an element at the front */\nfunc (s *linkedListDeque) pushFirst(value any) {\n    s.data.PushFront(value)\n}\n\n/* Enqueue an element at the rear */\nfunc (s *linkedListDeque) pushLast(value any) {\n    s.data.PushBack(value)\n}\n\n/* Dequeue the front element */\nfunc (s *linkedListDeque) popFirst() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Dequeue the rear element */\nfunc (s *linkedListDeque) popLast() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Access the front element */\nfunc (s *linkedListDeque) peekFirst() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    return e.Value\n}\n\n/* Access the rear element */\nfunc (s *linkedListDeque) peekLast() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    return e.Value\n}\n\n/* Get the queue size */\nfunc (s *linkedListDeque) size() int {\n    return s.data.Len()\n}\n\n/* Check whether the queue is empty */\nfunc (s *linkedListDeque) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get the list for printing */\nfunc (s *linkedListDeque) toList() *list.List {\n    return s.data\n}\n
    linkedlist_deque.swift
    /* Doubly linked list node */\nclass ListNode {\n    var val: Int // Node value\n    var next: ListNode? // Reference to the next node\n    weak var prev: ListNode? // Reference to the previous node\n\n    init(val: Int) {\n        self.val = val\n    }\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    private var front: ListNode? // Head node front\n    private var rear: ListNode? // Tail node rear\n    private var _size: Int // Deque size\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the deque size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the deque is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue operation */\n    private func push(num: Int, isFront: Bool) {\n        let node = ListNode(val: num)\n        // If the linked list is empty, make both front and rear point to node\n        if isEmpty() {\n            front = node\n            rear = node\n        }\n        // Enqueue at the front\n        else if isFront {\n            // Add node to the head of the linked list\n            front?.prev = node\n            node.next = front\n            front = node // Update the head node\n        }\n        // Enqueue at the rear\n        else {\n            // Add node to the tail of the linked list\n            rear?.next = node\n            node.prev = rear\n            rear = node // Update the tail node\n        }\n        _size += 1 // Update the deque size\n    }\n\n    /* Enqueue at the front */\n    func pushFirst(num: Int) {\n        push(num: num, isFront: true)\n    }\n\n    /* Enqueue at the rear */\n    func pushLast(num: Int) {\n        push(num: num, isFront: false)\n    }\n\n    /* Dequeue operation */\n    private func pop(isFront: Bool) -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        let val: Int\n        // When the deque size is 1, the head and tail point to the same node\n        if _size == 1 {\n            val = front!.val\n            front = nil\n            rear = nil\n        }\n        // Dequeue from the front\n        else if isFront {\n            val = front!.val // Save the head node value\n            // Remove the head node\n            let fNext = front?.next\n            fNext?.prev = nil\n            front?.next = nil\n            front = fNext // Update the head node\n        }\n        // Dequeue from the rear\n        else {\n            val = rear!.val // Save the tail node value\n            // Remove the tail node\n            let rPrev = rear?.prev\n            rPrev?.next = nil\n            rear?.prev = nil\n            rear = rPrev // Update the tail node\n        }\n        _size -= 1 // Update the deque size\n        return val\n    }\n\n    /* Dequeue from the front */\n    func popFirst() -> Int {\n        pop(isFront: true)\n    }\n\n    /* Dequeue from the rear */\n    func popLast() -> Int {\n        pop(isFront: false)\n    }\n\n    /* Access the front element */\n    func peekFirst() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return front!.val\n    }\n\n    /* Access the rear element */\n    func peekLast() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return rear!.val\n    }\n\n    /* Return an array for printing */\n    func toArray() -> [Int] {\n        var node = front\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
    linkedlist_deque.js
    /* Doubly linked list node */\nclass ListNode {\n    prev; // Reference (pointer) to the previous node\n    next; // Reference (pointer) to the next node\n    val; // Node value\n\n    constructor(val) {\n        this.val = val;\n        this.next = null;\n        this.prev = null;\n    }\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    #front; // Head node front\n    #rear; // Tail node rear\n    #queSize; // Deque size\n\n    constructor() {\n        this.#front = null;\n        this.#rear = null;\n        this.#queSize = 0;\n    }\n\n    /* Enqueue at the rear */\n    pushLast(val) {\n        const node = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.#queSize === 0) {\n            this.#front = node;\n            this.#rear = node;\n        } else {\n            // Add node to the tail of the linked list\n            this.#rear.next = node;\n            node.prev = this.#rear;\n            this.#rear = node; // Update the tail node\n        }\n        this.#queSize++;\n    }\n\n    /* Enqueue at the front */\n    pushFirst(val) {\n        const node = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.#queSize === 0) {\n            this.#front = node;\n            this.#rear = node;\n        } else {\n            // Add node to the head of the linked list\n            this.#front.prev = node;\n            node.next = this.#front;\n            this.#front = node; // Update the head node\n        }\n        this.#queSize++;\n    }\n\n    /* Dequeue from the rear */\n    popLast() {\n        if (this.#queSize === 0) {\n            return null;\n        }\n        const value = this.#rear.val; // Store the tail node value\n        // When the queue size is 1, the head and tail point to the same node\n        if (this.#queSize === 1) {\n            this.#front = null;\n            this.#rear = null;\n        }\n        // Remove the tail node\n        else {\n            let temp = this.#rear.prev;\n            temp.next = null;\n            this.#rear.prev = null;\n            this.#rear = temp; // Update the tail node\n        }\n        this.#queSize--;\n        return value;\n    }\n\n    /* Dequeue from the front */\n    popFirst() {\n        if (this.#queSize === 0) {\n            return null;\n        }\n        const value = this.#front.val; // Store the head node value\n        // When the queue size is 1, the head and tail point to the same node\n        if (this.#queSize === 1) {\n            this.#front = null;\n            this.#rear = null;\n        }\n        // Remove the head node\n        else {\n            let temp = this.#front.next;\n            temp.prev = null;\n            this.#front.next = null;\n            this.#front = temp; // Update the head node\n        }\n        this.#queSize--;\n        return value;\n    }\n\n    /* Access the rear element */\n    peekLast() {\n        return this.#queSize === 0 ? null : this.#rear.val;\n    }\n\n    /* Access the front element */\n    peekFirst() {\n        return this.#queSize === 0 ? null : this.#front.val;\n    }\n\n    /* Get the deque size */\n    size() {\n        return this.#queSize;\n    }\n\n    /* Check whether the deque is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Print the deque */\n    print() {\n        const arr = [];\n        let temp = this.#front;\n        while (temp !== null) {\n            arr.push(temp.val);\n            temp = temp.next;\n        }\n        console.log('[' + arr.join(', ') + ']');\n    }\n}\n
    linkedlist_deque.ts
    /* Doubly linked list node */\nclass ListNode {\n    prev: ListNode; // Reference (pointer) to the previous node\n    next: ListNode; // Reference (pointer) to the next node\n    val: number; // Node value\n\n    constructor(val: number) {\n        this.val = val;\n        this.next = null;\n        this.prev = null;\n    }\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    private front: ListNode; // Head node front\n    private rear: ListNode; // Tail node rear\n    private queSize: number; // Deque size\n\n    constructor() {\n        this.front = null;\n        this.rear = null;\n        this.queSize = 0;\n    }\n\n    /* Enqueue at the rear */\n    pushLast(val: number): void {\n        const node: ListNode = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.queSize === 0) {\n            this.front = node;\n            this.rear = node;\n        } else {\n            // Add node to the tail of the linked list\n            this.rear.next = node;\n            node.prev = this.rear;\n            this.rear = node; // Update the tail node\n        }\n        this.queSize++;\n    }\n\n    /* Enqueue at the front */\n    pushFirst(val: number): void {\n        const node: ListNode = new ListNode(val);\n        // If the linked list is empty, make both front and rear point to node\n        if (this.queSize === 0) {\n            this.front = node;\n            this.rear = node;\n        } else {\n            // Add node to the head of the linked list\n            this.front.prev = node;\n            node.next = this.front;\n            this.front = node; // Update the head node\n        }\n        this.queSize++;\n    }\n\n    /* Dequeue from the rear */\n    popLast(): number {\n        if (this.queSize === 0) {\n            return null;\n        }\n        const value: number = this.rear!.val; // Store the tail node value\n        // When the queue size is 1, the head and tail point to the same node\n        if (this.queSize === 1) {\n            this.front = null;\n            this.rear = null;\n        }\n        // Remove the tail node\n        else {\n            let temp: ListNode = this.rear!.prev;\n            temp!.next = null;\n            this.rear!.prev = null;\n            this.rear = temp; // Update the tail node\n        }\n        this.queSize--;\n        return value;\n    }\n\n    /* Dequeue from the front */\n    popFirst(): number {\n        if (this.queSize === 0) {\n            return null;\n        }\n        const value: number = this.front.val; // Store the head node value\n        // When the queue size is 1, the head and tail point to the same node\n        if (this.queSize === 1) {\n            this.front = null;\n            this.rear = null;\n        }\n        // Remove the head node\n        else {\n            let temp: ListNode = this.front!.next;\n            temp!.prev = null;\n            this.front!.next = null;\n            this.front = temp; // Update the head node\n        }\n        this.queSize--;\n        return value;\n    }\n\n    /* Access the rear element */\n    peekLast(): number {\n        return this.queSize === 0 ? null : this.rear.val;\n    }\n\n    /* Access the front element */\n    peekFirst(): number {\n        return this.queSize === 0 ? null : this.front.val;\n    }\n\n    /* Get the deque size */\n    size(): number {\n        return this.queSize;\n    }\n\n    /* Check whether the deque is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Print the deque */\n    print(): void {\n        const arr: number[] = [];\n        let temp: ListNode = this.front;\n        while (temp !== null) {\n            arr.push(temp.val);\n            temp = temp.next;\n        }\n        console.log('[' + arr.join(', ') + ']');\n    }\n}\n
    linkedlist_deque.dart
    /* Doubly linked list node */\nclass ListNode {\n  int val; // Node value\n  ListNode? next; // Reference to the next node\n  ListNode? prev; // Reference to the previous node\n\n  ListNode(this.val, {this.next, this.prev});\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n  late ListNode? _front; // Head node _front\n  late ListNode? _rear; // Tail node _rear\n  int _queSize = 0; // Deque size\n\n  LinkedListDeque() {\n    this._front = null;\n    this._rear = null;\n  }\n\n  /* Get the deque size */\n  int size() {\n    return this._queSize;\n  }\n\n  /* Check whether the deque is empty */\n  bool isEmpty() {\n    return size() == 0;\n  }\n\n  /* Enqueue operation */\n  void push(int _num, bool isFront) {\n    final ListNode node = ListNode(_num);\n    if (isEmpty()) {\n      // If the linked list is empty, make both _front and _rear point to node\n      _front = _rear = node;\n    } else if (isFront) {\n      // Enqueue at the front\n      // Add node to the head of the linked list\n      _front!.prev = node;\n      node.next = _front;\n      _front = node; // Update the head node\n    } else {\n      // Enqueue at the rear\n      // Add node to the tail of the linked list\n      _rear!.next = node;\n      node.prev = _rear;\n      _rear = node; // Update the tail node\n    }\n    _queSize++; // Update the queue size\n  }\n\n  /* Enqueue at the front */\n  void pushFirst(int _num) {\n    push(_num, true);\n  }\n\n  /* Enqueue at the rear */\n  void pushLast(int _num) {\n    push(_num, false);\n  }\n\n  /* Dequeue operation */\n  int? pop(bool isFront) {\n    // If the queue is empty, return null immediately\n    if (isEmpty()) {\n      return null;\n    }\n    final int val;\n    // When the queue size is 1, the head and tail point to the same node\n    if (_queSize == 1) {\n      val = _front!.val;\n      _front = null;\n      _rear = null;\n    }\n    // Dequeue from the front\n    else if (isFront) {\n      val = _front!.val; // Save the head node value\n      // Remove the head node\n      ListNode? fNext = _front!.next;\n      fNext!.prev = null;\n      _front!.next = null;\n      _front = fNext; // Update the head node\n    // Dequeue from the rear\n    } else {\n      val = _rear!.val; // Save the tail node value\n      // Remove the tail node\n      ListNode? rPrev = _rear!.prev;\n      rPrev!.next = null;\n      _rear!.prev = null;\n      _rear = rPrev; // Update the tail node\n    }\n    _queSize--; // Update the queue size\n    return val;\n  }\n\n  /* Dequeue from the front */\n  int? popFirst() {\n    return pop(true);\n  }\n\n  /* Dequeue from the rear */\n  int? popLast() {\n    return pop(false);\n  }\n\n  /* Access the front element */\n  int? peekFirst() {\n    return _front?.val;\n  }\n\n  /* Access the rear element */\n  int? peekLast() {\n    return _rear?.val;\n  }\n\n  /* Return an array for printing */\n  List<int> toArray() {\n    ListNode? node = _front;\n    final List<int> res = [];\n    for (int i = 0; i < _queSize; i++) {\n      res.add(node!.val);\n      node = node.next;\n    }\n    return res;\n  }\n}\n
    linkedlist_deque.rs
    /* Doubly linked list node */\npub struct ListNode<T> {\n    pub val: T,                                 // Node value\n    pub next: Option<Rc<RefCell<ListNode<T>>>>, // Pointer to the next node\n    pub prev: Option<Rc<RefCell<ListNode<T>>>>, // Pointer to the previous node\n}\n\nimpl<T> ListNode<T> {\n    pub fn new(val: T) -> Rc<RefCell<ListNode<T>>> {\n        Rc::new(RefCell::new(ListNode {\n            val,\n            next: None,\n            prev: None,\n        }))\n    }\n}\n\n/* Deque implemented with a doubly linked list */\n#[allow(dead_code)]\npub struct LinkedListDeque<T> {\n    front: Option<Rc<RefCell<ListNode<T>>>>, // Head node front\n    rear: Option<Rc<RefCell<ListNode<T>>>>,  // Tail node rear\n    que_size: usize,                         // Deque size\n}\n\nimpl<T: Copy> LinkedListDeque<T> {\n    pub fn new() -> Self {\n        Self {\n            front: None,\n            rear: None,\n            que_size: 0,\n        }\n    }\n\n    /* Get the deque size */\n    pub fn size(&self) -> usize {\n        return self.que_size;\n    }\n\n    /* Check whether the deque is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.que_size == 0;\n    }\n\n    /* Enqueue operation */\n    fn push(&mut self, num: T, is_front: bool) {\n        let node = ListNode::new(num);\n        // Enqueue at the front\n        if is_front {\n            match self.front.take() {\n                // If the linked list is empty, make both front and rear point to node\n                None => {\n                    self.rear = Some(node.clone());\n                    self.front = Some(node);\n                }\n                // Add node to the head of the linked list\n                Some(old_front) => {\n                    old_front.borrow_mut().prev = Some(node.clone());\n                    node.borrow_mut().next = Some(old_front);\n                    self.front = Some(node); // Update the head node\n                }\n            }\n        }\n        // Enqueue at the rear\n        else {\n            match self.rear.take() {\n                // If the linked list is empty, make both front and rear point to node\n                None => {\n                    self.front = Some(node.clone());\n                    self.rear = Some(node);\n                }\n                // Add node to the tail of the linked list\n                Some(old_rear) => {\n                    old_rear.borrow_mut().next = Some(node.clone());\n                    node.borrow_mut().prev = Some(old_rear);\n                    self.rear = Some(node); // Update the tail node\n                }\n            }\n        }\n        self.que_size += 1; // Update the deque size\n    }\n\n    /* Enqueue at the front */\n    pub fn push_first(&mut self, num: T) {\n        self.push(num, true);\n    }\n\n    /* Enqueue at the rear */\n    pub fn push_last(&mut self, num: T) {\n        self.push(num, false);\n    }\n\n    /* Dequeue operation */\n    fn pop(&mut self, is_front: bool) -> Option<T> {\n        // If the deque is empty, return None immediately\n        if self.is_empty() {\n            return None;\n        };\n        // Dequeue from the front\n        if is_front {\n            self.front.take().map(|old_front| {\n                match old_front.borrow_mut().next.take() {\n                    Some(new_front) => {\n                        new_front.borrow_mut().prev.take();\n                        self.front = Some(new_front); // Update the head node\n                    }\n                    None => {\n                        self.rear.take();\n                    }\n                }\n                self.que_size -= 1; // Update the deque size\n                old_front.borrow().val\n            })\n        }\n        // Dequeue from the rear\n        else {\n            self.rear.take().map(|old_rear| {\n                match old_rear.borrow_mut().prev.take() {\n                    Some(new_rear) => {\n                        new_rear.borrow_mut().next.take();\n                        self.rear = Some(new_rear); // Update the tail node\n                    }\n                    None => {\n                        self.front.take();\n                    }\n                }\n                self.que_size -= 1; // Update the deque size\n                old_rear.borrow().val\n            })\n        }\n    }\n\n    /* Dequeue from the front */\n    pub fn pop_first(&mut self) -> Option<T> {\n        return self.pop(true);\n    }\n\n    /* Dequeue from the rear */\n    pub fn pop_last(&mut self) -> Option<T> {\n        return self.pop(false);\n    }\n\n    /* Access the front element */\n    pub fn peek_first(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.front.as_ref()\n    }\n\n    /* Access the rear element */\n    pub fn peek_last(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.rear.as_ref()\n    }\n\n    /* Return a vector for printing */\n    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n        let mut res: Vec<T> = Vec::new();\n        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {\n            if let Some(cur) = cur {\n                res.push(cur.borrow().val);\n                recur(cur.borrow().next.as_ref(), res);\n            }\n        }\n\n        recur(head, &mut res);\n        res\n    }\n}\n
    linkedlist_deque.c
    /* Doubly linked list node */\ntypedef struct DoublyListNode {\n    int val;                     // Node value\n    struct DoublyListNode *next; // Next node\n    struct DoublyListNode *prev; // Previous node\n} DoublyListNode;\n\n/* Constructor */\nDoublyListNode *newDoublyListNode(int num) {\n    DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode));\n    new->val = num;\n    new->next = NULL;\n    new->prev = NULL;\n    return new;\n}\n\n/* Destructor */\nvoid delDoublyListNode(DoublyListNode *node) {\n    free(node);\n}\n\n/* Deque implemented with a doubly linked list */\ntypedef struct {\n    DoublyListNode *front, *rear; // Head node front and tail node rear\n    int queSize;                  // Deque size\n} LinkedListDeque;\n\n/* Constructor */\nLinkedListDeque *newLinkedListDeque() {\n    LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque));\n    deque->front = NULL;\n    deque->rear = NULL;\n    deque->queSize = 0;\n    return deque;\n}\n\n/* Destructor */\nvoid delLinkedListdeque(LinkedListDeque *deque) {\n    // Free all nodes\n    for (int i = 0; i < deque->queSize && deque->front != NULL; i++) {\n        DoublyListNode *tmp = deque->front;\n        deque->front = deque->front->next;\n        free(tmp);\n    }\n    // Free the deque structure\n    free(deque);\n}\n\n/* Get the queue size */\nint size(LinkedListDeque *deque) {\n    return deque->queSize;\n}\n\n/* Check whether the queue is empty */\nbool empty(LinkedListDeque *deque) {\n    return (size(deque) == 0);\n}\n\n/* Enqueue */\nvoid push(LinkedListDeque *deque, int num, bool isFront) {\n    DoublyListNode *node = newDoublyListNode(num);\n    // If the list is empty, make both front and rear point to node\n    if (empty(deque)) {\n        deque->front = deque->rear = node;\n    }\n    // Enqueue at the front\n    else if (isFront) {\n        // Add node to the head of the linked list\n        deque->front->prev = node;\n        node->next = deque->front;\n        deque->front = node; // Update the head node\n    }\n    // Enqueue at the rear\n    else {\n        // Add node to the tail of the linked list\n        deque->rear->next = node;\n        node->prev = deque->rear;\n        deque->rear = node;\n    }\n    deque->queSize++; // Update the queue size\n}\n\n/* Enqueue at the front */\nvoid pushFirst(LinkedListDeque *deque, int num) {\n    push(deque, num, true);\n}\n\n/* Enqueue at the rear */\nvoid pushLast(LinkedListDeque *deque, int num) {\n    push(deque, num, false);\n}\n\n/* Access the front element */\nint peekFirst(LinkedListDeque *deque) {\n    assert(size(deque) && deque->front);\n    return deque->front->val;\n}\n\n/* Access the rear element */\nint peekLast(LinkedListDeque *deque) {\n    assert(size(deque) && deque->rear);\n    return deque->rear->val;\n}\n\n/* Dequeue */\nint pop(LinkedListDeque *deque, bool isFront) {\n    if (empty(deque))\n        return -1;\n    int val;\n    // When the queue size is 1, the head and tail point to the same node\n    if (deque->queSize == 1) {\n        val = deque->front->val;\n        delDoublyListNode(deque->front); // Both point to the same node, so free it only once\n        deque->front = NULL;\n        deque->rear = NULL;\n    }\n    // Dequeue from the front\n    else if (isFront) {\n        val = peekFirst(deque); // Save the head node value\n        DoublyListNode *fNext = deque->front->next;\n        fNext->prev = NULL;\n        deque->front->next = NULL;\n        delDoublyListNode(deque->front);\n        deque->front = fNext; // Update the head node\n    }\n    // Dequeue from the rear\n    else {\n        val = peekLast(deque); // Save the tail node value\n        DoublyListNode *rPrev = deque->rear->prev;\n        rPrev->next = NULL;\n        deque->rear->prev = NULL;\n        delDoublyListNode(deque->rear);\n        deque->rear = rPrev; // Update the tail node\n    }\n    deque->queSize--; // Update the queue size\n    return val;\n}\n\n/* Dequeue from the front */\nint popFirst(LinkedListDeque *deque) {\n    return pop(deque, true);\n}\n\n/* Dequeue from the rear */\nint popLast(LinkedListDeque *deque) {\n    return pop(deque, false);\n}\n\n/* Print the queue */\nvoid printLinkedListDeque(LinkedListDeque *deque) {\n    int *arr = malloc(sizeof(int) * deque->queSize);\n    // Copy the linked list data into an array\n    int i;\n    DoublyListNode *node;\n    for (i = 0, node = deque->front; i < deque->queSize; i++) {\n        arr[i] = node->val;\n        node = node->next;\n    }\n    printArray(arr, deque->queSize);\n    free(arr);\n}\n
    linkedlist_deque.kt
    /* Doubly linked list node */\nclass ListNode(var _val: Int) {\n    // The node value is stored in _val\n    var next: ListNode? = null // Reference to the next node\n    var prev: ListNode? = null // Reference to the previous node\n}\n\n/* Deque implemented with a doubly linked list */\nclass LinkedListDeque {\n    private var front: ListNode? = null // Head node front\n    private var rear: ListNode? = null // Tail node rear\n    private var queSize: Int = 0 // Deque size\n\n    /* Get the deque size */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check whether the deque is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Enqueue operation */\n    fun push(num: Int, isFront: Boolean) {\n        val node = ListNode(num)\n        // If the linked list is empty, make both front and rear point to node\n        if (isEmpty()) {\n            rear = node\n            front = rear\n            // Enqueue at the front\n        } else if (isFront) {\n            // Add node to the head of the linked list\n            front?.prev = node\n            node.next = front\n            front = node // Update the head node\n            // Enqueue at the rear\n        } else {\n            // Add node to the tail of the linked list\n            rear?.next = node\n            node.prev = rear\n            rear = node // Update the tail node\n        }\n        queSize++ // Update the queue size\n    }\n\n    /* Enqueue at the front */\n    fun pushFirst(num: Int) {\n        push(num, true)\n    }\n\n    /* Enqueue at the rear */\n    fun pushLast(num: Int) {\n        push(num, false)\n    }\n\n    /* Dequeue operation */\n    fun pop(isFront: Boolean): Int {\n        if (isEmpty()) \n            throw IndexOutOfBoundsException()\n        val _val: Int\n        // When the queue size is 1, the head and tail point to the same node\n        if (queSize == 1) {\n            _val = front!!._val\n            front = null\n            rear = null\n        }\n        // Dequeue from the front\n        else if (isFront) {\n            _val = front!!._val // Save the head node value\n            // Remove the head node\n            val fNext = front!!.next\n            fNext!!.prev = null\n            front!!.next = null\n            front = fNext // Update the head node\n            // Dequeue from the rear\n        } else {\n            _val = rear!!._val // Save the tail node value\n            // Remove the tail node\n            val rPrev = rear!!.prev\n            rPrev!!.next = null\n            rear!!.prev = null\n            rear = rPrev // Update the tail node\n        }\n        queSize-- // Update the queue size\n        return _val\n    }\n\n    /* Dequeue from the front */\n    fun popFirst(): Int {\n        return pop(true)\n    }\n\n    /* Dequeue from the rear */\n    fun popLast(): Int {\n        return pop(false)\n    }\n\n    /* Access the front element */\n    fun peekFirst(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return front!!._val\n    }\n\n    /* Access the rear element */\n    fun peekLast(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return rear!!._val\n    }\n\n    /* Return an array for printing */\n    fun toArray(): IntArray {\n        var node = front\n        val res = IntArray(size())\n        for (i in res.indices) {\n            res[i] = node!!._val\n            node = node.next\n        }\n        return res\n    }\n}\n
    linkedlist_deque.rb
    =begin\nFile: linkedlist_deque.rb\nCreated Time: 2024-04-06\nAuthor: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)\n=end\n\n### Doubly linked list node\nclass ListNode\n  attr_accessor :val\n  attr_accessor :next # Reference to the next node\n  attr_accessor :prev # Reference to the previous node\n\n  ### Constructor ###\n  def initialize(val)\n    @val = val\n  end\nend\n\n### Deque implemented with a doubly linked list ###\nclass LinkedListDeque\n  ### Get the deque size ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @front = nil  # Head node front\n    @rear = nil   # Tail node rear\n    @size = 0     # Deque size\n  end\n\n  ### Check whether the deque is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue operation ###\n  def push(num, is_front)\n    node = ListNode.new(num)\n    # If the list is empty, point both front and rear to node\n    if is_empty?\n      @front = @rear = node\n    # Enqueue at the front\n    elsif is_front\n      # Add node to the head of the linked list\n      @front.prev = node\n      node.next = @front\n      @front = node # Update the head node\n    # Enqueue at the rear\n    else\n      # Add node to the tail of the linked list\n      @rear.next = node\n      node.prev = @rear\n      @rear = node # Update the tail node\n    end\n    @size += 1 # Update the queue size\n  end\n\n  ### Enqueue at the front ###\n  def push_first(num)\n    push(num, true)\n  end\n\n  ### Enqueue at the rear ###\n  def push_last(num)\n    push(num, false)\n  end\n\n  ### Dequeue operation ###\n  def pop(is_front)\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    # When the queue size is 1, the head and tail point to the same node\n    if @size == 1\n      val = @front.val\n      @front = nil\n      @rear = nil\n    # Dequeue from the front\n    elsif is_front\n      val = @front.val # Save the head node value\n      # Remove the head node\n      fnext = @front.next\n      fnext.prev = nil\n      @front.next = nil\n      @front = fnext # Update the head node\n    # Dequeue from the rear\n    else\n      val = @rear.val # Save the tail node value\n      # Remove the tail node\n      rprev = @rear.prev\n      rprev.next = nil\n      @rear.prev = nil\n      @rear = rprev # Update the tail node\n    end\n    @size -= 1 # Update the queue size\n\n    val\n  end\n\n  ### Dequeue from the front ###\n  def pop_first\n    pop(true)\n  end\n\n  ### Dequeue from the rear ###\n  def pop_last\n    pop(false)\n  end\n\n  ### Access the front element ###\n  def peek_first\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @front.val\n  end\n\n  ### Access the rear element ###\n  def peek_last\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @rear.val\n  end\n\n  ### Return an array for printing ###\n  def to_array\n    node = @front\n    res = Array.new(size, 0)\n    for i in 0...size\n      res[i] = node.val\n      node = node.next\n    end\n    res\n  end\nend\n
    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#2-implementation-with-an-array","level":3,"title":"2.   Implementation with an array","text":"

    As with an array-based queue, we can use a circular array to implement a deque, as shown below.

    <1><2><3><4><5>

    Figure 5-9   Insertion and removal in an array-based deque

    Starting with the queue implementation, we need only add methods for insertion at the front and removal at the rear:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_deque.py
    class ArrayDeque:\n    \"\"\"Deque implemented with a circular array\"\"\"\n\n    def __init__(self, capacity: int):\n        \"\"\"Constructor\"\"\"\n        self._nums: list[int] = [0] * capacity\n        self._front: int = 0\n        self._size: int = 0\n\n    def capacity(self) -> int:\n        \"\"\"Get the deque capacity\"\"\"\n        return len(self._nums)\n\n    def size(self) -> int:\n        \"\"\"Get the deque size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the deque is empty\"\"\"\n        return self._size == 0\n\n    def index(self, i: int) -> int:\n        \"\"\"Calculate an index in the circular array\"\"\"\n        # Use modulo to connect the end of the array to its beginning\n        # When i passes the end of the array, wrap around to the beginning\n        # When i passes the beginning of the array, wrap around to the end\n        return (i + self.capacity()) % self.capacity()\n\n    def push_first(self, num: int):\n        \"\"\"Enqueue at the front\"\"\"\n        if self._size == self.capacity():\n            print(\"Deque is full\")\n            return\n        # Move the front pointer one position to the left\n        # Use modulo to wrap front to the end when it passes the beginning of the array\n        self._front = self.index(self._front - 1)\n        # Add num to the front\n        self._nums[self._front] = num\n        self._size += 1\n\n    def push_last(self, num: int):\n        \"\"\"Enqueue at the rear\"\"\"\n        if self._size == self.capacity():\n            print(\"Deque is full\")\n            return\n        # Calculate the rear pointer, which points one position past the last element\n        rear = self.index(self._front + self._size)\n        # Add num to the rear\n        self._nums[rear] = num\n        self._size += 1\n\n    def pop_first(self) -> int:\n        \"\"\"Dequeue from the front\"\"\"\n        num = self.peek_first()\n        # Advance the front pointer by one position\n        self._front = self.index(self._front + 1)\n        self._size -= 1\n        return num\n\n    def pop_last(self) -> int:\n        \"\"\"Dequeue from the rear\"\"\"\n        num = self.peek_last()\n        self._size -= 1\n        return num\n\n    def peek_first(self) -> int:\n        \"\"\"Access the front element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        return self._nums[self._front]\n\n    def peek_last(self) -> int:\n        \"\"\"Access the rear element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Deque is empty\")\n        # Calculate the index of the last element\n        last = self.index(self._front + self._size - 1)\n        return self._nums[last]\n\n    def to_array(self) -> list[int]:\n        \"\"\"Return an array for printing\"\"\"\n        # Convert only the list elements within the occupied range\n        res = []\n        for i in range(self._size):\n            res.append(self._nums[self.index(self._front + i)])\n        return res\n
    array_deque.cpp
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n  private:\n    vector<int> nums; // Array for storing deque elements\n    int front;        // Front index, identifying the first element\n    int queSize;      // Deque size\n\n  public:\n    /* Constructor */\n    ArrayDeque(int capacity) {\n        nums.resize(capacity);\n        front = queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    int capacity() {\n        return nums.size();\n    }\n\n    /* Get the deque size */\n    int size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    bool isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate an index in the circular array */\n    int index(int i) {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + capacity()) % capacity();\n    }\n\n    /* Enqueue at the front */\n    void pushFirst(int num) {\n        if (queSize == capacity()) {\n            cout << \"Deque is full\" << endl;\n            return;\n        }\n        // Move the front index one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = index(front - 1);\n        // Add num to the front\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Enqueue at the rear */\n    void pushLast(int num) {\n        if (queSize == capacity()) {\n            cout << \"Deque is full\" << endl;\n            return;\n        }\n        // Calculate the rear index, one position past the last element\n        int rear = index(front + queSize);\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue from the front */\n    int popFirst() {\n        int num = peekFirst();\n        // Advance the front index by one position\n        front = index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    int popLast() {\n        int num = peekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    int peekFirst() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        return nums[front];\n    }\n\n    /* Access the rear element */\n    int peekLast() {\n        if (isEmpty())\n            throw out_of_range(\"Deque is empty\");\n        // Calculate the index of the last element\n        int last = index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return a vector for printing */\n    vector<int> toVector() {\n        // Copy only the elements in the occupied range\n        vector<int> res(queSize);\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[index(j)];\n        }\n        return res;\n    }\n};\n
    array_deque.java
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    private int[] nums; // Array that stores the deque elements\n    private int front; // Front pointer, pointing to the first element\n    private int queSize; // Deque size\n\n    /* Constructor */\n    public ArrayDeque(int capacity) {\n        this.nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    public int capacity() {\n        return nums.length;\n    }\n\n    /* Get the deque size */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    public boolean isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate an index in the circular array */\n    private int index(int i) {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + capacity()) % capacity();\n    }\n\n    /* Enqueue at the front */\n    public void pushFirst(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Deque is full\");\n            return;\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = index(front - 1);\n        // Add num to the front\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Enqueue at the rear */\n    public void pushLast(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Deque is full\");\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        int rear = index(front + queSize);\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue from the front */\n    public int popFirst() {\n        int num = peekFirst();\n        // Advance the front pointer by one position\n        front = index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    public int popLast() {\n        int num = peekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int peekFirst() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return nums[front];\n    }\n\n    /* Access the rear element */\n    public int peekLast() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        // Calculate the index of the last element\n        int last = index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return an array for printing */\n    public int[] toArray() {\n        // Convert only the list elements within the occupied range\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[index(j)];\n        }\n        return res;\n    }\n}\n
    array_deque.cs
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    int[] nums;  // Array that stores the deque elements\n    int front;   // Front pointer, pointing to the first element\n    int queSize; // Deque size\n\n    /* Constructor */\n    public ArrayDeque(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    int Capacity() {\n        return nums.Length;\n    }\n\n    /* Get the deque size */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check whether the deque is empty */\n    public bool IsEmpty() {\n        return queSize == 0;\n    }\n\n    /* Calculate an index in the circular array */\n    int Index(int i) {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + Capacity()) % Capacity();\n    }\n\n    /* Enqueue at the front */\n    public void PushFirst(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Deque is full\");\n            return;\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = Index(front - 1);\n        // Add num to the front\n        nums[front] = num;\n        queSize++;\n    }\n\n    /* Enqueue at the rear */\n    public void PushLast(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Deque is full\");\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        int rear = Index(front + queSize);\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue from the front */\n    public int PopFirst() {\n        int num = PeekFirst();\n        // Advance the front pointer by one position\n        front = Index(front + 1);\n        queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    public int PopLast() {\n        int num = PeekLast();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int PeekFirst() {\n        if (IsEmpty()) {\n            throw new InvalidOperationException();\n        }\n        return nums[front];\n    }\n\n    /* Access the rear element */\n    public int PeekLast() {\n        if (IsEmpty()) {\n            throw new InvalidOperationException();\n        }\n        // Calculate the index of the last element\n        int last = Index(front + queSize - 1);\n        return nums[last];\n    }\n\n    /* Return an array for printing */\n    public int[] ToArray() {\n        // Copy only the elements within the occupied range\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[Index(j)];\n        }\n        return res;\n    }\n}\n
    array_deque.go
    /* Deque implemented with a circular array */\ntype arrayDeque struct {\n    nums        []int // Array that stores the deque elements\n    front       int   // Front pointer, pointing to the first element\n    queSize     int   // Deque size\n    queCapacity int   // Queue capacity (the maximum number of elements)\n}\n\n/* Initialize the queue */\nfunc newArrayDeque(queCapacity int) *arrayDeque {\n    return &arrayDeque{\n        nums:        make([]int, queCapacity),\n        queCapacity: queCapacity,\n        front:       0,\n        queSize:     0,\n    }\n}\n\n/* Get the deque size */\nfunc (q *arrayDeque) size() int {\n    return q.queSize\n}\n\n/* Check whether the deque is empty */\nfunc (q *arrayDeque) isEmpty() bool {\n    return q.queSize == 0\n}\n\n/* Calculate an index in the circular array */\nfunc (q *arrayDeque) index(i int) int {\n    // Use modulo to connect the end of the array to its beginning\n    // When i passes the end of the array, wrap around to the beginning\n    // When i passes the beginning of the array, wrap around to the end\n    return (i + q.queCapacity) % q.queCapacity\n}\n\n/* Enqueue at the front */\nfunc (q *arrayDeque) pushFirst(num int) {\n    if q.queSize == q.queCapacity {\n        fmt.Println(\"Deque is full\")\n        return\n    }\n    // Move the front pointer one position to the left\n    // Use modulo to wrap front to the end when it passes the beginning of the array\n    q.front = q.index(q.front - 1)\n    // Add num to the front\n    q.nums[q.front] = num\n    q.queSize++\n}\n\n/* Enqueue at the rear */\nfunc (q *arrayDeque) pushLast(num int) {\n    if q.queSize == q.queCapacity {\n        fmt.Println(\"Deque is full\")\n        return\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    rear := q.index(q.front + q.queSize)\n    // Add num to the rear\n    q.nums[rear] = num\n    q.queSize++\n}\n\n/* Dequeue from the front */\nfunc (q *arrayDeque) popFirst() any {\n    num := q.peekFirst()\n    if num == nil {\n        return nil\n    }\n    // Advance the front pointer by one position\n    q.front = q.index(q.front + 1)\n    q.queSize--\n    return num\n}\n\n/* Dequeue from the rear */\nfunc (q *arrayDeque) popLast() any {\n    num := q.peekLast()\n    if num == nil {\n        return nil\n    }\n    q.queSize--\n    return num\n}\n\n/* Access the front element */\nfunc (q *arrayDeque) peekFirst() any {\n    if q.isEmpty() {\n        return nil\n    }\n    return q.nums[q.front]\n}\n\n/* Access the rear element */\nfunc (q *arrayDeque) peekLast() any {\n    if q.isEmpty() {\n        return nil\n    }\n    // Calculate the index of the last element\n    last := q.index(q.front + q.queSize - 1)\n    return q.nums[last]\n}\n\n/* Get a slice for printing */\nfunc (q *arrayDeque) toSlice() []int {\n    // Convert only the list elements within the occupied range\n    res := make([]int, q.queSize)\n    for i, j := 0, q.front; i < q.queSize; i++ {\n        res[i] = q.nums[q.index(j)]\n        j++\n    }\n    return res\n}\n
    array_deque.swift
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    private var nums: [Int] // Array that stores the deque elements\n    private var front: Int // Front index, identifying the first element\n    private var _size: Int // Deque size\n\n    /* Constructor */\n    init(capacity: Int) {\n        nums = Array(repeating: 0, count: capacity)\n        front = 0\n        _size = 0\n    }\n\n    /* Get the deque capacity */\n    func capacity() -> Int {\n        nums.count\n    }\n\n    /* Get the deque size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the deque is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Calculate an index in the circular array */\n    private func index(i: Int) -> Int {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        (i + capacity()) % capacity()\n    }\n\n    /* Enqueue at the front */\n    func pushFirst(num: Int) {\n        if size() == capacity() {\n            print(\"Deque is full\")\n            return\n        }\n        // Move the front index one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = index(i: front - 1)\n        // Add num to the front\n        nums[front] = num\n        _size += 1\n    }\n\n    /* Enqueue at the rear */\n    func pushLast(num: Int) {\n        if size() == capacity() {\n            print(\"Deque is full\")\n            return\n        }\n        // Calculate the rear index, one position past the last element\n        let rear = index(i: front + size())\n        // Add num to the rear\n        nums[rear] = num\n        _size += 1\n    }\n\n    /* Dequeue from the front */\n    func popFirst() -> Int {\n        let num = peekFirst()\n        // Advance the front index by one position\n        front = index(i: front + 1)\n        _size -= 1\n        return num\n    }\n\n    /* Dequeue from the rear */\n    func popLast() -> Int {\n        let num = peekLast()\n        _size -= 1\n        return num\n    }\n\n    /* Access the front element */\n    func peekFirst() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        return nums[front]\n    }\n\n    /* Access the rear element */\n    func peekLast() -> Int {\n        if isEmpty() {\n            fatalError(\"Deque is empty\")\n        }\n        // Calculate the index of the last element\n        let last = index(i: front + size() - 1)\n        return nums[last]\n    }\n\n    /* Return an array for printing */\n    func toArray() -> [Int] {\n        // Include only the elements in the occupied range\n        (front ..< front + size()).map { nums[index(i: $0)] }\n    }\n}\n
    array_deque.js
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    #nums; // Array for storing deque elements\n    #front; // Front pointer, pointing to the first element\n    #queSize; // Deque size\n\n    /* Constructor */\n    constructor(capacity) {\n        this.#nums = new Array(capacity);\n        this.#front = 0;\n        this.#queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    capacity() {\n        return this.#nums.length;\n    }\n\n    /* Get the deque size */\n    size() {\n        return this.#queSize;\n    }\n\n    /* Check whether the deque is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Calculate an index in the circular array */\n    index(i) {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + this.capacity()) % this.capacity();\n    }\n\n    /* Enqueue at the front */\n    pushFirst(num) {\n        if (this.#queSize === this.capacity()) {\n            console.log('Deque is full');\n            return;\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        this.#front = this.index(this.#front - 1);\n        // Add num to the front\n        this.#nums[this.#front] = num;\n        this.#queSize++;\n    }\n\n    /* Enqueue at the rear */\n    pushLast(num) {\n        if (this.#queSize === this.capacity()) {\n            console.log('Deque is full');\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        const rear = this.index(this.#front + this.#queSize);\n        // Add num to the rear\n        this.#nums[rear] = num;\n        this.#queSize++;\n    }\n\n    /* Dequeue from the front */\n    popFirst() {\n        const num = this.peekFirst();\n        // Advance the front pointer by one position\n        this.#front = this.index(this.#front + 1);\n        this.#queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    popLast() {\n        const num = this.peekLast();\n        this.#queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peekFirst() {\n        if (this.isEmpty()) throw new Error('Deque is empty');\n        return this.#nums[this.#front];\n    }\n\n    /* Access the rear element */\n    peekLast() {\n        if (this.isEmpty()) throw new Error('Deque is empty');\n        // Calculate the index of the last element\n        const last = this.index(this.#front + this.#queSize - 1);\n        return this.#nums[last];\n    }\n\n    /* Return an array for printing */\n    toArray() {\n        // Convert only the list elements within the occupied range\n        const res = [];\n        for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) {\n            res[i] = this.#nums[this.index(j)];\n        }\n        return res;\n    }\n}\n
    array_deque.ts
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n    private nums: number[]; // Array for storing deque elements\n    private front: number; // Front pointer, pointing to the first element\n    private queSize: number; // Deque size\n\n    /* Constructor */\n    constructor(capacity: number) {\n        this.nums = new Array(capacity);\n        this.front = 0;\n        this.queSize = 0;\n    }\n\n    /* Get the deque capacity */\n    capacity(): number {\n        return this.nums.length;\n    }\n\n    /* Get the deque size */\n    size(): number {\n        return this.queSize;\n    }\n\n    /* Check whether the deque is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Calculate an index in the circular array */\n    index(i: number): number {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + this.capacity()) % this.capacity();\n    }\n\n    /* Enqueue at the front */\n    pushFirst(num: number): void {\n        if (this.queSize === this.capacity()) {\n            console.log('Deque is full');\n            return;\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        this.front = this.index(this.front - 1);\n        // Add num to the front\n        this.nums[this.front] = num;\n        this.queSize++;\n    }\n\n    /* Enqueue at the rear */\n    pushLast(num: number): void {\n        if (this.queSize === this.capacity()) {\n            console.log('Deque is full');\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        const rear: number = this.index(this.front + this.queSize);\n        // Add num to the rear\n        this.nums[rear] = num;\n        this.queSize++;\n    }\n\n    /* Dequeue from the front */\n    popFirst(): number {\n        const num: number = this.peekFirst();\n        // Advance the front pointer by one position\n        this.front = this.index(this.front + 1);\n        this.queSize--;\n        return num;\n    }\n\n    /* Dequeue from the rear */\n    popLast(): number {\n        const num: number = this.peekLast();\n        this.queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peekFirst(): number {\n        if (this.isEmpty()) throw new Error('Deque is empty');\n        return this.nums[this.front];\n    }\n\n    /* Access the rear element */\n    peekLast(): number {\n        if (this.isEmpty()) throw new Error('Deque is empty');\n        // Calculate the index of the last element\n        const last = this.index(this.front + this.queSize - 1);\n        return this.nums[last];\n    }\n\n    /* Return an array for printing */\n    toArray(): number[] {\n        // Convert only the list elements within the occupied range\n        const res: number[] = [];\n        for (let i = 0, j = this.front; i < this.queSize; i++, j++) {\n            res[i] = this.nums[this.index(j)];\n        }\n        return res;\n    }\n}\n
    array_deque.dart
    /* Deque implemented with a circular array */\nclass ArrayDeque {\n  late List<int> _nums; // Array for storing deque elements\n  late int _front; // Front pointer, pointing to the first element\n  late int _queSize; // Deque size\n\n  /* Constructor */\n  ArrayDeque(int capacity) {\n    this._nums = List.filled(capacity, 0);\n    this._front = this._queSize = 0;\n  }\n\n  /* Get the deque capacity */\n  int capacity() {\n    return _nums.length;\n  }\n\n  /* Get the deque size */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check whether the deque is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Calculate an index in the circular array */\n  int index(int i) {\n    // Use modulo to connect the end of the array to its beginning\n    // When i passes the end of the array, wrap around to the beginning\n    // When i passes the beginning of the array, wrap around to the end\n    return (i + capacity()) % capacity();\n  }\n\n  /* Enqueue at the front */\n  void pushFirst(int _num) {\n    if (_queSize == capacity()) {\n      throw Exception(\"Deque is full\");\n    }\n    // Move the front pointer one position to the left\n    // Use modulo to wrap _front to the end when it passes the beginning of the array\n    _front = index(_front - 1);\n    // Add _num to the front\n    _nums[_front] = _num;\n    _queSize++;\n  }\n\n  /* Enqueue at the rear */\n  void pushLast(int _num) {\n    if (_queSize == capacity()) {\n      throw Exception(\"Deque is full\");\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    int rear = index(_front + _queSize);\n    // Add _num to the rear\n    _nums[rear] = _num;\n    _queSize++;\n  }\n\n  /* Dequeue from the front */\n  int popFirst() {\n    int _num = peekFirst();\n    // Move the front pointer one position to the right\n    _front = index(_front + 1);\n    _queSize--;\n    return _num;\n  }\n\n  /* Dequeue from the rear */\n  int popLast() {\n    int _num = peekLast();\n    _queSize--;\n    return _num;\n  }\n\n  /* Access the front element */\n  int peekFirst() {\n    if (isEmpty()) {\n      throw Exception(\"Deque is empty\");\n    }\n    return _nums[_front];\n  }\n\n  /* Access the rear element */\n  int peekLast() {\n    if (isEmpty()) {\n      throw Exception(\"Deque is empty\");\n    }\n    // Calculate the index of the last element\n    int last = index(_front + _queSize - 1);\n    return _nums[last];\n  }\n\n  /* Return an array for printing */\n  List<int> toArray() {\n    // Convert only the list elements within the occupied range\n    List<int> res = List.filled(_queSize, 0);\n    for (int i = 0, j = _front; i < _queSize; i++, j++) {\n      res[i] = _nums[index(j)];\n    }\n    return res;\n  }\n}\n
    array_deque.rs
    /* Deque implemented with a circular array */\nstruct ArrayDeque<T> {\n    nums: Vec<T>,    // Array for storing deque elements\n    front: usize,    // Front index, identifying the first element\n    que_size: usize, // Deque size\n}\n\nimpl<T: Copy + Default> ArrayDeque<T> {\n    /* Constructor */\n    pub fn new(capacity: usize) -> Self {\n        Self {\n            nums: vec![T::default(); capacity],\n            front: 0,\n            que_size: 0,\n        }\n    }\n\n    /* Get the deque capacity */\n    pub fn capacity(&self) -> usize {\n        self.nums.len()\n    }\n\n    /* Get the deque size */\n    pub fn size(&self) -> usize {\n        self.que_size\n    }\n\n    /* Check whether the deque is empty */\n    pub fn is_empty(&self) -> bool {\n        self.que_size == 0\n    }\n\n    /* Calculate an index in the circular array */\n    fn index(&self, i: i32) -> usize {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        ((i + self.capacity() as i32) % self.capacity() as i32) as usize\n    }\n\n    /* Enqueue at the front */\n    pub fn push_first(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Deque is full\");\n            return;\n        }\n        // Move the front index one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        self.front = self.index(self.front as i32 - 1);\n        // Add num to the front\n        self.nums[self.front] = num;\n        self.que_size += 1;\n    }\n\n    /* Enqueue at the rear */\n    pub fn push_last(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Deque is full\");\n            return;\n        }\n        // Calculate the rear index, one position past the last element\n        let rear = self.index(self.front as i32 + self.que_size as i32);\n        // Add num to the rear\n        self.nums[rear] = num;\n        self.que_size += 1;\n    }\n\n    /* Dequeue from the front */\n    fn pop_first(&mut self) -> T {\n        let num = self.peek_first();\n        // Advance the front index by one position\n        self.front = self.index(self.front as i32 + 1);\n        self.que_size -= 1;\n        num\n    }\n\n    /* Dequeue from the rear */\n    fn pop_last(&mut self) -> T {\n        let num = self.peek_last();\n        self.que_size -= 1;\n        num\n    }\n\n    /* Access the front element */\n    fn peek_first(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Deque is empty\")\n        };\n        self.nums[self.front]\n    }\n\n    /* Access the rear element */\n    fn peek_last(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Deque is empty\")\n        };\n        // Calculate the index of the last element\n        let last = self.index(self.front as i32 + self.que_size as i32 - 1);\n        self.nums[last]\n    }\n\n    /* Return a vector for printing */\n    fn to_array(&self) -> Vec<T> {\n        // Convert only the list elements within the occupied range\n        let mut res = vec![T::default(); self.que_size];\n        let mut j = self.front;\n        for i in 0..self.que_size {\n            res[i] = self.nums[self.index(j as i32)];\n            j += 1;\n        }\n        res\n    }\n}\n
    array_deque.c
    /* Deque implemented with a circular array */\ntypedef struct {\n    int *nums;       // Array that stores the queue elements\n    int front;       // Front pointer, pointing to the first element\n    int queSize;     // Current number of elements in the deque\n    int queCapacity; // Queue capacity\n} ArrayDeque;\n\n/* Constructor */\nArrayDeque *newArrayDeque(int capacity) {\n    ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque));\n    // Initialize the array\n    deque->queCapacity = capacity;\n    deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity);\n    deque->front = deque->queSize = 0;\n    return deque;\n}\n\n/* Destructor */\nvoid delArrayDeque(ArrayDeque *deque) {\n    free(deque->nums);\n    free(deque);\n}\n\n/* Get the deque capacity */\nint capacity(ArrayDeque *deque) {\n    return deque->queCapacity;\n}\n\n/* Get the deque size */\nint size(ArrayDeque *deque) {\n    return deque->queSize;\n}\n\n/* Check whether the deque is empty */\nbool empty(ArrayDeque *deque) {\n    return deque->queSize == 0;\n}\n\n/* Calculate an index in the circular array */\nint dequeIndex(ArrayDeque *deque, int i) {\n    // Use modulo to connect the end of the array to its beginning\n    // When i passes the end of the array, wrap to the beginning\n    // When i passes the beginning of the array, wrap around to the end\n    return ((i + capacity(deque)) % capacity(deque));\n}\n\n/* Enqueue at the front */\nvoid pushFirst(ArrayDeque *deque, int num) {\n    if (deque->queSize == capacity(deque)) {\n        printf(\"Deque is full\\r\\n\");\n        return;\n    }\n    // Move the front pointer one position to the left\n    // Use modulo to wrap front to the end when it passes the beginning\n    deque->front = dequeIndex(deque, deque->front - 1);\n    // Add num to the front\n    deque->nums[deque->front] = num;\n    deque->queSize++;\n}\n\n/* Enqueue at the rear */\nvoid pushLast(ArrayDeque *deque, int num) {\n    if (deque->queSize == capacity(deque)) {\n        printf(\"Deque is full\\r\\n\");\n        return;\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    int rear = dequeIndex(deque, deque->front + deque->queSize);\n    // Add num to the rear\n    deque->nums[rear] = num;\n    deque->queSize++;\n}\n\n/* Access the front element */\nint peekFirst(ArrayDeque *deque) {\n    // Access error: the deque is empty\n    assert(empty(deque) == 0);\n    return deque->nums[deque->front];\n}\n\n/* Access the rear element */\nint peekLast(ArrayDeque *deque) {\n    // Access error: the deque is empty\n    assert(empty(deque) == 0);\n    int last = dequeIndex(deque, deque->front + deque->queSize - 1);\n    return deque->nums[last];\n}\n\n/* Dequeue from the front */\nint popFirst(ArrayDeque *deque) {\n    int num = peekFirst(deque);\n    // Advance the front pointer by one position\n    deque->front = dequeIndex(deque, deque->front + 1);\n    deque->queSize--;\n    return num;\n}\n\n/* Dequeue from the rear */\nint popLast(ArrayDeque *deque) {\n    int num = peekLast(deque);\n    deque->queSize--;\n    return num;\n}\n\n/* Return an array for printing */\nint *toArray(ArrayDeque *deque, int *queSize) {\n    *queSize = deque->queSize;\n    int *res = (int *)calloc(deque->queSize, sizeof(int));\n    int j = deque->front;\n    for (int i = 0; i < deque->queSize; i++) {\n        res[i] = deque->nums[j % deque->queCapacity];\n        j++;\n    }\n    return res;\n}\n
    array_deque.kt
    /* Constructor */\nclass ArrayDeque(capacity: Int) {\n    private var nums: IntArray = IntArray(capacity) // Array that stores the deque elements\n    private var front: Int = 0 // Front pointer, pointing to the first element\n    private var queSize: Int = 0 // Deque size\n\n    /* Get the deque capacity */\n    fun capacity(): Int {\n        return nums.size\n    }\n\n    /* Get the deque size */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check whether the deque is empty */\n    fun isEmpty(): Boolean {\n        return queSize == 0\n    }\n\n    /* Calculate an index in the circular array */\n    private fun index(i: Int): Int {\n        // Use modulo to connect the end of the array to its beginning\n        // When i passes the end of the array, wrap around to the beginning\n        // When i passes the beginning of the array, wrap around to the end\n        return (i + capacity()) % capacity()\n    }\n\n    /* Enqueue at the front */\n    fun pushFirst(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Deque is full\")\n            return\n        }\n        // Move the front pointer one position to the left\n        // Use modulo to wrap front to the end when it passes the beginning of the array\n        front = index(front - 1)\n        // Add num to the front\n        nums[front] = num\n        queSize++\n    }\n\n    /* Enqueue at the rear */\n    fun pushLast(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Deque is full\")\n            return\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        val rear = index(front + queSize)\n        // Add num to the rear\n        nums[rear] = num\n        queSize++\n    }\n\n    /* Dequeue from the front */\n    fun popFirst(): Int {\n        val num = peekFirst()\n        // Advance the front pointer by one position\n        front = index(front + 1)\n        queSize--\n        return num\n    }\n\n    /* Dequeue from the rear */\n    fun popLast(): Int {\n        val num = peekLast()\n        queSize--\n        return num\n    }\n\n    /* Access the front element */\n    fun peekFirst(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return nums[front]\n    }\n\n    /* Access the rear element */\n    fun peekLast(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        // Calculate the index of the last element\n        val last = index(front + queSize - 1)\n        return nums[last]\n    }\n\n    /* Return an array for printing */\n    fun toArray(): IntArray {\n        // Copy only the elements within the occupied range\n        val res = IntArray(queSize)\n        var i = 0\n        var j = front\n        while (i < queSize) {\n            res[i] = nums[index(j)]\n            i++\n            j++\n        }\n        return res\n    }\n}\n
    array_deque.rb
    ### Deque implemented with a circular array ###\nclass ArrayDeque\n  ### Get the deque size ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize(capacity)\n    @nums = Array.new(capacity, 0)\n    @front = 0\n    @size = 0\n  end\n\n  ### Get the deque capacity ###\n  def capacity\n    @nums.length\n  end\n\n  ### Check whether the deque is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue at the front ###\n  def push_first(num)\n    if size == capacity\n      puts 'Deque is full'\n      return\n    end\n\n    # Move the front pointer one position to the left\n    # Use modulo to wrap front to the end when it passes the beginning of the array\n    @front = index(@front - 1)\n    # Add num to the front\n    @nums[@front] = num\n    @size += 1\n  end\n\n  ### Enqueue at the rear ###\n  def push_last(num)\n    if size == capacity\n      puts 'Deque is full'\n      return\n    end\n\n    # Calculate the rear pointer, which points one position past the last element\n    rear = index(@front + size)\n    # Add num to the rear\n    @nums[rear] = num\n    @size += 1\n  end\n\n  ### Dequeue from the front ###\n  def pop_first\n    num = peek_first\n    # Advance the front pointer by one position\n    @front = index(@front + 1)\n    @size -= 1\n    num\n  end\n\n  ### Dequeue from the rear ###\n  def pop_last\n    num = peek_last\n    @size -= 1\n    num\n  end\n\n  ### Access the front element ###\n  def peek_first\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    @nums[@front]\n  end\n\n  ### Access the rear element ###\n  def peek_last\n    raise IndexError, 'Deque is empty' if is_empty?\n\n    # Calculate the index of the last element\n    last = index(@front + size - 1)\n    @nums[last]\n  end\n\n  ### Return an array for printing ###\n  def to_array\n    # Convert only the list elements within the occupied range\n    res = []\n    for i in 0...size\n      res << @nums[index(@front + i)]\n    end\n    res\n  end\n\n  private\n\n  ### Calculate an index in the circular array ###\n  def index(i)\n    # Use modulo to connect the end of the array to its beginning\n    # When i passes the end of the array, wrap around to the beginning\n    # When i passes the beginning of the array, wrap around to the end\n    (i + capacity) % capacity\n  end\nend\n
    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#533-applications","level":2,"title":"5.3.3   Applications","text":"

    A deque combines stack and queue behavior, so it supports all their applications while offering greater flexibility.

    Software undo is usually implemented with a stack: the system uses push to record each change and pop to undo it. Because resources are limited, software often restricts the history to a fixed number of steps, such as \\(50\\). When the stack grows beyond \\(50\\), the oldest entry must be removed from the bottom, the front of the deque. A stack cannot provide this operation, so a deque is needed instead. The core undo behavior still follows last-in, first-out order; the deque simply provides flexibility for the additional logic.

    ","path":["Chapter 5.   Stacks and queues","5.3   Deques"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/","level":1,"title":"5.5   Exercises","text":"","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#551-check-your-understanding","level":2,"title":"5.5.1   Check your understanding","text":"","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#1-which-element-comes-out-first-in-a-stack-or-queue","level":3,"title":"1.   Which element comes out first in a stack or queue?","text":"

    Start with an empty stack S and an empty queue Q. Perform the same operations on each:

    Step 1: Add A. Step 2: Add B. Step 3: Remove and record one element. Step 4: Add C. Step 5: Keep removing and recording elements until the container is empty.

    Write the order in which elements are removed from S and from Q. Explain the difference using last in, first out or first in, first out.

    Reference answer

    The removal order for stack S is B, C, A. After adding A, B, we first pop B, the most recently added element, and then add C. We then pop C, A in order. This is last in, first out.

    The removal order for queue Q is A, B, C. After adding A, B, we first remove A, the earliest element added. We then add C and remove B, C in order. This is first in, first out.

    ","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#2-what-happens-when-the-rear-passes-the-end-of-the-array","level":3,"title":"2.   What happens when the rear passes the end of the array?","text":"

    A queue uses a circular array of length 5, with indices 0-4. Currently, front = 3 and size = 2, with queue elements A, B at indices 3 and 4, respectively.

    1. When enqueueing C, at which index should C be placed? What is size afterward?
    2. If we then dequeue once, which element is removed? What are the new values of front and size?
    3. What is the logical order from front to rear now? Does dequeuing require moving the other array elements? Why?
    Reference answer
    1. The new element's position is (front + size) % 5 = (3 + 2) % 5 = 0, so C goes at index 0. After enqueueing, size = 3.

    2. Dequeueing removes the current front element, A. The new front index is (3 + 1) % 5 = 4, so front = 4 and size = 2.

    3. The valid elements are logically ordered as B, C, with B at index 4 and C at index 0. Dequeueing only advances front and updates size. The circular array uses the remainder operation to wrap indices back to the beginning, so the other elements do not need to shift forward together.

    ","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#3-operations-at-both-ends-of-a-deque","level":3,"title":"3.   Operations at both ends of a deque","text":"

    Here, push_first adds at the front, push_last adds at the rear, pop_first removes from the front, and pop_last removes from the rear.

    Starting with an empty deque deq, perform:

    1. push_last(A)
    2. push_last(B)
    3. push_first(C)
    4. pop_last()
    5. push_last(D)
    6. pop_first()
    1. Which elements are removed by the two pop operations?
    2. Which elements remain, from front to rear, after all the operations?
    3. Examine these six steps. Can a queue that allows additions only at the rear and removals only at the front perform them all? If not, identify the unsupported operations. Then explain whether a deque can perform them all, and why.
    Reference answer

    After the first three steps, the deque contains [C, A, B] from front to rear.

    1. pop_last() removes B. After adding D, the deque is [C, A, D]. Then pop_first() removes C.

    2. The remaining elements are [A, D].

    3. A queue that allows additions only at the rear and removals only at the front cannot perform every operation: step 3, push_first(C), adds at the front, and step 4, pop_last(), removes from the rear. Neither operation is supported by that queue. A deque supports additions and removals at both ends, so it can perform all six steps.

    ","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#552-programming-exercises","level":2,"title":"5.5.2   Programming exercises","text":"","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#1-checking-a-bracket-sequence","level":3,"title":"1.   Checking a bracket sequence","text":"

    Given a string s containing only the three types of brackets (), [], and {}, use a stack to determine whether it is valid.

    A valid sequence must satisfy both conditions: each closing bracket matches the type of the most recent unmatched opening bracket, and no unmatched opening brackets remain at the end. Return a Boolean result.

    Hints
    1. You can map each closing bracket to its matching opening bracket.
    2. When you encounter a closing bracket, first check whether the stack is empty, then check whether its top matches.
    3. The stack must also be empty when the scan finishes.

    LeetCode

    ","path":["Chapter 5.   Stacks and queues","5.5   Exercises"],"tags":[]},{"location":"chapter_stack_and_queue/queue/","level":1,"title":"5.2   Queues","text":"

    A queue is a linear data structure that follows a first-in, first-out rule. It models a line of people: newcomers join at the back, while those at the front leave one by one.

    As shown below, the two ends are called the “front” and the “rear.” Adding an element at the rear is called “enqueueing,” and removing an element from the front is called “dequeueing.”

    Figure 5-4   The first-in, first-out rule of a queue

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#521-common-queue-operations","level":2,"title":"5.2.1   Common queue operations","text":"

    Table 5-2 lists common operations. Method names vary between programming languages; here we use the same names as for stacks.

    Table 5-2   Efficiency of queue operations

    Method Description Time complexity push() Enqueue an element at the rear \\(O(1)\\) pop() Dequeue the front element \\(O(1)\\) peek() Access the front element \\(O(1)\\)

    We can use a queue class already provided by the programming language:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby queue.py
    from collections import deque\n\n# Initialize a queue\n# In Python, we usually use the deque class as a queue\n# Although queue.Queue() is a dedicated queue class, it is less convenient and is not recommended here\nque: deque[int] = deque()\n\n# Enqueue elements\nque.append(1)\nque.append(3)\nque.append(2)\nque.append(5)\nque.append(4)\n\n# Access the front element\nfront: int = que[0]\n\n# Dequeue an element\npop: int = que.popleft()\n\n# Get the queue size\nsize: int = len(que)\n\n# Check whether the queue is empty\nis_empty: bool = len(que) == 0\n
    queue.cpp
    /* Initialize a queue */\nqueue<int> queue;\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access the front element */\nint front = queue.front();\n\n/* Dequeue an element */\nqueue.pop();\n\n/* Get the queue size */\nint size = queue.size();\n\n/* Check whether the queue is empty */\nbool empty = queue.empty();\n
    queue.java
    /* Initialize a queue */\nQueue<Integer> queue = new LinkedList<>();\n\n/* Enqueue elements */\nqueue.offer(1);\nqueue.offer(3);\nqueue.offer(2);\nqueue.offer(5);\nqueue.offer(4);\n\n/* Access the front element */\nint peek = queue.peek();\n\n/* Dequeue an element */\nint pop = queue.poll();\n\n/* Get the queue size */\nint size = queue.size();\n\n/* Check whether the queue is empty */\nboolean isEmpty = queue.isEmpty();\n
    queue.cs
    /* Initialize a queue */\nQueue<int> queue = new();\n\n/* Enqueue elements */\nqueue.Enqueue(1);\nqueue.Enqueue(3);\nqueue.Enqueue(2);\nqueue.Enqueue(5);\nqueue.Enqueue(4);\n\n/* Access the front element */\nint peek = queue.Peek();\n\n/* Dequeue an element */\nint pop = queue.Dequeue();\n\n/* Get the queue size */\nint size = queue.Count;\n\n/* Check whether the queue is empty */\nbool isEmpty = queue.Count == 0;\n
    queue_test.go
    /* Initialize a queue */\n// In Go, use a list as a queue\nqueue := list.New()\n\n/* Enqueue elements */\nqueue.PushBack(1)\nqueue.PushBack(3)\nqueue.PushBack(2)\nqueue.PushBack(5)\nqueue.PushBack(4)\n\n/* Access the front element */\npeek := queue.Front()\n\n/* Dequeue an element */\npop := queue.Front()\nqueue.Remove(pop)\n\n/* Get the queue size */\nsize := queue.Len()\n\n/* Check whether the queue is empty */\nisEmpty := queue.Len() == 0\n
    queue.swift
    /* Initialize a queue */\n// Swift has no built-in queue class; use an Array as a queue\nvar queue: [Int] = []\n\n/* Enqueue elements */\nqueue.append(1)\nqueue.append(3)\nqueue.append(2)\nqueue.append(5)\nqueue.append(4)\n\n/* Access the front element */\nlet peek = queue.first!\n\n/* Dequeue an element */\n// Because this is an array, removeFirst takes O(n) time\nlet pop = queue.removeFirst()\n\n/* Get the queue size */\nlet size = queue.count\n\n/* Check whether the queue is empty */\nlet isEmpty = queue.isEmpty\n
    queue.js
    /* Initialize a queue */\n// JavaScript has no built-in queue; use an Array as a queue\nconst queue = [];\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access the front element */\nconst peek = queue[0];\n\n/* Dequeue an element */\n// The underlying structure is an array, so shift() takes O(n) time\nconst pop = queue.shift();\n\n/* Get the queue size */\nconst size = queue.length;\n\n/* Check whether the queue is empty */\nconst empty = queue.length === 0;\n
    queue.ts
    /* Initialize a queue */\n// TypeScript has no built-in queue; use an Array as a queue\nconst queue: number[] = [];\n\n/* Enqueue elements */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* Access the front element */\nconst peek = queue[0];\n\n/* Dequeue an element */\n// The underlying structure is an array, so shift() takes O(n) time\nconst pop = queue.shift();\n\n/* Get the queue size */\nconst size = queue.length;\n\n/* Check whether the queue is empty */\nconst empty = queue.length === 0;\n
    queue.dart
    /* Initialize a queue */\n// In Dart, Queue is a deque and can also be used as a queue\nQueue<int> queue = Queue();\n\n/* Enqueue elements */\nqueue.add(1);\nqueue.add(3);\nqueue.add(2);\nqueue.add(5);\nqueue.add(4);\n\n/* Access the front element */\nint peek = queue.first;\n\n/* Dequeue an element */\nint pop = queue.removeFirst();\n\n/* Get the queue size */\nint size = queue.length;\n\n/* Check whether the queue is empty */\nbool isEmpty = queue.isEmpty;\n
    queue.rs
    /* Initialize a deque */\n// In Rust, use a deque as an ordinary queue\nlet mut deque: VecDeque<u32> = VecDeque::new();\n\n/* Enqueue elements */\ndeque.push_back(1);\ndeque.push_back(3);\ndeque.push_back(2);\ndeque.push_back(5);\ndeque.push_back(4);\n\n/* Access the front element */\nif let Some(front) = deque.front() {\n}\n\n/* Dequeue an element */\nif let Some(pop) = deque.pop_front() {\n}\n\n/* Get the queue size */\nlet size = deque.len();\n\n/* Check whether the queue is empty */\nlet is_empty = deque.is_empty();\n
    queue.c
    // C provides no built-in queue\n
    queue.kt
    /* Initialize a queue */\nval queue = LinkedList<Int>()\n\n/* Enqueue elements */\nqueue.offer(1)\nqueue.offer(3)\nqueue.offer(2)\nqueue.offer(5)\nqueue.offer(4)\n\n/* Access the front element */\nval peek = queue.peek()\n\n/* Dequeue an element */\nval pop = queue.poll()\n\n/* Get the queue size */\nval size = queue.size\n\n/* Check whether the queue is empty */\nval isEmpty = queue.isEmpty()\n
    queue.rb
    # Initialize a queue\n# Ruby's built-in Thread::Queue has no peek or traversal methods; use an Array as a queue\nqueue = []\n\n# Enqueue elements\nqueue.push(1)\nqueue.push(3)\nqueue.push(2)\nqueue.push(5)\nqueue.push(4)\n\n# Access the front element of the queue\npeek = queue.first\n\n# Dequeue an element\n# Note that this is an array, so Array#shift takes O(n) time\npop = queue.shift\n\n# Get the queue size\nsize = queue.length\n\n# Check whether the queue is empty\nis_empty = queue.empty?\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#522-implementing-a-queue","level":2,"title":"5.2.2   Implementing a queue","text":"

    A queue needs a data structure that allows insertion at one end and deletion at the other. Both linked lists and arrays can meet this requirement.

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#1-implementation-with-a-linked-list","level":3,"title":"1.   Implementation with a linked list","text":"

    As shown below, treat the linked list's head and tail as the queue's front and rear. Nodes may be added only at the rear and removed only from the front.

    <1><2><3>

    Figure 5-5   Enqueue and dequeue in a linked-list queue

    Here is an implementation using a linked list:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_queue.py
    class LinkedListQueue:\n    \"\"\"Queue implemented with a linked list\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._front: ListNode | None = None  # Head node front\n        self._rear: ListNode | None = None  # Tail node rear\n        self._size: int = 0\n\n    def size(self) -> int:\n        \"\"\"Get the queue size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int):\n        \"\"\"Enqueue\"\"\"\n        # Add num after the tail node\n        node = ListNode(num)\n        # If the queue is empty, make both the head and tail point to this node\n        if self._front is None:\n            self._front = node\n            self._rear = node\n        # If the queue is not empty, add this node after the tail node\n        else:\n            self._rear.next = node\n            self._rear = node\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Dequeue\"\"\"\n        num = self.peek()\n        # Remove the head node\n        self._front = self._front.next\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access the front element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Queue is empty\")\n        return self._front.val\n\n    def to_list(self) -> list[int]:\n        \"\"\"Convert to a list for printing\"\"\"\n        queue = []\n        temp = self._front\n        while temp:\n            queue.append(temp.val)\n            temp = temp.next\n        return queue\n
    linkedlist_queue.cpp
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n  private:\n    ListNode *front, *rear; // Head node front and tail node rear\n    int queSize;\n\n  public:\n    LinkedListQueue() {\n        front = nullptr;\n        rear = nullptr;\n        queSize = 0;\n    }\n\n    ~LinkedListQueue() {\n        // Traverse the linked list and delete each node to free its memory\n        freeMemoryLinkedList(front);\n    }\n\n    /* Get the queue size */\n    int size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    bool isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    void push(int num) {\n        // Add num after the tail node\n        ListNode *node = new ListNode(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (front == nullptr) {\n            front = node;\n            rear = node;\n        }\n        // If the queue is not empty, add this node after the tail node\n        else {\n            rear->next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    int pop() {\n        int num = peek();\n        // Remove the head node\n        ListNode *tmp = front;\n        front = front->next;\n        // Free memory\n        delete tmp;\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    int peek() {\n        if (size() == 0)\n            throw out_of_range(\"Queue is empty\");\n        return front->val;\n    }\n\n    /* Convert the linked list to a vector and return it */\n    vector<int> toVector() {\n        ListNode *node = front;\n        vector<int> res(size());\n        for (int i = 0; i < res.size(); i++) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
    linkedlist_queue.java
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    private ListNode front, rear; // Head node front and tail node rear\n    private int queSize = 0;\n\n    public LinkedListQueue() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the queue size */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue */\n    public void push(int num) {\n        // Add num after the tail node\n        ListNode node = new ListNode(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (front == null) {\n            front = node;\n            rear = node;\n        // If the queue is not empty, add this node after the tail node\n        } else {\n            rear.next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int pop() {\n        int num = peek();\n        // Remove the head node\n        front = front.next;\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return front.val;\n    }\n\n    /* Convert the linked list to an array and return it */\n    public int[] toArray() {\n        ListNode node = front;\n        int[] res = new int[size()];\n        for (int i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_queue.cs
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    ListNode? front, rear;  // Head node front and tail node rear \n    int queSize = 0;\n\n    public LinkedListQueue() {\n        front = null;\n        rear = null;\n    }\n\n    /* Get the queue size */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Enqueue */\n    public void Push(int num) {\n        // Add num after the tail node\n        ListNode node = new(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (front == null) {\n            front = node;\n            rear = node;\n            // If the queue is not empty, add this node after the tail node\n        } else if (rear != null) {\n            rear.next = node;\n            rear = node;\n        }\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int Pop() {\n        int num = Peek();\n        // Remove the head node\n        front = front?.next;\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return front!.val;\n    }\n\n    /* Convert the linked list to an array and return it */\n    public int[] ToArray() {\n        if (front == null)\n            return [];\n\n        ListNode? node = front;\n        int[] res = new int[Size()];\n        for (int i = 0; i < res.Length; i++) {\n            res[i] = node!.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_queue.go
    /* Queue implemented with a linked list */\ntype linkedListQueue struct {\n    // Use the standard library list package to implement the queue\n    data *list.List\n}\n\n/* Initialize the queue */\nfunc newLinkedListQueue() *linkedListQueue {\n    return &linkedListQueue{\n        data: list.New(),\n    }\n}\n\n/* Enqueue */\nfunc (s *linkedListQueue) push(value any) {\n    s.data.PushBack(value)\n}\n\n/* Dequeue */\nfunc (s *linkedListQueue) pop() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Access the front element */\nfunc (s *linkedListQueue) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Front()\n    return e.Value\n}\n\n/* Get the queue size */\nfunc (s *linkedListQueue) size() int {\n    return s.data.Len()\n}\n\n/* Check whether the queue is empty */\nfunc (s *linkedListQueue) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get the list for printing */\nfunc (s *linkedListQueue) toList() *list.List {\n    return s.data\n}\n
    linkedlist_queue.swift
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    private var front: ListNode? // Head node\n    private var rear: ListNode? // Tail node\n    private var _size: Int\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the queue size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue */\n    func push(num: Int) {\n        // Add num after the tail node\n        let node = ListNode(x: num)\n        // If the queue is empty, make both the head and tail point to this node\n        if front == nil {\n            front = node\n            rear = node\n        }\n        // If the queue is not empty, add this node after the tail node\n        else {\n            rear?.next = node\n            rear = node\n        }\n        _size += 1\n    }\n\n    /* Dequeue */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        // Remove the head node\n        front = front?.next\n        _size -= 1\n        return num\n    }\n\n    /* Access the front element */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Queue is empty\")\n        }\n        return front!.val\n    }\n\n    /* Convert the linked list to an array and return it */\n    func toArray() -> [Int] {\n        var node = front\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
    linkedlist_queue.js
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    #front; // Head node #front\n    #rear; // Tail node #rear\n    #queSize = 0;\n\n    constructor() {\n        this.#front = null;\n        this.#rear = null;\n    }\n\n    /* Get the queue size */\n    get size() {\n        return this.#queSize;\n    }\n\n    /* Check whether the queue is empty */\n    isEmpty() {\n        return this.size === 0;\n    }\n\n    /* Enqueue */\n    push(num) {\n        // Add num after the tail node\n        const node = new ListNode(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (!this.#front) {\n            this.#front = node;\n            this.#rear = node;\n            // If the queue is not empty, add this node after the tail node\n        } else {\n            this.#rear.next = node;\n            this.#rear = node;\n        }\n        this.#queSize++;\n    }\n\n    /* Dequeue */\n    pop() {\n        const num = this.peek();\n        // Remove the head node\n        this.#front = this.#front.next;\n        this.#queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peek() {\n        if (this.size === 0) throw new Error('Queue is empty');\n        return this.#front.val;\n    }\n\n    /* Convert the linked list to an Array and return it */\n    toArray() {\n        let node = this.#front;\n        const res = new Array(this.size);\n        for (let i = 0; i < res.length; i++) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_queue.ts
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n    private front: ListNode | null; // Head node front\n    private rear: ListNode | null; // Tail node rear\n    private queSize: number = 0;\n\n    constructor() {\n        this.front = null;\n        this.rear = null;\n    }\n\n    /* Get the queue size */\n    get size(): number {\n        return this.queSize;\n    }\n\n    /* Check whether the queue is empty */\n    isEmpty(): boolean {\n        return this.size === 0;\n    }\n\n    /* Enqueue */\n    push(num: number): void {\n        // Add num after the tail node\n        const node = new ListNode(num);\n        // If the queue is empty, make both the head and tail point to this node\n        if (!this.front) {\n            this.front = node;\n            this.rear = node;\n            // If the queue is not empty, add this node after the tail node\n        } else {\n            this.rear!.next = node;\n            this.rear = node;\n        }\n        this.queSize++;\n    }\n\n    /* Dequeue */\n    pop(): number {\n        const num = this.peek();\n        if (!this.front) throw new Error('Queue is empty');\n        // Remove the head node\n        this.front = this.front.next;\n        this.queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peek(): number {\n        if (this.size === 0) throw new Error('Queue is empty');\n        return this.front!.val;\n    }\n\n    /* Convert the linked list to an Array and return it */\n    toArray(): number[] {\n        let node = this.front;\n        const res = new Array<number>(this.size);\n        for (let i = 0; i < res.length; i++) {\n            res[i] = node!.val;\n            node = node!.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_queue.dart
    /* Queue implemented with a linked list */\nclass LinkedListQueue {\n  ListNode? _front; // Head node _front\n  ListNode? _rear; // Tail node _rear\n  int _queSize = 0; // Queue size\n\n  LinkedListQueue() {\n    _front = null;\n    _rear = null;\n  }\n\n  /* Get the queue size */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check whether the queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Enqueue */\n  void push(int _num) {\n    // Add _num after the tail node\n    final node = ListNode(_num);\n    // If the queue is empty, make both the head and tail point to this node\n    if (_front == null) {\n      _front = node;\n      _rear = node;\n    } else {\n      // If the queue is not empty, add this node after the tail node\n      _rear!.next = node;\n      _rear = node;\n    }\n    _queSize++;\n  }\n\n  /* Dequeue */\n  int pop() {\n    final int _num = peek();\n    // Remove the head node\n    _front = _front!.next;\n    _queSize--;\n    return _num;\n  }\n\n  /* Access the front element */\n  int peek() {\n    if (_queSize == 0) {\n      throw Exception('Queue is empty');\n    }\n    return _front!.val;\n  }\n\n  /* Convert the linked list to an array and return it */\n  List<int> toArray() {\n    ListNode? node = _front;\n    final List<int> queue = [];\n    while (node != null) {\n      queue.add(node.val);\n      node = node.next;\n    }\n    return queue;\n  }\n}\n
    linkedlist_queue.rs
    /* Queue implemented with a linked list */\n#[allow(dead_code)]\npub struct LinkedListQueue<T> {\n    front: Option<Rc<RefCell<ListNode<T>>>>, // Head node front\n    rear: Option<Rc<RefCell<ListNode<T>>>>,  // Tail node rear\n    que_size: usize,                         // Queue size\n}\n\nimpl<T: Copy> LinkedListQueue<T> {\n    pub fn new() -> Self {\n        Self {\n            front: None,\n            rear: None,\n            que_size: 0,\n        }\n    }\n\n    /* Get the queue size */\n    pub fn size(&self) -> usize {\n        return self.que_size;\n    }\n\n    /* Check whether the queue is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.que_size == 0;\n    }\n\n    /* Enqueue */\n    pub fn push(&mut self, num: T) {\n        // Add num after the tail node\n        let new_rear = ListNode::new(num);\n        match self.rear.take() {\n            // If the queue is not empty, add this node after the tail node\n            Some(old_rear) => {\n                old_rear.borrow_mut().next = Some(new_rear.clone());\n                self.rear = Some(new_rear);\n            }\n            // If the queue is empty, make both the head and tail point to this node\n            None => {\n                self.front = Some(new_rear.clone());\n                self.rear = Some(new_rear);\n            }\n        }\n        self.que_size += 1;\n    }\n\n    /* Dequeue */\n    pub fn pop(&mut self) -> Option<T> {\n        self.front.take().map(|old_front| {\n            match old_front.borrow_mut().next.take() {\n                Some(new_front) => {\n                    self.front = Some(new_front);\n                }\n                None => {\n                    self.rear.take();\n                }\n            }\n            self.que_size -= 1;\n            old_front.borrow().val\n        })\n    }\n\n    /* Access the front element */\n    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.front.as_ref()\n    }\n\n    /* Convert the linked list to a vector and return it */\n    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n        let mut res: Vec<T> = Vec::new();\n\n        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {\n            if let Some(cur) = cur {\n                res.push(cur.borrow().val);\n                recur(cur.borrow().next.as_ref(), res);\n            }\n        }\n\n        recur(head, &mut res);\n\n        res\n    }\n}\n
    linkedlist_queue.c
    /* Queue implemented with a linked list */\ntypedef struct {\n    ListNode *front, *rear;\n    int queSize;\n} LinkedListQueue;\n\n/* Constructor */\nLinkedListQueue *newLinkedListQueue() {\n    LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue));\n    queue->front = NULL;\n    queue->rear = NULL;\n    queue->queSize = 0;\n    return queue;\n}\n\n/* Destructor */\nvoid delLinkedListQueue(LinkedListQueue *queue) {\n    // Free all nodes\n    while (queue->front != NULL) {\n        ListNode *tmp = queue->front;\n        queue->front = queue->front->next;\n        free(tmp);\n    }\n    // Free the queue structure\n    free(queue);\n}\n\n/* Get the queue size */\nint size(LinkedListQueue *queue) {\n    return queue->queSize;\n}\n\n/* Check whether the queue is empty */\nbool empty(LinkedListQueue *queue) {\n    return (size(queue) == 0);\n}\n\n/* Enqueue */\nvoid push(LinkedListQueue *queue, int num) {\n    // Add node at the tail\n    ListNode *node = newListNode(num);\n    // If the queue is empty, make both the head and tail point to this node\n    if (queue->front == NULL) {\n        queue->front = node;\n        queue->rear = node;\n    }\n    // If the queue is not empty, add this node after the tail node\n    else {\n        queue->rear->next = node;\n        queue->rear = node;\n    }\n    queue->queSize++;\n}\n\n/* Access the front element */\nint peek(LinkedListQueue *queue) {\n    assert(size(queue) && queue->front);\n    return queue->front->val;\n}\n\n/* Dequeue */\nint pop(LinkedListQueue *queue) {\n    int num = peek(queue);\n    ListNode *tmp = queue->front;\n    queue->front = queue->front->next;\n    free(tmp);\n    queue->queSize--;\n    return num;\n}\n\n/* Print the queue */\nvoid printLinkedListQueue(LinkedListQueue *queue) {\n    int *arr = malloc(sizeof(int) * queue->queSize);\n    // Copy the linked list data into an array\n    int i;\n    ListNode *node;\n    for (i = 0, node = queue->front; i < queue->queSize; i++) {\n        arr[i] = node->val;\n        node = node->next;\n    }\n    printArray(arr, queue->queSize);\n    free(arr);\n}\n
    linkedlist_queue.kt
    /* Queue implemented with a linked list */\nclass LinkedListQueue(\n    // Head node front and tail node rear\n    private var front: ListNode? = null,\n    private var rear: ListNode? = null,\n    private var queSize: Int = 0\n) {\n\n    /* Get the queue size */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check whether the queue is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Enqueue */\n    fun push(num: Int) {\n        // Add num after the tail node\n        val node = ListNode(num)\n        // If the queue is empty, make both the head and tail point to this node\n        if (front == null) {\n            front = node\n            rear = node\n            // If the queue is not empty, add this node after the tail node\n        } else {\n            rear?.next = node\n            rear = node\n        }\n        queSize++\n    }\n\n    /* Dequeue */\n    fun pop(): Int {\n        val num = peek()\n        // Remove the head node\n        front = front?.next\n        queSize--\n        return num\n    }\n\n    /* Access the front element */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return front!!._val\n    }\n\n    /* Convert the linked list to an array and return it */\n    fun toArray(): IntArray {\n        var node = front\n        val res = IntArray(size())\n        for (i in res.indices) {\n            res[i] = node!!._val\n            node = node.next\n        }\n        return res\n    }\n}\n
    linkedlist_queue.rb
    ### Queue implemented with a linked list ###\nclass LinkedListQueue\n  ### Get the queue size ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @front = nil  # Head node front\n    @rear = nil   # Tail node rear\n    @size = 0\n  end\n\n  ### Check whether the queue is empty ###\n  def is_empty?\n    @front.nil?\n  end\n\n  ### Enqueue ###\n  def push(num)\n    # Add num after the tail node\n    node = ListNode.new(num)\n\n    # If the queue is empty, point both the head and tail to this node\n    if @front.nil?\n      @front = node\n      @rear = node\n    # If the queue is not empty, append this node after the tail\n    else\n      @rear.next = node\n      @rear = node\n    end\n\n    @size += 1\n  end\n\n  ### Dequeue ###\n  def pop\n    num = peek\n    # Remove the head node\n    @front = @front.next\n    @size -= 1\n    num\n  end\n\n  ### Access the front element ###\n  def peek\n    raise IndexError, 'Queue is empty' if is_empty?\n\n    @front.val\n  end\n\n  ### Convert the linked list to an Array and return it ###\n  def to_array\n    queue = []\n    temp = @front\n    while temp\n      queue << temp.val\n      temp = temp.next\n    end\n    queue\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#2-implementation-with-an-array","level":3,"title":"2.   Implementation with an array","text":"

    Removing the first element of an array takes \\(O(n)\\) time, which would make dequeueing inefficient. A useful technique avoids this problem.

    Use a variable front for the index of the front element and a variable size for the queue's length. Define rear = front + size, so rear points to the position immediately after the last element.

    With this design, the occupied interval is [front, rear - 1]. Operations work as shown below.

    • Enqueue: assign the new element to index rear and increase size by 1.
    • Dequeue: increase front by 1 and decrease size by 1.

    Both operations require only a constant amount of work and take \\(O(1)\\) time.

    <1><2><3>

    Figure 5-6   Enqueue and dequeue in an array-based queue

    There is a problem: repeated enqueueing and dequeueing move both front and rear to the right, and they cannot continue once they reach the end of the array. To solve this, treat the array as circular, with its end connected to its beginning.

    When front or rear passes the array's end, wrap it back to the beginning. The remainder operation implements this cyclic behavior:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_queue.py
    class ArrayQueue:\n    \"\"\"Queue implemented with a circular array\"\"\"\n\n    def __init__(self, size: int):\n        \"\"\"Constructor\"\"\"\n        self._nums: list[int] = [0] * size  # Array that stores the queue elements\n        self._front: int = 0  # Front pointer, pointing to the first element\n        self._size: int = 0  # Queue size\n\n    def capacity(self) -> int:\n        \"\"\"Get the queue capacity\"\"\"\n        return len(self._nums)\n\n    def size(self) -> int:\n        \"\"\"Get the queue size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the queue is empty\"\"\"\n        return self._size == 0\n\n    def push(self, num: int):\n        \"\"\"Enqueue\"\"\"\n        if self._size == self.capacity():\n            raise IndexError(\"Queue is full\")\n        # Calculate the rear pointer, which points one position past the last element\n        # Use modulo to wrap rear to the beginning when it passes the end of the array\n        rear: int = (self._front + self._size) % self.capacity()\n        # Add num to the rear\n        self._nums[rear] = num\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Dequeue\"\"\"\n        num: int = self.peek()\n        # Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        self._front = (self._front + 1) % self.capacity()\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access the front element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Queue is empty\")\n        return self._nums[self._front]\n\n    def to_list(self) -> list[int]:\n        \"\"\"Return a list for printing\"\"\"\n        res = [0] * self.size()\n        j: int = self._front\n        for i in range(self.size()):\n            res[i] = self._nums[(j % self.capacity())]\n            j += 1\n        return res\n
    array_queue.cpp
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n  private:\n    int *nums;       // Array that stores the queue elements\n    int front;       // Front index, identifying the first element\n    int queSize;     // Queue size\n    int queCapacity; // Queue capacity\n\n  public:\n    ArrayQueue(int capacity) {\n        // Initialize the array\n        nums = new int[capacity];\n        queCapacity = capacity;\n        front = queSize = 0;\n    }\n\n    ~ArrayQueue() {\n        delete[] nums;\n    }\n\n    /* Get the queue capacity */\n    int capacity() {\n        return queCapacity;\n    }\n\n    /* Get the queue size */\n    int size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Enqueue */\n    void push(int num) {\n        if (queSize == queCapacity) {\n            cout << \"Queue is full\" << endl;\n            return;\n        }\n        // Calculate the rear index, one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        int rear = (front + queSize) % queCapacity;\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    int pop() {\n        int num = peek();\n        // Advance the front index by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % queCapacity;\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    int peek() {\n        if (isEmpty())\n            throw out_of_range(\"Queue is empty\");\n        return nums[front];\n    }\n\n    /* Convert the array to a vector and return it */\n    vector<int> toVector() {\n        // Copy only the elements in the occupied range\n        vector<int> arr(queSize);\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            arr[i] = nums[j % queCapacity];\n        }\n        return arr;\n    }\n};\n
    array_queue.java
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    private int[] nums; // Array that stores the queue elements\n    private int front; // Front pointer, pointing to the first element\n    private int queSize; // Queue size\n\n    public ArrayQueue(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the queue capacity */\n    public int capacity() {\n        return nums.length;\n    }\n\n    /* Get the queue size */\n    public int size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    public boolean isEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    public void push(int num) {\n        if (queSize == capacity()) {\n            System.out.println(\"Queue is full\");\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        int rear = (front + queSize) % capacity();\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int pop() {\n        int num = peek();\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % capacity();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return nums[front];\n    }\n\n    /* Return an array */\n    public int[] toArray() {\n        // Convert only the list elements within the occupied range\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[j % capacity()];\n        }\n        return res;\n    }\n}\n
    array_queue.cs
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    int[] nums;  // Array that stores the queue elements\n    int front;   // Front pointer, pointing to the first element\n    int queSize; // Queue size\n\n    public ArrayQueue(int capacity) {\n        nums = new int[capacity];\n        front = queSize = 0;\n    }\n\n    /* Get the queue capacity */\n    int Capacity() {\n        return nums.Length;\n    }\n\n    /* Get the queue size */\n    public int Size() {\n        return queSize;\n    }\n\n    /* Check whether the queue is empty */\n    public bool IsEmpty() {\n        return queSize == 0;\n    }\n\n    /* Enqueue */\n    public void Push(int num) {\n        if (queSize == Capacity()) {\n            Console.WriteLine(\"Queue is full\");\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        int rear = (front + queSize) % Capacity();\n        // Add num to the rear\n        nums[rear] = num;\n        queSize++;\n    }\n\n    /* Dequeue */\n    public int Pop() {\n        int num = Peek();\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % Capacity();\n        queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return nums[front];\n    }\n\n    /* Return an array */\n    public int[] ToArray() {\n        // Copy only the elements within the occupied range\n        int[] res = new int[queSize];\n        for (int i = 0, j = front; i < queSize; i++, j++) {\n            res[i] = nums[j % this.Capacity()];\n        }\n        return res;\n    }\n}\n
    array_queue.go
    /* Queue implemented with a circular array */\ntype arrayQueue struct {\n    nums        []int // Array that stores the queue elements\n    front       int   // Front pointer, pointing to the first element\n    queSize     int   // Queue size\n    queCapacity int   // Queue capacity (the maximum number of elements)\n}\n\n/* Initialize the queue */\nfunc newArrayQueue(queCapacity int) *arrayQueue {\n    return &arrayQueue{\n        nums:        make([]int, queCapacity),\n        queCapacity: queCapacity,\n        front:       0,\n        queSize:     0,\n    }\n}\n\n/* Get the queue size */\nfunc (q *arrayQueue) size() int {\n    return q.queSize\n}\n\n/* Check whether the queue is empty */\nfunc (q *arrayQueue) isEmpty() bool {\n    return q.queSize == 0\n}\n\n/* Enqueue */\nfunc (q *arrayQueue) push(num int) {\n    // The queue is full when queSize == queCapacity\n    if q.queSize == q.queCapacity {\n        return\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    // Use modulo to wrap rear to the beginning when it passes the end of the array\n    rear := (q.front + q.queSize) % q.queCapacity\n    // Add num to the rear\n    q.nums[rear] = num\n    q.queSize++\n}\n\n/* Dequeue */\nfunc (q *arrayQueue) pop() any {\n    num := q.peek()\n    if num == nil {\n        return nil\n    }\n\n    // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n    q.front = (q.front + 1) % q.queCapacity\n    q.queSize--\n    return num\n}\n\n/* Access the front element */\nfunc (q *arrayQueue) peek() any {\n    if q.isEmpty() {\n        return nil\n    }\n    return q.nums[q.front]\n}\n\n/* Get a slice for printing */\nfunc (q *arrayQueue) toSlice() []int {\n    rear := (q.front + q.queSize)\n    if rear >= q.queCapacity {\n        rear %= q.queCapacity\n        return append(q.nums[q.front:], q.nums[:rear]...)\n    }\n    return q.nums[q.front:rear]\n}\n
    array_queue.swift
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    private var nums: [Int] // Array that stores the queue elements\n    private var front: Int // Front index, identifying the first element\n    private var _size: Int // Queue size\n\n    init(capacity: Int) {\n        // Initialize the array\n        nums = Array(repeating: 0, count: capacity)\n        front = 0\n        _size = 0\n    }\n\n    /* Get the queue capacity */\n    func capacity() -> Int {\n        nums.count\n    }\n\n    /* Get the queue size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the queue is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Enqueue */\n    func push(num: Int) {\n        if size() == capacity() {\n            print(\"Queue is full\")\n            return\n        }\n        // Calculate the rear index, one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        let rear = (front + size()) % capacity()\n        // Add num to the rear\n        nums[rear] = num\n        _size += 1\n    }\n\n    /* Dequeue */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        // Advance the front index by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % capacity()\n        _size -= 1\n        return num\n    }\n\n    /* Access the front element */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Queue is empty\")\n        }\n        return nums[front]\n    }\n\n    /* Return an array */\n    func toArray() -> [Int] {\n        // Include only the elements in the occupied range\n        (front ..< front + size()).map { nums[$0 % capacity()] }\n    }\n}\n
    array_queue.js
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    #nums; // Array that stores the queue elements\n    #front = 0; // Front pointer, pointing to the first element\n    #queSize = 0; // Queue size\n\n    constructor(capacity) {\n        this.#nums = new Array(capacity);\n    }\n\n    /* Get the queue capacity */\n    get capacity() {\n        return this.#nums.length;\n    }\n\n    /* Get the queue size */\n    get size() {\n        return this.#queSize;\n    }\n\n    /* Check whether the queue is empty */\n    isEmpty() {\n        return this.#queSize === 0;\n    }\n\n    /* Enqueue */\n    push(num) {\n        if (this.size === this.capacity) {\n            console.log('Queue is full');\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        const rear = (this.#front + this.size) % this.capacity;\n        // Add num to the rear\n        this.#nums[rear] = num;\n        this.#queSize++;\n    }\n\n    /* Dequeue */\n    pop() {\n        const num = this.peek();\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        this.#front = (this.#front + 1) % this.capacity;\n        this.#queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peek() {\n        if (this.isEmpty()) throw new Error('Queue is empty');\n        return this.#nums[this.#front];\n    }\n\n    /* Return an Array */\n    toArray() {\n        // Convert only the list elements within the occupied range\n        const arr = new Array(this.size);\n        for (let i = 0, j = this.#front; i < this.size; i++, j++) {\n            arr[i] = this.#nums[j % this.capacity];\n        }\n        return arr;\n    }\n}\n
    array_queue.ts
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n    private nums: number[]; // Array that stores the queue elements\n    private front: number; // Front pointer, pointing to the first element\n    private queSize: number; // Queue size\n\n    constructor(capacity: number) {\n        this.nums = new Array(capacity);\n        this.front = this.queSize = 0;\n    }\n\n    /* Get the queue capacity */\n    get capacity(): number {\n        return this.nums.length;\n    }\n\n    /* Get the queue size */\n    get size(): number {\n        return this.queSize;\n    }\n\n    /* Check whether the queue is empty */\n    isEmpty(): boolean {\n        return this.queSize === 0;\n    }\n\n    /* Enqueue */\n    push(num: number): void {\n        if (this.size === this.capacity) {\n            console.log('Queue is full');\n            return;\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        const rear = (this.front + this.queSize) % this.capacity;\n        // Add num to the rear\n        this.nums[rear] = num;\n        this.queSize++;\n    }\n\n    /* Dequeue */\n    pop(): number {\n        const num = this.peek();\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        this.front = (this.front + 1) % this.capacity;\n        this.queSize--;\n        return num;\n    }\n\n    /* Access the front element */\n    peek(): number {\n        if (this.isEmpty()) throw new Error('Queue is empty');\n        return this.nums[this.front];\n    }\n\n    /* Return an Array */\n    toArray(): number[] {\n        // Convert only the list elements within the occupied range\n        const arr = new Array(this.size);\n        for (let i = 0, j = this.front; i < this.size; i++, j++) {\n            arr[i] = this.nums[j % this.capacity];\n        }\n        return arr;\n    }\n}\n
    array_queue.dart
    /* Queue implemented with a circular array */\nclass ArrayQueue {\n  late List<int> _nums; // Array that stores the queue elements\n  late int _front; // Front pointer, pointing to the first element\n  late int _queSize; // Queue size\n\n  ArrayQueue(int capacity) {\n    _nums = List.filled(capacity, 0);\n    _front = _queSize = 0;\n  }\n\n  /* Get the queue capacity */\n  int capaCity() {\n    return _nums.length;\n  }\n\n  /* Get the queue size */\n  int size() {\n    return _queSize;\n  }\n\n  /* Check whether the queue is empty */\n  bool isEmpty() {\n    return _queSize == 0;\n  }\n\n  /* Enqueue */\n  void push(int _num) {\n    if (_queSize == capaCity()) {\n      throw Exception(\"Queue is full\");\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    // Use modulo to wrap rear to the beginning when it passes the end of the array\n    int rear = (_front + _queSize) % capaCity();\n    // Add _num to the rear\n    _nums[rear] = _num;\n    _queSize++;\n  }\n\n  /* Dequeue */\n  int pop() {\n    int _num = peek();\n    // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n    _front = (_front + 1) % capaCity();\n    _queSize--;\n    return _num;\n  }\n\n  /* Access the front element */\n  int peek() {\n    if (isEmpty()) {\n      throw Exception(\"Queue is empty\");\n    }\n    return _nums[_front];\n  }\n\n  /* Return an array */\n  List<int> toArray() {\n    // Convert only the list elements within the occupied range\n    final List<int> res = List.filled(_queSize, 0);\n    for (int i = 0, j = _front; i < _queSize; i++, j++) {\n      res[i] = _nums[j % capaCity()];\n    }\n    return res;\n  }\n}\n
    array_queue.rs
    /* Queue implemented with a circular array */\nstruct ArrayQueue<T> {\n    nums: Vec<T>,      // Array that stores the queue elements\n    front: i32,        // Front index, identifying the first element\n    que_size: i32,     // Queue size\n    que_capacity: i32, // Queue capacity\n}\n\nimpl<T: Copy + Default> ArrayQueue<T> {\n    /* Constructor */\n    fn new(capacity: i32) -> ArrayQueue<T> {\n        ArrayQueue {\n            nums: vec![T::default(); capacity as usize],\n            front: 0,\n            que_size: 0,\n            que_capacity: capacity,\n        }\n    }\n\n    /* Get the queue capacity */\n    fn capacity(&self) -> i32 {\n        self.que_capacity\n    }\n\n    /* Get the queue size */\n    fn size(&self) -> i32 {\n        self.que_size\n    }\n\n    /* Check whether the queue is empty */\n    fn is_empty(&self) -> bool {\n        self.que_size == 0\n    }\n\n    /* Enqueue */\n    fn push(&mut self, num: T) {\n        if self.que_size == self.capacity() {\n            println!(\"Queue is full\");\n            return;\n        }\n        // Calculate the rear index, one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        let rear = (self.front + self.que_size) % self.que_capacity;\n        // Add num to the rear\n        self.nums[rear as usize] = num;\n        self.que_size += 1;\n    }\n\n    /* Dequeue */\n    fn pop(&mut self) -> T {\n        let num = self.peek();\n        // Advance the front index by one position, wrapping to the beginning if it passes the end\n        self.front = (self.front + 1) % self.que_capacity;\n        self.que_size -= 1;\n        num\n    }\n\n    /* Access the front element */\n    fn peek(&self) -> T {\n        if self.is_empty() {\n            panic!(\"Index out of bounds\");\n        }\n        self.nums[self.front as usize]\n    }\n\n    /* Return a vector */\n    fn to_vector(&self) -> Vec<T> {\n        let cap = self.que_capacity;\n        let mut j = self.front;\n        let mut arr = vec![T::default(); cap as usize];\n        for i in 0..self.que_size {\n            arr[i as usize] = self.nums[(j % cap) as usize];\n            j += 1;\n        }\n        arr\n    }\n}\n
    array_queue.c
    /* Queue implemented with a circular array */\ntypedef struct {\n    int *nums;       // Array that stores the queue elements\n    int front;       // Front pointer, pointing to the first element\n    int queSize;     // Current number of elements in the queue\n    int queCapacity; // Queue capacity\n} ArrayQueue;\n\n/* Constructor */\nArrayQueue *newArrayQueue(int capacity) {\n    ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));\n    // Initialize the array\n    queue->queCapacity = capacity;\n    queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity);\n    queue->front = queue->queSize = 0;\n    return queue;\n}\n\n/* Destructor */\nvoid delArrayQueue(ArrayQueue *queue) {\n    free(queue->nums);\n    free(queue);\n}\n\n/* Get the queue capacity */\nint capacity(ArrayQueue *queue) {\n    return queue->queCapacity;\n}\n\n/* Get the queue size */\nint size(ArrayQueue *queue) {\n    return queue->queSize;\n}\n\n/* Check whether the queue is empty */\nbool empty(ArrayQueue *queue) {\n    return queue->queSize == 0;\n}\n\n/* Access the front element */\nint peek(ArrayQueue *queue) {\n    assert(size(queue) != 0);\n    return queue->nums[queue->front];\n}\n\n/* Enqueue */\nvoid push(ArrayQueue *queue, int num) {\n    if (size(queue) == capacity(queue)) {\n        printf(\"Queue is full\\r\\n\");\n        return;\n    }\n    // Calculate the rear pointer, which points one position past the last element\n    // Use modulo to wrap rear to the beginning when it passes the end of the array\n    int rear = (queue->front + queue->queSize) % queue->queCapacity;\n    // Add num to the rear\n    queue->nums[rear] = num;\n    queue->queSize++;\n}\n\n/* Dequeue */\nint pop(ArrayQueue *queue) {\n    int num = peek(queue);\n    // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n    queue->front = (queue->front + 1) % queue->queCapacity;\n    queue->queSize--;\n    return num;\n}\n\n/* Return an array for printing */\nint *toArray(ArrayQueue *queue, int *queSize) {\n    *queSize = queue->queSize;\n    int *res = (int *)calloc(queue->queSize, sizeof(int));\n    int j = queue->front;\n    for (int i = 0; i < queue->queSize; i++) {\n        res[i] = queue->nums[j % queue->queCapacity];\n        j++;\n    }\n    return res;\n}\n
    array_queue.kt
    /* Queue implemented with a circular array */\nclass ArrayQueue(capacity: Int) {\n    private val nums: IntArray = IntArray(capacity) // Array that stores the queue elements\n    private var front: Int = 0 // Front pointer, pointing to the first element\n    private var queSize: Int = 0 // Queue size\n\n    /* Get the queue capacity */\n    fun capacity(): Int {\n        return nums.size\n    }\n\n    /* Get the queue size */\n    fun size(): Int {\n        return queSize\n    }\n\n    /* Check whether the queue is empty */\n    fun isEmpty(): Boolean {\n        return queSize == 0\n    }\n\n    /* Enqueue */\n    fun push(num: Int) {\n        if (queSize == capacity()) {\n            println(\"Queue is full\")\n            return\n        }\n        // Calculate the rear pointer, which points one position past the last element\n        // Use modulo to wrap rear to the beginning when it passes the end of the array\n        val rear = (front + queSize) % capacity()\n        // Add num to the rear\n        nums[rear] = num\n        queSize++\n    }\n\n    /* Dequeue */\n    fun pop(): Int {\n        val num = peek()\n        // Advance the front pointer by one position, wrapping to the beginning if it passes the end\n        front = (front + 1) % capacity()\n        queSize--\n        return num\n    }\n\n    /* Access the front element */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return nums[front]\n    }\n\n    /* Return an array */\n    fun toArray(): IntArray {\n        // Copy only the elements within the occupied range\n        val res = IntArray(queSize)\n        var i = 0\n        var j = front\n        while (i < queSize) {\n            res[i] = nums[j % capacity()]\n            i++\n            j++\n        }\n        return res\n    }\n}\n
    array_queue.rb
    ### Queue implemented with a circular array ###\nclass ArrayQueue\n  ### Get the queue size ###\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize(size)\n    @nums = Array.new(size, 0) # Array that stores the queue elements\n    @front = 0 # Front pointer, pointing to the first element\n    @size = 0 # Queue size\n  end\n\n  ### Get the queue capacity ###\n  def capacity\n    @nums.length\n  end\n\n  ### Check whether the queue is empty ###\n  def is_empty?\n    size.zero?\n  end\n\n  ### Enqueue ###\n  def push(num)\n    raise IndexError, 'Queue is full' if size == capacity\n\n    # Calculate the rear pointer, which points one position past the last element\n    # Use modulo to wrap rear to the beginning when it passes the end of the array\n    rear = (@front + size) % capacity\n    # Add num to the rear\n    @nums[rear] = num\n    @size += 1\n  end\n\n  ### Dequeue ###\n  def pop\n    num = peek\n    # Advance the front pointer by one position, wrapping to the beginning if it passes the end\n    @front = (@front + 1) % capacity\n    @size -= 1\n    num\n  end\n\n  ### Access the front element ###\n  def peek\n    raise IndexError, 'Queue is empty' if is_empty?\n\n    @nums[@front]\n  end\n\n  ### Return a list for printing ###\n  def to_array\n    res = Array.new(size, 0)\n    j = @front\n\n    for i in 0...size\n      res[i] = @nums[j % capacity]\n      j += 1\n    end\n\n    res\n  end\nend\n
    Visualize execution

    View full screen >

    This queue still has a limitation: its capacity is fixed. We can address it by replacing the array with a dynamic array and adding resizing. Interested readers can try implementing this themselves.

    The comparison between these two implementations is the same as for stacks, so we will not repeat it here.

    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#523-typical-applications","level":2,"title":"5.2.3   Typical applications","text":"
    • Taobao orders. When a shopper places an order, it enters a queue, and the system processes orders in sequence. During the Singles' Day shopping festival, enormous numbers of orders arrive in a short time, making high concurrency a major engineering challenge.
    • Tasks awaiting processing. Queues preserve processing order wherever first-come, first-served behavior is needed, such as printer jobs or restaurant orders.
    ","path":["Chapter 5.   Stacks and queues","5.2   Queues"],"tags":[]},{"location":"chapter_stack_and_queue/stack/","level":1,"title":"5.1   Stacks","text":"

    A stack is a linear data structure that follows a last-in, first-out rule.

    Think of a stack of plates on a table. If we can move only one plate at a time, reaching the bottom plate requires removing all the plates above it first. Replace the plates with elements such as integers, characters, or objects, and we have a stack.

    As shown below, the upper end is the “top” of the stack and the lower end is the “bottom.” Adding an element to the top is called “pushing,” and removing the top element is called “popping.”

    Figure 5-1   The last-in, first-out rule of a stack

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#511-common-stack-operations","level":2,"title":"5.1.1   Common stack operations","text":"

    Table 5-1 lists common operations. Actual method names depend on the programming language; here we use the familiar names push(), pop(), and peek().

    Table 5-1   Efficiency of stack operations

    Method Description Time complexity push() Push an element onto the top \\(O(1)\\) pop() Remove the top element \\(O(1)\\) peek() Access the top element \\(O(1)\\)

    We can usually use a language's built-in stack class. If no dedicated class is provided, use an array or linked list as a stack and avoid operations unrelated to stack behavior.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby stack.py
    # Initialize a stack\n# Python has no built-in stack class; use a list as a stack\nstack: list[int] = []\n\n# Push elements\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n# Access the top element\npeek: int = stack[-1]\n\n# Pop an element\npop: int = stack.pop()\n\n# Get the stack size\nsize: int = len(stack)\n\n# Check whether the stack is empty\nis_empty: bool = len(stack) == 0\n
    stack.cpp
    /* Initialize a stack */\nstack<int> stack;\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nint top = stack.top();\n\n/* Pop an element */\nstack.pop(); // No return value\n\n/* Get the stack size */\nint size = stack.size();\n\n/* Check whether the stack is empty */\nbool empty = stack.empty();\n
    stack.java
    /* Initialize a stack */\nStack<Integer> stack = new Stack<>();\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nint peek = stack.peek();\n\n/* Pop an element */\nint pop = stack.pop();\n\n/* Get the stack size */\nint size = stack.size();\n\n/* Check whether the stack is empty */\nboolean isEmpty = stack.isEmpty();\n
    stack.cs
    /* Initialize a stack */\nStack<int> stack = new();\n\n/* Push elements */\nstack.Push(1);\nstack.Push(3);\nstack.Push(2);\nstack.Push(5);\nstack.Push(4);\n\n/* Access the top element */\nint peek = stack.Peek();\n\n/* Pop an element */\nint pop = stack.Pop();\n\n/* Get the stack size */\nint size = stack.Count;\n\n/* Check whether the stack is empty */\nbool isEmpty = stack.Count == 0;\n
    stack_test.go
    /* Initialize a stack */\n// In Go, a slice is recommended for use as a stack\nvar stack []int\n\n/* Push elements */\nstack = append(stack, 1)\nstack = append(stack, 3)\nstack = append(stack, 2)\nstack = append(stack, 5)\nstack = append(stack, 4)\n\n/* Access the top element */\npeek := stack[len(stack)-1]\n\n/* Pop an element */\npop := stack[len(stack)-1]\nstack = stack[:len(stack)-1]\n\n/* Get the stack size */\nsize := len(stack)\n\n/* Check whether the stack is empty */\nisEmpty := len(stack) == 0\n
    stack.swift
    /* Initialize a stack */\n// Swift has no built-in stack class; use an Array as a stack\nvar stack: [Int] = []\n\n/* Push elements */\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n/* Access the top element */\nlet peek = stack.last!\n\n/* Pop an element */\nlet pop = stack.removeLast()\n\n/* Get the stack size */\nlet size = stack.count\n\n/* Check whether the stack is empty */\nlet isEmpty = stack.isEmpty\n
    stack.js
    /* Initialize a stack */\n// JavaScript has no built-in stack class; use an Array as a stack\nconst stack = [];\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nconst peek = stack[stack.length-1];\n\n/* Pop an element */\nconst pop = stack.pop();\n\n/* Get the stack size */\nconst size = stack.length;\n\n/* Check whether the stack is empty */\nconst is_empty = stack.length === 0;\n
    stack.ts
    /* Initialize a stack */\n// TypeScript has no built-in stack class; use an Array as a stack\nconst stack: number[] = [];\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nconst peek = stack[stack.length - 1];\n\n/* Pop an element */\nconst pop = stack.pop();\n\n/* Get the stack size */\nconst size = stack.length;\n\n/* Check whether the stack is empty */\nconst is_empty = stack.length === 0;\n
    stack.dart
    /* Initialize a stack */\n// Dart has no built-in stack class; use a List as a stack\nList<int> stack = [];\n\n/* Push elements */\nstack.add(1);\nstack.add(3);\nstack.add(2);\nstack.add(5);\nstack.add(4);\n\n/* Access the top element */\nint peek = stack.last;\n\n/* Pop an element */\nint pop = stack.removeLast();\n\n/* Get the stack size */\nint size = stack.length;\n\n/* Check whether the stack is empty */\nbool isEmpty = stack.isEmpty;\n
    stack.rs
    /* Initialize a stack */\n// Use a Vec as a stack\nlet mut stack: Vec<i32> = Vec::new();\n\n/* Push elements */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* Access the top element */\nlet top = stack.last().unwrap();\n\n/* Pop an element */\nlet pop = stack.pop().unwrap();\n\n/* Get the stack size */\nlet size = stack.len();\n\n/* Check whether the stack is empty */\nlet is_empty = stack.is_empty();\n
    stack.c
    // C provides no built-in stack\n
    stack.kt
    /* Initialize a stack */\nval stack = Stack<Int>()\n\n/* Push elements */\nstack.push(1)\nstack.push(3)\nstack.push(2)\nstack.push(5)\nstack.push(4)\n\n/* Access the top element */\nval peek = stack.peek()\n\n/* Pop an element */\nval pop = stack.pop()\n\n/* Get the stack size */\nval size = stack.size\n\n/* Check whether the stack is empty */\nval isEmpty = stack.isEmpty()\n
    stack.rb
    # Initialize a stack\n# Ruby has no built-in stack class; use an Array as a stack\nstack = []\n\n# Push elements\nstack << 1\nstack << 3\nstack << 2\nstack << 5\nstack << 4\n\n# Access the top element\npeek = stack.last\n\n# Pop an element\npop = stack.pop\n\n# Get the stack size\nsize = stack.length\n\n# Check whether the stack is empty\nis_empty = stack.empty?\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#512-implementing-a-stack","level":2,"title":"5.1.2   Implementing a stack","text":"

    To understand how a stack works more deeply, let us implement a stack class ourselves.

    A stack follows last-in, first-out order, so elements can be added or removed only at the top. Arrays and linked lists allow insertion and deletion at arbitrary positions, so a stack can be viewed as a restricted array or linked list. By hiding irrelevant operations, we make its public behavior conform to the stack's rules.

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#1-implementation-with-a-linked-list","level":3,"title":"1.   Implementation with a linked list","text":"

    When implementing a stack with a linked list, treat the head as the top and the tail as the bottom.

    As shown below, pushing simply inserts a node at the head, an operation called “head insertion.” Popping removes the head node.

    <1><2><3>

    Figure 5-2   Push and pop in a linked-list stack

    Here is an implementation using a linked list:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_stack.py
    class LinkedListStack:\n    \"\"\"Stack implemented with a linked list\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._peek: ListNode | None = None\n        self._size: int = 0\n\n    def size(self) -> int:\n        \"\"\"Get the stack size\"\"\"\n        return self._size\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the stack is empty\"\"\"\n        return self._size == 0\n\n    def push(self, val: int):\n        \"\"\"Push\"\"\"\n        node = ListNode(val)\n        node.next = self._peek\n        self._peek = node\n        self._size += 1\n\n    def pop(self) -> int:\n        \"\"\"Pop\"\"\"\n        num = self.peek()\n        self._peek = self._peek.next\n        self._size -= 1\n        return num\n\n    def peek(self) -> int:\n        \"\"\"Access the top element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._peek.val\n\n    def to_list(self) -> list[int]:\n        \"\"\"Convert to a list for printing\"\"\"\n        arr = []\n        node = self._peek\n        while node:\n            arr.append(node.val)\n            node = node.next\n        arr.reverse()\n        return arr\n
    linkedlist_stack.cpp
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n  private:\n    ListNode *stackTop; // Use the head node as the stack top\n    int stkSize;        // Stack size\n\n  public:\n    LinkedListStack() {\n        stackTop = nullptr;\n        stkSize = 0;\n    }\n\n    ~LinkedListStack() {\n        // Traverse the linked list and delete each node to free its memory\n        freeMemoryLinkedList(stackTop);\n    }\n\n    /* Get the stack size */\n    int size() {\n        return stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    bool isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    void push(int num) {\n        ListNode *node = new ListNode(num);\n        node->next = stackTop;\n        stackTop = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    int pop() {\n        int num = top();\n        ListNode *tmp = stackTop;\n        stackTop = stackTop->next;\n        // Free memory\n        delete tmp;\n        stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    int top() {\n        if (isEmpty())\n            throw out_of_range(\"Stack is empty\");\n        return stackTop->val;\n    }\n\n    /* Convert the linked list to a vector and return it */\n    vector<int> toVector() {\n        ListNode *node = stackTop;\n        vector<int> res(size());\n        for (int i = res.size() - 1; i >= 0; i--) {\n            res[i] = node->val;\n            node = node->next;\n        }\n        return res;\n    }\n};\n
    linkedlist_stack.java
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    private ListNode stackPeek; // Use the head node as the stack top\n    private int stkSize = 0; // Stack size\n\n    public LinkedListStack() {\n        stackPeek = null;\n    }\n\n    /* Get the stack size */\n    public int size() {\n        return stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    public void push(int num) {\n        ListNode node = new ListNode(num);\n        node.next = stackPeek;\n        stackPeek = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    public int pop() {\n        int num = peek();\n        stackPeek = stackPeek.next;\n        stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stackPeek.val;\n    }\n\n    /* Convert the list to an array and return it */\n    public int[] toArray() {\n        ListNode node = stackPeek;\n        int[] res = new int[size()];\n        for (int i = res.length - 1; i >= 0; i--) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_stack.cs
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    ListNode? stackPeek;  // Use the head node as the stack top\n    int stkSize = 0;   // Stack size\n\n    public LinkedListStack() {\n        stackPeek = null;\n    }\n\n    /* Get the stack size */\n    public int Size() {\n        return stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Push */\n    public void Push(int num) {\n        ListNode node = new(num) {\n            next = stackPeek\n        };\n        stackPeek = node;\n        stkSize++;\n    }\n\n    /* Pop */\n    public int Pop() {\n        int num = Peek();\n        stackPeek = stackPeek!.next;\n        stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return stackPeek!.val;\n    }\n\n    /* Convert the linked list to an array and return it */\n    public int[] ToArray() {\n        if (stackPeek == null)\n            return [];\n\n        ListNode? node = stackPeek;\n        int[] res = new int[Size()];\n        for (int i = res.Length - 1; i >= 0; i--) {\n            res[i] = node!.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_stack.go
    /* Stack implemented with a linked list */\ntype linkedListStack struct {\n    // Use the standard library list package to implement the stack\n    data *list.List\n}\n\n/* Initialize the stack */\nfunc newLinkedListStack() *linkedListStack {\n    return &linkedListStack{\n        data: list.New(),\n    }\n}\n\n/* Push */\nfunc (s *linkedListStack) push(value int) {\n    s.data.PushBack(value)\n}\n\n/* Pop */\nfunc (s *linkedListStack) pop() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    s.data.Remove(e)\n    return e.Value\n}\n\n/* Access the top element */\nfunc (s *linkedListStack) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    e := s.data.Back()\n    return e.Value\n}\n\n/* Get the stack size */\nfunc (s *linkedListStack) size() int {\n    return s.data.Len()\n}\n\n/* Check whether the stack is empty */\nfunc (s *linkedListStack) isEmpty() bool {\n    return s.data.Len() == 0\n}\n\n/* Get the list for printing */\nfunc (s *linkedListStack) toList() *list.List {\n    return s.data\n}\n
    linkedlist_stack.swift
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    private var _peek: ListNode? // Use the head node as the stack top\n    private var _size: Int // Stack size\n\n    init() {\n        _size = 0\n    }\n\n    /* Get the stack size */\n    func size() -> Int {\n        _size\n    }\n\n    /* Check whether the stack is empty */\n    func isEmpty() -> Bool {\n        size() == 0\n    }\n\n    /* Push */\n    func push(num: Int) {\n        let node = ListNode(x: num)\n        node.next = _peek\n        _peek = node\n        _size += 1\n    }\n\n    /* Pop */\n    @discardableResult\n    func pop() -> Int {\n        let num = peek()\n        _peek = _peek?.next\n        _size -= 1\n        return num\n    }\n\n    /* Access the top element */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return _peek!.val\n    }\n\n    /* Convert the linked list to an array and return it */\n    func toArray() -> [Int] {\n        var node = _peek\n        var res = Array(repeating: 0, count: size())\n        for i in res.indices.reversed() {\n            res[i] = node!.val\n            node = node?.next\n        }\n        return res\n    }\n}\n
    linkedlist_stack.js
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    #stackPeek; // Use the head node as the stack top\n    #stkSize = 0; // Stack size\n\n    constructor() {\n        this.#stackPeek = null;\n    }\n\n    /* Get the stack size */\n    get size() {\n        return this.#stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    isEmpty() {\n        return this.size === 0;\n    }\n\n    /* Push */\n    push(num) {\n        const node = new ListNode(num);\n        node.next = this.#stackPeek;\n        this.#stackPeek = node;\n        this.#stkSize++;\n    }\n\n    /* Pop */\n    pop() {\n        const num = this.peek();\n        this.#stackPeek = this.#stackPeek.next;\n        this.#stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    peek() {\n        if (!this.#stackPeek) throw new Error('Stack is empty');\n        return this.#stackPeek.val;\n    }\n\n    /* Convert the linked list to an Array and return it */\n    toArray() {\n        let node = this.#stackPeek;\n        const res = new Array(this.size);\n        for (let i = res.length - 1; i >= 0; i--) {\n            res[i] = node.val;\n            node = node.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_stack.ts
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n    private stackPeek: ListNode | null; // Use the head node as the stack top\n    private stkSize: number = 0; // Stack size\n\n    constructor() {\n        this.stackPeek = null;\n    }\n\n    /* Get the stack size */\n    get size(): number {\n        return this.stkSize;\n    }\n\n    /* Check whether the stack is empty */\n    isEmpty(): boolean {\n        return this.size === 0;\n    }\n\n    /* Push */\n    push(num: number): void {\n        const node = new ListNode(num);\n        node.next = this.stackPeek;\n        this.stackPeek = node;\n        this.stkSize++;\n    }\n\n    /* Pop */\n    pop(): number {\n        const num = this.peek();\n        if (!this.stackPeek) throw new Error('Stack is empty');\n        this.stackPeek = this.stackPeek.next;\n        this.stkSize--;\n        return num;\n    }\n\n    /* Access the top element */\n    peek(): number {\n        if (!this.stackPeek) throw new Error('Stack is empty');\n        return this.stackPeek.val;\n    }\n\n    /* Convert the linked list to an Array and return it */\n    toArray(): number[] {\n        let node = this.stackPeek;\n        const res = new Array<number>(this.size);\n        for (let i = res.length - 1; i >= 0; i--) {\n            res[i] = node!.val;\n            node = node!.next;\n        }\n        return res;\n    }\n}\n
    linkedlist_stack.dart
    /* Stack implemented with a linked list */\nclass LinkedListStack {\n  ListNode? _stackPeek; // Use the head node as the stack top\n  int _stkSize = 0; // Stack size\n\n  LinkedListStack() {\n    _stackPeek = null;\n  }\n\n  /* Get the stack size */\n  int size() {\n    return _stkSize;\n  }\n\n  /* Check whether the stack is empty */\n  bool isEmpty() {\n    return _stkSize == 0;\n  }\n\n  /* Push */\n  void push(int _num) {\n    final ListNode node = ListNode(_num);\n    node.next = _stackPeek;\n    _stackPeek = node;\n    _stkSize++;\n  }\n\n  /* Pop */\n  int pop() {\n    final int _num = peek();\n    _stackPeek = _stackPeek!.next;\n    _stkSize--;\n    return _num;\n  }\n\n  /* Access the top element */\n  int peek() {\n    if (_stackPeek == null) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stackPeek!.val;\n  }\n\n  /* Convert the linked list to a List and return it */\n  List<int> toList() {\n    ListNode? node = _stackPeek;\n    List<int> list = [];\n    while (node != null) {\n      list.add(node.val);\n      node = node.next;\n    }\n    list = list.reversed.toList();\n    return list;\n  }\n}\n
    linkedlist_stack.rs
    /* Stack implemented with a linked list */\n#[allow(dead_code)]\npub struct LinkedListStack<T> {\n    stack_peek: Option<Rc<RefCell<ListNode<T>>>>, // Use the head node as the stack top\n    stk_size: usize,                              // Stack size\n}\n\nimpl<T: Copy> LinkedListStack<T> {\n    pub fn new() -> Self {\n        Self {\n            stack_peek: None,\n            stk_size: 0,\n        }\n    }\n\n    /* Get the stack size */\n    pub fn size(&self) -> usize {\n        return self.stk_size;\n    }\n\n    /* Check whether the stack is empty */\n    pub fn is_empty(&self) -> bool {\n        return self.size() == 0;\n    }\n\n    /* Push */\n    pub fn push(&mut self, num: T) {\n        let node = ListNode::new(num);\n        node.borrow_mut().next = self.stack_peek.take();\n        self.stack_peek = Some(node);\n        self.stk_size += 1;\n    }\n\n    /* Pop */\n    pub fn pop(&mut self) -> Option<T> {\n        self.stack_peek.take().map(|old_head| {\n            self.stack_peek = old_head.borrow_mut().next.take();\n            self.stk_size -= 1;\n\n            old_head.borrow().val\n        })\n    }\n\n    /* Access the top element */\n    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {\n        self.stack_peek.as_ref()\n    }\n\n    /* Convert the linked list to a vector and return it */\n    pub fn to_array(&self) -> Vec<T> {\n        fn _to_array<T: Sized + Copy>(head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {\n            if let Some(node) = head {\n                let mut nums = _to_array(node.borrow().next.as_ref());\n                nums.push(node.borrow().val);\n                return nums;\n            }\n            return Vec::new();\n        }\n\n        _to_array(self.peek())\n    }\n}\n
    linkedlist_stack.c
    /* Stack implemented with a linked list */\ntypedef struct {\n    ListNode *top; // Use the head node as the stack top\n    int size;      // Stack size\n} LinkedListStack;\n\n/* Constructor */\nLinkedListStack *newLinkedListStack() {\n    LinkedListStack *s = malloc(sizeof(LinkedListStack));\n    s->top = NULL;\n    s->size = 0;\n    return s;\n}\n\n/* Destructor */\nvoid delLinkedListStack(LinkedListStack *s) {\n    while (s->top) {\n        ListNode *n = s->top->next;\n        free(s->top);\n        s->top = n;\n    }\n    free(s);\n}\n\n/* Get the stack size */\nint size(LinkedListStack *s) {\n    return s->size;\n}\n\n/* Check whether the stack is empty */\nbool isEmpty(LinkedListStack *s) {\n    return size(s) == 0;\n}\n\n/* Push */\nvoid push(LinkedListStack *s, int num) {\n    ListNode *node = (ListNode *)malloc(sizeof(ListNode));\n    node->next = s->top; // Set the new node's pointer field\n    node->val = num;     // Set the new node's data field\n    s->top = node;       // Update the stack top\n    s->size++;           // Update the stack size\n}\n\n/* Access the top element */\nint peek(LinkedListStack *s) {\n    if (s->size == 0) {\n        printf(\"Stack is empty\\n\");\n        return INT_MAX;\n    }\n    return s->top->val;\n}\n\n/* Pop */\nint pop(LinkedListStack *s) {\n    int val = peek(s);\n    ListNode *tmp = s->top;\n    s->top = s->top->next;\n    // Free memory\n    free(tmp);\n    s->size--;\n    return val;\n}\n
    linkedlist_stack.kt
    /* Stack implemented with a linked list */\nclass LinkedListStack(\n    private var stackPeek: ListNode? = null, // Use the head node as the stack top\n    private var stkSize: Int = 0 // Stack size\n) {\n\n    /* Get the stack size */\n    fun size(): Int {\n        return stkSize\n    }\n\n    /* Check whether the stack is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Push */\n    fun push(num: Int) {\n        val node = ListNode(num)\n        node.next = stackPeek\n        stackPeek = node\n        stkSize++\n    }\n\n    /* Pop */\n    fun pop(): Int? {\n        val num = peek()\n        stackPeek = stackPeek?.next\n        stkSize--\n        return num\n    }\n\n    /* Access the top element */\n    fun peek(): Int? {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stackPeek?._val\n    }\n\n    /* Convert the list to an array and return it */\n    fun toArray(): IntArray {\n        var node = stackPeek\n        val res = IntArray(size())\n        for (i in res.size - 1 downTo 0) {\n            res[i] = node?._val!!\n            node = node.next\n        }\n        return res\n    }\n}\n
    linkedlist_stack.rb
    ### Stack implemented with a linked list ###\nclass LinkedListStack\n  attr_reader :size\n\n  ### Constructor ###\n  def initialize\n    @size = 0\n  end\n\n  ### Check whether the stack is empty ###\n  def is_empty?\n    @peek.nil?\n  end\n\n  ### Push ###\n  def push(val)\n    node = ListNode.new(val)\n    node.next = @peek\n    @peek = node\n    @size += 1\n  end\n\n  ### Pop ###\n  def pop\n    num = peek\n    @peek = @peek.next\n    @size -= 1\n    num\n  end\n\n  ### Access the top element ###\n  def peek\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @peek.val\n  end\n\n  ### Convert the linked list to an Array and return it ###\n  def to_array\n    arr = []\n    node = @peek\n    while node\n      arr << node.val\n      node = node.next\n    end\n    arr.reverse\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#2-implementation-with-an-array","level":3,"title":"2.   Implementation with an array","text":"

    For an array-based stack, use the end of the array as the top. As shown below, pushing and popping correspond to adding and removing the last element, both taking \\(O(1)\\) time.

    <1><2><3>

    Figure 5-3   Push and pop in an array-based stack

    Since elements may keep arriving, use a dynamic array so that resizing is handled for us. An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_stack.py
    class ArrayStack:\n    \"\"\"Stack implemented with an array\"\"\"\n\n    def __init__(self):\n        \"\"\"Constructor\"\"\"\n        self._stack: list[int] = []\n\n    def size(self) -> int:\n        \"\"\"Get the stack size\"\"\"\n        return len(self._stack)\n\n    def is_empty(self) -> bool:\n        \"\"\"Check whether the stack is empty\"\"\"\n        return self.size() == 0\n\n    def push(self, item: int):\n        \"\"\"Push\"\"\"\n        self._stack.append(item)\n\n    def pop(self) -> int:\n        \"\"\"Pop\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._stack.pop()\n\n    def peek(self) -> int:\n        \"\"\"Access the top element\"\"\"\n        if self.is_empty():\n            raise IndexError(\"Stack is empty\")\n        return self._stack[-1]\n\n    def to_list(self) -> list[int]:\n        \"\"\"Return a list for printing\"\"\"\n        return self._stack\n
    array_stack.cpp
    /* Stack implemented with an array */\nclass ArrayStack {\n  private:\n    vector<int> stack;\n\n  public:\n    /* Get the stack size */\n    int size() {\n        return stack.size();\n    }\n\n    /* Check whether the stack is empty */\n    bool isEmpty() {\n        return stack.size() == 0;\n    }\n\n    /* Push */\n    void push(int num) {\n        stack.push_back(num);\n    }\n\n    /* Pop */\n    int pop() {\n        int num = top();\n        stack.pop_back();\n        return num;\n    }\n\n    /* Access the top element */\n    int top() {\n        if (isEmpty())\n            throw out_of_range(\"Stack is empty\");\n        return stack.back();\n    }\n\n    /* Return a vector */\n    vector<int> toVector() {\n        return stack;\n    }\n};\n
    array_stack.java
    /* Stack implemented with an array */\nclass ArrayStack {\n    private ArrayList<Integer> stack;\n\n    public ArrayStack() {\n        // Initialize the list (dynamic array)\n        stack = new ArrayList<>();\n    }\n\n    /* Get the stack size */\n    public int size() {\n        return stack.size();\n    }\n\n    /* Check whether the stack is empty */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /* Push */\n    public void push(int num) {\n        stack.add(num);\n    }\n\n    /* Pop */\n    public int pop() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stack.remove(size() - 1);\n    }\n\n    /* Access the top element */\n    public int peek() {\n        if (isEmpty())\n            throw new IndexOutOfBoundsException();\n        return stack.get(size() - 1);\n    }\n\n    /* Convert the list to an array and return it */\n    public Object[] toArray() {\n        return stack.toArray();\n    }\n}\n
    array_stack.cs
    /* Stack implemented with an array */\nclass ArrayStack {\n    List<int> stack;\n    public ArrayStack() {\n        // Initialize the list (dynamic array)\n        stack = [];\n    }\n\n    /* Get the stack size */\n    public int Size() {\n        return stack.Count;\n    }\n\n    /* Check whether the stack is empty */\n    public bool IsEmpty() {\n        return Size() == 0;\n    }\n\n    /* Push */\n    public void Push(int num) {\n        stack.Add(num);\n    }\n\n    /* Pop */\n    public int Pop() {\n        if (IsEmpty())\n            throw new Exception();\n        var val = Peek();\n        stack.RemoveAt(Size() - 1);\n        return val;\n    }\n\n    /* Access the top element */\n    public int Peek() {\n        if (IsEmpty())\n            throw new Exception();\n        return stack[Size() - 1];\n    }\n\n    /* Convert the list to an array and return it */\n    public int[] ToArray() {\n        return [.. stack];\n    }\n}\n
    array_stack.go
    /* Stack implemented with an array */\ntype arrayStack struct {\n    data []int // Data\n}\n\n/* Initialize the stack */\nfunc newArrayStack() *arrayStack {\n    return &arrayStack{\n        // Set the stack length to 0 and its capacity to 16\n        data: make([]int, 0, 16),\n    }\n}\n\n/* Stack size */\nfunc (s *arrayStack) size() int {\n    return len(s.data)\n}\n\n/* Check whether the stack is empty */\nfunc (s *arrayStack) isEmpty() bool {\n    return s.size() == 0\n}\n\n/* Push */\nfunc (s *arrayStack) push(v int) {\n    // Appending grows the slice capacity automatically when needed\n    s.data = append(s.data, v)\n}\n\n/* Pop */\nfunc (s *arrayStack) pop() any {\n    val := s.peek()\n    s.data = s.data[:len(s.data)-1]\n    return val\n}\n\n/* Get the top element */\nfunc (s *arrayStack) peek() any {\n    if s.isEmpty() {\n        return nil\n    }\n    val := s.data[len(s.data)-1]\n    return val\n}\n\n/* Get a slice for printing */\nfunc (s *arrayStack) toSlice() []int {\n    return s.data\n}\n
    array_stack.swift
    /* Stack implemented with an array */\nclass ArrayStack {\n    private var stack: [Int]\n\n    init() {\n        // Initialize the list (dynamic array)\n        stack = []\n    }\n\n    /* Get the stack size */\n    func size() -> Int {\n        stack.count\n    }\n\n    /* Check whether the stack is empty */\n    func isEmpty() -> Bool {\n        stack.isEmpty\n    }\n\n    /* Push */\n    func push(num: Int) {\n        stack.append(num)\n    }\n\n    /* Pop */\n    @discardableResult\n    func pop() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return stack.removeLast()\n    }\n\n    /* Access the top element */\n    func peek() -> Int {\n        if isEmpty() {\n            fatalError(\"Stack is empty\")\n        }\n        return stack.last!\n    }\n\n    /* Return the stack elements as an array */\n    func toArray() -> [Int] {\n        stack\n    }\n}\n
    array_stack.js
    /* Stack implemented with an array */\nclass ArrayStack {\n    #stack;\n    constructor() {\n        this.#stack = [];\n    }\n\n    /* Get the stack size */\n    get size() {\n        return this.#stack.length;\n    }\n\n    /* Check whether the stack is empty */\n    isEmpty() {\n        return this.#stack.length === 0;\n    }\n\n    /* Push */\n    push(num) {\n        this.#stack.push(num);\n    }\n\n    /* Pop */\n    pop() {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.#stack.pop();\n    }\n\n    /* Access the top element */\n    top() {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.#stack[this.#stack.length - 1];\n    }\n\n    /* Return an Array */\n    toArray() {\n        return this.#stack;\n    }\n}\n
    array_stack.ts
    /* Stack implemented with an array */\nclass ArrayStack {\n    private stack: number[];\n    constructor() {\n        this.stack = [];\n    }\n\n    /* Get the stack size */\n    get size(): number {\n        return this.stack.length;\n    }\n\n    /* Check whether the stack is empty */\n    isEmpty(): boolean {\n        return this.stack.length === 0;\n    }\n\n    /* Push */\n    push(num: number): void {\n        this.stack.push(num);\n    }\n\n    /* Pop */\n    pop(): number | undefined {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.stack.pop();\n    }\n\n    /* Access the top element */\n    top(): number | undefined {\n        if (this.isEmpty()) throw new Error('Stack is empty');\n        return this.stack[this.stack.length - 1];\n    }\n\n    /* Return an Array */\n    toArray() {\n        return this.stack;\n    }\n}\n
    array_stack.dart
    /* Stack implemented with an array */\nclass ArrayStack {\n  late List<int> _stack;\n  ArrayStack() {\n    _stack = [];\n  }\n\n  /* Get the stack size */\n  int size() {\n    return _stack.length;\n  }\n\n  /* Check whether the stack is empty */\n  bool isEmpty() {\n    return _stack.isEmpty;\n  }\n\n  /* Push */\n  void push(int _num) {\n    _stack.add(_num);\n  }\n\n  /* Pop */\n  int pop() {\n    if (isEmpty()) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stack.removeLast();\n  }\n\n  /* Access the top element */\n  int peek() {\n    if (isEmpty()) {\n      throw Exception(\"Stack is empty\");\n    }\n    return _stack.last;\n  }\n\n  /* Return the underlying stack array */\n  List<int> toArray() => _stack;\n}\n
    array_stack.rs
    /* Stack implemented with an array */\nstruct ArrayStack<T> {\n    stack: Vec<T>,\n}\n\nimpl<T> ArrayStack<T> {\n    /* Initialize the stack */\n    fn new() -> ArrayStack<T> {\n        ArrayStack::<T> {\n            stack: Vec::<T>::new(),\n        }\n    }\n\n    /* Get the stack size */\n    fn size(&self) -> usize {\n        self.stack.len()\n    }\n\n    /* Check whether the stack is empty */\n    fn is_empty(&self) -> bool {\n        self.size() == 0\n    }\n\n    /* Push */\n    fn push(&mut self, num: T) {\n        self.stack.push(num);\n    }\n\n    /* Pop */\n    fn pop(&mut self) -> Option<T> {\n        self.stack.pop()\n    }\n\n    /* Access the top element */\n    fn peek(&self) -> Option<&T> {\n        if self.is_empty() {\n            panic!(\"Stack is empty\")\n        };\n        self.stack.last()\n    }\n\n    /* Return a reference to the vector */\n    fn to_array(&self) -> &Vec<T> {\n        &self.stack\n    }\n}\n
    array_stack.c
    /* Stack implemented with an array */\ntypedef struct {\n    int *data;\n    int size;\n} ArrayStack;\n\n/* Constructor */\nArrayStack *newArrayStack() {\n    ArrayStack *stack = malloc(sizeof(ArrayStack));\n    // Initialize with a large capacity to avoid resizing\n    stack->data = malloc(sizeof(int) * MAX_SIZE);\n    stack->size = 0;\n    return stack;\n}\n\n/* Destructor */\nvoid delArrayStack(ArrayStack *stack) {\n    free(stack->data);\n    free(stack);\n}\n\n/* Get the stack size */\nint size(ArrayStack *stack) {\n    return stack->size;\n}\n\n/* Check whether the stack is empty */\nbool isEmpty(ArrayStack *stack) {\n    return stack->size == 0;\n}\n\n/* Push */\nvoid push(ArrayStack *stack, int num) {\n    if (stack->size == MAX_SIZE) {\n        printf(\"Stack is full\\n\");\n        return;\n    }\n    stack->data[stack->size] = num;\n    stack->size++;\n}\n\n/* Access the top element */\nint peek(ArrayStack *stack) {\n    if (stack->size == 0) {\n        printf(\"Stack is empty\\n\");\n        return INT_MAX;\n    }\n    return stack->data[stack->size - 1];\n}\n\n/* Pop */\nint pop(ArrayStack *stack) {\n    int val = peek(stack);\n    stack->size--;\n    return val;\n}\n
    array_stack.kt
    /* Stack implemented with an array */\nclass ArrayStack {\n    // Initialize the list (dynamic array)\n    private val stack = mutableListOf<Int>()\n\n    /* Get the stack size */\n    fun size(): Int {\n        return stack.size\n    }\n\n    /* Check whether the stack is empty */\n    fun isEmpty(): Boolean {\n        return size() == 0\n    }\n\n    /* Push */\n    fun push(num: Int) {\n        stack.add(num)\n    }\n\n    /* Pop */\n    fun pop(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stack.removeAt(size() - 1)\n    }\n\n    /* Access the top element */\n    fun peek(): Int {\n        if (isEmpty()) throw IndexOutOfBoundsException()\n        return stack[size() - 1]\n    }\n\n    /* Convert the list to an array and return it */\n    fun toArray(): Array<Any> {\n        return stack.toTypedArray()\n    }\n}\n
    array_stack.rb
    ### Stack implemented with an array ###\nclass ArrayStack\n  ### Constructor ###\n  def initialize\n    @stack = []\n  end\n\n  ### Get the stack size ###\n  def size\n    @stack.length\n  end\n\n  ### Check whether the stack is empty ###\n  def is_empty?\n    @stack.empty?\n  end\n\n  ### Push ###\n  def push(item)\n    @stack << item\n  end\n\n  ### Pop ###\n  def pop\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @stack.pop\n  end\n\n  ### Access the top element ###\n  def peek\n    raise IndexError, 'Stack is empty' if is_empty?\n\n    @stack.last\n  end\n\n  ### Return a list for printing ###\n  def to_array\n    @stack\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#513-comparing-the-implementations","level":2,"title":"5.1.3   Comparing the implementations","text":"

    Supported operations

    Both implementations support all stack operations. An array also allows random access, but that is outside the stack abstraction and is generally not used.

    Time efficiency

    In an array-based stack, pushes and pops operate in preallocated contiguous memory, giving good cache locality and high efficiency. However, a push that exceeds the array's capacity triggers expansion, making that particular push an \\(O(n)\\) operation.

    A linked list grows flexibly and avoids the slowdown associated with array expansion. However, pushing requires creating a node object and updating pointers, so it is relatively slower. If the element is already a node object, the creation step can be skipped to improve efficiency.

    For elements of primitive types such as int or double, we can therefore draw the following conclusions.

    • An array-based stack slows down when expansion is triggered, but expansion is infrequent, so average efficiency is higher.
    • A linked-list stack provides more consistent performance.

    Space efficiency

    When a dynamic array is initialized, its initial capacity may exceed actual needs. Expansion typically multiplies the capacity by a fixed factor, such as 2, which may again leave excess space. An array-based stack can therefore waste some space.

    However, linked-list nodes also store pointers, so each node occupies relatively more space.

    We cannot say universally which implementation uses less memory; the answer depends on the situation.

    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#514-typical-applications","level":2,"title":"5.1.4   Typical applications","text":"
    • Browser back and forward navigation, and software undo and redo. When a new web page is opened, the browser pushes the previous page onto a stack. Going back pops that page. Supporting both back and forward requires two stacks working together.
    • Program memory management. Each function call adds a stack frame containing the function's context. During recursion, frames are pushed as calls descend and popped as they return.
    ","path":["Chapter 5.   Stacks and queues","5.1   Stacks"],"tags":[]},{"location":"chapter_stack_and_queue/summary/","level":1,"title":"5.4   Summary","text":"","path":["Chapter 5.   Stacks and queues","5.4   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • A stack follows last-in, first-out order and can be implemented with an array or linked list.
    • An array-based stack offers higher average time efficiency, but a push that triggers expansion takes \\(O(n)\\) time. A linked-list stack provides more consistent performance.
    • An array-based stack may waste some space. However, linked-list nodes require more memory than individual array elements.
    • A queue follows first-in, first-out order and can also be implemented with an array or linked list. Their time and space tradeoffs resemble those of stack implementations.
    • A deque is a more flexible queue that allows insertion and removal at both ends.
    ","path":["Chapter 5.   Stacks and queues","5.4   Summary"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: Are browser back and forward navigation implemented with a doubly linked list?

    Browser navigation fundamentally reflects stack behavior. Visiting a new page adds it to the top of a stack; clicking Back removes it. A deque conveniently supports additional operations, as discussed in the deque section.

    Q: After popping a node, do we need to free its memory?

    If the node will still be used, do not free it. If it is no longer needed, languages such as Java and Python use automatic garbage collection, so manual deallocation is unnecessary. In C and C++, memory must be released manually.

    Q: A deque looks like two stacks joined together. What is it useful for?

    A deque resembles a combination of a stack and a queue, or two stacks joined together. It combines stack and queue behavior, supporting all their applications with greater flexibility.

    Q: How exactly do undo and redo work?

    Use two stacks: A for undo and B for redo.

    1. Whenever the user performs an action, push it onto A and clear B.
    2. To undo, pop the most recent action from A and push it onto B.
    3. To redo, pop the most recent action from B and push it onto A.
    ","path":["Chapter 5.   Stacks and queues","5.4   Summary"],"tags":[]},{"location":"chapter_tree/","level":1,"title":"Chapter 7.   Trees","text":"

    Abstract

    A towering tree brims with life, its roots deep, its leaves lush, and its branches spreading wide.

    It gives vivid form to the idea of dividing data to conquer a problem.

    ","path":["Chapter 7.   Trees"],"tags":[]},{"location":"chapter_tree/#chapter-contents","level":2,"title":"Chapter contents","text":"
    • 7.1   Binary trees
    • 7.2   Traversing a binary tree
    • 7.3   Array representation of a binary tree
    • 7.4   Binary search trees
    • 7.5   AVL trees *
    • 7.6   Summary
    • 7.7   Exercises
    ","path":["Chapter 7.   Trees"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/","level":1,"title":"7.3   Array representation of a binary tree","text":"

    In a linked representation, a binary tree stores data in TreeNode nodes connected by pointers. The previous section introduced basic operations on binary trees in this form.

    Can we represent a binary tree with an array instead? Yes.

    ","path":["Chapter 7.   Trees","7.3   Array representation of a binary tree"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#731-representing-a-perfect-binary-tree","level":2,"title":"7.3.1   Representing a perfect binary tree","text":"

    Start with a simple case. Store every node of a perfect binary tree in an array in level order. Each node then corresponds to a unique array index.

    Level-order traversal gives us a mapping between parent and child indices: if a node has index \\(i\\), its left child has index \\(2i + 1\\) and its right child has index \\(2i + 2\\). Figure 7-12 illustrates this relationship.

    Figure 7-12   Array representation of a perfect binary tree

    The mapping formulas serve the same role as node references or pointers in a linked representation. Given any node in the array, we can use them to access its left or right child.

    ","path":["Chapter 7.   Trees","7.3   Array representation of a binary tree"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#732-representing-an-arbitrary-binary-tree","level":2,"title":"7.3.2   Representing an arbitrary binary tree","text":"

    A perfect binary tree is a special case. Other binary trees often contain many missing nodes, None, at intermediate levels. Ordinary level-order traversal omits these positions, so its sequence alone cannot tell us their number or locations. Multiple tree structures can therefore produce the same level-order sequence.

    As shown below, the array representation above no longer works for a non-perfect binary tree.

    Figure 7-13   Multiple binary trees can share a level-order sequence

    To solve this, include every None position explicitly in the level-order sequence. The sequence then represents the tree uniquely, as shown below. Example code follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    # Array representation of a binary tree\n# Use None for empty positions\ntree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]\n
    /* Array representation of a binary tree */\n// Use INT_MAX, the maximum int value, for empty positions\nvector<int> tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n
    /* Array representation of a binary tree */\n// Use the wrapper class Integer so that null can mark empty positions\nInteger[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };\n
    /* Array representation of a binary tree */\n// Use the nullable type int? so that null can mark empty positions\nint?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
    /* Array representation of a binary tree */\n// Use a slice of any so that nil can mark empty positions\ntree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15}\n
    /* Array representation of a binary tree */\n// Use the optional type Int? so that nil can mark empty positions\nlet tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n
    /* Array representation of a binary tree */\n// Use null for empty positions\nlet tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
    /* Array representation of a binary tree */\n// Use null for empty positions\nlet tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
    /* Array representation of a binary tree */\n// Use the nullable type int? so that null can mark empty positions\nList<int?> tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n
    /* Array representation of a binary tree */\n// Use None for empty positions\nlet tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)];\n
    /* Array representation of a binary tree */\n// Use the maximum int value for empty positions, so node values must not equal INT_MAX\nint tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n
    /* Array representation of a binary tree */\n// Use null for empty positions\nval tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )\n
    ### Array representation of a binary tree ###\n# Use nil for empty positions\ntree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n

    Figure 7-14   Array representation of an arbitrary binary tree

    Complete binary trees are particularly well suited to array representation. By definition, missing positions occur only at the right end of the lowest level, so all None entries appear at the end of the level-order sequence.

    We can therefore omit all None entries when storing a complete binary tree in an array. An example is shown below.

    Figure 7-15   Array representation of a complete binary tree

    The following code implements an array-based binary tree with these operations.

    • Given a node, get its value, left or right child, and parent.
    • Obtain preorder, inorder, postorder, and level-order traversal sequences.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_binary_tree.py
    class ArrayBinaryTree:\n    \"\"\"Binary tree class using an array representation\"\"\"\n\n    def __init__(self, arr: list[int | None]):\n        \"\"\"Constructor\"\"\"\n        self._tree = list(arr)\n\n    def size(self):\n        \"\"\"Length of the backing list\"\"\"\n        return len(self._tree)\n\n    def val(self, i: int) -> int | None:\n        \"\"\"Get the value of the node at index i\"\"\"\n        # If the index is out of bounds, return None to represent an empty position\n        if i < 0 or i >= self.size():\n            return None\n        return self._tree[i]\n\n    def left(self, i: int) -> int | None:\n        \"\"\"Get the left child index of the node at index i\"\"\"\n        return 2 * i + 1\n\n    def right(self, i: int) -> int | None:\n        \"\"\"Get the right child index of the node at index i\"\"\"\n        return 2 * i + 2\n\n    def parent(self, i: int) -> int | None:\n        \"\"\"Get the parent index of the node at index i\"\"\"\n        return (i - 1) // 2\n\n    def level_order(self) -> list[int]:\n        \"\"\"Level-order traversal\"\"\"\n        self.res = []\n        # Traverse the array directly\n        for i in range(self.size()):\n            if self.val(i) is not None:\n                self.res.append(self.val(i))\n        return self.res\n\n    def dfs(self, i: int, order: str):\n        \"\"\"Depth-first traversal\"\"\"\n        if self.val(i) is None:\n            return\n        # Preorder traversal\n        if order == \"pre\":\n            self.res.append(self.val(i))\n        self.dfs(self.left(i), order)\n        # Inorder traversal\n        if order == \"in\":\n            self.res.append(self.val(i))\n        self.dfs(self.right(i), order)\n        # Postorder traversal\n        if order == \"post\":\n            self.res.append(self.val(i))\n\n    def pre_order(self) -> list[int]:\n        \"\"\"Preorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"pre\")\n        return self.res\n\n    def in_order(self) -> list[int]:\n        \"\"\"Inorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"in\")\n        return self.res\n\n    def post_order(self) -> list[int]:\n        \"\"\"Postorder traversal\"\"\"\n        self.res = []\n        self.dfs(0, order=\"post\")\n        return self.res\n
    array_binary_tree.cpp
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n  public:\n    /* Constructor */\n    ArrayBinaryTree(vector<int> arr) {\n        tree = arr;\n    }\n\n    /* Number of positions in the array */\n    int size() {\n        return tree.size();\n    }\n\n    /* Get the value of the node at index i */\n    int val(int i) {\n        // If the index is out of bounds, return INT_MAX to represent an empty position\n        if (i < 0 || i >= size())\n            return INT_MAX;\n        return tree[i];\n    }\n\n    /* Get the left child index of the node at index i */\n    int left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    int right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    int parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    vector<int> levelOrder() {\n        vector<int> res;\n        // Traverse the array directly\n        for (int i = 0; i < size(); i++) {\n            if (val(i) != INT_MAX)\n                res.push_back(val(i));\n        }\n        return res;\n    }\n\n    /* Preorder traversal */\n    vector<int> preOrder() {\n        vector<int> res;\n        dfs(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    vector<int> inOrder() {\n        vector<int> res;\n        dfs(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    vector<int> postOrder() {\n        vector<int> res;\n        dfs(0, \"post\", res);\n        return res;\n    }\n\n  private:\n    vector<int> tree;\n\n    /* Depth-first traversal */\n    void dfs(int i, string order, vector<int> &res) {\n        // Return if this position is empty\n        if (val(i) == INT_MAX)\n            return;\n        // Preorder traversal\n        if (order == \"pre\")\n            res.push_back(val(i));\n        dfs(left(i), order, res);\n        // Inorder traversal\n        if (order == \"in\")\n            res.push_back(val(i));\n        dfs(right(i), order, res);\n        // Postorder traversal\n        if (order == \"post\")\n            res.push_back(val(i));\n    }\n};\n
    array_binary_tree.java
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n    private List<Integer> tree;\n\n    /* Constructor */\n    public ArrayBinaryTree(List<Integer> arr) {\n        tree = new ArrayList<>(arr);\n    }\n\n    /* List size */\n    public int size() {\n        return tree.size();\n    }\n\n    /* Get the value of the node at index i */\n    public Integer val(int i) {\n        // If the index is out of bounds, return null to represent an empty position\n        if (i < 0 || i >= size())\n            return null;\n        return tree.get(i);\n    }\n\n    /* Get the left child index of the node at index i */\n    public Integer left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    public Integer right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    public Integer parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    public List<Integer> levelOrder() {\n        List<Integer> res = new ArrayList<>();\n        // Traverse the array directly\n        for (int i = 0; i < size(); i++) {\n            if (val(i) != null)\n                res.add(val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    private void dfs(Integer i, String order, List<Integer> res) {\n        // Return if this position is empty\n        if (val(i) == null)\n            return;\n        // Preorder traversal\n        if (\"pre\".equals(order))\n            res.add(val(i));\n        dfs(left(i), order, res);\n        // Inorder traversal\n        if (\"in\".equals(order))\n            res.add(val(i));\n        dfs(right(i), order, res);\n        // Postorder traversal\n        if (\"post\".equals(order))\n            res.add(val(i));\n    }\n\n    /* Preorder traversal */\n    public List<Integer> preOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    public List<Integer> inOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    public List<Integer> postOrder() {\n        List<Integer> res = new ArrayList<>();\n        dfs(0, \"post\", res);\n        return res;\n    }\n}\n
    array_binary_tree.cs
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree(List<int?> arr) {\n    List<int?> tree = new(arr);\n\n    /* List size */\n    public int Size() {\n        return tree.Count;\n    }\n\n    /* Get the value of the node at index i */\n    public int? Val(int i) {\n        // If the index is out of bounds, return null to represent an empty position\n        if (i < 0 || i >= Size())\n            return null;\n        return tree[i];\n    }\n\n    /* Get the left child index of the node at index i */\n    public int Left(int i) {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    public int Right(int i) {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    public int Parent(int i) {\n        return (i - 1) / 2;\n    }\n\n    /* Level-order traversal */\n    public List<int> LevelOrder() {\n        List<int> res = [];\n        // Traverse the array directly\n        for (int i = 0; i < Size(); i++) {\n            if (Val(i).HasValue)\n                res.Add(Val(i)!.Value);\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    void DFS(int i, string order, List<int> res) {\n        // Return if this position is empty\n        if (!Val(i).HasValue)\n            return;\n        // Preorder traversal\n        if (order == \"pre\")\n            res.Add(Val(i)!.Value);\n        DFS(Left(i), order, res);\n        // Inorder traversal\n        if (order == \"in\")\n            res.Add(Val(i)!.Value);\n        DFS(Right(i), order, res);\n        // Postorder traversal\n        if (order == \"post\")\n            res.Add(Val(i)!.Value);\n    }\n\n    /* Preorder traversal */\n    public List<int> PreOrder() {\n        List<int> res = [];\n        DFS(0, \"pre\", res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    public List<int> InOrder() {\n        List<int> res = [];\n        DFS(0, \"in\", res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    public List<int> PostOrder() {\n        List<int> res = [];\n        DFS(0, \"post\", res);\n        return res;\n    }\n}\n
    array_binary_tree.go
    /* Binary tree type using an array representation */\ntype arrayBinaryTree struct {\n    tree []any\n}\n\n/* Constructor */\nfunc newArrayBinaryTree(arr []any) *arrayBinaryTree {\n    return &arrayBinaryTree{\n        tree: arr,\n    }\n}\n\n/* Length of the backing slice */\nfunc (abt *arrayBinaryTree) size() int {\n    return len(abt.tree)\n}\n\n/* Get the value of the node at index i */\nfunc (abt *arrayBinaryTree) val(i int) any {\n    // If the index is out of bounds, return nil to represent an empty position\n    if i < 0 || i >= abt.size() {\n        return nil\n    }\n    return abt.tree[i]\n}\n\n/* Get the left child index of the node at index i */\nfunc (abt *arrayBinaryTree) left(i int) int {\n    return 2*i + 1\n}\n\n/* Get the right child index of the node at index i */\nfunc (abt *arrayBinaryTree) right(i int) int {\n    return 2*i + 2\n}\n\n/* Get the parent index of the node at index i */\nfunc (abt *arrayBinaryTree) parent(i int) int {\n    return (i - 1) / 2\n}\n\n/* Level-order traversal */\nfunc (abt *arrayBinaryTree) levelOrder() []any {\n    var res []any\n    // Traverse the array directly\n    for i := 0; i < abt.size(); i++ {\n        if abt.val(i) != nil {\n            res = append(res, abt.val(i))\n        }\n    }\n    return res\n}\n\n/* Depth-first traversal */\nfunc (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) {\n    // Return if this position is empty\n    if abt.val(i) == nil {\n        return\n    }\n    // Preorder traversal\n    if order == \"pre\" {\n        *res = append(*res, abt.val(i))\n    }\n    abt.dfs(abt.left(i), order, res)\n    // Inorder traversal\n    if order == \"in\" {\n        *res = append(*res, abt.val(i))\n    }\n    abt.dfs(abt.right(i), order, res)\n    // Postorder traversal\n    if order == \"post\" {\n        *res = append(*res, abt.val(i))\n    }\n}\n\n/* Preorder traversal */\nfunc (abt *arrayBinaryTree) preOrder() []any {\n    var res []any\n    abt.dfs(0, \"pre\", &res)\n    return res\n}\n\n/* Inorder traversal */\nfunc (abt *arrayBinaryTree) inOrder() []any {\n    var res []any\n    abt.dfs(0, \"in\", &res)\n    return res\n}\n\n/* Postorder traversal */\nfunc (abt *arrayBinaryTree) postOrder() []any {\n    var res []any\n    abt.dfs(0, \"post\", &res)\n    return res\n}\n
    array_binary_tree.swift
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n    private var tree: [Int?]\n\n    /* Constructor */\n    init(arr: [Int?]) {\n        tree = arr\n    }\n\n    /* Length of the backing array */\n    func size() -> Int {\n        tree.count\n    }\n\n    /* Get the value of the node at index i */\n    func val(i: Int) -> Int? {\n        // If the index is out of bounds, return nil to represent an empty position\n        if i < 0 || i >= size() {\n            return nil\n        }\n        return tree[i]\n    }\n\n    /* Get the left child index of the node at index i */\n    func left(i: Int) -> Int {\n        2 * i + 1\n    }\n\n    /* Get the right child index of the node at index i */\n    func right(i: Int) -> Int {\n        2 * i + 2\n    }\n\n    /* Get the parent index of the node at index i */\n    func parent(i: Int) -> Int {\n        (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    func levelOrder() -> [Int] {\n        var res: [Int] = []\n        // Traverse the array directly\n        for i in 0 ..< size() {\n            if let val = val(i: i) {\n                res.append(val)\n            }\n        }\n        return res\n    }\n\n    /* Depth-first traversal */\n    private func dfs(i: Int, order: String, res: inout [Int]) {\n        // Return if this position is empty\n        guard let val = val(i: i) else {\n            return\n        }\n        // Preorder traversal\n        if order == \"pre\" {\n            res.append(val)\n        }\n        dfs(i: left(i: i), order: order, res: &res)\n        // Inorder traversal\n        if order == \"in\" {\n            res.append(val)\n        }\n        dfs(i: right(i: i), order: order, res: &res)\n        // Postorder traversal\n        if order == \"post\" {\n            res.append(val)\n        }\n    }\n\n    /* Preorder traversal */\n    func preOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"pre\", res: &res)\n        return res\n    }\n\n    /* Inorder traversal */\n    func inOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"in\", res: &res)\n        return res\n    }\n\n    /* Postorder traversal */\n    func postOrder() -> [Int] {\n        var res: [Int] = []\n        dfs(i: 0, order: \"post\", res: &res)\n        return res\n    }\n}\n
    array_binary_tree.js
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n    #tree;\n\n    /* Constructor */\n    constructor(arr) {\n        this.#tree = arr;\n    }\n\n    /* Array length */\n    size() {\n        return this.#tree.length;\n    }\n\n    /* Get the value of the node at index i */\n    val(i) {\n        // If the index is out of bounds, return null to indicate an empty position\n        if (i < 0 || i >= this.size()) return null;\n        return this.#tree[i];\n    }\n\n    /* Get the left child index of the node at index i */\n    left(i) {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    right(i) {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    parent(i) {\n        return Math.floor((i - 1) / 2); // Floor division\n    }\n\n    /* Level-order traversal */\n    levelOrder() {\n        let res = [];\n        // Traverse the array directly\n        for (let i = 0; i < this.size(); i++) {\n            if (this.val(i) !== null) res.push(this.val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    #dfs(i, order, res) {\n        // Return if this position is empty\n        if (this.val(i) === null) return;\n        // Preorder traversal\n        if (order === 'pre') res.push(this.val(i));\n        this.#dfs(this.left(i), order, res);\n        // Inorder traversal\n        if (order === 'in') res.push(this.val(i));\n        this.#dfs(this.right(i), order, res);\n        // Postorder traversal\n        if (order === 'post') res.push(this.val(i));\n    }\n\n    /* Preorder traversal */\n    preOrder() {\n        const res = [];\n        this.#dfs(0, 'pre', res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    inOrder() {\n        const res = [];\n        this.#dfs(0, 'in', res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    postOrder() {\n        const res = [];\n        this.#dfs(0, 'post', res);\n        return res;\n    }\n}\n
    array_binary_tree.ts
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n    #tree: (number | null)[];\n\n    /* Constructor */\n    constructor(arr: (number | null)[]) {\n        this.#tree = arr;\n    }\n\n    /* Array length, including empty positions */\n    size(): number {\n        return this.#tree.length;\n    }\n\n    /* Get the value of the node at index i */\n    val(i: number): number | null {\n        // If the index is out of bounds, return null to indicate an empty position\n        if (i < 0 || i >= this.size()) return null;\n        return this.#tree[i];\n    }\n\n    /* Get the left child index of the node at index i */\n    left(i: number): number {\n        return 2 * i + 1;\n    }\n\n    /* Get the right child index of the node at index i */\n    right(i: number): number {\n        return 2 * i + 2;\n    }\n\n    /* Get the parent index of the node at index i */\n    parent(i: number): number {\n        return Math.floor((i - 1) / 2); // Floor division\n    }\n\n    /* Level-order traversal */\n    levelOrder(): number[] {\n        let res = [];\n        // Traverse the array directly\n        for (let i = 0; i < this.size(); i++) {\n            if (this.val(i) !== null) res.push(this.val(i));\n        }\n        return res;\n    }\n\n    /* Depth-first traversal */\n    #dfs(i: number, order: Order, res: (number | null)[]): void {\n        // Return if this position is empty\n        if (this.val(i) === null) return;\n        // Preorder traversal\n        if (order === 'pre') res.push(this.val(i));\n        this.#dfs(this.left(i), order, res);\n        // Inorder traversal\n        if (order === 'in') res.push(this.val(i));\n        this.#dfs(this.right(i), order, res);\n        // Postorder traversal\n        if (order === 'post') res.push(this.val(i));\n    }\n\n    /* Preorder traversal */\n    preOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'pre', res);\n        return res;\n    }\n\n    /* Inorder traversal */\n    inOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'in', res);\n        return res;\n    }\n\n    /* Postorder traversal */\n    postOrder(): (number | null)[] {\n        const res = [];\n        this.#dfs(0, 'post', res);\n        return res;\n    }\n}\n
    array_binary_tree.dart
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree {\n  late List<int?> _tree;\n\n  /* Constructor */\n  ArrayBinaryTree(this._tree);\n\n  /* Array length, including empty positions */\n  int size() {\n    return _tree.length;\n  }\n\n  /* Get the value of the node at index i */\n  int? val(int i) {\n    // If the index is out of bounds, return null to indicate an empty position\n    if (i < 0 || i >= size()) {\n      return null;\n    }\n    return _tree[i];\n  }\n\n  /* Get the left child index of the node at index i */\n  int? left(int i) {\n    return 2 * i + 1;\n  }\n\n  /* Get the right child index of the node at index i */\n  int? right(int i) {\n    return 2 * i + 2;\n  }\n\n  /* Get the parent index of the node at index i */\n  int? parent(int i) {\n    return (i - 1) ~/ 2;\n  }\n\n  /* Level-order traversal */\n  List<int> levelOrder() {\n    List<int> res = [];\n    for (int i = 0; i < size(); i++) {\n      if (val(i) != null) {\n        res.add(val(i)!);\n      }\n    }\n    return res;\n  }\n\n  /* Depth-first traversal */\n  void dfs(int i, String order, List<int?> res) {\n    // Return if this position is empty\n    if (val(i) == null) {\n      return;\n    }\n    // Preorder traversal\n    if (order == 'pre') {\n      res.add(val(i));\n    }\n    dfs(left(i)!, order, res);\n    // Inorder traversal\n    if (order == 'in') {\n      res.add(val(i));\n    }\n    dfs(right(i)!, order, res);\n    // Postorder traversal\n    if (order == 'post') {\n      res.add(val(i));\n    }\n  }\n\n  /* Preorder traversal */\n  List<int?> preOrder() {\n    List<int?> res = [];\n    dfs(0, 'pre', res);\n    return res;\n  }\n\n  /* Inorder traversal */\n  List<int?> inOrder() {\n    List<int?> res = [];\n    dfs(0, 'in', res);\n    return res;\n  }\n\n  /* Postorder traversal */\n  List<int?> postOrder() {\n    List<int?> res = [];\n    dfs(0, 'post', res);\n    return res;\n  }\n}\n
    array_binary_tree.rs
    /* Binary tree type using an array representation */\nstruct ArrayBinaryTree {\n    tree: Vec<Option<i32>>,\n}\n\nimpl ArrayBinaryTree {\n    /* Constructor */\n    fn new(arr: Vec<Option<i32>>) -> Self {\n        Self { tree: arr }\n    }\n\n    /* Number of positions in the array */\n    fn size(&self) -> i32 {\n        self.tree.len() as i32\n    }\n\n    /* Get the value of the node at index i */\n    fn val(&self, i: i32) -> Option<i32> {\n        // If the index is out of bounds, return None to represent an empty position\n        if i < 0 || i >= self.size() {\n            None\n        } else {\n            self.tree[i as usize]\n        }\n    }\n\n    /* Get the left child index of the node at index i */\n    fn left(&self, i: i32) -> i32 {\n        2 * i + 1\n    }\n\n    /* Get the right child index of the node at index i */\n    fn right(&self, i: i32) -> i32 {\n        2 * i + 2\n    }\n\n    /* Get the parent index of the node at index i */\n    fn parent(&self, i: i32) -> i32 {\n        (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    fn level_order(&self) -> Vec<i32> {\n        self.tree.iter().filter_map(|&x| x).collect()\n    }\n\n    /* Depth-first traversal */\n    fn dfs(&self, i: i32, order: &'static str, res: &mut Vec<i32>) {\n        if self.val(i).is_none() {\n            return;\n        }\n        let val = self.val(i).unwrap();\n        // Preorder traversal\n        if order == \"pre\" {\n            res.push(val);\n        }\n        self.dfs(self.left(i), order, res);\n        // Inorder traversal\n        if order == \"in\" {\n            res.push(val);\n        }\n        self.dfs(self.right(i), order, res);\n        // Postorder traversal\n        if order == \"post\" {\n            res.push(val);\n        }\n    }\n\n    /* Preorder traversal */\n    fn pre_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"pre\", &mut res);\n        res\n    }\n\n    /* Inorder traversal */\n    fn in_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"in\", &mut res);\n        res\n    }\n\n    /* Postorder traversal */\n    fn post_order(&self) -> Vec<i32> {\n        let mut res = vec![];\n        self.dfs(0, \"post\", &mut res);\n        res\n    }\n}\n
    array_binary_tree.c
    /* Binary tree structure represented by an array */\ntypedef struct {\n    int *tree;\n    int size;\n} ArrayBinaryTree;\n\n/* Constructor */\nArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) {\n    ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree));\n    abt->tree = malloc(sizeof(int) * arrSize);\n    memcpy(abt->tree, arr, sizeof(int) * arrSize);\n    abt->size = arrSize;\n    return abt;\n}\n\n/* Destructor */\nvoid delArrayBinaryTree(ArrayBinaryTree *abt) {\n    free(abt->tree);\n    free(abt);\n}\n\n/* Array length */\nint size(ArrayBinaryTree *abt) {\n    return abt->size;\n}\n\n/* Get the value of the node at index i */\nint val(ArrayBinaryTree *abt, int i) {\n    // If the index is out of bounds, return INT_MAX to represent an empty position\n    if (i < 0 || i >= size(abt))\n        return INT_MAX;\n    return abt->tree[i];\n}\n\n/* Level-order traversal */\nint *levelOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    // Traverse the array directly\n    for (int i = 0; i < size(abt); i++) {\n        if (val(abt, i) != INT_MAX)\n            res[index++] = val(abt, i);\n    }\n    *returnSize = index;\n    return res;\n}\n\n/* Depth-first traversal */\nvoid dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) {\n    // Return if this position is empty\n    if (val(abt, i) == INT_MAX)\n        return;\n    // Preorder traversal\n    if (strcmp(order, \"pre\") == 0)\n        res[(*index)++] = val(abt, i);\n    dfs(abt, left(i), order, res, index);\n    // Inorder traversal\n    if (strcmp(order, \"in\") == 0)\n        res[(*index)++] = val(abt, i);\n    dfs(abt, right(i), order, res, index);\n    // Postorder traversal\n    if (strcmp(order, \"post\") == 0)\n        res[(*index)++] = val(abt, i);\n}\n\n/* Preorder traversal */\nint *preOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"pre\", res, &index);\n    *returnSize = index;\n    return res;\n}\n\n/* Inorder traversal */\nint *inOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"in\", res, &index);\n    *returnSize = index;\n    return res;\n}\n\n/* Postorder traversal */\nint *postOrder(ArrayBinaryTree *abt, int *returnSize) {\n    int *res = (int *)malloc(sizeof(int) * size(abt));\n    int index = 0;\n    dfs(abt, 0, \"post\", res, &index);\n    *returnSize = index;\n    return res;\n}\n
    array_binary_tree.kt
    /* Binary tree class using an array representation */\nclass ArrayBinaryTree(val tree: MutableList<Int?>) {\n    /* List size */\n    fun size(): Int {\n        return tree.size\n    }\n\n    /* Get the value of the node at index i */\n    fun _val(i: Int): Int? {\n        // If the index is out of bounds, return null to represent an empty position\n        if (i < 0 || i >= size()) return null\n        return tree[i]\n    }\n\n    /* Get the left child index of the node at index i */\n    fun left(i: Int): Int {\n        return 2 * i + 1\n    }\n\n    /* Get the right child index of the node at index i */\n    fun right(i: Int): Int {\n        return 2 * i + 2\n    }\n\n    /* Get the parent index of the node at index i */\n    fun parent(i: Int): Int {\n        return (i - 1) / 2\n    }\n\n    /* Level-order traversal */\n    fun levelOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        // Traverse the array directly\n        for (i in 0..<size()) {\n            if (_val(i) != null)\n                res.add(_val(i))\n        }\n        return res\n    }\n\n    /* Depth-first traversal */\n    fun dfs(i: Int, order: String, res: MutableList<Int?>) {\n        // Return if this position is empty\n        if (_val(i) == null)\n            return\n        // Preorder traversal\n        if (\"pre\" == order)\n            res.add(_val(i))\n        dfs(left(i), order, res)\n        // Inorder traversal\n        if (\"in\" == order)\n            res.add(_val(i))\n        dfs(right(i), order, res)\n        // Postorder traversal\n        if (\"post\" == order)\n            res.add(_val(i))\n    }\n\n    /* Preorder traversal */\n    fun preOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"pre\", res)\n        return res\n    }\n\n    /* Inorder traversal */\n    fun inOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"in\", res)\n        return res\n    }\n\n    /* Postorder traversal */\n    fun postOrder(): MutableList<Int?> {\n        val res = mutableListOf<Int?>()\n        dfs(0, \"post\", res)\n        return res\n    }\n}\n
    array_binary_tree.rb
    ### Binary tree class using an array representation ###\nclass ArrayBinaryTree\n  ### Constructor ###\n  def initialize(arr)\n    @tree = arr.to_a\n  end\n\n  ### Length of the array representation ###\n  def size\n    @tree.length\n  end\n\n  ### Get the value of the node at index i ###\n  def val(i)\n    # If the index is out of bounds, return nil to indicate an empty position\n    return if i < 0 || i >= size\n\n    @tree[i]\n  end\n\n  ### Get the left child index of the node at index i ###\n  def left(i)\n    2 * i + 1\n  end\n\n  ### Get the right child index of the node at index i ###\n  def right(i)\n    2 * i + 2\n  end\n\n  ### Get the parent index of the node at index i ###\n  def parent(i)\n    (i - 1) / 2\n  end\n\n  ### Level-order traversal ###\n  def level_order\n    @res = []\n\n    # Traverse the array directly\n    for i in 0...size\n      @res << val(i) unless val(i).nil?\n    end\n\n    @res\n  end\n\n  ### Depth-first traversal ###\n  def dfs(i, order)\n    return if val(i).nil?\n    # Preorder traversal\n    @res << val(i) if order == :pre\n    dfs(left(i), order)\n    # Inorder traversal\n    @res << val(i) if order == :in\n    dfs(right(i), order)\n    # Postorder traversal\n    @res << val(i) if order == :post\n  end\n\n  ### Preorder traversal ###\n  def pre_order\n    @res = []\n    dfs(0, :pre)\n    @res\n  end\n\n  ### Inorder traversal ###\n  def in_order\n    @res = []\n    dfs(0, :in)\n    @res\n  end\n\n  ### Postorder traversal ###\n  def post_order\n    @res = []\n    dfs(0, :post)\n    @res\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.3   Array representation of a binary tree"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#733-strengths-and-limitations","level":2,"title":"7.3.3   Strengths and limitations","text":"

    Array representation offers the following advantages.

    • Contiguous memory is cache-friendly, making access and traversal fast.
    • No pointers need to be stored, saving space.
    • Nodes can be accessed randomly by index.

    However, it also has limitations.

    • Arrays require contiguous memory, making them unsuitable for extremely large trees.
    • Adding and removing nodes requires array insertion and deletion operations, which are inefficient.
    • If the tree has many missing nodes, actual node data occupies only a small fraction of the array, wasting space.
    ","path":["Chapter 7.   Trees","7.3   Array representation of a binary tree"],"tags":[]},{"location":"chapter_tree/avl_tree/","level":1,"title":"7.5   AVL trees *","text":"

    The binary-search-tree section explained that repeated insertions and deletions can make a tree degenerate into a linked list. All operations then degrade from \\(O(\\log n)\\) to \\(O(n)\\) time.

    In the example below, just two deletions turn the binary search tree into a linked list.

    Figure 7-24   An AVL-tree example degenerating after deletions

    Likewise, inserting two nodes into the perfect binary tree below makes it heavily skewed to the left, degrading lookup performance.

    Figure 7-25   An AVL-tree example degenerating after insertions

    In 1962, G. M. Adelson-Velsky and E. M. Landis introduced the AVL tree in their paper “An algorithm for the organization of information.” They described operations that prevent degeneration through repeated insertions and deletions, keeping the time complexity of operations at \\(O(\\log n)\\). AVL trees therefore maintain efficient data operations when insertions, deletions, lookups, and updates are frequent, making them valuable in practice.

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#751-avl-tree-terminology","level":2,"title":"7.5.1   AVL tree terminology","text":"

    An AVL tree is both a binary search tree and a balanced binary tree, satisfying the properties of both. It is therefore a balanced binary search tree.

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-node-height","level":3,"title":"1.   Node height","text":"

    AVL operations need node heights, so add a height variable to the node class:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class TreeNode:\n    \"\"\"AVL tree node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                 # Node value\n        self.height: int = 0                # Node height\n        self.left: TreeNode | None = None   # Left child reference\n        self.right: TreeNode | None = None  # Right child reference\n
    /* AVL tree node class */\nstruct TreeNode {\n    int val{};          // Node value\n    int height = 0;     // Node height\n    TreeNode *left{};   // Left child\n    TreeNode *right{};  // Right child\n    TreeNode() = default;\n    explicit TreeNode(int x) : val(x){}\n};\n
    /* AVL tree node class */\nclass TreeNode {\n    public int val;        // Node value\n    public int height;     // Node height\n    public TreeNode left;  // Left child\n    public TreeNode right; // Right child\n    public TreeNode(int x) { val = x; }\n}\n
    /* AVL tree node class */\nclass TreeNode(int? x) {\n    public int? val = x;    // Node value\n    public int height;      // Node height\n    public TreeNode? left;  // Left child reference\n    public TreeNode? right; // Right child reference\n}\n
    /* AVL tree node struct */\ntype TreeNode struct {\n    Val    int       // Node value\n    Height int       // Node height\n    Left   *TreeNode // Left child reference\n    Right  *TreeNode // Right child reference\n}\n
    /* AVL tree node class */\nclass TreeNode {\n    var val: Int // Node value\n    var height: Int // Node height\n    var left: TreeNode? // Left child\n    var right: TreeNode? // Right child\n\n    init(x: Int) {\n        val = x\n        height = 0\n    }\n}\n
    /* AVL tree node class */\nclass TreeNode {\n    val; // Node value\n    height; //Node height\n    left; // Left child pointer\n    right; // Right child pointer\n    constructor(val, left, right, height) {\n        this.val = val === undefined ? 0 : val;\n        this.height = height === undefined ? 0 : height;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
    /* AVL tree node class */\nclass TreeNode {\n    val: number;            // Node value\n    height: number;         // Node height\n    left: TreeNode | null;  // Left child pointer\n    right: TreeNode | null; // Right child pointer\n    constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) {\n        this.val = val === undefined ? 0 : val;\n        this.height = height === undefined ? 0 : height;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
    /* AVL tree node class */\nclass TreeNode {\n  int val;         // Node value\n  int height;      // Node height\n  TreeNode? left;  // Left child\n  TreeNode? right; // Right child\n  TreeNode(this.val, [this.height = 0, this.left, this.right]);\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* AVL tree node struct */\nstruct TreeNode {\n    val: i32,                               // Node value\n    height: i32,                            // Node height\n    left: Option<Rc<RefCell<TreeNode>>>,    // Left child\n    right: Option<Rc<RefCell<TreeNode>>>,   // Right child\n}\n\nimpl TreeNode {\n    /* Constructor */\n    fn new(val: i32) -> Rc<RefCell<Self>> {\n        Rc::new(RefCell::new(Self {\n            val,\n            height: 0,\n            left: None,\n            right: None\n        }))\n    }\n}\n
    /* AVL tree node struct */\ntypedef struct TreeNode {\n    int val;\n    int height;\n    struct TreeNode *left;\n    struct TreeNode *right;\n} TreeNode;\n\n/* Constructor */\nTreeNode *newTreeNode(int val) {\n    TreeNode *node;\n\n    node = (TreeNode *)malloc(sizeof(TreeNode));\n    node->val = val;\n    node->height = 0;\n    node->left = NULL;\n    node->right = NULL;\n    return node;\n}\n
    /* AVL tree node class */\nclass TreeNode(val _val: Int) {  // Node value\n    var height: Int = 0          // Node height\n    var left: TreeNode? = null   // Left child\n    var right: TreeNode? = null  // Right child\n}\n
    ### AVL tree node class ###\nclass TreeNode\n  attr_accessor :val    # Node value\n  attr_accessor :height # Node height\n  attr_accessor :left   # Left child reference\n  attr_accessor :right  # Right child reference\n\n  def initialize(val)\n    @val = val\n    @height = 0\n  end\nend\n

    A node's height is the distance to its farthest leaf, measured in edges. A leaf has height \\(0\\), while a missing node has height \\(-1\\). We create two helper functions to retrieve and update a node's height:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def height(self, node: TreeNode | None) -> int:\n    \"\"\"Get the node height\"\"\"\n    # An empty subtree has height -1; a leaf has height 0\n    if node is not None:\n        return node.height\n    return -1\n\ndef update_height(self, node: TreeNode | None):\n    \"\"\"Update the node height\"\"\"\n    # The node height equals the height of its taller subtree + 1\n    node.height = max([self.height(node.left), self.height(node.right)]) + 1\n
    avl_tree.cpp
    /* Get the node height */\nint height(TreeNode *node) {\n    // A null node has height -1; a leaf has height 0\n    return node == nullptr ? -1 : node->height;\n}\n\n/* Update the node height */\nvoid updateHeight(TreeNode *node) {\n    // The node height equals the height of its taller subtree + 1\n    node->height = max(height(node->left), height(node->right)) + 1;\n}\n
    avl_tree.java
    /* Get the node height */\nint height(TreeNode node) {\n    // A null node has height -1; a leaf has height 0\n    return node == null ? -1 : node.height;\n}\n\n/* Update the node height */\nvoid updateHeight(TreeNode node) {\n    // The node height equals the height of its taller subtree + 1\n    node.height = Math.max(height(node.left), height(node.right)) + 1;\n}\n
    avl_tree.cs
    /* Get the node height */\nint Height(TreeNode? node) {\n    // A null node has height -1; a leaf has height 0\n    return node == null ? -1 : node.height;\n}\n\n/* Update the node height */\nvoid UpdateHeight(TreeNode node) {\n    // The node height equals the height of its taller subtree + 1\n    node.height = Math.Max(Height(node.left), Height(node.right)) + 1;\n}\n
    avl_tree.go
    /* Get the node height */\nfunc (t *aVLTree) height(node *TreeNode) int {\n    // A nil node has height -1; a leaf has height 0\n    if node != nil {\n        return node.Height\n    }\n    return -1\n}\n\n/* Update the node height */\nfunc (t *aVLTree) updateHeight(node *TreeNode) {\n    lh := t.height(node.Left)\n    rh := t.height(node.Right)\n    // The node height equals the height of its taller subtree + 1\n    if lh > rh {\n        node.Height = lh + 1\n    } else {\n        node.Height = rh + 1\n    }\n}\n
    avl_tree.swift
    /* Get the node height */\nfunc height(node: TreeNode?) -> Int {\n    // A nil node has height -1; a leaf has height 0\n    node?.height ?? -1\n}\n\n/* Update the node height */\nfunc updateHeight(node: TreeNode?) {\n    // The node height equals the height of its taller subtree + 1\n    node?.height = max(height(node: node?.left), height(node: node?.right)) + 1\n}\n
    avl_tree.js
    /* Get the node height */\nheight(node) {\n    // A null node has height -1; a leaf has height 0\n    return node === null ? -1 : node.height;\n}\n\n/* Update the node height */\n#updateHeight(node) {\n    // The node height equals the height of its taller subtree + 1\n    node.height =\n        Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
    avl_tree.ts
    /* Get the node height */\nheight(node: TreeNode): number {\n    // A null node has height -1; a leaf has height 0\n    return node === null ? -1 : node.height;\n}\n\n/* Update the node height */\nupdateHeight(node: TreeNode): void {\n    // The node height equals the height of its taller subtree + 1\n    node.height =\n        Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n
    avl_tree.dart
    /* Get the node height */\nint height(TreeNode? node) {\n  // A null node has height -1; a leaf has height 0\n  return node == null ? -1 : node.height;\n}\n\n/* Update the node height */\nvoid updateHeight(TreeNode? node) {\n  // The node height equals the height of its taller subtree + 1\n  node!.height = max(height(node.left), height(node.right)) + 1;\n}\n
    avl_tree.rs
    /* Get the node height */\nfn height(node: OptionTreeNodeRc) -> i32 {\n    // A null node has height -1; a leaf has height 0\n    match node {\n        Some(node) => node.borrow().height,\n        None => -1,\n    }\n}\n\n/* Update the node height */\nfn update_height(node: OptionTreeNodeRc) {\n    if let Some(node) = node {\n        let left = node.borrow().left.clone();\n        let right = node.borrow().right.clone();\n        // The node height equals the height of its taller subtree + 1\n        node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1;\n    }\n}\n
    avl_tree.c
    /* Get the node height */\nint height(TreeNode *node) {\n    // A null node has height -1; a leaf has height 0\n    if (node != NULL) {\n        return node->height;\n    }\n    return -1;\n}\n\n/* Update the node height */\nvoid updateHeight(TreeNode *node) {\n    int lh = height(node->left);\n    int rh = height(node->right);\n    // The node height equals the height of its taller subtree + 1\n    if (lh > rh) {\n        node->height = lh + 1;\n    } else {\n        node->height = rh + 1;\n    }\n}\n
    avl_tree.kt
    /* Get the node height */\nfun height(node: TreeNode?): Int {\n    // A null node has height -1; a leaf has height 0\n    return node?.height ?: -1\n}\n\n/* Update the node height */\nfun updateHeight(node: TreeNode?) {\n    // The node height equals the height of its taller subtree + 1\n    node?.height = max(height(node?.left), height(node?.right)) + 1\n}\n
    avl_tree.rb
    ### Get the node height ###\ndef height(node)\n  # A nil node has height -1; a leaf has height 0\n  return node.height unless node.nil?\n\n  -1\nend\n\n### Update the node height ###\ndef update_height(node)\n  # The node height equals the height of its taller subtree + 1\n  node.height = [height(node.left), height(node.right)].max + 1\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-balance-factor","level":3,"title":"2.   Balance factor","text":"

    A node's balance factor is its left subtree's height minus its right subtree's height. A missing node has balance factor \\(0\\). Wrap this calculation in a function for convenient use:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def balance_factor(self, node: TreeNode | None) -> int:\n    \"\"\"Get the balance factor\"\"\"\n    # An empty subtree has balance factor 0\n    if node is None:\n        return 0\n    # Node balance factor = left subtree height - right subtree height\n    return self.height(node.left) - self.height(node.right)\n
    avl_tree.cpp
    /* Get the balance factor */\nint balanceFactor(TreeNode *node) {\n    // A null node has balance factor 0\n    if (node == nullptr)\n        return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return height(node->left) - height(node->right);\n}\n
    avl_tree.java
    /* Get the balance factor */\nint balanceFactor(TreeNode node) {\n    // A null node has balance factor 0\n    if (node == null)\n        return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return height(node.left) - height(node.right);\n}\n
    avl_tree.cs
    /* Get the balance factor */\nint BalanceFactor(TreeNode? node) {\n    // A null node has balance factor 0\n    if (node == null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return Height(node.left) - Height(node.right);\n}\n
    avl_tree.go
    /* Get the balance factor */\nfunc (t *aVLTree) balanceFactor(node *TreeNode) int {\n    // A nil node has balance factor 0\n    if node == nil {\n        return 0\n    }\n    // Node balance factor = left subtree height - right subtree height\n    return t.height(node.Left) - t.height(node.Right)\n}\n
    avl_tree.swift
    /* Get the balance factor */\nfunc balanceFactor(node: TreeNode?) -> Int {\n    // A nil node has balance factor 0\n    guard let node = node else { return 0 }\n    // Node balance factor = left subtree height - right subtree height\n    return height(node: node.left) - height(node: node.right)\n}\n
    avl_tree.js
    /* Get the balance factor */\nbalanceFactor(node) {\n    // A null node has balance factor 0\n    if (node === null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return this.height(node.left) - this.height(node.right);\n}\n
    avl_tree.ts
    /* Get the balance factor */\nbalanceFactor(node: TreeNode): number {\n    // A null node has balance factor 0\n    if (node === null) return 0;\n    // Node balance factor = left subtree height - right subtree height\n    return this.height(node.left) - this.height(node.right);\n}\n
    avl_tree.dart
    /* Get the balance factor */\nint balanceFactor(TreeNode? node) {\n  // A null node has balance factor 0\n  if (node == null) return 0;\n  // Node balance factor = left subtree height - right subtree height\n  return height(node.left) - height(node.right);\n}\n
    avl_tree.rs
    /* Get the balance factor */\nfn balance_factor(node: OptionTreeNodeRc) -> i32 {\n    match node {\n        // A null node has balance factor 0\n        None => 0,\n        // Node balance factor = left subtree height - right subtree height\n        Some(node) => {\n            Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone())\n        }\n    }\n}\n
    avl_tree.c
    /* Get the balance factor */\nint balanceFactor(TreeNode *node) {\n    // A null node has balance factor 0\n    if (node == NULL) {\n        return 0;\n    }\n    // Node balance factor = left subtree height - right subtree height\n    return height(node->left) - height(node->right);\n}\n
    avl_tree.kt
    /* Get the balance factor */\nfun balanceFactor(node: TreeNode?): Int {\n    // A null node has balance factor 0\n    if (node == null) return 0\n    // Node balance factor = left subtree height - right subtree height\n    return height(node.left) - height(node.right)\n}\n
    avl_tree.rb
    ### Get the balance factor ###\ndef balance_factor(node)\n  # A nil node has balance factor 0\n  return 0 if node.nil?\n\n  # Node balance factor = left subtree height - right subtree height\n  height(node.left) - height(node.right)\nend\n

    Tip

    If a node's balance factor is \\(f\\), every node in an AVL tree satisfies \\(-1 \\le f \\le 1\\).

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#752-avl-tree-rotations","level":2,"title":"7.5.2   AVL tree rotations","text":"

    The defining operation of an AVL tree is rotation, which restores balance without changing the inorder traversal sequence. A rotation preserves the binary-search-tree property while restoring the balanced-tree property.

    A node whose balance factor has absolute value \\(> 1\\) is “unbalanced.” Depending on the imbalance, we use one of four rotations: right, left, right-then-left, or left-then-right. Let us examine them in detail.

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-right-rotation","level":3,"title":"1.   Right rotation","text":"

    In Figure 7-26, balance factors appear beneath the nodes. Viewed from the bottom up, node 3 is the first unbalanced node. Focus on the subtree rooted there, naming its root node and its left child child. A right rotation restores balance while preserving the binary-search-tree property.

    <1><2><3><4>

    Figure 7-26   Steps of a right rotation

    If child has a right child, denoted by grand_child, add one step to the rotation: make grand_child the left child of node, as shown below.

    Figure 7-27   A right rotation with grand_child

    “Rotating right” is a visual description; the implementation updates node pointers:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def right_rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Right rotation\"\"\"\n    child = node.left\n    grand_child = child.right\n    # Rotate node to the right around child\n    child.right = node\n    node.left = grand_child\n    # Update the node height\n    self.update_height(node)\n    self.update_height(child)\n    # Return the root of the rotated subtree\n    return child\n
    avl_tree.cpp
    /* Right rotation */\nTreeNode *rightRotate(TreeNode *node) {\n    TreeNode *child = node->left;\n    TreeNode *grandChild = child->right;\n    // Rotate node to the right around child\n    child->right = node;\n    node->left = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.java
    /* Right rotation */\nTreeNode rightRotate(TreeNode node) {\n    TreeNode child = node.left;\n    TreeNode grandChild = child.right;\n    // Rotate node to the right around child\n    child.right = node;\n    node.left = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.cs
    /* Right rotation */\nTreeNode? RightRotate(TreeNode? node) {\n    TreeNode? child = node?.left;\n    TreeNode? grandChild = child?.right;\n    // Rotate node to the right around child\n    child.right = node;\n    node.left = grandChild;\n    // Update the node height\n    UpdateHeight(node);\n    UpdateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.go
    /* Right rotation */\nfunc (t *aVLTree) rightRotate(node *TreeNode) *TreeNode {\n    child := node.Left\n    grandChild := child.Right\n    // Rotate node to the right around child\n    child.Right = node\n    node.Left = grandChild\n    // Update the node height\n    t.updateHeight(node)\n    t.updateHeight(child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.swift
    /* Right rotation */\nfunc rightRotate(node: TreeNode?) -> TreeNode? {\n    let child = node?.left\n    let grandChild = child?.right\n    // Rotate node to the right around child\n    child?.right = node\n    node?.left = grandChild\n    // Update the node height\n    updateHeight(node: node)\n    updateHeight(node: child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.js
    /* Right rotation */\n#rightRotate(node) {\n    const child = node.left;\n    const grandChild = child.right;\n    // Rotate node to the right around child\n    child.right = node;\n    node.left = grandChild;\n    // Update the node height\n    this.#updateHeight(node);\n    this.#updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.ts
    /* Right rotation */\nrightRotate(node: TreeNode): TreeNode {\n    const child = node.left;\n    const grandChild = child.right;\n    // Rotate node to the right around child\n    child.right = node;\n    node.left = grandChild;\n    // Update the node height\n    this.updateHeight(node);\n    this.updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.dart
    /* Right rotation */\nTreeNode? rightRotate(TreeNode? node) {\n  TreeNode? child = node!.left;\n  TreeNode? grandChild = child!.right;\n  // Rotate node to the right around child\n  child.right = node;\n  node.left = grandChild;\n  // Update the node height\n  updateHeight(node);\n  updateHeight(child);\n  // Return the root of the rotated subtree\n  return child;\n}\n
    avl_tree.rs
    /* Right rotation */\nfn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    match node {\n        Some(node) => {\n            let child = node.borrow().left.clone().unwrap();\n            let grand_child = child.borrow().right.clone();\n            // Rotate node to the right around child\n            child.borrow_mut().right = Some(node.clone());\n            node.borrow_mut().left = grand_child;\n            // Update the node height\n            Self::update_height(Some(node));\n            Self::update_height(Some(child.clone()));\n            // Return the root of the rotated subtree\n            Some(child)\n        }\n        None => None,\n    }\n}\n
    avl_tree.c
    /* Right rotation */\nTreeNode *rightRotate(TreeNode *node) {\n    TreeNode *child, *grandChild;\n    child = node->left;\n    grandChild = child->right;\n    // Rotate node to the right around child\n    child->right = node;\n    node->left = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.kt
    /* Right rotation */\nfun rightRotate(node: TreeNode?): TreeNode {\n    val child = node!!.left\n    val grandChild = child!!.right\n    // Rotate node to the right around child\n    child.right = node\n    node.left = grandChild\n    // Update the node height\n    updateHeight(node)\n    updateHeight(child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.rb
    ### Right rotation ###\ndef right_rotate(node)\n  child = node.left\n  grand_child = child.right\n  # Rotate node to the right around child\n  child.right = node\n  node.left = grand_child\n  # Update the node height\n  update_height(node)\n  update_height(child)\n  # Return the root of the rotated subtree\n  child\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-left-rotation","level":3,"title":"2.   Left rotation","text":"

    For the mirror image of the unbalanced tree above, perform a left rotation, as shown below.

    Figure 7-28   A left rotation

    Similarly, if child has a left child, denoted by grand_child, make it the right child of node during the rotation.

    Figure 7-29   A left rotation with grand_child

    Right and left rotations are mirror images, as are the imbalances they resolve. To obtain left-rotation code, exchange every left and right in the right-rotation implementation:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def left_rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Left rotation\"\"\"\n    child = node.right\n    grand_child = child.left\n    # Rotate node to the left around child\n    child.left = node\n    node.right = grand_child\n    # Update the node height\n    self.update_height(node)\n    self.update_height(child)\n    # Return the root of the rotated subtree\n    return child\n
    avl_tree.cpp
    /* Left rotation */\nTreeNode *leftRotate(TreeNode *node) {\n    TreeNode *child = node->right;\n    TreeNode *grandChild = child->left;\n    // Rotate node to the left around child\n    child->left = node;\n    node->right = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.java
    /* Left rotation */\nTreeNode leftRotate(TreeNode node) {\n    TreeNode child = node.right;\n    TreeNode grandChild = child.left;\n    // Rotate node to the left around child\n    child.left = node;\n    node.right = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.cs
    /* Left rotation */\nTreeNode? LeftRotate(TreeNode? node) {\n    TreeNode? child = node?.right;\n    TreeNode? grandChild = child?.left;\n    // Rotate node to the left around child\n    child.left = node;\n    node.right = grandChild;\n    // Update the node height\n    UpdateHeight(node);\n    UpdateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.go
    /* Left rotation */\nfunc (t *aVLTree) leftRotate(node *TreeNode) *TreeNode {\n    child := node.Right\n    grandChild := child.Left\n    // Rotate node to the left around child\n    child.Left = node\n    node.Right = grandChild\n    // Update the node height\n    t.updateHeight(node)\n    t.updateHeight(child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.swift
    /* Left rotation */\nfunc leftRotate(node: TreeNode?) -> TreeNode? {\n    let child = node?.right\n    let grandChild = child?.left\n    // Rotate node to the left around child\n    child?.left = node\n    node?.right = grandChild\n    // Update the node height\n    updateHeight(node: node)\n    updateHeight(node: child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.js
    /* Left rotation */\n#leftRotate(node) {\n    const child = node.right;\n    const grandChild = child.left;\n    // Rotate node to the left around child\n    child.left = node;\n    node.right = grandChild;\n    // Update the node height\n    this.#updateHeight(node);\n    this.#updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.ts
    /* Left rotation */\nleftRotate(node: TreeNode): TreeNode {\n    const child = node.right;\n    const grandChild = child.left;\n    // Rotate node to the left around child\n    child.left = node;\n    node.right = grandChild;\n    // Update the node height\n    this.updateHeight(node);\n    this.updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.dart
    /* Left rotation */\nTreeNode? leftRotate(TreeNode? node) {\n  TreeNode? child = node!.right;\n  TreeNode? grandChild = child!.left;\n  // Rotate node to the left around child\n  child.left = node;\n  node.right = grandChild;\n  // Update the node height\n  updateHeight(node);\n  updateHeight(child);\n  // Return the root of the rotated subtree\n  return child;\n}\n
    avl_tree.rs
    /* Left rotation */\nfn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    match node {\n        Some(node) => {\n            let child = node.borrow().right.clone().unwrap();\n            let grand_child = child.borrow().left.clone();\n            // Rotate node to the left around child\n            child.borrow_mut().left = Some(node.clone());\n            node.borrow_mut().right = grand_child;\n            // Update the node height\n            Self::update_height(Some(node));\n            Self::update_height(Some(child.clone()));\n            // Return the root of the rotated subtree\n            Some(child)\n        }\n        None => None,\n    }\n}\n
    avl_tree.c
    /* Left rotation */\nTreeNode *leftRotate(TreeNode *node) {\n    TreeNode *child, *grandChild;\n    child = node->right;\n    grandChild = child->left;\n    // Rotate node to the left around child\n    child->left = node;\n    node->right = grandChild;\n    // Update the node height\n    updateHeight(node);\n    updateHeight(child);\n    // Return the root of the rotated subtree\n    return child;\n}\n
    avl_tree.kt
    /* Left rotation */\nfun leftRotate(node: TreeNode?): TreeNode {\n    val child = node!!.right\n    val grandChild = child!!.left\n    // Rotate node to the left around child\n    child.left = node\n    node.right = grandChild\n    // Update the node height\n    updateHeight(node)\n    updateHeight(child)\n    // Return the root of the rotated subtree\n    return child\n}\n
    avl_tree.rb
    ### Left rotation ###\ndef left_rotate(node)\n  child = node.right\n  grand_child = child.left\n  # Rotate node to the left around child\n  child.left = node\n  node.right = grand_child\n  # Update the node height\n  update_height(node)\n  update_height(child)\n  # Return the root of the rotated subtree\n  child\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3-left-then-right-rotation","level":3,"title":"3.   Left-then-right rotation","text":"

    For unbalanced node 3 below, neither a left nor a right rotation alone restores balance. First rotate child to the left, then rotate node to the right.

    Figure 7-30   A left-then-right rotation

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#4-right-then-left-rotation","level":3,"title":"4.   Right-then-left rotation","text":"

    For the mirror-image case below, first rotate child to the right, then rotate node to the left.

    Figure 7-31   A right-then-left rotation

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#5-choosing-a-rotation","level":3,"title":"5.   Choosing a rotation","text":"

    The four cases below correspond to the examples above and require, respectively, right, left-then-right, right-then-left, and left rotations.

    Figure 7-32   The four AVL rotation cases

    As Table 7-3 shows, the signs of the unbalanced node's balance factor and that of the child on its taller side determine the case.

    Table 7-3   Conditions for choosing a rotation

    Unbalanced node's balance factor Child's balance factor Rotation \\(> 1\\) (left-heavy) \\(\\geq 0\\) Right \\(> 1\\) (left-heavy) \\(<0\\) Left, then right \\(< -1\\) (right-heavy) \\(\\leq 0\\) Left \\(< -1\\) (right-heavy) \\(>0\\) Right, then left

    Wrap the rotation logic in one function. It can then handle every imbalance and restore the affected node's balance. The code is as follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def rotate(self, node: TreeNode | None) -> TreeNode | None:\n    \"\"\"Perform rotations to restore balance in this subtree\"\"\"\n    # Get the balance factor of node\n    balance_factor = self.balance_factor(node)\n    # Left-heavy tree\n    if balance_factor > 1:\n        if self.balance_factor(node.left) >= 0:\n            # Rotate right\n            return self.right_rotate(node)\n        else:\n            # Rotate left, then right\n            node.left = self.left_rotate(node.left)\n            return self.right_rotate(node)\n    # Right-heavy tree\n    elif balance_factor < -1:\n        if self.balance_factor(node.right) <= 0:\n            # Rotate left\n            return self.left_rotate(node)\n        else:\n            # Rotate right, then left\n            node.right = self.right_rotate(node.right)\n            return self.left_rotate(node)\n    # The tree is balanced; return without rotating\n    return node\n
    avl_tree.cpp
    /* Perform rotations to restore balance in this subtree */\nTreeNode *rotate(TreeNode *node) {\n    // Get the balance factor of node\n    int _balanceFactor = balanceFactor(node);\n    // Left-heavy tree\n    if (_balanceFactor > 1) {\n        if (balanceFactor(node->left) >= 0) {\n            // Rotate right\n            return rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node->left = leftRotate(node->left);\n            return rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (_balanceFactor < -1) {\n        if (balanceFactor(node->right) <= 0) {\n            // Rotate left\n            return leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node->right = rightRotate(node->right);\n            return leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.java
    /* Perform rotations to restore balance in this subtree */\nTreeNode rotate(TreeNode node) {\n    // Get the balance factor of node\n    int balanceFactor = balanceFactor(node);\n    // Left-heavy tree\n    if (balanceFactor > 1) {\n        if (balanceFactor(node.left) >= 0) {\n            // Rotate right\n            return rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node.left = leftRotate(node.left);\n            return rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactor < -1) {\n        if (balanceFactor(node.right) <= 0) {\n            // Rotate left\n            return leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node.right = rightRotate(node.right);\n            return leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.cs
    /* Perform rotations to restore balance in this subtree */\nTreeNode? Rotate(TreeNode? node) {\n    // Get the balance factor of node\n    int balanceFactorInt = BalanceFactor(node);\n    // Left-heavy tree\n    if (balanceFactorInt > 1) {\n        if (BalanceFactor(node?.left) >= 0) {\n            // Rotate right\n            return RightRotate(node);\n        } else {\n            // Rotate left, then right\n            node!.left = LeftRotate(node!.left);\n            return RightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactorInt < -1) {\n        if (BalanceFactor(node?.right) <= 0) {\n            // Rotate left\n            return LeftRotate(node);\n        } else {\n            // Rotate right, then left\n            node!.right = RightRotate(node!.right);\n            return LeftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.go
    /* Perform rotations to restore balance in this subtree */\nfunc (t *aVLTree) rotate(node *TreeNode) *TreeNode {\n    // Get the balance factor of node\n    // Go favors short local variable names; bf holds the result of t.balanceFactor(node)\n    bf := t.balanceFactor(node)\n    // Left-heavy tree\n    if bf > 1 {\n        if t.balanceFactor(node.Left) >= 0 {\n            // Rotate right\n            return t.rightRotate(node)\n        } else {\n            // Rotate left, then right\n            node.Left = t.leftRotate(node.Left)\n            return t.rightRotate(node)\n        }\n    }\n    // Right-heavy tree\n    if bf < -1 {\n        if t.balanceFactor(node.Right) <= 0 {\n            // Rotate left\n            return t.leftRotate(node)\n        } else {\n            // Rotate right, then left\n            node.Right = t.rightRotate(node.Right)\n            return t.leftRotate(node)\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node\n}\n
    avl_tree.swift
    /* Perform rotations to restore balance in this subtree */\nfunc rotate(node: TreeNode?) -> TreeNode? {\n    // Get the balance factor of node\n    let balanceFactor = balanceFactor(node: node)\n    // Left-heavy tree\n    if balanceFactor > 1 {\n        if self.balanceFactor(node: node?.left) >= 0 {\n            // Rotate right\n            return rightRotate(node: node)\n        } else {\n            // Rotate left, then right\n            node?.left = leftRotate(node: node?.left)\n            return rightRotate(node: node)\n        }\n    }\n    // Right-heavy tree\n    if balanceFactor < -1 {\n        if self.balanceFactor(node: node?.right) <= 0 {\n            // Rotate left\n            return leftRotate(node: node)\n        } else {\n            // Rotate right, then left\n            node?.right = rightRotate(node: node?.right)\n            return leftRotate(node: node)\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node\n}\n
    avl_tree.js
    /* Perform rotations to restore balance in this subtree */\n#rotate(node) {\n    // Get the balance factor of node\n    const balanceFactor = this.balanceFactor(node);\n    // Left-heavy tree\n    if (balanceFactor > 1) {\n        if (this.balanceFactor(node.left) >= 0) {\n            // Rotate right\n            return this.#rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node.left = this.#leftRotate(node.left);\n            return this.#rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactor < -1) {\n        if (this.balanceFactor(node.right) <= 0) {\n            // Rotate left\n            return this.#leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node.right = this.#rightRotate(node.right);\n            return this.#leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.ts
    /* Perform rotations to restore balance in this subtree */\nrotate(node: TreeNode): TreeNode {\n    // Get the balance factor of node\n    const balanceFactor = this.balanceFactor(node);\n    // Left-heavy tree\n    if (balanceFactor > 1) {\n        if (this.balanceFactor(node.left) >= 0) {\n            // Rotate right\n            return this.rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node.left = this.leftRotate(node.left);\n            return this.rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactor < -1) {\n        if (this.balanceFactor(node.right) <= 0) {\n            // Rotate left\n            return this.leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node.right = this.rightRotate(node.right);\n            return this.leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.dart
    /* Perform rotations to restore balance in this subtree */\nTreeNode? rotate(TreeNode? node) {\n  // Get the balance factor of node\n  int factor = balanceFactor(node);\n  // Left-heavy tree\n  if (factor > 1) {\n    if (balanceFactor(node!.left) >= 0) {\n      // Rotate right\n      return rightRotate(node);\n    } else {\n      // Rotate left, then right\n      node.left = leftRotate(node.left);\n      return rightRotate(node);\n    }\n  }\n  // Right-heavy tree\n  if (factor < -1) {\n    if (balanceFactor(node!.right) <= 0) {\n      // Rotate left\n      return leftRotate(node);\n    } else {\n      // Rotate right, then left\n      node.right = rightRotate(node.right);\n      return leftRotate(node);\n    }\n  }\n  // The tree is balanced; return without rotating\n  return node;\n}\n
    avl_tree.rs
    /* Perform rotations to restore balance in this subtree */\nfn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {\n    // Get the balance factor of node\n    let balance_factor = Self::balance_factor(node.clone());\n    // Left-heavy tree\n    if balance_factor > 1 {\n        let node = node.unwrap();\n        if Self::balance_factor(node.borrow().left.clone()) >= 0 {\n            // Rotate right\n            Self::right_rotate(Some(node))\n        } else {\n            // Rotate left, then right\n            let left = node.borrow().left.clone();\n            node.borrow_mut().left = Self::left_rotate(left);\n            Self::right_rotate(Some(node))\n        }\n    }\n    // Right-heavy tree\n    else if balance_factor < -1 {\n        let node = node.unwrap();\n        if Self::balance_factor(node.borrow().right.clone()) <= 0 {\n            // Rotate left\n            Self::left_rotate(Some(node))\n        } else {\n            // Rotate right, then left\n            let right = node.borrow().right.clone();\n            node.borrow_mut().right = Self::right_rotate(right);\n            Self::left_rotate(Some(node))\n        }\n    } else {\n        // The tree is balanced; return without rotating\n        node\n    }\n}\n
    avl_tree.c
    /* Perform rotations to restore balance in this subtree */\nTreeNode *rotate(TreeNode *node) {\n    // Get the balance factor of node\n    int bf = balanceFactor(node);\n    // Left-heavy tree\n    if (bf > 1) {\n        if (balanceFactor(node->left) >= 0) {\n            // Rotate right\n            return rightRotate(node);\n        } else {\n            // Rotate left, then right\n            node->left = leftRotate(node->left);\n            return rightRotate(node);\n        }\n    }\n    // Right-heavy tree\n    if (bf < -1) {\n        if (balanceFactor(node->right) <= 0) {\n            // Rotate left\n            return leftRotate(node);\n        } else {\n            // Rotate right, then left\n            node->right = rightRotate(node->right);\n            return leftRotate(node);\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node;\n}\n
    avl_tree.kt
    /* Perform rotations to restore balance in this subtree */\nfun rotate(node: TreeNode): TreeNode {\n    // Get the balance factor of node\n    val balanceFactor = balanceFactor(node)\n    // Left-heavy tree\n    if (balanceFactor > 1) {\n        if (balanceFactor(node.left) >= 0) {\n            // Rotate right\n            return rightRotate(node)\n        } else {\n            // Rotate left, then right\n            node.left = leftRotate(node.left)\n            return rightRotate(node)\n        }\n    }\n    // Right-heavy tree\n    if (balanceFactor < -1) {\n        if (balanceFactor(node.right) <= 0) {\n            // Rotate left\n            return leftRotate(node)\n        } else {\n            // Rotate right, then left\n            node.right = rightRotate(node.right)\n            return leftRotate(node)\n        }\n    }\n    // The tree is balanced; return without rotating\n    return node\n}\n
    avl_tree.rb
    ### Perform rotations to restore balance in this subtree ###\ndef rotate(node)\n  # Get the balance factor of node\n  balance_factor = balance_factor(node)\n  # Left-heavy tree\n  if balance_factor > 1\n    if balance_factor(node.left) >= 0\n      # Rotate right\n      return right_rotate(node)\n    else\n      # Rotate left, then right\n      node.left = left_rotate(node.left)\n      return right_rotate(node)\n    end\n  # Right-heavy tree\n  elsif balance_factor < -1\n    if balance_factor(node.right) <= 0\n      # Rotate left\n      return left_rotate(node)\n    else\n      # Rotate right, then left\n      node.right = right_rotate(node.right)\n      return left_rotate(node)\n    end\n  end\n  # The tree is balanced; return without rotating\n  node\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#753-common-avl-tree-operations","level":2,"title":"7.5.3   Common AVL tree operations","text":"","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1-inserting-a-node","level":3,"title":"1.   Inserting a node","text":"

    Insertion largely follows the binary-search-tree procedure. The difference is that inserting a node may unbalance nodes along the path to the root. Starting at the inserted node, work upward and perform rotations to restore balance at every affected node:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def insert(self, val):\n    \"\"\"Insert a node\"\"\"\n    self._root = self.insert_helper(self._root, val)\n\ndef insert_helper(self, node: TreeNode | None, val: int) -> TreeNode:\n    \"\"\"Insert a node recursively (helper method)\"\"\"\n    if node is None:\n        return TreeNode(val)\n    # 1. Find the insertion position and insert the node\n    if val < node.val:\n        node.left = self.insert_helper(node.left, val)\n    elif val > node.val:\n        node.right = self.insert_helper(node.right, val)\n    else:\n        # Return without inserting a duplicate node\n        return node\n    # Update the node height\n    self.update_height(node)\n    # 2. Perform rotations to restore balance in this subtree\n    return self.rotate(node)\n
    avl_tree.cpp
    /* Insert a node */\nvoid insert(int val) {\n    root = insertHelper(root, val);\n}\n\n/* Insert a node recursively (helper method) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n    if (node == nullptr)\n        return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node->val)\n        node->left = insertHelper(node->left, val);\n    else if (val > node->val)\n        node->right = insertHelper(node->right, val);\n    else\n        return node;    // Return without inserting a duplicate node\n    updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.java
    /* Insert a node */\nvoid insert(int val) {\n    root = insertHelper(root, val);\n}\n\n/* Insert a node recursively (helper method) */\nTreeNode insertHelper(TreeNode node, int val) {\n    if (node == null)\n        return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node.val)\n        node.left = insertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = insertHelper(node.right, val);\n    else\n        return node; // Return without inserting a duplicate node\n    updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the subtree root\n    return node;\n}\n
    avl_tree.cs
    /* Insert a node */\nvoid Insert(int val) {\n    root = InsertHelper(root, val);\n}\n\n/* Insert a node recursively (helper method) */\nTreeNode? InsertHelper(TreeNode? node, int val) {\n    if (node == null) return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node.val)\n        node.left = InsertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = InsertHelper(node.right, val);\n    else\n        return node;     // Return without inserting a duplicate node\n    UpdateHeight(node);  // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = Rotate(node);\n    // Return the subtree root\n    return node;\n}\n
    avl_tree.go
    /* Insert a node */\nfunc (t *aVLTree) insert(val int) {\n    t.root = t.insertHelper(t.root, val)\n}\n\n/* Insert a node recursively (helper function) */\nfunc (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode {\n    if node == nil {\n        return NewTreeNode(val)\n    }\n    /* 1. Find the insertion position and insert the node */\n    if val < node.Val.(int) {\n        node.Left = t.insertHelper(node.Left, val)\n    } else if val > node.Val.(int) {\n        node.Right = t.insertHelper(node.Right, val)\n    } else {\n        // Return without inserting a duplicate node\n        return node\n    }\n    // Update the node height\n    t.updateHeight(node)\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = t.rotate(node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.swift
    /* Insert a node */\nfunc insert(val: Int) {\n    root = insertHelper(node: root, val: val)\n}\n\n/* Insert a node recursively (helper method) */\nfunc insertHelper(node: TreeNode?, val: Int) -> TreeNode? {\n    var node = node\n    if node == nil {\n        return TreeNode(x: val)\n    }\n    /* 1. Find the insertion position and insert the node */\n    if val < node!.val {\n        node?.left = insertHelper(node: node?.left, val: val)\n    } else if val > node!.val {\n        node?.right = insertHelper(node: node?.right, val: val)\n    } else {\n        return node // Return without inserting a duplicate node\n    }\n    updateHeight(node: node) // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node: node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.js
    /* Insert a node */\ninsert(val) {\n    this.root = this.#insertHelper(this.root, val);\n}\n\n/* Insert a node recursively (helper method) */\n#insertHelper(node, val) {\n    if (node === null) return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node.val) node.left = this.#insertHelper(node.left, val);\n    else if (val > node.val)\n        node.right = this.#insertHelper(node.right, val);\n    else return node; // Return without inserting a duplicate node\n    this.#updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = this.#rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.ts
    /* Insert a node */\ninsert(val: number): void {\n    this.root = this.insertHelper(this.root, val);\n}\n\n/* Insert a node recursively (helper method) */\ninsertHelper(node: TreeNode, val: number): TreeNode {\n    if (node === null) return new TreeNode(val);\n    /* 1. Find the insertion position and insert the node */\n    if (val < node.val) {\n        node.left = this.insertHelper(node.left, val);\n    } else if (val > node.val) {\n        node.right = this.insertHelper(node.right, val);\n    } else {\n        return node; // Return without inserting a duplicate node\n    }\n    this.updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = this.rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.dart
    /* Insert a node */\nvoid insert(int val) {\n  root = insertHelper(root, val);\n}\n\n/* Insert a node recursively (helper method) */\nTreeNode? insertHelper(TreeNode? node, int val) {\n  if (node == null) return TreeNode(val);\n  /* 1. Find the insertion position and insert the node */\n  if (val < node.val)\n    node.left = insertHelper(node.left, val);\n  else if (val > node.val)\n    node.right = insertHelper(node.right, val);\n  else\n    return node; // Return without inserting a duplicate node\n  updateHeight(node); // Update the node height\n  /* 2. Perform rotations to restore balance in this subtree */\n  node = rotate(node);\n  // Return the root of the subtree\n  return node;\n}\n
    avl_tree.rs
    /* Insert a node */\nfn insert(&mut self, val: i32) {\n    self.root = Self::insert_helper(self.root.clone(), val);\n}\n\n/* Insert a node recursively (helper method) */\nfn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {\n    match node {\n        Some(mut node) => {\n            /* 1. Find the insertion position and insert the node */\n            match {\n                let node_val = node.borrow().val;\n                node_val\n            }\n            .cmp(&val)\n            {\n                Ordering::Greater => {\n                    let left = node.borrow().left.clone();\n                    node.borrow_mut().left = Self::insert_helper(left, val);\n                }\n                Ordering::Less => {\n                    let right = node.borrow().right.clone();\n                    node.borrow_mut().right = Self::insert_helper(right, val);\n                }\n                Ordering::Equal => {\n                    return Some(node); // Return without inserting a duplicate node\n                }\n            }\n            Self::update_height(Some(node.clone())); // Update the node height\n\n            /* 2. Perform rotations to restore balance in this subtree */\n            node = Self::rotate(Some(node)).unwrap();\n            // Return the root of the subtree\n            Some(node)\n        }\n        None => Some(TreeNode::new(val)),\n    }\n}\n
    avl_tree.c
    /* Insert a node */\nvoid insert(AVLTree *tree, int val) {\n    tree->root = insertHelper(tree->root, val);\n}\n\n/* Recursively insert a node (helper function) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n    if (node == NULL) {\n        return newTreeNode(val);\n    }\n    /* 1. Find the insertion position and insert the node */\n    if (val < node->val) {\n        node->left = insertHelper(node->left, val);\n    } else if (val > node->val) {\n        node->right = insertHelper(node->right, val);\n    } else {\n        // Return without inserting a duplicate node\n        return node;\n    }\n    // Update the node height\n    updateHeight(node);\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.kt
    /* Insert a node */\nfun insert(_val: Int) {\n    root = insertHelper(root, _val)\n}\n\n/* Insert a node recursively (helper method) */\nfun insertHelper(n: TreeNode?, _val: Int): TreeNode {\n    if (n == null)\n        return TreeNode(_val)\n    var node = n\n    /* 1. Find the insertion position and insert the node */\n    if (_val < node._val)\n        node.left = insertHelper(node.left, _val)\n    else if (_val > node._val)\n        node.right = insertHelper(node.right, _val)\n    else\n        return node // Return without inserting a duplicate node\n    updateHeight(node) // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.rb
    ### Insert a node ###\ndef insert(val)\n  @root = insert_helper(@root, val)\nend\n\n### Insert a node recursively (helper method)###\ndef insert_helper(node, val)\n  return TreeNode.new(val) if node.nil?\n  # 1. Find the insertion position and insert the node\n  if val < node.val\n    node.left = insert_helper(node.left, val)\n  elsif val > node.val\n    node.right = insert_helper(node.right, val)\n  else\n    # Return without inserting a duplicate node\n    return node\n  end\n  # Update the node height\n  update_height(node)\n  # 2. Perform rotations to restore balance in this subtree\n  rotate(node)\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2-deleting-a-node","level":3,"title":"2.   Deleting a node","text":"

    Similarly, extend binary-search-tree deletion with rotations from the bottom up to restore balance at every affected node:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py
    def remove(self, val: int):\n    \"\"\"Remove a node\"\"\"\n    self._root = self.remove_helper(self._root, val)\n\ndef remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None:\n    \"\"\"Remove a node recursively (helper method)\"\"\"\n    if node is None:\n        return None\n    # 1. Find and remove the node\n    if val < node.val:\n        node.left = self.remove_helper(node.left, val)\n    elif val > node.val:\n        node.right = self.remove_helper(node.right, val)\n    else:\n        if node.left is None or node.right is None:\n            child = node.left or node.right\n            # Number of children = 0: remove node and return\n            if child is None:\n                return None\n            # Number of children = 1: remove node directly\n            else:\n                node = child\n        else:\n            # Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            temp = node.right\n            while temp.left is not None:\n                temp = temp.left\n            node.right = self.remove_helper(node.right, temp.val)\n            node.val = temp.val\n    # Update the node height\n    self.update_height(node)\n    # 2. Perform rotations to restore balance in this subtree\n    return self.rotate(node)\n
    avl_tree.cpp
    /* Remove a node */\nvoid remove(int val) {\n    root = removeHelper(root, val);\n}\n\n/* Remove a node recursively (helper method) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n    if (node == nullptr)\n        return nullptr;\n    /* 1. Find and remove the node */\n    if (val < node->val)\n        node->left = removeHelper(node->left, val);\n    else if (val > node->val)\n        node->right = removeHelper(node->right, val);\n    else {\n        if (node->left == nullptr || node->right == nullptr) {\n            TreeNode *child = node->left != nullptr ? node->left : node->right;\n            // Number of children = 0: remove node and return\n            if (child == nullptr) {\n                delete node;\n                return nullptr;\n            }\n            // Number of children = 1: remove node directly\n            else {\n                delete node;\n                node = child;\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            TreeNode *temp = node->right;\n            while (temp->left != nullptr) {\n                temp = temp->left;\n            }\n            int tempVal = temp->val;\n            node->right = removeHelper(node->right, temp->val);\n            node->val = tempVal;\n        }\n    }\n    updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.java
    /* Remove a node */\nvoid remove(int val) {\n    root = removeHelper(root, val);\n}\n\n/* Remove a node recursively (helper method) */\nTreeNode removeHelper(TreeNode node, int val) {\n    if (node == null)\n        return null;\n    /* 1. Find and remove the node */\n    if (val < node.val)\n        node.left = removeHelper(node.left, val);\n    else if (val > node.val)\n        node.right = removeHelper(node.right, val);\n    else {\n        if (node.left == null || node.right == null) {\n            TreeNode child = node.left != null ? node.left : node.right;\n            // Number of children = 0: remove node and return\n            if (child == null)\n                return null;\n            // Number of children = 1: remove node directly\n            else\n                node = child;\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            TreeNode temp = node.right;\n            while (temp.left != null) {\n                temp = temp.left;\n            }\n            node.right = removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the subtree root\n    return node;\n}\n
    avl_tree.cs
    /* Remove a node */\nvoid Remove(int val) {\n    root = RemoveHelper(root, val);\n}\n\n/* Remove a node recursively (helper method) */\nTreeNode? RemoveHelper(TreeNode? node, int val) {\n    if (node == null) return null;\n    /* 1. Find and remove the node */\n    if (val < node.val)\n        node.left = RemoveHelper(node.left, val);\n    else if (val > node.val)\n        node.right = RemoveHelper(node.right, val);\n    else {\n        if (node.left == null || node.right == null) {\n            TreeNode? child = node.left ?? node.right;\n            // Number of children = 0: remove node and return\n            if (child == null)\n                return null;\n            // Number of children = 1: remove node directly\n            else\n                node = child;\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            TreeNode? temp = node.right;\n            while (temp.left != null) {\n                temp = temp.left;\n            }\n            node.right = RemoveHelper(node.right, temp.val!.Value);\n            node.val = temp.val;\n        }\n    }\n    UpdateHeight(node);  // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = Rotate(node);\n    // Return the subtree root\n    return node;\n}\n
    avl_tree.go
    /* Remove a node */\nfunc (t *aVLTree) remove(val int) {\n    t.root = t.removeHelper(t.root, val)\n}\n\n/* Remove a node recursively (helper function) */\nfunc (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode {\n    if node == nil {\n        return nil\n    }\n    /* 1. Find and remove the node */\n    if val < node.Val.(int) {\n        node.Left = t.removeHelper(node.Left, val)\n    } else if val > node.Val.(int) {\n        node.Right = t.removeHelper(node.Right, val)\n    } else {\n        if node.Left == nil || node.Right == nil {\n            child := node.Left\n            if node.Right != nil {\n                child = node.Right\n            }\n            if child == nil {\n                // Number of children = 0: remove node and return\n                return nil\n            } else {\n                // Number of children = 1: remove node directly\n                node = child\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            temp := node.Right\n            for temp.Left != nil {\n                temp = temp.Left\n            }\n            node.Right = t.removeHelper(node.Right, temp.Val.(int))\n            node.Val = temp.Val\n        }\n    }\n    // Update the node height\n    t.updateHeight(node)\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = t.rotate(node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.swift
    /* Remove a node */\nfunc remove(val: Int) {\n    root = removeHelper(node: root, val: val)\n}\n\n/* Remove a node recursively (helper method) */\nfunc removeHelper(node: TreeNode?, val: Int) -> TreeNode? {\n    var node = node\n    if node == nil {\n        return nil\n    }\n    /* 1. Find and remove the node */\n    if val < node!.val {\n        node?.left = removeHelper(node: node?.left, val: val)\n    } else if val > node!.val {\n        node?.right = removeHelper(node: node?.right, val: val)\n    } else {\n        if node?.left == nil || node?.right == nil {\n            let child = node?.left ?? node?.right\n            // Number of children = 0: remove node and return\n            if child == nil {\n                return nil\n            }\n            // Number of children = 1: remove node directly\n            else {\n                node = child\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            var temp = node?.right\n            while temp?.left != nil {\n                temp = temp?.left\n            }\n            node?.right = removeHelper(node: node?.right, val: temp!.val)\n            node?.val = temp!.val\n        }\n    }\n    updateHeight(node: node) // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node: node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.js
    /* Remove a node */\nremove(val) {\n    this.root = this.#removeHelper(this.root, val);\n}\n\n/* Remove a node recursively (helper method) */\n#removeHelper(node, val) {\n    if (node === null) return null;\n    /* 1. Find and remove the node */\n    if (val < node.val) node.left = this.#removeHelper(node.left, val);\n    else if (val > node.val)\n        node.right = this.#removeHelper(node.right, val);\n    else {\n        if (node.left === null || node.right === null) {\n            const child = node.left !== null ? node.left : node.right;\n            // Number of children = 0: remove node and return\n            if (child === null) return null;\n            // Number of children = 1: remove node directly\n            else node = child;\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            let temp = node.right;\n            while (temp.left !== null) {\n                temp = temp.left;\n            }\n            node.right = this.#removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    this.#updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = this.#rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.ts
    /* Remove a node */\nremove(val: number): void {\n    this.root = this.removeHelper(this.root, val);\n}\n\n/* Remove a node recursively (helper method) */\nremoveHelper(node: TreeNode, val: number): TreeNode {\n    if (node === null) return null;\n    /* 1. Find and remove the node */\n    if (val < node.val) {\n        node.left = this.removeHelper(node.left, val);\n    } else if (val > node.val) {\n        node.right = this.removeHelper(node.right, val);\n    } else {\n        if (node.left === null || node.right === null) {\n            const child = node.left !== null ? node.left : node.right;\n            // Number of children = 0: remove node and return\n            if (child === null) {\n                return null;\n            } else {\n                // Number of children = 1: remove node directly\n                node = child;\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            let temp = node.right;\n            while (temp.left !== null) {\n                temp = temp.left;\n            }\n            node.right = this.removeHelper(node.right, temp.val);\n            node.val = temp.val;\n        }\n    }\n    this.updateHeight(node); // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = this.rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.dart
    /* Remove a node */\nvoid remove(int val) {\n  root = removeHelper(root, val);\n}\n\n/* Remove a node recursively (helper method) */\nTreeNode? removeHelper(TreeNode? node, int val) {\n  if (node == null) return null;\n  /* 1. Find and remove the node */\n  if (val < node.val)\n    node.left = removeHelper(node.left, val);\n  else if (val > node.val)\n    node.right = removeHelper(node.right, val);\n  else {\n    if (node.left == null || node.right == null) {\n      TreeNode? child = node.left ?? node.right;\n      // Number of children = 0: remove node and return\n      if (child == null)\n        return null;\n      // Number of children = 1: remove node directly\n      else\n        node = child;\n    } else {\n      // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n      TreeNode? temp = node.right;\n      while (temp!.left != null) {\n        temp = temp.left;\n      }\n      node.right = removeHelper(node.right, temp.val);\n      node.val = temp.val;\n    }\n  }\n  updateHeight(node); // Update the node height\n  /* 2. Perform rotations to restore balance in this subtree */\n  node = rotate(node);\n  // Return the root of the subtree\n  return node;\n}\n
    avl_tree.rs
    /* Remove a node */\nfn remove(&self, val: i32) {\n    Self::remove_helper(self.root.clone(), val);\n}\n\n/* Remove a node recursively (helper method) */\nfn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {\n    match node {\n        Some(mut node) => {\n            /* 1. Find and remove the node */\n            if val < node.borrow().val {\n                let left = node.borrow().left.clone();\n                node.borrow_mut().left = Self::remove_helper(left, val);\n            } else if val > node.borrow().val {\n                let right = node.borrow().right.clone();\n                node.borrow_mut().right = Self::remove_helper(right, val);\n            } else if node.borrow().left.is_none() || node.borrow().right.is_none() {\n                let child = if node.borrow().left.is_some() {\n                    node.borrow().left.clone()\n                } else {\n                    node.borrow().right.clone()\n                };\n                match child {\n                    // Number of children = 0: remove node and return\n                    None => {\n                        return None;\n                    }\n                    // Number of children = 1: remove node directly\n                    Some(child) => node = child,\n                }\n            } else {\n                // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n                let mut temp = node.borrow().right.clone().unwrap();\n                loop {\n                    let temp_left = temp.borrow().left.clone();\n                    if temp_left.is_none() {\n                        break;\n                    }\n                    temp = temp_left.unwrap();\n                }\n                let right = node.borrow().right.clone();\n                node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val);\n                node.borrow_mut().val = temp.borrow().val;\n            }\n            Self::update_height(Some(node.clone())); // Update the node height\n\n            /* 2. Perform rotations to restore balance in this subtree */\n            node = Self::rotate(Some(node)).unwrap();\n            // Return the root of the subtree\n            Some(node)\n        }\n        None => None,\n    }\n}\n
    avl_tree.c
    /* Remove a node */\n// The name remove conflicts with the function declared in stdio.h\nvoid removeItem(AVLTree *tree, int val) {\n    TreeNode *root = removeHelper(tree->root, val);\n}\n\n/* Recursively remove a node (helper function) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n    TreeNode *child, *grandChild;\n    if (node == NULL) {\n        return NULL;\n    }\n    /* 1. Find and remove the node */\n    if (val < node->val) {\n        node->left = removeHelper(node->left, val);\n    } else if (val > node->val) {\n        node->right = removeHelper(node->right, val);\n    } else {\n        if (node->left == NULL || node->right == NULL) {\n            child = node->left;\n            if (node->right != NULL) {\n                child = node->right;\n            }\n            // Number of children = 0: remove node and return\n            if (child == NULL) {\n                return NULL;\n            } else {\n                // Number of children = 1: remove node directly\n                node = child;\n            }\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            TreeNode *temp = node->right;\n            while (temp->left != NULL) {\n                temp = temp->left;\n            }\n            int tempVal = temp->val;\n            node->right = removeHelper(node->right, temp->val);\n            node->val = tempVal;\n        }\n    }\n    // Update the node height\n    updateHeight(node);\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node);\n    // Return the root of the subtree\n    return node;\n}\n
    avl_tree.kt
    /* Remove a node */\nfun remove(_val: Int) {\n    root = removeHelper(root, _val)\n}\n\n/* Remove a node recursively (helper method) */\nfun removeHelper(n: TreeNode?, _val: Int): TreeNode? {\n    var node = n ?: return null\n    /* 1. Find and remove the node */\n    if (_val < node._val)\n        node.left = removeHelper(node.left, _val)\n    else if (_val > node._val)\n        node.right = removeHelper(node.right, _val)\n    else {\n        if (node.left == null || node.right == null) {\n            val child = if (node.left != null)\n                node.left\n            else\n                node.right\n            // Number of children = 0: remove node and return\n            if (child == null)\n                return null\n            // Number of children = 1: remove node directly\n            else\n                node = child\n        } else {\n            // Number of children = 2: remove the inorder successor and use its value to replace the current node value\n            var temp = node.right\n            while (temp!!.left != null) {\n                temp = temp.left\n            }\n            node.right = removeHelper(node.right, temp._val)\n            node._val = temp._val\n        }\n    }\n    updateHeight(node) // Update the node height\n    /* 2. Perform rotations to restore balance in this subtree */\n    node = rotate(node)\n    // Return the subtree root\n    return node\n}\n
    avl_tree.rb
    ### Remove a node ###\ndef remove(val)\n  @root = remove_helper(@root, val)\nend\n\n### Remove a node recursively (helper method)###\ndef remove_helper(node, val)\n  return if node.nil?\n  # 1. Find and remove the node\n  if val < node.val\n    node.left = remove_helper(node.left, val)\n  elsif val > node.val\n    node.right = remove_helper(node.right, val)\n  else\n    if node.left.nil? || node.right.nil?\n      child = node.left || node.right\n      # Number of children = 0: remove node and return\n      return if child.nil?\n      # Number of children = 1: remove node directly\n      node = child\n    else\n      # Number of children = 2: remove the inorder successor and use its value to replace the current node value\n      temp = node.right\n      while !temp.left.nil?\n        temp = temp.left\n      end\n      node.right = remove_helper(node.right, temp.val)\n      node.val = temp.val\n    end\n  end\n  # Update the node height\n  update_height(node)\n  # 2. Perform rotations to restore balance in this subtree\n  rotate(node)\nend\n
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3-finding-a-node","level":3,"title":"3.   Finding a node","text":"

    Lookup is identical to binary-search-tree lookup, so we will not repeat it here.

    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/avl_tree/#754-typical-applications","level":2,"title":"7.5.4   Typical applications","text":"
    • Organizing and storing large datasets, particularly when lookups are frequent and insertions and deletions are less frequent.
    • Building database indexing systems.
    • Red-black trees are another common kind of balanced binary search tree. Their balance requirements are looser than those of AVL trees, so insertion and deletion need fewer rotations and are more efficient on average.
    ","path":["Chapter 7.   Trees","7.5   AVL trees *"],"tags":[]},{"location":"chapter_tree/binary_search_tree/","level":1,"title":"7.4   Binary search trees","text":"

    A binary search tree satisfies the following conditions, as shown below.

    1. Every value in the left subtree is \\(<\\) the root value, which is \\(<\\) every value in the right subtree.
    2. The left and right subtrees of every node are also binary search trees and satisfy condition 1..

    Figure 7-16   A binary search tree

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#741-binary-search-tree-operations","level":2,"title":"7.4.1   Binary search tree operations","text":"

    We encapsulate the tree in a BinarySearchTree class with a member variable root pointing to its root node.

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#1-finding-a-node","level":3,"title":"1.   Finding a node","text":"

    To find a target value num, use the tree's ordering property. As shown below, initialize a node reference cur at root and repeatedly compare cur.val with num.

    • If cur.val < num, the target lies in the right subtree, so set cur = cur.right.
    • If cur.val > num, the target lies in the left subtree, so set cur = cur.left.
    • If cur.val = num, the target has been found; exit the loop and return the node.
    <1><2><3><4>

    Figure 7-17   Finding a node in a binary search tree

    Searching a binary search tree follows the same principle as binary search: each round eliminates half the possibilities. The number of iterations is at most the tree's height, giving \\(O(\\log n)\\) time when the tree is balanced. An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
    def search(self, num: int) -> TreeNode | None:\n    \"\"\"Find a node\"\"\"\n    cur = self._root\n    # Search in a loop, stopping if cur becomes None\n    while cur is not None:\n        # If present, the target node is in the right subtree of cur\n        if cur.val < num:\n            cur = cur.right\n        # If present, the target node is in the left subtree of cur\n        elif cur.val > num:\n            cur = cur.left\n        # The target node was found; exit the loop\n        else:\n            break\n    return cur\n
    binary_search_tree.cpp
    /* Find a node */\nTreeNode *search(int num) {\n    TreeNode *cur = root;\n    // Continue searching while the current node pointer is not null\n    while (cur != nullptr) {\n        // The target node lies in the right subtree of cur\n        if (cur->val < num)\n            cur = cur->right;\n        // The target node lies in the left subtree of cur\n        else if (cur->val > num)\n            cur = cur->left;\n        // The target node was found; exit the loop\n        else\n            break;\n    }\n    // Return the target node, or nullptr if it was not found\n    return cur;\n}\n
    binary_search_tree.java
    /* Find a node */\nTreeNode search(int num) {\n    TreeNode cur = root;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != null) {\n        // The target node lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The target node lies in the left subtree of cur\n        else if (cur.val > num)\n            cur = cur.left;\n        // The target node was found; exit the loop\n        else\n            break;\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.cs
    /* Find a node */\nTreeNode? Search(int num) {\n    TreeNode? cur = root;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur != null) {\n        // The target node lies in the right subtree of cur\n        if (cur.val < num) cur =\n            cur.right;\n        // The target node lies in the left subtree of cur\n        else if (cur.val > num)\n            cur = cur.left;\n        // The target node was found; exit the loop\n        else\n            break;\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.go
    /* Find a node */\nfunc (bst *binarySearchTree) search(num int) *TreeNode {\n    node := bst.root\n    // Continue searching while the current node is not nil\n    for node != nil {\n        if node.Val.(int) < num {\n            // If the target node exists, it lies in the right subtree of node\n            node = node.Right\n        } else if node.Val.(int) > num {\n            // If the target node exists, it lies in the left subtree of node\n            node = node.Left\n        } else {\n            // The target node was found; exit the loop\n            break\n        }\n    }\n    // Return the target node, or nil if it was not found\n    return node\n}\n
    binary_search_tree.swift
    /* Find a node */\nfunc search(num: Int) -> TreeNode? {\n    var cur = root\n    // Search in a loop, stopping if cur becomes nil\n    while cur != nil {\n        // If present, the target node lies in the right subtree of cur\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // If present, the target node lies in the left subtree of cur\n        else if cur!.val > num {\n            cur = cur?.left\n        }\n        // The target node was found; exit the loop\n        else {\n            break\n        }\n    }\n    // Return the target node, or nil if it was not found\n    return cur\n}\n
    binary_search_tree.js
    /* Find a node */\nsearch(num) {\n    let cur = this.root;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur !== null) {\n        // The target node lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The target node lies in the left subtree of cur\n        else if (cur.val > num) cur = cur.left;\n        // The target node was found; exit the loop\n        else break;\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.ts
    /* Find a node */\nsearch(num: number): TreeNode | null {\n    let cur = this.root;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur !== null) {\n        // The target node lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The target node lies in the left subtree of cur\n        else if (cur.val > num) cur = cur.left;\n        // The target node was found; exit the loop\n        else break;\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.dart
    /* Find a node */\nTreeNode? search(int _num) {\n  TreeNode? cur = _root;\n  // Search in a loop, stopping if a null reference is reached\n  while (cur != null) {\n    // The target node lies in the right subtree of cur\n    if (cur.val < _num)\n      cur = cur.right;\n    // The target node lies in the left subtree of cur\n    else if (cur.val > _num)\n      cur = cur.left;\n    // The target node was found; exit the loop\n    else\n      break;\n  }\n  // Return the target node\n  return cur;\n}\n
    binary_search_tree.rs
    /* Find a node */\npub fn search(&self, num: i32) -> OptionTreeNodeRc {\n    let mut cur = self.root.clone();\n    // Search while cur refers to a node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // The target node lies in the right subtree of cur\n            Ordering::Greater => cur = node.borrow().right.clone(),\n            // The target node lies in the left subtree of cur\n            Ordering::Less => cur = node.borrow().left.clone(),\n            // The target node was found; exit the loop\n            Ordering::Equal => break,\n        }\n    }\n\n    // Return the matching node, or None if no match was found\n    cur\n}\n
    binary_search_tree.c
    /* Find a node */\nTreeNode *search(BinarySearchTree *bst, int num) {\n    TreeNode *cur = bst->root;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != NULL) {\n        if (cur->val < num) {\n            // The target node lies in the right subtree of cur\n            cur = cur->right;\n        } else if (cur->val > num) {\n            // The target node lies in the left subtree of cur\n            cur = cur->left;\n        } else {\n            // The target node was found; exit the loop\n            break;\n        }\n    }\n    // Return the target node\n    return cur;\n}\n
    binary_search_tree.kt
    /* Find a node */\nfun search(num: Int): TreeNode? {\n    var cur = root\n    // Continue searching while cur is not null\n    while (cur != null) {\n        // The target node lies in the right subtree of cur\n        cur = if (cur._val < num)\n            cur.right\n        // The target node lies in the left subtree of cur\n        else if (cur._val > num)\n            cur.left\n        // The target node was found; exit the loop\n        else\n            break\n    }\n    // Return the target node\n    return cur\n}\n
    binary_search_tree.rb
    ### Find a node ###\ndef search(num)\n  cur = @root\n\n  # Search in a loop, stopping if cur becomes nil\n  while !cur.nil?\n    # The target node lies in the right subtree of cur\n    if cur.val < num\n      cur = cur.right\n    # The target node lies in the left subtree of cur\n    elsif cur.val > num\n      cur = cur.left\n    # The target node was found; exit the loop\n    else\n      break\n    end\n  end\n\n  cur\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#2-inserting-a-node","level":3,"title":"2.   Inserting a node","text":"

    To insert num while preserving the ordering “left subtree < root < right subtree,” follow the process below.

    1. Find the insertion position: as in a search, start at the root and move down according to comparisons with num. Stop when the traversal passes a leaf and reaches None.
    2. Insert at that position: create a node containing num and place it at the None position.

    Figure 7-18   Inserting a node into a binary search tree

    Keep two details in mind when implementing this.

    • Duplicate values are not allowed under this definition. If the value already exists, return without inserting it.
    • Keep a reference pre to the node from the previous iteration. When the traversal reaches None, pre gives its parent so that the new node can be attached.
    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
    def insert(self, num: int):\n    \"\"\"Insert a node\"\"\"\n    # If the tree is empty, initialize the root node\n    if self._root is None:\n        self._root = TreeNode(num)\n        return\n    # Search in a loop, stopping if cur becomes None\n    cur, pre = self._root, None\n    while cur is not None:\n        # Return immediately if a duplicate node is found\n        if cur.val == num:\n            return\n        pre = cur\n        # The insertion position lies in the right subtree of cur\n        if cur.val < num:\n            cur = cur.right\n        # The insertion position lies in the left subtree of cur\n        else:\n            cur = cur.left\n    # Insert a node\n    node = TreeNode(num)\n    if pre.val < num:\n        pre.right = node\n    else:\n        pre.left = node\n
    binary_search_tree.cpp
    /* Insert a node */\nvoid insert(int num) {\n    // If the tree is empty, initialize the root node\n    if (root == nullptr) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode *cur = root, *pre = nullptr;\n    // Continue searching while the current node pointer is not null\n    while (cur != nullptr) {\n        // Return immediately if a duplicate node is found\n        if (cur->val == num)\n            return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur->val < num)\n            cur = cur->right;\n        // The insertion position lies in the left subtree of cur\n        else\n            cur = cur->left;\n    }\n    // Insert a node\n    TreeNode *node = new TreeNode(num);\n    if (pre->val < num)\n        pre->right = node;\n    else\n        pre->left = node;\n}\n
    binary_search_tree.java
    /* Insert a node */\nvoid insert(int num) {\n    // If the tree is empty, initialize the root node\n    if (root == null) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode cur = root, pre = null;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != null) {\n        // Return immediately if a duplicate node is found\n        if (cur.val == num)\n            return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The insertion position lies in the left subtree of cur\n        else\n            cur = cur.left;\n    }\n    // Insert a node\n    TreeNode node = new TreeNode(num);\n    if (pre.val < num)\n        pre.right = node;\n    else\n        pre.left = node;\n}\n
    binary_search_tree.cs
    /* Insert a node */\nvoid Insert(int num) {\n    // If the tree is empty, initialize the root node\n    if (root == null) {\n        root = new TreeNode(num);\n        return;\n    }\n    TreeNode? cur = root, pre = null;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur != null) {\n        // Return immediately if a duplicate node is found\n        if (cur.val == num)\n            return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The insertion position lies in the left subtree of cur\n        else\n            cur = cur.left;\n    }\n\n    // Insert a node\n    TreeNode node = new(num);\n    if (pre != null) {\n        if (pre.val < num)\n            pre.right = node;\n        else\n            pre.left = node;\n    }\n}\n
    binary_search_tree.go
    /* Insert a node */\nfunc (bst *binarySearchTree) insert(num int) {\n    cur := bst.root\n    // If the tree is empty, initialize the root node\n    if cur == nil {\n        bst.root = NewTreeNode(num)\n        return\n    }\n    // Track the parent of the node to insert\n    var pre *TreeNode = nil\n    // Continue searching while the current node is not nil\n    for cur != nil {\n        if cur.Val == num {\n            return\n        }\n        pre = cur\n        if cur.Val.(int) < num {\n            cur = cur.Right\n        } else {\n            cur = cur.Left\n        }\n    }\n    // Insert a node\n    node := NewTreeNode(num)\n    if pre.Val.(int) < num {\n        pre.Right = node\n    } else {\n        pre.Left = node\n    }\n}\n
    binary_search_tree.swift
    /* Insert a node */\nfunc insert(num: Int) {\n    // If the tree is empty, initialize the root node\n    if root == nil {\n        root = TreeNode(x: num)\n        return\n    }\n    var cur = root\n    var pre: TreeNode?\n    // Search in a loop, stopping if cur becomes nil\n    while cur != nil {\n        // Return immediately if a duplicate node is found\n        if cur!.val == num {\n            return\n        }\n        pre = cur\n        // The insertion position lies in the right subtree of cur\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // The insertion position lies in the left subtree of cur\n        else {\n            cur = cur?.left\n        }\n    }\n    // Insert a node\n    let node = TreeNode(x: num)\n    if pre!.val < num {\n        pre?.right = node\n    } else {\n        pre?.left = node\n    }\n}\n
    binary_search_tree.js
    /* Insert a node */\ninsert(num) {\n    // If the tree is empty, initialize the root node\n    if (this.root === null) {\n        this.root = new TreeNode(num);\n        return;\n    }\n    let cur = this.root,\n        pre = null;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur !== null) {\n        // Return immediately if a duplicate node is found\n        if (cur.val === num) return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The insertion position lies in the left subtree of cur\n        else cur = cur.left;\n    }\n    // Insert a node\n    const node = new TreeNode(num);\n    if (pre.val < num) pre.right = node;\n    else pre.left = node;\n}\n
    binary_search_tree.ts
    /* Insert a node */\ninsert(num: number): void {\n    // If the tree is empty, initialize the root node\n    if (this.root === null) {\n        this.root = new TreeNode(num);\n        return;\n    }\n    let cur: TreeNode | null = this.root,\n        pre: TreeNode | null = null;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur !== null) {\n        // Return immediately if a duplicate node is found\n        if (cur.val === num) return;\n        pre = cur;\n        // The insertion position lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The insertion position lies in the left subtree of cur\n        else cur = cur.left;\n    }\n    // Insert a node\n    const node = new TreeNode(num);\n    if (pre!.val < num) pre!.right = node;\n    else pre!.left = node;\n}\n
    binary_search_tree.dart
    /* Insert a node */\nvoid insert(int _num) {\n  // If the tree is empty, initialize the root node\n  if (_root == null) {\n    _root = TreeNode(_num);\n    return;\n  }\n  TreeNode? cur = _root;\n  TreeNode? pre = null;\n  // Search in a loop, stopping if a null reference is reached\n  while (cur != null) {\n    // Return immediately if a duplicate node is found\n    if (cur.val == _num) return;\n    pre = cur;\n    // The insertion position lies in the right subtree of cur\n    if (cur.val < _num)\n      cur = cur.right;\n    // The insertion position lies in the left subtree of cur\n    else\n      cur = cur.left;\n  }\n  // Insert a node\n  TreeNode? node = TreeNode(_num);\n  if (pre!.val < _num)\n    pre.right = node;\n  else\n    pre.left = node;\n}\n
    binary_search_tree.rs
    /* Insert a node */\npub fn insert(&mut self, num: i32) {\n    // If the tree is empty, initialize the root node\n    if self.root.is_none() {\n        self.root = Some(TreeNode::new(num));\n        return;\n    }\n    let mut cur = self.root.clone();\n    let mut pre = None;\n    // Search while cur refers to a node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // Return immediately if a duplicate node is found\n            Ordering::Equal => return,\n            // The insertion position lies in the right subtree of cur\n            Ordering::Greater => {\n                pre = cur.clone();\n                cur = node.borrow().right.clone();\n            }\n            // The insertion position lies in the left subtree of cur\n            Ordering::Less => {\n                pre = cur.clone();\n                cur = node.borrow().left.clone();\n            }\n        }\n    }\n    // Insert a node\n    let pre = pre.unwrap();\n    let node = Some(TreeNode::new(num));\n    if num > pre.borrow().val {\n        pre.borrow_mut().right = node;\n    } else {\n        pre.borrow_mut().left = node;\n    }\n}\n
    binary_search_tree.c
    /* Insert a node */\nvoid insert(BinarySearchTree *bst, int num) {\n    // If the tree is empty, initialize the root node\n    if (bst->root == NULL) {\n        bst->root = newTreeNode(num);\n        return;\n    }\n    TreeNode *cur = bst->root, *pre = NULL;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != NULL) {\n        // Return immediately if a duplicate node is found\n        if (cur->val == num) {\n            return;\n        }\n        pre = cur;\n        if (cur->val < num) {\n            // The insertion position lies in the right subtree of cur\n            cur = cur->right;\n        } else {\n            // The insertion position lies in the left subtree of cur\n            cur = cur->left;\n        }\n    }\n    // Insert a node\n    TreeNode *node = newTreeNode(num);\n    if (pre->val < num) {\n        pre->right = node;\n    } else {\n        pre->left = node;\n    }\n}\n
    binary_search_tree.kt
    /* Insert a node */\nfun insert(num: Int) {\n    // If the tree is empty, initialize the root node\n    if (root == null) {\n        root = TreeNode(num)\n        return\n    }\n    var cur = root\n    var pre: TreeNode? = null\n    // Continue searching while cur is not null\n    while (cur != null) {\n        // Return immediately if a duplicate node is found\n        if (cur._val == num)\n            return\n        pre = cur\n        // The insertion position lies in the right subtree of cur\n        cur = if (cur._val < num)\n            cur.right\n        // The insertion position lies in the left subtree of cur\n        else\n            cur.left\n    }\n    // Insert a node\n    val node = TreeNode(num)\n    if (pre?._val!! < num)\n        pre.right = node\n    else\n        pre.left = node\n}\n
    binary_search_tree.rb
    ### Insert a node ###\ndef insert(num)\n  # If the tree is empty, initialize the root node\n  if @root.nil?\n    @root = TreeNode.new(num)\n    return\n  end\n\n  # Search in a loop, stopping if cur becomes nil\n  cur, pre = @root, nil\n  while !cur.nil?\n    # Return immediately if a duplicate node is found\n    return if cur.val == num\n\n    pre = cur\n    # The insertion position lies in the right subtree of cur\n    if cur.val < num\n      cur = cur.right\n    # The insertion position lies in the left subtree of cur\n    else\n      cur = cur.left\n    end\n  end\n\n  # Insert a node\n  node = TreeNode.new(num)\n  if pre.val < num\n    pre.right = node\n  else\n    pre.left = node\n  end\nend\n
    Visualize execution

    View full screen >

    Like lookup, insertion takes \\(O(\\log n)\\) time.

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#3-deleting-a-node","level":3,"title":"3.   Deleting a node","text":"

    First locate the target node, then remove it while preserving “left subtree < root < right subtree.” The procedure depends on whether the target has 0, 1, or 2 children.

    As shown below, a node of degree \\(0\\) is a leaf and can be removed directly.

    Figure 7-19   Deleting a node of degree 0

    As shown below, replace a node of degree \\(1\\) with its only child.

    Figure 7-20   Deleting a node of degree 1

    A node of degree \\(2\\) cannot simply be removed; another node must replace it. To preserve “left subtree \\(<\\) root \\(<\\) right subtree,” use either the smallest node in the right subtree or the largest node in the left subtree.

    Suppose we choose the smallest node in the right subtree, the next node in inorder traversal. The process is shown below.

    1. Find the target node's successor in inorder traversal and call it tmp.
    2. Replace the target node's value with tmp's value, then recursively delete tmp from the tree.
    <1><2><3><4>

    Figure 7-21   Deleting a node of degree 2

    Deletion also takes \\(O(\\log n)\\) time: locating the target takes \\(O(\\log n)\\), and finding its inorder successor takes \\(O(\\log n)\\). An implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py
    def remove(self, num: int):\n    \"\"\"Remove a node\"\"\"\n    # Return immediately if the tree is empty\n    if self._root is None:\n        return\n    # Search in a loop, stopping if cur becomes None\n    cur, pre = self._root, None\n    while cur is not None:\n        # The node to remove was found; exit the loop\n        if cur.val == num:\n            break\n        pre = cur\n        # If present, the node to remove is in the right subtree of cur\n        if cur.val < num:\n            cur = cur.right\n        # If present, the node to remove is in the left subtree of cur\n        else:\n            cur = cur.left\n    # Return immediately if the node to remove does not exist\n    if cur is None:\n        return\n\n    # Number of children = 0 or 1\n    if cur.left is None or cur.right is None:\n        # With 0 or 1 child, child is None or that child, respectively\n        child = cur.left or cur.right\n        # Remove node cur\n        if cur != self._root:\n            if pre.left == cur:\n                pre.left = child\n            else:\n                pre.right = child\n        else:\n            # If the removed node is the root, assign the new root\n            self._root = child\n    # Number of children = 2\n    else:\n        # Get the inorder successor of cur\n        tmp: TreeNode = cur.right\n        while tmp.left is not None:\n            tmp = tmp.left\n        # Recursively remove node tmp\n        self.remove(tmp.val)\n        # Replace the value of cur with the value of tmp\n        cur.val = tmp.val\n
    binary_search_tree.cpp
    /* Remove a node */\nvoid remove(int num) {\n    // Return immediately if the tree is empty\n    if (root == nullptr)\n        return;\n    TreeNode *cur = root, *pre = nullptr;\n    // Continue searching while the current node pointer is not null\n    while (cur != nullptr) {\n        // The node to remove was found; exit the loop\n        if (cur->val == num)\n            break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur->val < num)\n            cur = cur->right;\n        // The node to remove lies in the left subtree of cur\n        else\n            cur = cur->left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == nullptr)\n        return;\n    // Number of children = 0 or 1\n    if (cur->left == nullptr || cur->right == nullptr) {\n        // With 0 or 1 child, child is nullptr or that child, respectively\n        TreeNode *child = cur->left != nullptr ? cur->left : cur->right;\n        // Remove node cur\n        if (cur != root) {\n            if (pre->left == cur)\n                pre->left = child;\n            else\n                pre->right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child;\n        }\n        // Free memory\n        delete cur;\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        TreeNode *tmp = cur->right;\n        while (tmp->left != nullptr) {\n            tmp = tmp->left;\n        }\n        int tmpVal = tmp->val;\n        // Recursively remove node tmp\n        remove(tmp->val);\n        // Replace the value of cur with the value of tmp\n        cur->val = tmpVal;\n    }\n}\n
    binary_search_tree.java
    /* Remove a node */\nvoid remove(int num) {\n    // Return immediately if the tree is empty\n    if (root == null)\n        return;\n    TreeNode cur = root, pre = null;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != null) {\n        // The node to remove was found; exit the loop\n        if (cur.val == num)\n            break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The node to remove lies in the left subtree of cur\n        else\n            cur = cur.left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == null)\n        return;\n    // Number of children = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        TreeNode child = cur.left != null ? cur.left : cur.right;\n        // Remove node cur\n        if (cur != root) {\n            if (pre.left == cur)\n                pre.left = child;\n            else\n                pre.right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child;\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        TreeNode tmp = cur.right;\n        while (tmp.left != null) {\n            tmp = tmp.left;\n        }\n        // Recursively remove node tmp\n        remove(tmp.val);\n        // Replace the value of cur with the value of tmp\n        cur.val = tmp.val;\n    }\n}\n
    binary_search_tree.cs
    /* Remove a node */\nvoid Remove(int num) {\n    // Return immediately if the tree is empty\n    if (root == null)\n        return;\n    TreeNode? cur = root, pre = null;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur != null) {\n        // The node to remove was found; exit the loop\n        if (cur.val == num)\n            break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur.val < num)\n            cur = cur.right;\n        // The node to remove lies in the left subtree of cur\n        else\n            cur = cur.left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == null)\n        return;\n    // Number of children = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        TreeNode? child = cur.left ?? cur.right;\n        // Remove node cur\n        if (cur != root) {\n            if (pre!.left == cur)\n                pre.left = child;\n            else\n                pre.right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child;\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        TreeNode? tmp = cur.right;\n        while (tmp.left != null) {\n            tmp = tmp.left;\n        }\n        // Recursively remove node tmp\n        Remove(tmp.val!.Value);\n        // Replace the value of cur with the value of tmp\n        cur.val = tmp.val;\n    }\n}\n
    binary_search_tree.go
    /* Remove a node */\nfunc (bst *binarySearchTree) remove(num int) {\n    cur := bst.root\n    // Return immediately if the tree is empty\n    if cur == nil {\n        return\n    }\n    // Track the parent of the node to remove\n    var pre *TreeNode = nil\n    // Continue searching while the current node is not nil\n    for cur != nil {\n        if cur.Val == num {\n            break\n        }\n        pre = cur\n        if cur.Val.(int) < num {\n            // The node to remove is in the right subtree\n            cur = cur.Right\n        } else {\n            // The node to remove is in the left subtree\n            cur = cur.Left\n        }\n    }\n    // Return immediately if the node to remove does not exist\n    if cur == nil {\n        return\n    }\n    // The node has 0 or 1 child\n    if cur.Left == nil || cur.Right == nil {\n        var child *TreeNode = nil\n        // Get the child of the node to remove\n        if cur.Left != nil {\n            child = cur.Left\n        } else {\n            child = cur.Right\n        }\n        // Remove node cur\n        if cur != bst.root {\n            if pre.Left == cur {\n                pre.Left = child\n            } else {\n                pre.Right = child\n            }\n        } else {\n            // If the removed node is the root, assign the new root\n            bst.root = child\n        }\n        // The node has 2 children\n    } else {\n        // Get the inorder successor of cur, the node to remove\n        tmp := cur.Right\n        for tmp.Left != nil {\n            tmp = tmp.Left\n        }\n        // Recursively remove node tmp\n        bst.remove(tmp.Val.(int))\n        // Replace the value of cur with the value of tmp\n        cur.Val = tmp.Val\n    }\n}\n
    binary_search_tree.swift
    /* Remove a node */\nfunc remove(num: Int) {\n    // Return immediately if the tree is empty\n    if root == nil {\n        return\n    }\n    var cur = root\n    var pre: TreeNode?\n    // Search in a loop, stopping if cur becomes nil\n    while cur != nil {\n        // The node to remove was found; exit the loop\n        if cur!.val == num {\n            break\n        }\n        pre = cur\n        // If present, the node to remove lies in the right subtree of cur\n        if cur!.val < num {\n            cur = cur?.right\n        }\n        // If present, the node to remove lies in the left subtree of cur\n        else {\n            cur = cur?.left\n        }\n    }\n    // Return immediately if the node to remove does not exist\n    if cur == nil {\n        return\n    }\n    // Number of children = 0 or 1\n    if cur?.left == nil || cur?.right == nil {\n        // With 0 / 1 children, child is nil / the child node, respectively\n        let child = cur?.left ?? cur?.right\n        // Remove node cur\n        if cur !== root {\n            if pre?.left === cur {\n                pre?.left = child\n            } else {\n                pre?.right = child\n            }\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        var tmp = cur?.right\n        while tmp?.left != nil {\n            tmp = tmp?.left\n        }\n        // Recursively remove node tmp\n        remove(num: tmp!.val)\n        // Replace the value of cur with the value of tmp\n        cur?.val = tmp!.val\n    }\n}\n
    binary_search_tree.js
    /* Remove a node */\nremove(num) {\n    // Return immediately if the tree is empty\n    if (this.root === null) return;\n    let cur = this.root,\n        pre = null;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur !== null) {\n        // The node to remove was found; exit the loop\n        if (cur.val === num) break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The node to remove lies in the left subtree of cur\n        else cur = cur.left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur === null) return;\n    // Number of children = 0 or 1\n    if (cur.left === null || cur.right === null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        const child = cur.left !== null ? cur.left : cur.right;\n        // Remove node cur\n        if (cur !== this.root) {\n            if (pre.left === cur) pre.left = child;\n            else pre.right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            this.root = child;\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        let tmp = cur.right;\n        while (tmp.left !== null) {\n            tmp = tmp.left;\n        }\n        // Recursively remove node tmp\n        this.remove(tmp.val);\n        // Replace the value of cur with the value of tmp\n        cur.val = tmp.val;\n    }\n}\n
    binary_search_tree.ts
    /* Remove a node */\nremove(num: number): void {\n    // Return immediately if the tree is empty\n    if (this.root === null) return;\n    let cur: TreeNode | null = this.root,\n        pre: TreeNode | null = null;\n    // Search in a loop, stopping if a null reference is reached\n    while (cur !== null) {\n        // The node to remove was found; exit the loop\n        if (cur.val === num) break;\n        pre = cur;\n        // The node to remove lies in the right subtree of cur\n        if (cur.val < num) cur = cur.right;\n        // The node to remove lies in the left subtree of cur\n        else cur = cur.left;\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur === null) return;\n    // Number of children = 0 or 1\n    if (cur.left === null || cur.right === null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        const child: TreeNode | null =\n            cur.left !== null ? cur.left : cur.right;\n        // Remove node cur\n        if (cur !== this.root) {\n            if (pre!.left === cur) pre!.left = child;\n            else pre!.right = child;\n        } else {\n            // If the removed node is the root, assign the new root\n            this.root = child;\n        }\n    }\n    // Number of children = 2\n    else {\n        // Get the inorder successor of cur\n        let tmp: TreeNode | null = cur.right;\n        while (tmp!.left !== null) {\n            tmp = tmp!.left;\n        }\n        // Recursively remove node tmp\n        this.remove(tmp!.val);\n        // Replace the value of cur with the value of tmp\n        cur.val = tmp!.val;\n    }\n}\n
    binary_search_tree.dart
    /* Remove a node */\nvoid remove(int _num) {\n  // Return immediately if the tree is empty\n  if (_root == null) return;\n  TreeNode? cur = _root;\n  TreeNode? pre = null;\n  // Search in a loop, stopping if a null reference is reached\n  while (cur != null) {\n    // The node to remove was found; exit the loop\n    if (cur.val == _num) break;\n    pre = cur;\n    // The node to remove lies in the right subtree of cur\n    if (cur.val < _num)\n      cur = cur.right;\n    // The node to remove lies in the left subtree of cur\n    else\n      cur = cur.left;\n  }\n  // Return if the node to remove does not exist\n  if (cur == null) return;\n  // Number of children = 0 or 1\n  if (cur.left == null || cur.right == null) {\n    // With 0 / 1 children, child is null / the child node, respectively\n    TreeNode? child = cur.left ?? cur.right;\n    // Remove node cur\n    if (cur != _root) {\n      if (pre!.left == cur)\n        pre.left = child;\n      else\n        pre.right = child;\n    } else {\n      // If the removed node is the root, assign the new root\n      _root = child;\n    }\n  } else {\n    // Number of children = 2\n    // Get the inorder successor of cur\n    TreeNode? tmp = cur.right;\n    while (tmp!.left != null) {\n      tmp = tmp.left;\n    }\n    // Recursively remove node tmp\n    remove(tmp.val);\n    // Replace the value of cur with the value of tmp\n    cur.val = tmp.val;\n  }\n}\n
    binary_search_tree.rs
    /* Remove a node */\npub fn remove(&mut self, num: i32) {\n    // Return immediately if the tree is empty\n    if self.root.is_none() {\n        return;\n    }\n    let mut cur = self.root.clone();\n    let mut pre = None;\n    // Search while cur refers to a node\n    while let Some(node) = cur.clone() {\n        match num.cmp(&node.borrow().val) {\n            // The node to remove was found; exit the loop\n            Ordering::Equal => break,\n            // The node to remove lies in the right subtree of cur\n            Ordering::Greater => {\n                pre = cur.clone();\n                cur = node.borrow().right.clone();\n            }\n            // The node to remove lies in the left subtree of cur\n            Ordering::Less => {\n                pre = cur.clone();\n                cur = node.borrow().left.clone();\n            }\n        }\n    }\n    // Return immediately if the node to remove does not exist\n    if cur.is_none() {\n        return;\n    }\n    let cur = cur.unwrap();\n    let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone());\n    match (left_child.clone(), right_child.clone()) {\n        // Number of children = 0 or 1\n        (None, None) | (Some(_), None) | (None, Some(_)) => {\n            // With 0 or 1 child, child is None or that child, respectively\n            let child = left_child.or(right_child);\n            let pre = pre.unwrap();\n            // Remove node cur\n            if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) {\n                let left = pre.borrow().left.clone();\n                if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) {\n                    pre.borrow_mut().left = child;\n                } else {\n                    pre.borrow_mut().right = child;\n                }\n            } else {\n                // If the removed node is the root, assign the new root\n                self.root = child;\n            }\n        }\n        // Number of children = 2\n        (Some(_), Some(_)) => {\n            // Get the inorder successor of cur\n            let mut tmp = cur.borrow().right.clone();\n            while let Some(node) = tmp.clone() {\n                if node.borrow().left.is_some() {\n                    tmp = node.borrow().left.clone();\n                } else {\n                    break;\n                }\n            }\n            let tmp_val = tmp.unwrap().borrow().val;\n            // Recursively remove node tmp\n            self.remove(tmp_val);\n            // Replace the value of cur with the value of tmp\n            cur.borrow_mut().val = tmp_val;\n        }\n    }\n}\n
    binary_search_tree.c
    /* Remove a node */\n// The name remove conflicts with the function declared in stdio.h\nvoid removeItem(BinarySearchTree *bst, int num) {\n    // Return immediately if the tree is empty\n    if (bst->root == NULL)\n        return;\n    TreeNode *cur = bst->root, *pre = NULL;\n    // Search in a loop until reaching a null reference beyond a leaf\n    while (cur != NULL) {\n        // The node to remove was found; exit the loop\n        if (cur->val == num)\n            break;\n        pre = cur;\n        if (cur->val < num) {\n            // The node to remove is in cur's right subtree\n            cur = cur->right;\n        } else {\n            // The node to remove is in cur's left subtree\n            cur = cur->left;\n        }\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == NULL)\n        return;\n    // Check whether the node to remove has children\n    if (cur->left == NULL || cur->right == NULL) {\n        /* Number of children = 0 or 1 */\n        // With 0 or 1 child, child is NULL or that child, respectively\n        TreeNode *child = cur->left != NULL ? cur->left : cur->right;\n        // Remove node cur\n        if (pre->left == cur) {\n            pre->left = child;\n        } else {\n            pre->right = child;\n        }\n        // Free memory\n        free(cur);\n    } else {\n        /* Number of children = 2 */\n        // Get the inorder successor of cur\n        TreeNode *tmp = cur->right;\n        while (tmp->left != NULL) {\n            tmp = tmp->left;\n        }\n        int tmpVal = tmp->val;\n        // Recursively remove node tmp\n        removeItem(bst, tmp->val);\n        // Replace the value of cur with the value of tmp\n        cur->val = tmpVal;\n    }\n}\n
    binary_search_tree.kt
    /* Remove a node */\nfun remove(num: Int) {\n    // Return immediately if the tree is empty\n    if (root == null)\n        return\n    var cur = root\n    var pre: TreeNode? = null\n    // Continue searching while cur is not null\n    while (cur != null) {\n        // The node to remove was found; exit the loop\n        if (cur._val == num)\n            break\n        pre = cur\n        // The node to remove lies in the right subtree of cur\n        cur = if (cur._val < num)\n            cur.right\n        // The node to remove lies in the left subtree of cur\n        else\n            cur.left\n    }\n    // Return immediately if the node to remove does not exist\n    if (cur == null)\n        return\n    // Number of children = 0 or 1\n    if (cur.left == null || cur.right == null) {\n        // With 0 / 1 children, child is null / the child node, respectively\n        val child = if (cur.left != null)\n            cur.left\n        else\n            cur.right\n        // Remove node cur\n        if (cur != root) {\n            if (pre!!.left == cur)\n                pre.left = child\n            else\n                pre.right = child\n        } else {\n            // If the removed node is the root, assign the new root\n            root = child\n        }\n        // Number of children = 2\n    } else {\n        // Get the inorder successor of cur\n        var tmp = cur.right\n        while (tmp!!.left != null) {\n            tmp = tmp.left\n        }\n        // Recursively remove node tmp\n        remove(tmp._val)\n        // Replace the value of cur with the value of tmp\n        cur._val = tmp._val\n    }\n}\n
    binary_search_tree.rb
    ### Remove a node ###\ndef remove(num)\n  # Return immediately if the tree is empty\n  return if @root.nil?\n\n  # Search in a loop, stopping if cur becomes nil\n  cur, pre = @root, nil\n  while !cur.nil?\n    # The node to remove was found; exit the loop\n    break if cur.val == num\n\n    pre = cur\n    # The node to remove lies in the right subtree of cur\n    if cur.val < num\n      cur = cur.right\n    # The node to remove lies in the left subtree of cur\n    else\n      cur = cur.left\n    end\n  end\n  # Return immediately if the node to remove does not exist\n  return if cur.nil?\n\n  # Number of children = 0 or 1\n  if cur.left.nil? || cur.right.nil?\n    # With 0 / 1 children, child is nil / the child node, respectively\n    child = cur.left || cur.right\n    # Remove node cur\n    if cur != @root\n      if pre.left == cur\n        pre.left = child\n      else\n        pre.right = child\n      end\n    else\n      # If the removed node is the root, assign the new root\n      @root = child\n    end\n  # Number of children = 2\n  else\n    # Get the inorder successor of cur\n    tmp = cur.right\n    while !tmp.left.nil?\n      tmp = tmp.left\n    end\n    # Recursively remove node tmp\n    remove(tmp.val)\n    # Replace the value of cur with the value of tmp\n    cur.val = tmp.val\n  end\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#4-inorder-traversal-produces-sorted-data","level":3,"title":"4.   Inorder traversal produces sorted data","text":"

    As shown below, inorder traversal visits “left \\(\\rightarrow\\) root \\(\\rightarrow\\) right,” while a binary search tree obeys “left child \\(<\\) root \\(<\\) right child.”

    Thus, inorder traversal always visits the next smallest node, giving an important property: the inorder traversal of a binary search tree is in ascending order.

    This lets us retrieve sorted data from the tree in just \\(O(n)\\) time, with no additional sorting.

    Figure 7-22   Inorder traversal of a binary search tree

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#742-efficiency","level":2,"title":"7.4.2   Efficiency","text":"

    Suppose we want to store a dataset in an array or a binary search tree. As Table 7-2 shows, the tree's operations take logarithmic time, offering consistent and efficient performance. An array is faster only when insertions are frequent but lookups and deletions are infrequent.

    Table 7-2   Efficiency of arrays and search trees

    Unsorted array Binary search tree Find an element \\(O(n)\\) \\(O(\\log n)\\) Insert an element \\(O(1)\\) \\(O(\\log n)\\) Delete an element \\(O(n)\\) \\(O(\\log n)\\)

    Ideally, the tree is balanced, allowing any node to be found within \\(\\log n\\) iterations.

    However, repeated insertions and deletions may cause it to degenerate into a linked list, as shown below. The time complexity of its operations then degrades to \\(O(n)\\).

    Figure 7-23   Degeneration of a binary search tree

    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#743-common-applications","level":2,"title":"7.4.3   Common applications","text":"
    • Multilevel indexes in systems, supporting efficient lookup, insertion, and deletion.
    • The underlying data structure for certain search algorithms.
    • Storing a data stream while maintaining its order.
    ","path":["Chapter 7.   Trees","7.4   Binary search trees"],"tags":[]},{"location":"chapter_tree/binary_tree/","level":1,"title":"7.1   Binary trees","text":"

    A binary tree is a nonlinear data structure representing relationships between ancestors and descendants. It embodies the divide-and-conquer idea of splitting in two. Like a linked list, it is built from nodes, each containing a value and references to its left and right children.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby
    class TreeNode:\n    \"\"\"Binary tree node class\"\"\"\n    def __init__(self, val: int):\n        self.val: int = val                # Node value\n        self.left: TreeNode | None = None  # Left child reference\n        self.right: TreeNode | None = None # Right child reference\n
    /* Binary tree node struct */\nstruct TreeNode {\n    int val;          // Node value\n    TreeNode *left;   // Left child pointer\n    TreeNode *right;  // Right child pointer\n    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}\n};\n
    /* Binary tree node class */\nclass TreeNode {\n    int val;         // Node value\n    TreeNode left;   // Left child reference\n    TreeNode right;  // Right child reference\n    TreeNode(int x) { val = x; }\n}\n
    /* Binary tree node class */\nclass TreeNode(int? x) {\n    public int? val = x;    // Node value\n    public TreeNode? left;  // Left child reference\n    public TreeNode? right; // Right child reference\n}\n
    /* Binary tree node struct */\ntype TreeNode struct {\n    Val   int\n    Left  *TreeNode\n    Right *TreeNode\n}\n/* Constructor */\nfunc NewTreeNode(v int) *TreeNode {\n    return &TreeNode{\n        Left:  nil, // Left child pointer\n        Right: nil, // Right child pointer\n        Val:   v,   // Node value\n    }\n}\n
    /* Binary tree node class */\nclass TreeNode {\n    var val: Int // Node value\n    var left: TreeNode? // Left child reference\n    var right: TreeNode? // Right child reference\n\n    init(x: Int) {\n        val = x\n    }\n}\n
    /* Binary tree node class */\nclass TreeNode {\n    val; // Node value\n    left; // Left child pointer\n    right; // Right child pointer\n    constructor(val, left, right) {\n        this.val = val === undefined ? 0 : val;\n        this.left = left === undefined ? null : left;\n        this.right = right === undefined ? null : right;\n    }\n}\n
    /* Binary tree node class */\nclass TreeNode {\n    val: number;\n    left: TreeNode | null;\n    right: TreeNode | null;\n\n    constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {\n        this.val = val === undefined ? 0 : val; // Node value\n        this.left = left === undefined ? null : left; // Left child reference\n        this.right = right === undefined ? null : right; // Right child reference\n    }\n}\n
    /* Binary tree node class */\nclass TreeNode {\n  int val;         // Node value\n  TreeNode? left;  // Left child reference\n  TreeNode? right; // Right child reference\n  TreeNode(this.val, [this.left, this.right]);\n}\n
    use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* Binary tree node struct */\nstruct TreeNode {\n    val: i32,                               // Node value\n    left: Option<Rc<RefCell<TreeNode>>>,    // Left child reference\n    right: Option<Rc<RefCell<TreeNode>>>,   // Right child reference\n}\n\nimpl TreeNode {\n    /* Constructor */\n    fn new(val: i32) -> Rc<RefCell<Self>> {\n        Rc::new(RefCell::new(Self {\n            val,\n            left: None,\n            right: None\n        }))\n    }\n}\n
    /* Binary tree node struct */\ntypedef struct TreeNode {\n    int val;                // Node value\n    int height;             // Node height\n    struct TreeNode *left;  // Left child pointer\n    struct TreeNode *right; // Right child pointer\n} TreeNode;\n\n/* Constructor */\nTreeNode *newTreeNode(int val) {\n    TreeNode *node;\n\n    node = (TreeNode *)malloc(sizeof(TreeNode));\n    node->val = val;\n    node->height = 0;\n    node->left = NULL;\n    node->right = NULL;\n    return node;\n}\n
    /* Binary tree node class */\nclass TreeNode(val _val: Int) {  // Node value\n    var left: TreeNode? = null   // Left child reference\n    var right: TreeNode? = null  // Right child reference\n}\n
    ### Binary tree node class ###\nclass TreeNode\n  attr_accessor :val    # Node value\n  attr_accessor :left   # Left child reference\n  attr_accessor :right  # Right child reference\n\n  def initialize(val)\n    @val = val\n  end\nend\n

    Each node has two references or pointers, one to its left child and one to its right child. The node is their parent. Its left child and all nodes below that child form its left subtree; the right subtree is defined similarly.

    Every non-leaf node has children and at least one nonempty subtree. In Figure 7-1, if node 2 is the parent, its left and right children are nodes 4 and 5. Its left subtree consists of node 4 and everything below it, and its right subtree consists of node 5 and everything below it.

    Figure 7-1   Parents, children, and subtrees

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#711-common-terminology","level":2,"title":"7.1.1   Common terminology","text":"

    Figure 7-2 illustrates common binary-tree terminology.

    • Root: the topmost node, which has no parent.
    • Leaf: a node with no children; both of its child references point to None.
    • Edge: a connection between two nodes, represented by a reference or pointer.
    • A node's level: levels increase from top to bottom, with the root at level 1.
    • A node's degree: its number of children, which is 0, 1, or 2 in a binary tree.
    • A tree's height: the number of edges from the root to its farthest leaf.
    • A node's depth: the number of edges from the root to that node.
    • A node's height: the number of edges from that node to its farthest leaf.

    Figure 7-2   Common binary-tree terminology

    Tip

    Height and depth are usually measured in edges, but some problems or textbooks count nodes instead. Under that convention, add 1 to both height and depth.

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#712-basic-operations","level":2,"title":"7.1.2   Basic operations","text":"","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#1-initializing-a-binary-tree","level":3,"title":"1.   Initializing a binary tree","text":"

    As with a linked list, first create the nodes, then connect them with references or pointers.

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py
    # Initialize a binary tree\n# Initialize nodes\nn1 = TreeNode(val=1)\nn2 = TreeNode(val=2)\nn3 = TreeNode(val=3)\nn4 = TreeNode(val=4)\nn5 = TreeNode(val=5)\n# Connect nodes with references (pointers)\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
    binary_tree.cpp
    /* Initialize a binary tree */\n// Initialize nodes\nTreeNode* n1 = new TreeNode(1);\nTreeNode* n2 = new TreeNode(2);\nTreeNode* n3 = new TreeNode(3);\nTreeNode* n4 = new TreeNode(4);\nTreeNode* n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1->left = n2;\nn1->right = n3;\nn2->left = n4;\nn2->right = n5;\n
    binary_tree.java
    // Initialize nodes\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.cs
    /* Initialize a binary tree */\n// Initialize nodes\nTreeNode n1 = new(1);\nTreeNode n2 = new(2);\nTreeNode n3 = new(3);\nTreeNode n4 = new(4);\nTreeNode n5 = new(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.go
    /* Initialize a binary tree */\n// Initialize nodes\nn1 := NewTreeNode(1)\nn2 := NewTreeNode(2)\nn3 := NewTreeNode(3)\nn4 := NewTreeNode(4)\nn5 := NewTreeNode(5)\n// Connect nodes with references (pointers)\nn1.Left = n2\nn1.Right = n3\nn2.Left = n4\nn2.Right = n5\n
    binary_tree.swift
    // Initialize nodes\nlet n1 = TreeNode(x: 1)\nlet n2 = TreeNode(x: 2)\nlet n3 = TreeNode(x: 3)\nlet n4 = TreeNode(x: 4)\nlet n5 = TreeNode(x: 5)\n// Connect nodes with references (pointers)\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
    binary_tree.js
    /* Initialize a binary tree */\n// Initialize nodes\nlet n1 = new TreeNode(1),\n    n2 = new TreeNode(2),\n    n3 = new TreeNode(3),\n    n4 = new TreeNode(4),\n    n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.ts
    /* Initialize a binary tree */\n// Initialize nodes\nlet n1 = new TreeNode(1),\n    n2 = new TreeNode(2),\n    n3 = new TreeNode(3),\n    n4 = new TreeNode(4),\n    n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.dart
    /* Initialize a binary tree */\n// Initialize nodes\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// Connect nodes with references (pointers)\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n
    binary_tree.rs
    // Initialize nodes\nlet n1 = TreeNode::new(1);\nlet n2 = TreeNode::new(2);\nlet n3 = TreeNode::new(3);\nlet n4 = TreeNode::new(4);\nlet n5 = TreeNode::new(5);\n// Connect nodes with references (pointers)\nn1.borrow_mut().left = Some(n2.clone());\nn1.borrow_mut().right = Some(n3);\nn2.borrow_mut().left = Some(n4);\nn2.borrow_mut().right = Some(n5);\n
    binary_tree.c
    /* Initialize a binary tree */\n// Initialize nodes\nTreeNode *n1 = newTreeNode(1);\nTreeNode *n2 = newTreeNode(2);\nTreeNode *n3 = newTreeNode(3);\nTreeNode *n4 = newTreeNode(4);\nTreeNode *n5 = newTreeNode(5);\n// Connect nodes with references (pointers)\nn1->left = n2;\nn1->right = n3;\nn2->left = n4;\nn2->right = n5;\n
    binary_tree.kt
    // Initialize nodes\nval n1 = TreeNode(1)\nval n2 = TreeNode(2)\nval n3 = TreeNode(3)\nval n4 = TreeNode(4)\nval n5 = TreeNode(5)\n// Connect nodes with references (pointers)\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
    binary_tree.rb
    # Initialize a binary tree\n# Initialize nodes\nn1 = TreeNode.new(1)\nn2 = TreeNode.new(2)\nn3 = TreeNode.new(3)\nn4 = TreeNode.new(4)\nn5 = TreeNode.new(5)\n# Connect nodes with references (pointers)\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#2-inserting-and-deleting-nodes","level":3,"title":"2.   Inserting and deleting nodes","text":"

    As with linked lists, insertion and deletion can be implemented by changing pointers. An example is shown below.

    Figure 7-3   Inserting and deleting a binary-tree node

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py
    # Insert and delete a node\np = TreeNode(0)\n# Insert node P between n1 and n2\nn1.left = p\np.left = n2\n# Delete node P\nn1.left = n2\n
    binary_tree.cpp
    /* Insert and delete a node */\nTreeNode* P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1->left = P;\nP->left = n2;\n// Delete node P\nn1->left = n2;\n// Free the memory\ndelete P;\n
    binary_tree.java
    TreeNode P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.cs
    /* Insert and delete a node */\nTreeNode P = new(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.go
    /* Insert and delete a node */\n// Insert node P between n1 and n2\np := NewTreeNode(0)\nn1.Left = p\np.Left = n2\n// Delete node P\nn1.Left = n2\n
    binary_tree.swift
    let P = TreeNode(x: 0)\n// Insert node P between n1 and n2\nn1.left = P\nP.left = n2\n// Delete node P\nn1.left = n2\n
    binary_tree.js
    /* Insert and delete a node */\nlet P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.ts
    /* Insert and delete a node */\nconst P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.dart
    /* Insert and delete a node */\nTreeNode P = new TreeNode(0);\n// Insert node P between n1 and n2\nn1.left = P;\nP.left = n2;\n// Delete node P\nn1.left = n2;\n
    binary_tree.rs
    let p = TreeNode::new(0);\n// Insert node P between n1 and n2\nn1.borrow_mut().left = Some(p.clone());\np.borrow_mut().left = Some(n2.clone());\n// Delete node p\nn1.borrow_mut().left = Some(n2);\n
    binary_tree.c
    /* Insert and delete a node */\nTreeNode *P = newTreeNode(0);\n// Insert node P between n1 and n2\nn1->left = P;\nP->left = n2;\n// Delete node P\nn1->left = n2;\n// Free the memory\nfree(P);\n
    binary_tree.kt
    val P = TreeNode(0)\n// Insert node P between n1 and n2\nn1.left = P\nP.left = n2\n// Delete node P\nn1.left = n2\n
    binary_tree.rb
    # Insert and delete a node\n_p = TreeNode.new(0)\n# Insert node _p between n1 and n2\nn1.left = _p\n_p.left = n2\n# Delete the node\nn1.left = n2\n
    Visualize execution

    View full screen >

    Tip

    Inserting a node may change the tree's logical structure, while deleting a node generally means deleting it and all its subtrees. Meaningful insertion and deletion operations therefore usually require several coordinated steps.

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#713-common-types-of-binary-trees","level":2,"title":"7.1.3   Common types of binary trees","text":"","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#1-perfect-binary-trees","level":3,"title":"1.   Perfect binary trees","text":"

    In a perfect binary tree, every level is completely filled, as shown below. Leaves have degree \\(0\\), and all other nodes have degree \\(2\\). A tree of height \\(h\\) has \\(2^{h+1} - 1\\) nodes, an exponential relationship reminiscent of cell division in nature.

    Tip

    In Chinese-language resources, a perfect binary tree is often called 满二叉树.

    Figure 7-4   A perfect binary tree

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#2-complete-binary-trees","level":3,"title":"2.   Complete binary trees","text":"

    Only the lowest level of a complete binary tree may be incomplete, and its nodes must fill that level consecutively from left to right, as shown below. Every perfect binary tree is also complete.

    Figure 7-5   A complete binary tree

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#3-full-binary-trees","level":3,"title":"3.   Full binary trees","text":"

    In a full binary tree, every node other than a leaf has exactly two children, as shown below.

    Figure 7-6   A full binary tree

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#4-balanced-binary-trees","level":3,"title":"4.   Balanced binary trees","text":"

    In a balanced binary tree, the heights of the left and right subtrees of every node differ by at most 1, as shown below.

    Figure 7-7   A balanced binary tree

    ","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree/#714-degeneration-of-a-binary-tree","level":2,"title":"7.1.4   Degeneration of a binary tree","text":"

    Figure 7-8 shows ideal and degenerate tree structures. When every level is full, the tree is perfect. When every node lies along one side, the tree degenerates into a linked list.

    • A perfect binary tree is the ideal case, making full use of divide and conquer.
    • A linked list is the opposite extreme: operations become linear, with time complexity degrading to \\(O(n)\\).

    Figure 7-8   Best and worst binary-tree structures

    As Table 7-1 shows, quantities such as leaf count, total node count, and height reach their maximum or minimum values in these best and worst structures.

    Table 7-1   Best and worst binary-tree structures

    Perfect binary tree Linked list Nodes at level \\(i\\) \\(2^{i-1}\\) \\(1\\) Leaves in a tree of height \\(h\\) \\(2^h\\) \\(1\\) Total nodes in a tree of height \\(h\\) \\(2^{h+1} - 1\\) \\(h + 1\\) Height of a tree with \\(n\\) nodes \\(\\log_2 (n+1) - 1\\) \\(n - 1\\)","path":["Chapter 7.   Trees","7.1   Binary trees"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/","level":1,"title":"7.2   Traversing a binary tree","text":"

    In its physical representation, a tree is a linked data structure, so traversal follows pointers from node to node. Unlike a linked list, however, a tree is nonlinear. Traversing it is more complex and requires a search algorithm.

    Common binary-tree traversals include level-order, preorder, inorder, and postorder traversal.

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#721-level-order-traversal","level":2,"title":"7.2.1   Level-order traversal","text":"

    As shown below, level-order traversal visits the tree level by level from top to bottom, moving from left to right within each level.

    Level-order traversal is a form of breadth-first traversal, also called breadth-first search (BFS). It explores outward one layer at a time.

    Figure 7-9   Level-order traversal of a binary tree

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1-implementation","level":3,"title":"1.   Implementation","text":"

    Breadth-first traversal usually uses a queue. A queue's first-in, first-out rule matches the traversal's level-by-level progression. The implementation follows:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_bfs.py
    def level_order(root: TreeNode | None) -> list[int]:\n    \"\"\"Level-order traversal\"\"\"\n    # Initialize a queue and enqueue the root\n    queue: deque[TreeNode] = deque()\n    queue.append(root)\n    # Initialize a list to store the traversal sequence\n    res = []\n    while queue:\n        node: TreeNode = queue.popleft()  # Dequeue from the queue\n        res.append(node.val)  # Store the node value\n        if node.left is not None:\n            queue.append(node.left)  # Enqueue the left child\n        if node.right is not None:\n            queue.append(node.right)  # Enqueue the right child\n    return res\n
    binary_tree_bfs.cpp
    /* Level-order traversal */\nvector<int> levelOrder(TreeNode *root) {\n    // Initialize a queue and enqueue the root\n    queue<TreeNode *> queue;\n    queue.push(root);\n    // Initialize a list to store the traversal sequence\n    vector<int> vec;\n    while (!queue.empty()) {\n        TreeNode *node = queue.front();\n        queue.pop();              // Dequeue from the queue\n        vec.push_back(node->val); // Store the node value\n        if (node->left != nullptr)\n            queue.push(node->left); // Enqueue the left child\n        if (node->right != nullptr)\n            queue.push(node->right); // Enqueue the right child\n    }\n    return vec;\n}\n
    binary_tree_bfs.java
    /* Level-order traversal */\nList<Integer> levelOrder(TreeNode root) {\n    // Initialize a queue and enqueue the root\n    Queue<TreeNode> queue = new LinkedList<>();\n    queue.add(root);\n    // Initialize a list to store the traversal sequence\n    List<Integer> list = new ArrayList<>();\n    while (!queue.isEmpty()) {\n        TreeNode node = queue.poll(); // Dequeue from the queue\n        list.add(node.val);           // Store the node value\n        if (node.left != null)\n            queue.offer(node.left);   // Enqueue the left child\n        if (node.right != null)\n            queue.offer(node.right);  // Enqueue the right child\n    }\n    return list;\n}\n
    binary_tree_bfs.cs
    /* Level-order traversal */\nList<int> LevelOrder(TreeNode root) {\n    // Initialize a queue and enqueue the root\n    Queue<TreeNode> queue = new();\n    queue.Enqueue(root);\n    // Initialize a list to store the traversal sequence\n    List<int> list = [];\n    while (queue.Count != 0) {\n        TreeNode node = queue.Dequeue(); // Dequeue from the queue\n        list.Add(node.val!.Value);       // Store the node value\n        if (node.left != null)\n            queue.Enqueue(node.left);    // Enqueue the left child\n        if (node.right != null)\n            queue.Enqueue(node.right);   // Enqueue the right child\n    }\n    return list;\n}\n
    binary_tree_bfs.go
    /* Level-order traversal */\nfunc levelOrder(root *TreeNode) []any {\n    // Initialize a queue and enqueue the root\n    queue := list.New()\n    queue.PushBack(root)\n    // Initialize a slice to store the traversal sequence\n    nums := make([]any, 0)\n    for queue.Len() > 0 {\n        // Dequeue from the queue\n        node := queue.Remove(queue.Front()).(*TreeNode)\n        // Store the node value\n        nums = append(nums, node.Val)\n        if node.Left != nil {\n            // Enqueue the left child\n            queue.PushBack(node.Left)\n        }\n        if node.Right != nil {\n            // Enqueue the right child\n            queue.PushBack(node.Right)\n        }\n    }\n    return nums\n}\n
    binary_tree_bfs.swift
    /* Level-order traversal */\nfunc levelOrder(root: TreeNode) -> [Int] {\n    // Initialize a queue and enqueue the root\n    var queue: [TreeNode] = [root]\n    // Initialize a list to store the traversal sequence\n    var list: [Int] = []\n    while !queue.isEmpty {\n        let node = queue.removeFirst() // Dequeue from the queue\n        list.append(node.val) // Store the node value\n        if let left = node.left {\n            queue.append(left) // Enqueue the left child\n        }\n        if let right = node.right {\n            queue.append(right) // Enqueue the right child\n        }\n    }\n    return list\n}\n
    binary_tree_bfs.js
    /* Level-order traversal */\nfunction levelOrder(root) {\n    // Initialize a queue and enqueue the root\n    const queue = [root];\n    // Initialize a list to store the traversal sequence\n    const list = [];\n    while (queue.length) {\n        let node = queue.shift(); // Dequeue from the queue\n        list.push(node.val); // Store the node value\n        if (node.left) queue.push(node.left); // Enqueue the left child\n        if (node.right) queue.push(node.right); // Enqueue the right child\n    }\n    return list;\n}\n
    binary_tree_bfs.ts
    /* Level-order traversal */\nfunction levelOrder(root: TreeNode | null): number[] {\n    // Initialize a queue and enqueue the root\n    const queue = [root];\n    // Initialize a list to store the traversal sequence\n    const list: number[] = [];\n    while (queue.length) {\n        let node = queue.shift() as TreeNode; // Dequeue from the queue\n        list.push(node.val); // Store the node value\n        if (node.left) {\n            queue.push(node.left); // Enqueue the left child\n        }\n        if (node.right) {\n            queue.push(node.right); // Enqueue the right child\n        }\n    }\n    return list;\n}\n
    binary_tree_bfs.dart
    /* Level-order traversal */\nList<int> levelOrder(TreeNode? root) {\n  // Initialize a queue and enqueue the root\n  Queue<TreeNode?> queue = Queue();\n  queue.add(root);\n  // Initialize a list to store the traversal sequence\n  List<int> res = [];\n  while (queue.isNotEmpty) {\n    TreeNode? node = queue.removeFirst(); // Dequeue from the queue\n    res.add(node!.val); // Store the node value\n    if (node.left != null) queue.add(node.left); // Enqueue the left child\n    if (node.right != null) queue.add(node.right); // Enqueue the right child\n  }\n  return res;\n}\n
    binary_tree_bfs.rs
    /* Level-order traversal */\nfn level_order(root: &Rc<RefCell<TreeNode>>) -> Vec<i32> {\n    // Initialize a queue and enqueue the root\n    let mut que = VecDeque::new();\n    que.push_back(root.clone());\n    // Initialize a list to store the traversal sequence\n    let mut vec = Vec::new();\n\n    while let Some(node) = que.pop_front() {\n        // Dequeue from the queue\n        vec.push(node.borrow().val); // Store the node value\n        if let Some(left) = node.borrow().left.as_ref() {\n            que.push_back(left.clone()); // Enqueue the left child\n        }\n        if let Some(right) = node.borrow().right.as_ref() {\n            que.push_back(right.clone()); // Enqueue the right child\n        };\n    }\n    vec\n}\n
    binary_tree_bfs.c
    /* Level-order traversal */\nint *levelOrder(TreeNode *root, int *size) {\n    /* Auxiliary queue */\n    int front, rear;\n    int index, *arr;\n    TreeNode *node;\n    TreeNode **queue;\n\n    /* Auxiliary queue */\n    queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE);\n    // Queue indices\n    front = 0, rear = 0;\n    // Enqueue the root node\n    queue[rear++] = root;\n    // Initialize a list to store the traversal sequence\n    /* Auxiliary array */\n    arr = (int *)malloc(sizeof(int) * MAX_SIZE);\n    // Array index\n    index = 0;\n    while (front < rear) {\n        // Dequeue from the queue\n        node = queue[front++];\n        // Store the node value\n        arr[index++] = node->val;\n        if (node->left != NULL) {\n            // Enqueue the left child\n            queue[rear++] = node->left;\n        }\n        if (node->right != NULL) {\n            // Enqueue the right child\n            queue[rear++] = node->right;\n        }\n    }\n    // Update the array length\n    *size = index;\n    arr = realloc(arr, sizeof(int) * (*size));\n\n    // Free the auxiliary queue array\n    free(queue);\n    return arr;\n}\n
    binary_tree_bfs.kt
    /* Level-order traversal */\nfun levelOrder(root: TreeNode?): MutableList<Int> {\n    // Initialize a queue and enqueue the root\n    val queue = LinkedList<TreeNode?>()\n    queue.add(root)\n    // Initialize a list to store the traversal sequence\n    val list = mutableListOf<Int>()\n    while (queue.isNotEmpty()) {\n        val node = queue.poll()      // Dequeue from the queue\n        list.add(node?._val!!)       // Store the node value\n        if (node.left != null)\n            queue.offer(node.left)   // Enqueue the left child\n        if (node.right != null)\n            queue.offer(node.right)  // Enqueue the right child\n    }\n    return list\n}\n
    binary_tree_bfs.rb
    ### Level-order traversal ###\ndef level_order(root)\n  # Initialize a queue and enqueue the root\n  queue = [root]\n  # Initialize a list to store the traversal sequence\n  res = []\n  while !queue.empty?\n    node = queue.shift # Dequeue from the queue\n    res << node.val # Store the node value\n    queue << node.left unless node.left.nil? # Enqueue the left child\n    queue << node.right unless node.right.nil? # Enqueue the right child\n  end\n  res\nend\n
    Visualize execution

    View full screen >

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2-complexity-analysis","level":3,"title":"2.   Complexity analysis","text":"
    • Time complexity: \\(O(n)\\). Each of the \\(n\\) nodes is visited once, taking \\(O(n)\\) time.
    • Space complexity: \\(O(n)\\). In the worst case, a perfect binary tree, the queue holds up to \\((n + 1) / 2\\) nodes just before the lowest level is visited, requiring \\(O(n)\\) space.
    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#722-preorder-inorder-and-postorder-traversal","level":2,"title":"7.2.2   Preorder, inorder, and postorder traversal","text":"

    Preorder, inorder, and postorder traversal are forms of depth-first traversal, also called depth-first search (DFS). They follow a path to its end, then backtrack and continue.

    Figure 7-10 illustrates depth-first traversal. Imagine walking once around the outside of the entire binary tree. At each node, you encounter three positions corresponding to preorder, inorder, and postorder visits.

    Figure 7-10   Preorder, inorder, and postorder traversal of a binary search tree

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1-implementation_1","level":3,"title":"1.   Implementation","text":"

    Depth-first search is usually implemented recursively:

    PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_dfs.py
    def pre_order(root: TreeNode | None):\n    \"\"\"Preorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit order: root -> left subtree -> right subtree\n    res.append(root.val)\n    pre_order(root=root.left)\n    pre_order(root=root.right)\n\ndef in_order(root: TreeNode | None):\n    \"\"\"Inorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit order: left subtree -> root -> right subtree\n    in_order(root=root.left)\n    res.append(root.val)\n    in_order(root=root.right)\n\ndef post_order(root: TreeNode | None):\n    \"\"\"Postorder traversal\"\"\"\n    if root is None:\n        return\n    # Visit order: left subtree -> right subtree -> root\n    post_order(root=root.left)\n    post_order(root=root.right)\n    res.append(root.val)\n
    binary_tree_dfs.cpp
    /* Preorder traversal */\nvoid preOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit order: root -> left subtree -> right subtree\n    vec.push_back(root->val);\n    preOrder(root->left);\n    preOrder(root->right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root->left);\n    vec.push_back(root->val);\n    inOrder(root->right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode *root) {\n    if (root == nullptr)\n        return;\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root->left);\n    postOrder(root->right);\n    vec.push_back(root->val);\n}\n
    binary_tree_dfs.java
    /* Preorder traversal */\nvoid preOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit order: root -> left subtree -> right subtree\n    list.add(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root.left);\n    list.add(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode root) {\n    if (root == null)\n        return;\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root.left);\n    postOrder(root.right);\n    list.add(root.val);\n}\n
    binary_tree_dfs.cs
    /* Preorder traversal */\nvoid PreOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit order: root -> left subtree -> right subtree\n    list.Add(root.val!.Value);\n    PreOrder(root.left);\n    PreOrder(root.right);\n}\n\n/* Inorder traversal */\nvoid InOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit order: left subtree -> root -> right subtree\n    InOrder(root.left);\n    list.Add(root.val!.Value);\n    InOrder(root.right);\n}\n\n/* Postorder traversal */\nvoid PostOrder(TreeNode? root) {\n    if (root == null) return;\n    // Visit order: left subtree -> right subtree -> root\n    PostOrder(root.left);\n    PostOrder(root.right);\n    list.Add(root.val!.Value);\n}\n
    binary_tree_dfs.go
    /* Preorder traversal */\nfunc preOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit order: root -> left subtree -> right subtree\n    nums = append(nums, node.Val)\n    preOrder(node.Left)\n    preOrder(node.Right)\n}\n\n/* Inorder traversal */\nfunc inOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(node.Left)\n    nums = append(nums, node.Val)\n    inOrder(node.Right)\n}\n\n/* Postorder traversal */\nfunc postOrder(node *TreeNode) {\n    if node == nil {\n        return\n    }\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(node.Left)\n    postOrder(node.Right)\n    nums = append(nums, node.Val)\n}\n
    binary_tree_dfs.swift
    /* Preorder traversal */\nfunc preOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit order: root -> left subtree -> right subtree\n    list.append(root.val)\n    preOrder(root: root.left)\n    preOrder(root: root.right)\n}\n\n/* Inorder traversal */\nfunc inOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root: root.left)\n    list.append(root.val)\n    inOrder(root: root.right)\n}\n\n/* Postorder traversal */\nfunc postOrder(root: TreeNode?) {\n    guard let root = root else {\n        return\n    }\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root: root.left)\n    postOrder(root: root.right)\n    list.append(root.val)\n}\n
    binary_tree_dfs.js
    /* Preorder traversal */\nfunction preOrder(root) {\n    if (root === null) return;\n    // Visit order: root -> left subtree -> right subtree\n    list.push(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nfunction inOrder(root) {\n    if (root === null) return;\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root.left);\n    list.push(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nfunction postOrder(root) {\n    if (root === null) return;\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root.left);\n    postOrder(root.right);\n    list.push(root.val);\n}\n
    binary_tree_dfs.ts
    /* Preorder traversal */\nfunction preOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit order: root -> left subtree -> right subtree\n    list.push(root.val);\n    preOrder(root.left);\n    preOrder(root.right);\n}\n\n/* Inorder traversal */\nfunction inOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root.left);\n    list.push(root.val);\n    inOrder(root.right);\n}\n\n/* Postorder traversal */\nfunction postOrder(root: TreeNode | null): void {\n    if (root === null) {\n        return;\n    }\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root.left);\n    postOrder(root.right);\n    list.push(root.val);\n}\n
    binary_tree_dfs.dart
    /* Preorder traversal */\nvoid preOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit order: root -> left subtree -> right subtree\n  list.add(node.val);\n  preOrder(node.left);\n  preOrder(node.right);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit order: left subtree -> root -> right subtree\n  inOrder(node.left);\n  list.add(node.val);\n  inOrder(node.right);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode? node) {\n  if (node == null) return;\n  // Visit order: left subtree -> right subtree -> root\n  postOrder(node.left);\n  postOrder(node.right);\n  list.add(node.val);\n}\n
    binary_tree_dfs.rs
    /* Preorder traversal */\nfn pre_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit order: root -> left subtree -> right subtree\n            let node = node.borrow();\n            res.push(node.val);\n            dfs(node.left.as_ref(), res);\n            dfs(node.right.as_ref(), res);\n        }\n    }\n    dfs(root, &mut result);\n\n    result\n}\n\n/* Inorder traversal */\nfn in_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit order: left subtree -> root -> right subtree\n            let node = node.borrow();\n            dfs(node.left.as_ref(), res);\n            res.push(node.val);\n            dfs(node.right.as_ref(), res);\n        }\n    }\n    dfs(root, &mut result);\n\n    result\n}\n\n/* Postorder traversal */\nfn post_order(root: Option<&Rc<RefCell<TreeNode>>>) -> Vec<i32> {\n    let mut result = vec![];\n\n    fn dfs(root: Option<&Rc<RefCell<TreeNode>>>, res: &mut Vec<i32>) {\n        if let Some(node) = root {\n            // Visit order: left subtree -> right subtree -> root\n            let node = node.borrow();\n            dfs(node.left.as_ref(), res);\n            dfs(node.right.as_ref(), res);\n            res.push(node.val);\n        }\n    }\n\n    dfs(root, &mut result);\n\n    result\n}\n
    binary_tree_dfs.c
    /* Preorder traversal */\nvoid preOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit order: root -> left subtree -> right subtree\n    arr[(*size)++] = root->val;\n    preOrder(root->left, size);\n    preOrder(root->right, size);\n}\n\n/* Inorder traversal */\nvoid inOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root->left, size);\n    arr[(*size)++] = root->val;\n    inOrder(root->right, size);\n}\n\n/* Postorder traversal */\nvoid postOrder(TreeNode *root, int *size) {\n    if (root == NULL)\n        return;\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root->left, size);\n    postOrder(root->right, size);\n    arr[(*size)++] = root->val;\n}\n
    binary_tree_dfs.kt
    /* Preorder traversal */\nfun preOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit order: root -> left subtree -> right subtree\n    list.add(root._val)\n    preOrder(root.left)\n    preOrder(root.right)\n}\n\n/* Inorder traversal */\nfun inOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit order: left subtree -> root -> right subtree\n    inOrder(root.left)\n    list.add(root._val)\n    inOrder(root.right)\n}\n\n/* Postorder traversal */\nfun postOrder(root: TreeNode?) {\n    if (root == null) return\n    // Visit order: left subtree -> right subtree -> root\n    postOrder(root.left)\n    postOrder(root.right)\n    list.add(root._val)\n}\n
    binary_tree_dfs.rb
    ### Preorder traversal ###\ndef pre_order(root)\n  return if root.nil?\n\n  # Visit order: root -> left subtree -> right subtree\n  $res << root.val\n  pre_order(root.left)\n  pre_order(root.right)\nend\n\n### Inorder traversal ###\ndef in_order(root)\n  return if root.nil?\n\n  # Visit order: left subtree -> root -> right subtree\n  in_order(root.left)\n  $res << root.val\n  in_order(root.right)\nend\n\n### Postorder traversal ###\ndef post_order(root)\n  return if root.nil?\n\n  # Visit order: left subtree -> right subtree -> root\n  post_order(root.left)\n  post_order(root.right)\n  $res << root.val\nend\n
    Visualize execution

    View full screen >

    Tip

    Depth-first search can also be implemented iteratively. Interested readers can explore this approach themselves.

    Figure 7-11 shows the recursive process for preorder traversal, with two opposing phases: descending into calls and returning from them.

    1. Descending starts a new function call and visits the next node.
    2. Returning ends a function call, indicating that the current node has been fully processed.
    <1><2><3><4><5><6><7><8><9><10><11>

    Figure 7-11   The recursive process of preorder traversal

    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2-complexity-analysis_1","level":3,"title":"2.   Complexity analysis","text":"
    • Time complexity: \\(O(n)\\). Every node is visited once, taking \\(O(n)\\) time.
    • Space complexity: \\(O(n)\\). In the worst case, the tree degenerates into a linked list, recursion reaches depth \\(n\\), and stack frames require \\(O(n)\\) space.
    ","path":["Chapter 7.   Trees","7.2   Traversing a binary tree"],"tags":[]},{"location":"chapter_tree/exercises/","level":1,"title":"7.7   Exercises","text":"","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#771-check-your-understanding","level":2,"title":"7.7.1   Check your understanding","text":"","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#1-complete-full-and-perfect-binary-trees","level":3,"title":"1.   Complete, full, and perfect binary trees","text":"

    The following arrays represent binary trees in level order, with None marking an empty position:

    • Tree A: [1, 2, 3, 4, 5, 6]
    • Tree B: [1, 2, 3, None, None, 6, 7]
    1. Which tree is a complete binary tree?
    2. Which is a full binary tree, meaning that every non-leaf node has two children?
    3. Is either tree a perfect binary tree? Explain for each tree.
    Reference answer
    1. Tree A is complete. Only its bottom level is not full, and the nodes occupy consecutive positions from left to right. Tree B is not complete: its bottom level has empty positions on the left but still has nodes farther to the right.

    2. Tree B is full: nodes 1 and 3 each have two children, and all the other nodes are leaves. Tree A is not full because node 3 has only its left child, 6.

    3. Neither tree is perfect, because neither has a completely filled bottom level.

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#2-three-traversal-orders-for-the-same-tree","level":3,"title":"2.   Three traversal orders for the same tree","text":"

    Store the array [1, 2, 3, 4, 5, 6, 7] in a complete binary tree in level order.

    1. Draw the tree.
    2. Write its preorder, inorder, and postorder traversal sequences.
    3. In the inorder sequence, which parts of the tree do the sequences to the left and right of root node 1 correspond to?
    Reference answer
    1. The tree is:

            1\n    /   \\\n   2     3\n  / \\   / \\\n 4   5 6   7\n
    2. Preorder: 1, 2, 4, 5, 3, 6, 7; inorder: 4, 2, 5, 1, 6, 3, 7; postorder: 4, 5, 2, 6, 7, 3, 1.

    3. The sequence 4, 2, 5 to the left of root node 1 is the inorder traversal of the left subtree. The sequence 6, 3, 7 to its right is the inorder traversal of the right subtree.

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#3-comparing-two-binary-search-trees","level":3,"title":"3.   Comparing two binary search trees","text":"

    Insert each of the following sequences from left to right into an empty binary search tree:

    • Sequence A: [4, 2, 6, 1, 3, 5, 7]
    • Sequence B: [1, 2, 3, 4, 5, 6, 7]
    1. Write the nodes visited when searching for 7 in each tree.
    2. If height is measured by the number of edges from the root to the farthest leaf, what is each tree's height?
    3. Based on your answers, do the two trees search for 7 equally efficiently? Explain using their shapes and search paths.
    Reference answer
    1. In the tree built from sequence A, the search path is 4 → 6 → 7. In the tree built from sequence B, it is 1 → 2 → 3 → 4 → 5 → 6 → 7.

    2. The first tree has every level filled and a height of 2. The second tree has only right children and a height of 6.

    3. The searches for 7 are not equally efficient. Insertion order changes the shape and height of a binary search tree. Searching for 7 visits only three nodes in the first tree, but must visit seven in sequence in the second. The taller the tree, the more nodes may need to be compared along a path in the worst case.

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#772-programming-exercises","level":2,"title":"7.7.2   Programming exercises","text":"","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#1-maximum-depth-of-a-binary-tree","level":3,"title":"1.   Maximum depth of a binary tree","text":"

    You are given the root node root of a binary tree. Each node contains an integer value and references to its left and right children.

    The maximum depth is the number of nodes on the path from the root to the farthest leaf. Return the maximum depth of the tree; an empty tree has maximum depth 0. Use recursion.

    Hints
    1. Depth is counted in nodes here. A tree containing only a root has maximum depth 1.
    2. Have the recursive function return the maximum depth of the subtree rooted at the current node.
    3. Return 0 for an empty node; otherwise, return max(depth(left), depth(right)) + 1.

    LeetCode

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#2-traversing-a-binary-tree-level-by-level","level":3,"title":"2.   Traversing a binary tree level by level","text":"

    Given the root node root of a binary tree, use a queue to visit all nodes level by level from top to bottom, visiting each level from left to right.

    Return a two-dimensional array: the first subarray contains the values on the root's level, the second contains those on the next level, and so on. Return an empty array if the tree is empty.

    Hints
    1. Level-order traversal visits earlier enqueued nodes first, so use a queue.
    2. At the start of each round, the nodes in the queue belong to exactly one level.
    3. Record the queue's length first, then dequeue that many nodes and enqueue their children.

    LeetCode

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/exercises/#3-the-kth-smallest-element-in-a-binary-search-tree","level":3,"title":"3.   The kth smallest element in a binary search tree","text":"

    A binary search tree contains n nodes with distinct values. If all node values are sorted in ascending order, their positions are numbered starting from 1.

    Given the root node root and an integer k satisfying 1 <= k <= n, return the node value in position k. Find the answer directly during inorder traversal, without first collecting all node values.

    Hints
    1. An inorder traversal of a binary search tree visits node values in ascending order.
    2. Inorder traversal processes the left subtree, the current node, and then the right subtree. Increment a counter by 1 when visiting the current node.
    3. When the counter first reaches k, the current node's value is the answer; there is no need to continue traversing.

    LeetCode

    ","path":["Chapter 7.   Trees","7.7   Exercises"],"tags":[]},{"location":"chapter_tree/summary/","level":1,"title":"7.6   Summary","text":"","path":["Chapter 7.   Trees","7.6   Summary"],"tags":[]},{"location":"chapter_tree/summary/#1-key-takeaways","level":3,"title":"1.   Key takeaways","text":"
    • A binary tree is a nonlinear data structure that embodies the divide-and-conquer idea of splitting in two. Each node contains a value and two pointers, one to each child.
    • A node's left or right child, together with everything below it, forms its left or right subtree.
    • Tree terminology includes root, leaf, level, degree, edge, height, and depth.
    • Initializing a binary tree and inserting or deleting nodes resemble linked-list operations.
    • Common types include perfect, complete, full, and balanced binary trees. A perfect tree is the ideal form; a linked list is the worst degenerate form.
    • A binary tree can be represented by an array of node values and empty positions in level order. Index mappings between parents and children replace pointers.
    • Level-order traversal is breadth-first search: it expands outward one layer at a time and usually uses a queue.
    • Preorder, inorder, and postorder traversal are depth-first search: they follow paths to their ends before backtracking and usually use recursion.
    • A binary search tree supports efficient lookup, insertion, and deletion in \\(O(\\log n)\\) time. If it degenerates into a linked list, all these operations degrade to \\(O(n)\\).
    • An AVL tree is a balanced binary search tree that uses rotations to maintain balance through insertions and deletions.
    • AVL rotations include right rotation, left rotation, right-then-left rotation, and left-then-right rotation. After an insertion or deletion, rotations are performed from the bottom up to restore balance.
    ","path":["Chapter 7.   Trees","7.6   Summary"],"tags":[]},{"location":"chapter_tree/summary/#2-q-a","level":3,"title":"2.   Q & A","text":"

    Q: For a binary tree with only one node, are both the tree's height and the root's depth \\(0\\)?

    Yes. Height and depth are usually defined by the number of edges traversed.

    Q: The text says binary-tree insertion and deletion usually involve a sequence of coordinated operations. What does that mean? Does it refer to releasing resources held by child nodes?

    For example, deleting a node from a binary search tree has three cases, and each case requires several steps involving nodes.

    Q: Why does depth-first traversal have preorder, inorder, and postorder variants? What is each used for?

    Just as an array can be traversed forward or backward, these are three ways to visit a binary tree and obtain a particular ordering. In a binary search tree, values satisfy left child value < root value < right child value, so visiting “left \\(\\rightarrow\\) root \\(\\rightarrow\\) right” produces a sorted node sequence.

    Q: A right rotation adjusts node, child, and grand_child. What happens to the link between node and its parent? Doesn't the rotation break it?

    View the operation recursively. right_rotate(root) receives a subtree's root and ends with return child, returning the new root of that subtree. The connection to its parent is updated after the function returns; it is outside the rotation function's responsibility.

    Q: In C++, how do you decide which methods are private and which are public? Why is height() public but updateHeight() private?

    The main consideration is where the method is used. A method used only within the class should be private. Calling updateHeight() alone is not meaningful to a user; it is just one step of insertion or deletion. In contrast, height() retrieves a node's height, much like vector.size(), so it is public for convenient access.

    Q: How do you build a binary search tree from input data? Is choosing the root important?

    Yes. The construction is provided by the build_tree() method in the binary-search-tree code. Usually, we sort the input, choose its middle element as the root, and recursively construct the left and right subtrees. This keeps the tree as balanced as possible.

    Q: Must strings in Java always be compared with equals()?

    For primitive types in Java, == compares variable values. For reference types, the two forms work differently.

    • == compares whether two variables refer to the same object, meaning the same location in memory.
    • equals() compares whether two objects have equal values.

    Use equals() when comparing values. However, strings initialized with String a = \"hi\"; String b = \"hi\"; are stored in the string pool and refer to the same object, so a == b also works for these strings.

    Q: Just before breadth-first traversal reaches the lowest level, does the queue contain \\(2^h\\) nodes?

    Yes. For example, a perfect binary tree of height \\(h = 2\\) has \\(n = 7\\) nodes, with \\(4 = 2^h = (n + 1) / 2\\) nodes on its lowest level.

    ","path":["Chapter 7.   Trees","7.6   Summary"],"tags":[]}]} \ No newline at end of file diff --git a/en/sitemap.xml b/en/sitemap.xml index 7075b27de..06eda04b9 100644 --- a/en/sitemap.xml +++ b/en/sitemap.xml @@ -354,7 +354,4 @@ https://www.hello-algo.com/en/chapter_reference/ - - https://www.hello-algo.com/en/chapter_paperbook/ - \ No newline at end of file diff --git a/en/stylesheets/animation_player.css b/en/stylesheets/animation_player.css index c7e789263..dda3acfe9 100644 --- a/en/stylesheets/animation_player.css +++ b/en/stylesheets/animation_player.css @@ -176,4 +176,4 @@ font-size: 0.7rem; } } -/*! update cache: 20261030193205 */ +/*! update cache: 20261030195404 */ diff --git a/en/stylesheets/extra.css b/en/stylesheets/extra.css index a1b5d751b..ba521bb2a 100644 --- a/en/stylesheets/extra.css +++ b/en/stylesheets/extra.css @@ -996,4 +996,4 @@ a:hover .device-on-hover { max-width: 100%; } } -/*! update cache: 20261030193205 */ +/*! update cache: 20261030195404 */ diff --git a/en/stylesheets/giscus-dark.css b/en/stylesheets/giscus-dark.css index 81590aebf..dd47e7379 100644 --- a/en/stylesheets/giscus-dark.css +++ b/en/stylesheets/giscus-dark.css @@ -122,4 +122,4 @@ main .gsc-loading-image { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193205 */ +/*! update cache: 20261030195404 */ diff --git a/en/stylesheets/giscus-light.css b/en/stylesheets/giscus-light.css index 8966f19a2..f5c5d361f 100644 --- a/en/stylesheets/giscus-light.css +++ b/en/stylesheets/giscus-light.css @@ -153,4 +153,4 @@ main { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193205 */ +/*! update cache: 20261030195404 */ diff --git a/ja/javascripts/animation_player.js b/ja/javascripts/animation_player.js index 588983256..90868dcba 100644 --- a/ja/javascripts/animation_player.js +++ b/ja/javascripts/animation_player.js @@ -301,4 +301,4 @@ initAutoSlide(); } })(); -/*! update cache: 20261030193234 */ +/*! update cache: 20261030195434 */ diff --git a/ja/javascripts/katex.js b/ja/javascripts/katex.js index 9c2faef50..20cc8d78f 100644 --- a/ja/javascripts/katex.js +++ b/ja/javascripts/katex.js @@ -8,4 +8,4 @@ document$.subscribe(({ body }) => { ], }); }); -/*! update cache: 20261030193234 */ +/*! update cache: 20261030195434 */ diff --git a/ja/javascripts/mathjax.js b/ja/javascripts/mathjax.js index 9e6c50ea0..9096198c8 100644 --- a/ja/javascripts/mathjax.js +++ b/ja/javascripts/mathjax.js @@ -15,4 +15,4 @@ window.MathJax = { document$.subscribe(() => { MathJax.typesetPromise(); }); -/*! update cache: 20261030193234 */ +/*! update cache: 20261030195434 */ diff --git a/ja/javascripts/starfield.js b/ja/javascripts/starfield.js index 63c30eff6..30f5d0b9c 100644 --- a/ja/javascripts/starfield.js +++ b/ja/javascripts/starfield.js @@ -469,4 +469,4 @@ return Starfield; }); -/*! update cache: 20261030193234 */ +/*! update cache: 20261030195434 */ diff --git a/ja/stylesheets/animation_player.css b/ja/stylesheets/animation_player.css index c107f3bce..37b3461bc 100644 --- a/ja/stylesheets/animation_player.css +++ b/ja/stylesheets/animation_player.css @@ -176,4 +176,4 @@ font-size: 0.7rem; } } -/*! update cache: 20261030193234 */ +/*! update cache: 20261030195434 */ diff --git a/ja/stylesheets/extra.css b/ja/stylesheets/extra.css index 6c9458382..7e2a73fa5 100644 --- a/ja/stylesheets/extra.css +++ b/ja/stylesheets/extra.css @@ -996,4 +996,4 @@ a:hover .device-on-hover { max-width: 100%; } } -/*! update cache: 20261030193234 */ +/*! update cache: 20261030195434 */ diff --git a/ja/stylesheets/giscus-dark.css b/ja/stylesheets/giscus-dark.css index 001572ff2..0a0692afb 100644 --- a/ja/stylesheets/giscus-dark.css +++ b/ja/stylesheets/giscus-dark.css @@ -122,4 +122,4 @@ main .gsc-loading-image { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193234 */ +/*! update cache: 20261030195434 */ diff --git a/ja/stylesheets/giscus-light.css b/ja/stylesheets/giscus-light.css index ab90b5f08..406e928cc 100644 --- a/ja/stylesheets/giscus-light.css +++ b/ja/stylesheets/giscus-light.css @@ -153,4 +153,4 @@ main { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193234 */ +/*! update cache: 20261030195434 */ diff --git a/javascripts/animation_player.js b/javascripts/animation_player.js index 9dfd45096..29215c0f5 100644 --- a/javascripts/animation_player.js +++ b/javascripts/animation_player.js @@ -301,4 +301,4 @@ initAutoSlide(); } })(); -/*! update cache: 20261030193105 */ +/*! update cache: 20261030195306 */ diff --git a/javascripts/katex.js b/javascripts/katex.js index 7b317d3c8..23bf1a562 100644 --- a/javascripts/katex.js +++ b/javascripts/katex.js @@ -8,4 +8,4 @@ document$.subscribe(({ body }) => { ], }); }); -/*! update cache: 20261030193105 */ +/*! update cache: 20261030195306 */ diff --git a/javascripts/mathjax.js b/javascripts/mathjax.js index 47aeb5d02..bef739f29 100644 --- a/javascripts/mathjax.js +++ b/javascripts/mathjax.js @@ -15,4 +15,4 @@ window.MathJax = { document$.subscribe(() => { MathJax.typesetPromise(); }); -/*! update cache: 20261030193105 */ +/*! update cache: 20261030195306 */ diff --git a/javascripts/starfield.js b/javascripts/starfield.js index 4c45f9187..6692af413 100644 --- a/javascripts/starfield.js +++ b/javascripts/starfield.js @@ -469,4 +469,4 @@ return Starfield; }); -/*! update cache: 20261030193105 */ +/*! update cache: 20261030195306 */ diff --git a/ko/javascripts/animation_player.js b/ko/javascripts/animation_player.js index 4c395737e..ac7599776 100644 --- a/ko/javascripts/animation_player.js +++ b/ko/javascripts/animation_player.js @@ -301,4 +301,4 @@ initAutoSlide(); } })(); -/*! update cache: 20261030193337 */ +/*! update cache: 20261030195536 */ diff --git a/ko/javascripts/katex.js b/ko/javascripts/katex.js index 8537df67e..47a5221f1 100644 --- a/ko/javascripts/katex.js +++ b/ko/javascripts/katex.js @@ -8,4 +8,4 @@ document$.subscribe(({ body }) => { ], }); }); -/*! update cache: 20261030193337 */ +/*! update cache: 20261030195536 */ diff --git a/ko/javascripts/mathjax.js b/ko/javascripts/mathjax.js index 59a33e0dd..4a3ed1c81 100644 --- a/ko/javascripts/mathjax.js +++ b/ko/javascripts/mathjax.js @@ -15,4 +15,4 @@ window.MathJax = { document$.subscribe(() => { MathJax.typesetPromise(); }); -/*! update cache: 20261030193337 */ +/*! update cache: 20261030195536 */ diff --git a/ko/javascripts/starfield.js b/ko/javascripts/starfield.js index 2d09c0fd9..d27f00bbc 100644 --- a/ko/javascripts/starfield.js +++ b/ko/javascripts/starfield.js @@ -469,4 +469,4 @@ return Starfield; }); -/*! update cache: 20261030193337 */ +/*! update cache: 20261030195536 */ diff --git a/ko/stylesheets/animation_player.css b/ko/stylesheets/animation_player.css index 34c931ed4..88fcb4dab 100644 --- a/ko/stylesheets/animation_player.css +++ b/ko/stylesheets/animation_player.css @@ -176,4 +176,4 @@ font-size: 0.7rem; } } -/*! update cache: 20261030193337 */ +/*! update cache: 20261030195536 */ diff --git a/ko/stylesheets/extra.css b/ko/stylesheets/extra.css index 76fdc1eb7..bb0b1d94c 100644 --- a/ko/stylesheets/extra.css +++ b/ko/stylesheets/extra.css @@ -996,4 +996,4 @@ a:hover .device-on-hover { max-width: 100%; } } -/*! update cache: 20261030193337 */ +/*! update cache: 20261030195536 */ diff --git a/ko/stylesheets/giscus-dark.css b/ko/stylesheets/giscus-dark.css index 9727cf830..a02b14d60 100644 --- a/ko/stylesheets/giscus-dark.css +++ b/ko/stylesheets/giscus-dark.css @@ -122,4 +122,4 @@ main .gsc-loading-image { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193337 */ +/*! update cache: 20261030195536 */ diff --git a/ko/stylesheets/giscus-light.css b/ko/stylesheets/giscus-light.css index 6c7a2d321..cfd3388b2 100644 --- a/ko/stylesheets/giscus-light.css +++ b/ko/stylesheets/giscus-light.css @@ -153,4 +153,4 @@ main { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193337 */ +/*! update cache: 20261030195536 */ diff --git a/ru/javascripts/animation_player.js b/ru/javascripts/animation_player.js index 7ea0f97c4..dc8dfb535 100644 --- a/ru/javascripts/animation_player.js +++ b/ru/javascripts/animation_player.js @@ -301,4 +301,4 @@ initAutoSlide(); } })(); -/*! update cache: 20261030193307 */ +/*! update cache: 20261030195506 */ diff --git a/ru/javascripts/katex.js b/ru/javascripts/katex.js index 14f2d921e..21689ffa5 100644 --- a/ru/javascripts/katex.js +++ b/ru/javascripts/katex.js @@ -8,4 +8,4 @@ document$.subscribe(({ body }) => { ], }); }); -/*! update cache: 20261030193307 */ +/*! update cache: 20261030195506 */ diff --git a/ru/javascripts/mathjax.js b/ru/javascripts/mathjax.js index ab0140850..a0fc0b602 100644 --- a/ru/javascripts/mathjax.js +++ b/ru/javascripts/mathjax.js @@ -15,4 +15,4 @@ window.MathJax = { document$.subscribe(() => { MathJax.typesetPromise(); }); -/*! update cache: 20261030193307 */ +/*! update cache: 20261030195506 */ diff --git a/ru/javascripts/starfield.js b/ru/javascripts/starfield.js index 9db5a22a7..fa74b5e07 100644 --- a/ru/javascripts/starfield.js +++ b/ru/javascripts/starfield.js @@ -469,4 +469,4 @@ return Starfield; }); -/*! update cache: 20261030193307 */ +/*! update cache: 20261030195506 */ diff --git a/ru/stylesheets/animation_player.css b/ru/stylesheets/animation_player.css index e548b90dc..d870ae67d 100644 --- a/ru/stylesheets/animation_player.css +++ b/ru/stylesheets/animation_player.css @@ -176,4 +176,4 @@ font-size: 0.7rem; } } -/*! update cache: 20261030193307 */ +/*! update cache: 20261030195506 */ diff --git a/ru/stylesheets/extra.css b/ru/stylesheets/extra.css index 10f5c97ae..7a848079d 100644 --- a/ru/stylesheets/extra.css +++ b/ru/stylesheets/extra.css @@ -996,4 +996,4 @@ a:hover .device-on-hover { max-width: 100%; } } -/*! update cache: 20261030193307 */ +/*! update cache: 20261030195506 */ diff --git a/ru/stylesheets/giscus-dark.css b/ru/stylesheets/giscus-dark.css index 6286db69a..b0107414f 100644 --- a/ru/stylesheets/giscus-dark.css +++ b/ru/stylesheets/giscus-dark.css @@ -122,4 +122,4 @@ main .gsc-loading-image { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193307 */ +/*! update cache: 20261030195506 */ diff --git a/ru/stylesheets/giscus-light.css b/ru/stylesheets/giscus-light.css index 9709948c9..b334b93d4 100644 --- a/ru/stylesheets/giscus-light.css +++ b/ru/stylesheets/giscus-light.css @@ -153,4 +153,4 @@ main { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193307 */ +/*! update cache: 20261030195506 */ diff --git a/stylesheets/animation_player.css b/stylesheets/animation_player.css index f237dc61d..fcf52a29f 100644 --- a/stylesheets/animation_player.css +++ b/stylesheets/animation_player.css @@ -176,4 +176,4 @@ font-size: 0.7rem; } } -/*! update cache: 20261030193105 */ +/*! update cache: 20261030195306 */ diff --git a/stylesheets/extra.css b/stylesheets/extra.css index 31e056026..76358f0a9 100644 --- a/stylesheets/extra.css +++ b/stylesheets/extra.css @@ -996,4 +996,4 @@ a:hover .device-on-hover { max-width: 100%; } } -/*! update cache: 20261030193105 */ +/*! update cache: 20261030195306 */ diff --git a/stylesheets/giscus-dark.css b/stylesheets/giscus-dark.css index 160d866fe..654e2ec09 100644 --- a/stylesheets/giscus-dark.css +++ b/stylesheets/giscus-dark.css @@ -122,4 +122,4 @@ main .gsc-loading-image { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193105 */ +/*! update cache: 20261030195306 */ diff --git a/stylesheets/giscus-light.css b/stylesheets/giscus-light.css index 8d19d1d85..47a1156d9 100644 --- a/stylesheets/giscus-light.css +++ b/stylesheets/giscus-light.css @@ -153,4 +153,4 @@ main { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193105 */ +/*! update cache: 20261030195306 */ diff --git a/zh-hant/javascripts/animation_player.js b/zh-hant/javascripts/animation_player.js index 5d482c682..7ca419f81 100644 --- a/zh-hant/javascripts/animation_player.js +++ b/zh-hant/javascripts/animation_player.js @@ -301,4 +301,4 @@ initAutoSlide(); } })(); -/*! update cache: 20261030193135 */ +/*! update cache: 20261030195335 */ diff --git a/zh-hant/javascripts/katex.js b/zh-hant/javascripts/katex.js index d6ee12aff..1ee51feee 100644 --- a/zh-hant/javascripts/katex.js +++ b/zh-hant/javascripts/katex.js @@ -8,4 +8,4 @@ document$.subscribe(({ body }) => { ], }); }); -/*! update cache: 20261030193135 */ +/*! update cache: 20261030195335 */ diff --git a/zh-hant/javascripts/mathjax.js b/zh-hant/javascripts/mathjax.js index 41169a4d9..a12052574 100644 --- a/zh-hant/javascripts/mathjax.js +++ b/zh-hant/javascripts/mathjax.js @@ -15,4 +15,4 @@ window.MathJax = { document$.subscribe(() => { MathJax.typesetPromise(); }); -/*! update cache: 20261030193135 */ +/*! update cache: 20261030195335 */ diff --git a/zh-hant/javascripts/starfield.js b/zh-hant/javascripts/starfield.js index e2872ff52..d285559ca 100644 --- a/zh-hant/javascripts/starfield.js +++ b/zh-hant/javascripts/starfield.js @@ -469,4 +469,4 @@ return Starfield; }); -/*! update cache: 20261030193135 */ +/*! update cache: 20261030195335 */ diff --git a/zh-hant/stylesheets/animation_player.css b/zh-hant/stylesheets/animation_player.css index b2a7aa697..872851b99 100644 --- a/zh-hant/stylesheets/animation_player.css +++ b/zh-hant/stylesheets/animation_player.css @@ -176,4 +176,4 @@ font-size: 0.7rem; } } -/*! update cache: 20261030193135 */ +/*! update cache: 20261030195335 */ diff --git a/zh-hant/stylesheets/extra.css b/zh-hant/stylesheets/extra.css index a0a4dbbac..c39dea934 100644 --- a/zh-hant/stylesheets/extra.css +++ b/zh-hant/stylesheets/extra.css @@ -996,4 +996,4 @@ a:hover .device-on-hover { max-width: 100%; } } -/*! update cache: 20261030193135 */ +/*! update cache: 20261030195335 */ diff --git a/zh-hant/stylesheets/giscus-dark.css b/zh-hant/stylesheets/giscus-dark.css index 3b37be952..3d3f9f5d3 100644 --- a/zh-hant/stylesheets/giscus-dark.css +++ b/zh-hant/stylesheets/giscus-dark.css @@ -122,4 +122,4 @@ main .gsc-loading-image { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193135 */ +/*! update cache: 20261030195335 */ diff --git a/zh-hant/stylesheets/giscus-light.css b/zh-hant/stylesheets/giscus-light.css index b77334411..de830bc85 100644 --- a/zh-hant/stylesheets/giscus-light.css +++ b/zh-hant/stylesheets/giscus-light.css @@ -153,4 +153,4 @@ main { .gsc-reply-content::-webkit-scrollbar-track { background: transparent; } -/*! update cache: 20261030193135 */ +/*! update cache: 20261030195335 */