diff --git a/322-dp-memo.jpg b/322-dp-memo.jpg new file mode 100644 index 0000000..ab8871f Binary files /dev/null and b/322-dp-memo.jpg differ diff --git a/Cargo.lock b/Cargo.lock index 9a49b34..0a28697 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,1245 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.1.3", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0dfe5e9e71bdcf4e4954f7d14da74d1cdb92a3a07686452d1509652684b1aab" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de36c2bee19fba779808f92bf5d9b0fa5a40095c277aba10c458a12b35d21d6" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "font-kit" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" +dependencies = [ + "bitflags 2.9.4", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leetcode-practice" version = "0.1.0" +dependencies = [ + "criterion", + "plotters", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags 2.9.4", + "libc", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-bitmap" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ce181e3f6bf82d6c1dc569103ca7b1bd964c60ba03d7e6cdfbb3e3eb7f7405" +dependencies = [ + "gif", + "image", + "plotters-backend", +] + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link 0.1.3", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 55c2bea..39d0c0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,14 @@ [package] name= "leetcode-practice" version = "0.1.0" -edition = "2021" \ No newline at end of file +edition = "2021" + +[dev-dependencies] +criterion = { version = "0.8", features = ["html_reports"] } + +[[bench]] +name = "coin_change" +harness = false + +[dependencies] +plotters = "0.3.7" diff --git a/benches/coin_change.rs b/benches/coin_change.rs new file mode 100644 index 0000000..8c3b5bb --- /dev/null +++ b/benches/coin_change.rs @@ -0,0 +1,71 @@ +/* + このコードはGPT-5.1を利用して出力しました。 +*/ + +use leetcode_practice::chart::draw_multi_line_chart; +use leetcode_practice::solutions::*; + +fn main() -> Result<(), Box> { + let coins = vec![1, 2, 5, 186, 419, 83, 408]; + let inputs = vec![10, 20, 40, 80, 160, 320, 6249]; + + // A vs B vs C の複数シリーズ + let mut series: Vec<(&str, Vec<(f64, f64)>)> = vec![]; + + let mut recursive_dfs = vec![]; + for amount in &inputs { + let t = measure(|| step1a::Solution::coin_change(coins.clone(), *amount)); + recursive_dfs.push((*amount as f64, t)); + } + series.push(("recursive_dfs(step1a.rs)", recursive_dfs)); + + let mut bfs = vec![]; + for amount in &inputs { + let t = measure(|| step1b_bfs::Solution::coin_change(coins.clone(), *amount)); + bfs.push((*amount as f64, t)); + } + series.push(("bfs(step1b_bfs.rs)", bfs)); + + let mut dp = vec![]; + for amount in &inputs { + let t = measure(|| step1c_dp::Solution::coin_change(coins.clone(), *amount)); + dp.push((*amount as f64, t)); + } + series.push(("dp(step1c_dp.rs)", dp)); + + let mut bfs_v2 = vec![]; + for amount in &inputs { + let t = measure(|| step2_bfs::Solution::coin_change(coins.clone(), *amount)); + bfs_v2.push((*amount as f64, t)); + } + series.push(("bfs_v2(step2_bfs.rs)", bfs_v2)); + + let mut dp_v2 = vec![]; + for amount in &inputs { + let t = measure(|| step2a_dp::Solution::coin_change(coins.clone(), *amount)); + dp_v2.push((*amount as f64, t)); + } + series.push(("dp_v2(step2a_dp)", dp_v2)); + + // グラフ出力 + draw_multi_line_chart( + "multi_line.svg", + &series, + "runtime comparison", + "amount", + "time per call [ns]", + )?; + + println!("multi_line.png を出力しました!"); + Ok(()) +} + +/// 単純な計測関数:closure を N 回実行し平均 ns を返す +fn measure i32>(f: F) -> f64 { + let repeat = 200; + let start = std::time::Instant::now(); + for _ in 0..repeat { + f(); + } + start.elapsed().as_nanos() as f64 / repeat as f64 +} diff --git a/multi_line.svg b/multi_line.svg new file mode 100644 index 0000000..326cc41 --- /dev/null +++ b/multi_line.svg @@ -0,0 +1,245 @@ + + + +runtime comparison + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +time per call [ns] + + +amount + + + + + + + + + + + + + + + + + + + + +50000.0 + + + +100000.0 + + + +150000.0 + + + +200000.0 + + + +250000.0 + + + +300000.0 + + + +350000.0 + + + +400000.0 + + + +450000.0 + + + +500000.0 + + + +550000.0 + + + + +1000.0 + + + +2000.0 + + + +3000.0 + + + +4000.0 + + + +5000.0 + + + +6000.0 + + + + + + + + + + +recursive_dfs(step1a.rs) + + +bfs(step1b_bfs.rs) + + +dp(step1c_dp.rs) + + +bfs_v2(step2_bfs.rs) + + +dp_v2(step2a_dp) + + + + + + + diff --git a/src/bin/step1.rs b/src/bin/step1.rs deleted file mode 100644 index d640da2..0000000 --- a/src/bin/step1.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Step1 -// 目的: 方法を思いつく - -// 方法 -// 5分考えてわからなかったら答えをみる -// 答えを見て理解したと思ったら全部消して答えを隠して書く -// 5分筆が止まったらもう一回みて全部消す -// 正解したら終わり - -/* - 何がわからなかったか - - - - 何を考えて解いていたか - - - - 想定ユースケース - - - - 正解してから気づいたこと - - -*/ - -pub struct Solution {} -impl Solution {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn step1_test() {} -} diff --git a/src/bin/step2.rs b/src/bin/step2.rs deleted file mode 100644 index e92520d..0000000 --- a/src/bin/step2.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Step2 -// 目的: 自然な書き方を考えて整理する - -// 方法 -// Step1のコードを読みやすくしてみる -// 他の人のコードを2つは読んでみること -// 正解したら終わり - -// 以下をメモに残すこと -// 講師陣はどのようなコメントを残すだろうか? -// 他の人のコードを読んで考えたこと -// 改善する時に考えたこと - -/* - 講師陣はどのようなコメントを残すだろうか? - - - - 他の人のコードを読んで考えたこと - - - - 他の想定ユースケース - - - - 改善する時に考えたこと - - -*/ - -pub struct Solution {} -impl Solution {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn step2_test() {} -} diff --git a/src/bin/step3.rs b/src/bin/step3.rs deleted file mode 100644 index 0af0a4a..0000000 --- a/src/bin/step3.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Step3 -// 目的: 覚えられないのは、なんか素直じゃないはずなので、そこを探し、ゴールに到達する - -// 方法 -// 時間を測りながらもう一度解く -// 10分以内に一度もエラーを吐かず正解 -// これを3回連続でできたら終わり -// レビューを受ける -// 作れないデータ構造があった場合は別途自作すること - -/* - 時間計算量: - 空間計算量: -*/ - -/* - 1回目: 分秒 - 2回目: 分秒 - 3回目: 分秒 -*/ - -pub struct Solution {} -impl Solution {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn step3_test() {} -} diff --git a/src/chart.rs b/src/chart.rs new file mode 100644 index 0000000..2ce2068 --- /dev/null +++ b/src/chart.rs @@ -0,0 +1,94 @@ +/* + このコードはGPT-5.1を利用して出力しました。 +*/ +use plotters::prelude::*; + +/// マルチライン折れ線グラフを SVG に出力する +/// +/// `series` は以下形式: +/// [ +/// ("step1", vec![(x, y), (x, y), ...]), +/// ("step1c_dp", vec![(x, y), (x, y), ...]), +/// ("step3", vec![(x, y), ...]), +/// ] +pub fn draw_multi_line_chart( + path: &str, // "multi_line.svg" + series: &[(&str, Vec<(f64, f64)>)], + title: &str, + x_label: &str, + y_label: &str, +) -> Result<(), Box> { + if series.is_empty() { + return Ok(()); + } + + // ----------------- 軸の最小・最大を求める ----------------- + + let mut min_x = f64::INFINITY; + let mut max_x = f64::NEG_INFINITY; + let mut min_y = f64::INFINITY; + let mut max_y = f64::NEG_INFINITY; + + for (_, pts) in series { + for (x, y) in pts { + min_x = min_x.min(*x); + max_x = max_x.max(*x); + min_y = min_y.min(*y); + max_y = max_y.max(*y); + } + } + + // Plotters の都合で min==max だと描けないため少し広げる + if (min_x - max_x).abs() < f64::EPSILON { + min_x -= 1.0; + max_x += 1.0; + } + if (min_y - max_y).abs() < f64::EPSILON { + min_y -= 1.0; + max_y += 1.0; + } + + // ----------------- SVG の描画領域作成 ----------------- + + let root = SVGBackend::new(path, (1000, 600)).into_drawing_area(); + root.fill(&WHITE)?; + + let mut chart = ChartBuilder::on(&root) + .caption(title, ("sans-serif", 24)) + .margin(20) + .x_label_area_size(50) + .y_label_area_size(60) + .build_cartesian_2d(min_x..max_x, min_y..max_y)?; + + chart + .configure_mesh() + .x_desc(x_label) + .y_desc(y_label) + .draw()?; + + // ----------------- 色リスト(必要に応じて追加可能) ----------------- + + let colors = vec![&RED, &BLUE, &GREEN, &MAGENTA, &CYAN, &BLACK]; + + // ----------------- 複数シリーズの描画 ----------------- + + for (idx, (name, pts)) in series.iter().enumerate() { + let color = colors[idx % colors.len()]; + + chart + .draw_series(LineSeries::new(pts.clone(), color.stroke_width(3)))? + .label(*name) + .legend(move |(x, y)| PathElement::new([(x, y), (x + 20, y)], color.stroke_width(3))); + } + + // ----------------- 凡例(レジェンド)表示 ----------------- + + chart + .configure_series_labels() + .border_style(&BLACK) + .background_style(&WHITE.mix(0.8)) + .draw()?; + + root.present()?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 752577e..11ea443 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,3 @@ // Rust-Analyzerを動作させるために空ファイルを置いています。 +pub mod chart; +pub mod solutions; diff --git a/src/solutions/mod.rs b/src/solutions/mod.rs new file mode 100644 index 0000000..1ad93e6 --- /dev/null +++ b/src/solutions/mod.rs @@ -0,0 +1,5 @@ +pub mod step1a; +pub mod step1b_bfs; +pub mod step1c_dp; +pub mod step2_bfs; +pub mod step2a_dp; diff --git a/src/solutions/step1.rs b/src/solutions/step1.rs new file mode 100644 index 0000000..4a96f65 --- /dev/null +++ b/src/solutions/step1.rs @@ -0,0 +1,183 @@ +// Step1 +// 目的: 方法を思いつく + +// 方法 +// 5分考えてわからなかったら答えをみる +// 答えを見て理解したと思ったら全部消して答えを隠して書く +// 5分筆が止まったらもう一回みて全部消す +// 正解したら終わり + +/* + 問題の理解 + - 異なる金額のコインを表す整数からなる配列coninsと合計金額を表すamountが与えられる。 + 合計金額amountになる最小のコインの枚数の組み合わせをcoins配列から探してコインの数を解として返す。 + 合計金額になるようなコインの組み合わせを見つけられなければ-1を返す。 + coinsに含まれるコインの種類は種類ごとに何回使っても良い。 + coins=[1, 2, 5] amount=11 output=3 5+5+1 + coins=[2] amount=3 output = -1 + coins=[1] amount=0 output = 0 + + 何がわからなかったか + - いわゆる動的計画法での解き方が分からず、思いついた貪欲法でコードを書いたが貪欲法では解けない入力ケースまで思いつかなかった。 + + 何を考えて解いていたか + - amount=0 のとき output=0 となるのがDP問題における初期状態のを表すような感じがする。合計金額が0となるコインの枚数は0なので納得できる。 + - amount=0のとき0を返す。coins.isempty()で-1を返す。 + - amoount - coins[i] の補数を求める操作が必要そう。 + - 最小のコインを使うので、coinsの大きい金額を使って補数を求める。 + - coinsをソートしたい。coinsの種類は制約上 coins.len() <= 12 なので計算量は問題なさそう。 + - 補数を求めるたびにcoin_change_countをインクリメントする。 + - coins[i] < amount である場合にのみ補数を求める whileでこの条件で減算し続ける + - coins[i] == amount となったら、その時点のcoin_change_countで早期リターン + - メソッド終了地点まで到達したら -1 + - この考え方でナイーブな実装をしてWrong Answerとなった。 + coins=[186, 419, 83, 408] amount=6249 expect=20 output=-1 + よく分からないので、自分の実装方法でこのテストケースだけ手作業で解いてみる。 + coinsを降順ソートする。coins=[419, 408, 186, 83] + 6249 - (419 * 14) = 383 + 383 - (186 * 2) = 11 <- ここで11より小さいコインを持たないので-1となる。 + + 5分だけDP的な解き方の方向で考えてみる。 + - 前提としてなるべく交換するコインを少なくしたいので、より大きい金額のコインで優先的に交換したいという気持ちがある。 + これ以上何も思い浮かばないので答えを見る。 + NeetCode解説動画: + https://www.youtube.com/watch?v=H9bfqozjoqs + 自分がナイーブな実装と読んでいたのは貪欲法というアプローチであり、この問題は解けないという説明がされていた。 + とりあえず再帰+メモ化のアプローチを考える。 + 知りたいのはコインの組み合わせ枚数のうち最小のコイン数 + 再帰ケースに入るたびに使ったコインの数を+1する必要がある。 + 補数が値が使えるコイン金額よりも小さくなったらNoneを返す。 + 補数が0になったら、Some(使ったコインの数)とする。 + コインの組み合わせの中から最小の組み合わせのコイン数をiter().flatten()でSome(x)だけを取り出して最小を求める。 + .unwrap_or(-1)としておいて全部None(コインの組み合わせが存在しない)のときは-1を返す。 + 入力制約は + - 1 <= coins.len() <= 12 + - 1 <= coins[i] <= (2 ^ 31) - 1 + - 0 <= amount <= 10 ^ 4 + amount - coins[i]をするので、i32だと桁溢れするかも?と思ったが、amountが0以下になったらそれ以上減算しなければ大丈夫そう。 + 再帰深さはどうなるだろうか。 + coins[1] amount=100 のようなケースで考えると、100回減算する。最悪ケースで(10 ^ 4)になる。キャッシュが効かない最大深さは10_000なので問題なさそう。 + coins種類は最大で12となり、メモ化すればO(coins.len())になるという感覚。 + メモ化は何をキャッシュすればよいのか正直ピンときていない。コードを書けば分かるかも知れない。 + メモ化を仕様として動かないコードが出来上がった。解法を理解できていないので、step1a.rsで書き直す。 + + 再帰の設計 + 基本ケース + - if complement == coins[i] return Some(coin_change_count) + 再帰ケース + - complement -= coins[i] 、coin_change_count+1して再帰に入る。 + - complementが0未満であれば、 return Noneとして再帰に入らないようにする。 + + 想定ユースケース + - 使えるコインの枚数を表現して全てcoins配列に入れる。使ったコインを消費すれば自販機のお釣り計算とかに使えそうだと思った。 +*/ + +use std::collections::HashMap; + +pub struct Solution {} +impl Solution { + pub fn coin_change(conins: Vec, amount: i32) -> i32 { + /* + このコードは動きません。 + */ + let mut amount_to_change_count: HashMap> = HashMap::new(); + Self::change_coin(&conins, amount, 0, &mut amount_to_change_count).unwrap_or(-1) + } + + fn change_coin( + coins: &[i32], + amount: i32, + change_count: i32, + amount_to_change_count: &mut HashMap>, + ) -> Option { + /* + このコードは動きません。 + */ + if amount == 0 { + return Some(change_count); + } + + if let Some(change_count_cache) = amount_to_change_count.get(&amount) { + return *change_count_cache; + } + + let change_count = coins + .iter() + .map(|coin| { + let Some(remaining_amount) = amount.checked_sub(*coin) else { + return None; + }; + + if remaining_amount < 0 { + return None; + } + + if remaining_amount == 0 { + return Some(change_count + 1); + } + + Self::change_coin( + coins, + remaining_amount, + change_count + 1, + amount_to_change_count, + ) + }) + .into_iter() + .flatten() + .min(); + amount_to_change_count.insert(amount, change_count); + + change_count + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn playground() { + assert!(i32::MIN.checked_sub(i32::MAX).is_none()); + + assert_eq!( + vec![None, Some(3)] + .into_iter() + .flatten() + .min() + .unwrap_or(-1), + 3 + ); + + assert_eq!( + vec![None, None].into_iter().flatten().min().unwrap_or(-1), + -1 + ); + } + #[test] + fn step1_test() { + assert_eq!(Solution::coin_change(vec![], 0), 0) + + // let coins = vec![1, 2, 5]; + // let amount = 11; + // assert_eq!(Solution::coin_change(coins, amount), 3); + + // let coins = vec![2]; + // let amount = 3; + // assert_eq!(Solution::coin_change(coins, amount), -1); + + // let coins = vec![1]; + // let amount = 0; + // assert_eq!(Solution::coin_change(coins, amount), 0); + + // let coins = vec![1, 2, 3, 4, 5]; + // let amount = 7; + // assert_eq!(Solution::coin_change(coins, amount), 2); + + // coins=[186, 419, 83, 408] amount=6249 + // let coins = vec![186, 419, 83, 408]; + // let amount = 6249; + // assert_eq!(Solution::coin_change(coins, amount), 20); + } +} diff --git a/src/solutions/step1a.rs b/src/solutions/step1a.rs new file mode 100644 index 0000000..d6b5198 --- /dev/null +++ b/src/solutions/step1a.rs @@ -0,0 +1,131 @@ +// Step1a +// 目的: 方法を思いつく + +// 方法 +// 5分考えてわからなかったら答えをみる +// 答えを見て理解したと思ったら全部消して答えを隠して書く +// 5分筆が止まったらもう一回みて全部消す +// 正解したら終わり + +/* + 問題の理解 + - 異なる金額のコインを表す整数からなる配列coninsと合計金額を表すamountが与えられる。 + 合計金額amountになる最小のコインの枚数の組み合わせをcoins配列から探してコインの数を解として返す。 + 合計金額になるようなコインの組み合わせを見つけられなければ-1を返す。 + coinsに含まれるコインの種類は種類ごとに何回使っても良い。 + coins=[1, 2, 5] amount=11 output=3 5+5+1 + coins=[2] amount=3 output = -1 + coins=[1] amount=0 output = 0 + + 何を考えて解いていたか + NeetCodeの解説動画 + https://www.youtube.com/watch?v=H9bfqozjoqs + - いきなりDPの解法は飛躍し過ぎだと感じてしまい理解できないので、決定木として問題を見て、再帰+メモ化の実装方法を行う。 + - 決定木の考え方 + - amount に対して 全てのコイン種類 coins[i] との差 remaining_amount を求める。 + - 求めた差 remaining_amount に対して 全てのコイン種類 coins[i] とその差を求める。 + - ここで remaining_amount が 0未満になるまで再帰的に続く。 + - remaining_amount == 0 となったとき、それまでに使ったコインの数を解として知りたい。 + - 得られたコイン数の内、最小のもののみを取っておく。 + - 使ったコインの数え方をどうしよう。コインの使用枚数は単調増加する。 + - とりあえず再帰呼び出しするたびに使ったコインの枚数を+1する + - amountが0になったときにそのパスで使ったコインの枚数を使いたい。 + - Some(0) を再帰の基本ケースで返す。 + - amountが0未満になるようなパスのコインの枚数は捨てたい。 + - None を返す。 + + 再帰の設計 + - 基本ケース + - if amount == 0 return Some(0) + - 再帰ケース + - 再帰の戻り値がNoneであれば値を捨てる + - 再帰の戻り値がSomeであれば値を足して、最小枚数を更新していく。 + + ここまで考えて、実装の途中で手が止まったのでGPT-5.1のコードを写経した。 + 分からなかったこと + - amount == 0 となった時にSome(0)とするのが思いつかなかったのと、これを見た時にすぐに理解できなかった。つまり再帰の基本ケースが分からなかった。 + + 写経した後の解法の理解 + - 深さ優先探索(DFS)になっている。amount - coins[i] が 0 以下になるまで潜っていくイメージ + - 0 < amount - coins[i] であれば、amountはまだcoins[i]と交換できる余地があるので再帰に入っている。 + - 部分問題で考えると、交換できたコインの枚数というよりは手持ちのamountでcoins[i]で交換できたかという考え方の方が自然だと思った。 + + 正解してから気づいたこと + - 決定木の考え方で解くなら、幅優先探索でamount == 0を見つけた時点で探索を打ち切るような実装の方がしっくりくる感じがする。 + 決定木から底に向かうに途中で答えを見つけたらそれ以上は探索しなくて良いので。 step1b_bfs.rsで実装してみる。 + +*/ + +use std::collections::HashMap; + +pub struct Solution {} +impl Solution { + pub fn coin_change(coins: Vec, amount: i32) -> i32 { + let mut amount_to_change_count: HashMap> = HashMap::new(); + Self::calculate_change_count(&coins, amount, &mut amount_to_change_count).unwrap_or(-1) + } + + fn calculate_change_count( + coins: &[i32], + amount: i32, + amount_to_change_count: &mut HashMap>, + ) -> Option { + if amount == 0 { + return Some(0); + } + if amount < 0 { + return None; + } + if let Some(change_count_cache) = amount_to_change_count.get(&amount) { + return *change_count_cache; + } + + let mut min_change_count = None; + for coin in coins { + let Some(change_count) = + Self::calculate_change_count(coins, amount - *coin, amount_to_change_count) + else { + continue; + }; + + let candidate = change_count + 1; + + min_change_count = match min_change_count { + None => Some(candidate), + Some(current) => Some(current.min(candidate)), + } + } + amount_to_change_count.insert(amount, min_change_count); + + min_change_count + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn step1a_test() { + let coins = vec![1, 2, 5]; + let amount = 11; + assert_eq!(Solution::coin_change(coins, amount), 3); + + let coins = vec![2]; + let amount = 3; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![1]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![1, 2, 3, 4, 5]; + let amount = 7; + assert_eq!(Solution::coin_change(coins, amount), 2); + + let coins = vec![186, 419, 83, 408]; + let amount = 6249; + assert_eq!(Solution::coin_change(coins, amount), 20); + } +} diff --git a/src/solutions/step1b_bfs.rs b/src/solutions/step1b_bfs.rs new file mode 100644 index 0000000..77dd53b --- /dev/null +++ b/src/solutions/step1b_bfs.rs @@ -0,0 +1,142 @@ +// Step1b_BFS +// 目的: 別の実装方針を練習する。幅優先探査(BFS) + +/* + 問題の理解 + - 異なる金額のコインを表す整数からなる配列coninsと合計金額を表すamountが与えられる。 + 合計金額amountになる最小のコインの枚数の組み合わせをcoins配列から探してコインの数を解として返す。 + 合計金額になるようなコインの組み合わせを見つけられなければ-1を返す。 + coinsに含まれるコインの種類は種類ごとに何回使っても良い。 + coins=[1, 2, 5] amount=11 output=3 5+5+1 + coins=[2] amount=3 output = -1 + coins=[1] amount=0 output = 0 + + 何を考えて解いていたか + - 幅優先探査では深い位置に向かうにつれて幅が広くなるので、ナイーブな実装をすると計算量が爆発するので、キャッシュを利用する必要がある。 + 入力制約は + - 1 <= coins.len() <= 12 + - 1 <= coins[i] <= (2 ^ 31) - 1 + - 0 <= amount <= 10 ^ 4 + 深さについては、amount - coins[i] < 0 になるまで続くが、この場合の計算量の表し方がよく分からない。 + O(深さ ^ coins.len()) となるので計算量が爆発することだけは分かる。 + ナイーブな実装では今見ている深さを更新しながらamountが0になるものを発見したら、このときの深さを返せば良いという考え方になる。 + しかし、キャッシュを利用することを考えると、amountに対して深さの情報を紐づけるのは正しく動作しない。 + なぜならamountは単純にcoins[i]との差であるので、深さは関係ない。浅い位置で出現したamountがより深い位置で出現することはあり得る。 + なのでキャッシュを利用するのであれば、深さの情報をamountに紐づけるような実装はできない。 + ここまで考えたが手が止まってので、GPT-5.1に聞いて写経する。 + + 解答の理解 + - この解法は手持ちのコインを使ってなるべく少ない枚数でamountを作ろうという考え方だと理解した。 + テーブルの上に厚みの異なるコインを平積みしてコインタワーを作り、ぴったりamountの高さになるか。 + ある高さになった時、その高さを構成する枚数はどうでも良くて、その高さから目標の高さにするために与えられたコインの種類で到達できるかだけわかれば良い。 + なので一度見た高さはキャッシュしているが、枚数はキャッシュしていない。 + コインタワーの高さが同じであっても、同じ枚数(同じコイン種類)とは限らないので、高さとコイン枚数を紐づけてキャッシュするとおかしなことになると理解した。 + e.g. 厚み1のコイン10枚と厚み5のコイン2枚どちらもコインタワーの高さは10になる。しかし、使っているコインの枚数は違う。 + + 正解してから気づいたこと + - 再帰+メモ化の時に何をキャッシュすればよいのか分からず、ある時点のamountと枚数をキャッシュしていた。 + これが間違いであることを理解できず混乱していた。 + 個人的には amount - coins[i]で0になるかを見るよりも、0からamountにぴったりなるcoins[i]の組み合わせを考えるほうが理解しやすかった。 + - 答えを消して実装できたので覚えることはできている。 + + - 計算量の見積もり方がわからない。コインの種類 * (0~amountに到達するまでの計算回数) といった感覚だが0からamountまでの~という部分は計算するまでわからないのでは?という感覚。 + GPT-5.1に聞いた。 + - 上記の考え方で方向は合っていた。 + - amount_cacheで0~amountの値は一度しか訪れない。つまりamount - 0回計算を実行することになるので、m = amountとなる。 + n = coins.len() + m = amount + 時間計算量: O(n * m) + 空間計算量: O(n) + + step1c_dp.rsで模範解答写経してstep2へ進む +*/ + +use std::collections::{HashSet, VecDeque}; + +pub struct Solution {} +impl Solution { + pub fn coin_change(coins: Vec, amount: i32) -> i32 { + if amount == 0 { + return 0; + } + + let mut frontiers = VecDeque::new(); + let mut amount_cache = HashSet::new(); + + frontiers.push_back((0, 0)); + while let Some((previous_amount, change_count)) = frontiers.pop_front() { + for coin in &coins { + let current_amount = previous_amount + *coin; + + if current_amount == amount { + return change_count + 1; + } + if amount < current_amount { + continue; + } + if !amount_cache.insert(current_amount) { + continue; + } + + frontiers.push_back((current_amount, change_count + 1)); + } + } + + -1 + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn step1b_bfs_test() { + let coins = vec![1, 2, 5]; + let amount = 11; + assert_eq!(Solution::coin_change(coins, amount), 3); + + let coins = vec![2]; + let amount = 3; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![1]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![1, 2, 3, 4, 5]; + let amount = 7; + assert_eq!(Solution::coin_change(coins, amount), 2); + + let coins = vec![186, 419, 83, 408]; + let amount = 6249; + assert_eq!(Solution::coin_change(coins, amount), 20); + + let coins = vec![186, 419, 83, 408]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![]; + let amount = 5; + assert_eq!(Solution::coin_change(coins, amount), -1); + } + + #[test] + fn step1b_bfs_minus_amount_test() { + let coins = vec![1, 2, 3, 5]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), -1); + } + + #[test] + fn step1b_bfs_have_minus_coin_test() { + let coins = vec![1, 2, 3, -6]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), 2); + } +} diff --git a/src/solutions/step1c_dp.rs b/src/solutions/step1c_dp.rs new file mode 100644 index 0000000..1b31ab7 --- /dev/null +++ b/src/solutions/step1c_dp.rs @@ -0,0 +1,127 @@ +// Step1b_BFS +// 目的: 別の実装方針を練習する。動的計画法(Dynamic Programming) + +/* + 問題の理解 + - 異なる金額のコインを表す整数からなる配列coninsと合計金額を表すamountが与えられる。 + 合計金額amountになる最小のコインの枚数の組み合わせをcoins配列から探してコインの数を解として返す。 + 合計金額になるようなコインの組み合わせを見つけられなければ-1を返す。 + coinsに含まれるコインの種類は種類ごとに何回使っても良い。 + coins=[1, 2, 5] amount=11 output=3 5+5+1 + coins=[2] amount=3 output = -1 + coins=[1] amount=0 output = 0 + + 何を考えて解いていたか + - 時間切れなのでDP解法を写経する。 + https://www.youtube.com/watch?v=H9bfqozjoqs + + 解答の理解 + - 322-dp-memo.pngにまとめた。 + + 正解してから気づいたこと + - DP解法においてcoinsに負のコインが含まれるケースに対応できないことに気付くのに時間がかかった。GPT-5.1に聞いて対応できないことは分かったが理由は難しくてよく分からなかった。 + 動的計画法は過去の状態に依存して現在の状態を決定する。負のコインが含まれると過去の状態を変更してしまうので、前提が崩れて計算がおかしくなるという理解をした。 + - LeetCode採点システム上での相対的な実行速度がstep1b_bfs.rsよりも10倍ほど速い。 + 個人的にはBFS実装の方が何をしているのか分かりやすいので、大きなプログラムの内この部分の処理が占める割合がそこまで大きくなければBFS実装をしたくなる気持ちはある。 + このコードを読むであろうチームの人によって変えるかなと思った。 + 自分は今まで ms 単位でパフォーマンスを追求するようなコードを要求されたことが無い。このような環境ではライブラリの背後に隠蔽されないコードではBFSで実装するななどと思った。 + 実行時間が65ms -> 6ms になることを説明して、このコードを読ませるのは憚られるという感覚。 + + n = coins.len() + m = amount + 時間計算量: O(n * m) + 空間計算量: O(n) + - step1b_bfs.rsの実装とBig-O記法による時間計算量は全く同じだが、実際の実行速度には相対的に見て10倍前後の有意な差がある。 + Big-O記法での時間計算量には現れない定数倍の差が明確に出る良い例だと思った。 + step1b_bfs.rs実装 + - HashSetによるハッシュ化、挿入、探索をO(n * m)回行っている。 + - 可変長配列(VecDeque)への挿入、取り出しをO(n * m)回行っている。 + step1c_dp.rs実装 + - 配列への添字アクセスや加算、減算、比較、代入といった比較的軽量な操作のみ行っている。 +*/ + +pub struct Solution {} +impl Solution { + pub fn coin_change(coins: Vec, amount: i32) -> i32 { + if amount < 0 { + return -1; + } + if coins.iter().any(|v| *v < 0) { + panic!("coins must be contain only positive values") + } + + let amount = amount as usize; + let mut coin_counts = vec![amount + 1; amount + 1]; + coin_counts[0] = 0; + + for current_amount in 1..amount + 1 { + for coin in &coins { + let Some(remaining_amount) = current_amount.checked_sub(*coin as usize) else { + continue; + }; + + coin_counts[current_amount] = + coin_counts[current_amount].min(1 + coin_counts[remaining_amount as usize]); + } + } + + if coin_counts[amount] == amount + 1 { + return -1; + } + + coin_counts[amount] as i32 + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn step1c_dp_test() { + let coins = vec![1, 2, 5]; + let amount = 11; + assert_eq!(Solution::coin_change(coins, amount), 3); + + let coins = vec![2]; + let amount = 3; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![1]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![1, 2, 3, 4, 5]; + let amount = 7; + assert_eq!(Solution::coin_change(coins, amount), 2); + + let coins = vec![186, 419, 83, 408]; + let amount = 6249; + assert_eq!(Solution::coin_change(coins, amount), 20); + + let coins = vec![186, 419, 83, 408]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![1, 2, 3, 5]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![]; + let amount = 5; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + } + + #[test] + #[should_panic] + fn step1c_dp_panic_test() { + let coins = vec![1, 2, 3, -6]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), 2); + } +} diff --git a/src/solutions/step2_bfs.rs b/src/solutions/step2_bfs.rs new file mode 100644 index 0000000..403d6ff --- /dev/null +++ b/src/solutions/step2_bfs.rs @@ -0,0 +1,162 @@ +// Step2 +// 目的: 自然な書き方を考えて整理する + +// 方法 +// Step1のコードを読みやすくしてみる +// 他の人のコードを2つは読んでみること +// 正解したら終わり + +// 以下をメモに残すこと +// 講師陣はどのようなコメントを残すだろうか? +// 他の人のコードを読んで考えたこと +// 改善する時に考えたこと + +/* + 他の人のコードを読んで考えたこと + - コメント集 + https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.ic8466had15a + - 「配るDP」について。 + > メモ探索ではできない + 自分が詰まったところを表しているのかなと思った。具体的には、メモ化しようとしてamountとコイン枚数を紐づけてキャッシュしようとしたが、この方法は間違っており、メモ化できなかった。 + + - ダイクストラ法というのもこの問題に適用できるらしい。 + https://discord.com/channels/1084280443945353267/1403227402984755271/1408287137559744624 + + - コインの組み合わせが見つからなかったときの戻り値 -1 を定数にしたいというレビュー + 確かにしない方がいい理由は思い浮かばないので、定数として-1を入れて置くのが良さそう。 + https://github.com/h1rosaka/arai60/pull/42#discussion_r2384901626 + + - BFSの方が早くなる実装があるらしい。なんとなく自分の中でより分かりにくいコードの方が実行速度は速いみたいな感覚があったので驚いた。良くない思考の癖だと思った。 + https://github.com/nanae772/leetcode-arai60/pull/39/files#diff-762c75d9f29079e87b6e0c8549c01ba137eb3b4a426da323f08df2a1d7f09ac1R82 + - とりあえず読んでみて速くなっている要因となっていそうな箇所 + - 訪問済みのamountをHashSetではなく、配列で管理している。ハッシュ化のコストを無くせる。 + - coinsの内amountより大きいものを取り除いてループの総回数を減らしている + - coinsを降順ソートしている。なるべく少ないコインの組み合わせが知りたいので、より大きいコインから見ていった方が効率が良いくらいの理解。 + - coins=[1,50] amount=100 のようなケースを考えると分かりやすい。 + - コードのコメントに「最短経路問題としてみなして~」とある。 + https://github.com/nanae772/leetcode-arai60/pull/39/files#diff-3b6f683e2abf409ce92edb1fa882377eca64fff579d04113b07c4a039de2748dR28 + - 今回の問題ではコインの組み合わせの内「最小」となるコイン枚数を求めるので最短経路問題として考えられると理解した。 + https://ja.wikipedia.org/wiki/%E6%9C%80%E7%9F%AD%E7%B5%8C%E8%B7%AF%E5%95%8F%E9%A1%8C + + 改善する時に考えたこと + - step1b_bfs.rsの実装を改善する。 + - visitedをHashSetではなく、配列で行う。 + - coinsを降順ソートにして大きいコインから見る。 + - coinsの内amountを超えるようなコインを取り除く + + 所感 + - step1b_bfs.rsと同じ実装方針で定数倍に影響する処理を調整したところ、有意に処理速度が向上した。 + 具体的にはLeetCode採点システムで 60ms前後 -> 6ms前後 になったので10倍程度改善したことになる。 + - DP解法(step1c_dp.rs)でもcoinsのフィルターとソートを行ってみる。step2a_dp.rs + 改善による効果有無の予想としては + - coinsのフィルターは効果があると考える。ループ回数を減らせるので。 + - coinsのソートは効果が無い気がする。DP[amouont]まで全て計算したうえで最小を探す必要があるため。 +*/ + +use std::collections::VecDeque; + +pub struct Solution {} +impl Solution { + const COMBINATION_NOT_FOUND: i32 = -1; + + pub fn coin_change(coins: Vec, amount: i32) -> i32 { + if amount == 0 { + return 0; + } + if amount < 0 { + return Self::COMBINATION_NOT_FOUND; + } + if coins.iter().any(|v| *v < 0) { + panic!("coins must be contain only positive values"); + } + + let mut visited_amount = vec![false; (amount + 1) as usize]; + let mut frontiers = VecDeque::new(); + let mut coins = coins + .into_iter() + .filter(|coin| *coin <= amount) + .collect::>(); + + coins.sort(); + coins.reverse(); + frontiers.push_back((0, 0)); + while let Some((previous_amount, change_count)) = frontiers.pop_front() { + for coin in &coins { + let current_amount = previous_amount + *coin; + + if current_amount == amount { + return change_count + 1; + } + if amount < current_amount { + continue; + } + if visited_amount + .get(current_amount as usize) + .is_some_and(|is_visited| *is_visited) + { + continue; + } + visited_amount[current_amount as usize] = true; + + frontiers.push_back((current_amount, change_count + 1)); + } + } + + Self::COMBINATION_NOT_FOUND + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2_test() { + let coins = vec![1, 2, 5]; + let amount = 11; + assert_eq!(Solution::coin_change(coins, amount), 3); + + let coins = vec![2]; + let amount = 3; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![1]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![1, 2, 3, 4, 5]; + let amount = 7; + assert_eq!(Solution::coin_change(coins, amount), 2); + + let coins = vec![186, 419, 83, 408]; + let amount = 6249; + assert_eq!(Solution::coin_change(coins, amount), 20); + + let coins = vec![186, 419, 83, 408]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![]; + let amount = 5; + assert_eq!(Solution::coin_change(coins, amount), -1); + } + + #[test] + fn step2_minus_amount_test() { + let coins = vec![1, 2, 3, 5]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), -1); + } + + #[test] + #[should_panic] + fn step2_have_minus_coin_test() { + let coins = vec![1, 2, 3, -6]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), 2); + } +} diff --git a/src/solutions/step2a_dp.rs b/src/solutions/step2a_dp.rs new file mode 100644 index 0000000..22cd503 --- /dev/null +++ b/src/solutions/step2a_dp.rs @@ -0,0 +1,115 @@ +// Step2a_dp +// 目的: step1c_dp.rs解法のリファクタリング + +/* + 改善する時に考えたこと + - step1c_dp.rsの実装を改善する。 + - coinsを昇順ソートにして小さいコインから見る。 + - 今見ているamountよりも大きいコインを見つけたらcoins loopをbreakする。 + - ただし、この方法はcoinsが昇順ソートされていて、今見ているよりamountよりも大きなコインを見つけたら探索を終了するということを理解している前提でないと読み手に負荷を与える。 + https://github.com/nanae772/leetcode-arai60/pull/39/files#r2442801687 + 人が読むことを考えると、この最適化はしないほうがエンジニアリングの観点から良いと考えた。 + - coinsの内amountを超えるようなコインを取り除く + + 所感 + - LeetCode採点システム上では変更前後(step1c_dp.rs -> step2a_dp.rs)で実行速度に有意な差は見られなかった。 + - coins.len()は1~12と値が小さいので取り除いたところで大きな差が出ないと理解した。 + - それぞれの実装でベンチマークを取ってみる。 +*/ + +pub struct Solution {} +impl Solution { + const COMBINATION_NOT_FOUND: i32 = -1; + + pub fn coin_change(coins: Vec, amount: i32) -> i32 { + if amount < 0 { + return Self::COMBINATION_NOT_FOUND; + } + if amount == 0 { + return 0; + }; + if coins.iter().any(|coin| *coin < 0) { + panic!("coins must be contains only positive values"); + } + + let mut coins = coins; + coins = coins + .into_iter() + .filter(|coin| *coin <= amount) + .collect::>(); + + let sentinel_coin_count = amount + 1; + let amount = amount as usize; + let mut amount_to_coin_count = vec![sentinel_coin_count; amount + 1]; + amount_to_coin_count[0] = 0; + + for current_amount in 1..amount + 1 { + for coin in &coins { + let Some(remaining_amount) = current_amount.checked_sub(*coin as usize) else { + continue; + }; + + amount_to_coin_count[current_amount] = amount_to_coin_count[current_amount] + .min(1 + amount_to_coin_count[remaining_amount]); + } + } + + if amount_to_coin_count[amount] == sentinel_coin_count { + return Self::COMBINATION_NOT_FOUND; + } + + amount_to_coin_count[amount] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2a_dp_test() { + let coins = vec![1, 2, 5]; + let amount = 11; + assert_eq!(Solution::coin_change(coins, amount), 3); + + let coins = vec![2]; + let amount = 3; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![1]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![1, 2, 3, 4, 5]; + let amount = 7; + assert_eq!(Solution::coin_change(coins, amount), 2); + + let coins = vec![186, 419, 83, 408]; + let amount = 6249; + assert_eq!(Solution::coin_change(coins, amount), 20); + + let coins = vec![186, 419, 83, 408]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![1, 2, 3, 5]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![]; + let amount = 5; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + } + + #[test] + #[should_panic] + fn step2a_dp_panic_test() { + let coins = vec![1, 2, 3, -6]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), 2); + } +} diff --git a/src/solutions/step3.rs b/src/solutions/step3.rs new file mode 100644 index 0000000..e3d6b5e --- /dev/null +++ b/src/solutions/step3.rs @@ -0,0 +1,135 @@ +// Step3 +// 目的: 覚えられないのは、なんか素直じゃないはずなので、そこを探し、ゴールに到達する + +// 方法 +// 時間を測りながらもう一度解く +// 10分以内に一度もエラーを吐かず正解 +// これを3回連続でできたら終わり +// レビューを受ける +// 作れないデータ構造があった場合は別途自作すること + +/* + n = coins.len() + m = amount + 時間計算量: O(n * m) + 空間計算量: O(n) +*/ + +/* + 1回目: 6分53秒 + 2回目: 5分47秒 + 3回目: 5分16秒 +*/ + +/* + 所感 + - 決定木として考えてcoinsのコインを使ってコインタワー作るイメージが一番しっくり来た。高さが0(コインなし)からamountの高さになるコインの組み合わせを探すという感じ。 +*/ + +use std::collections::VecDeque; + +pub struct Solution {} +impl Solution { + const COMBINATION_NOT_FOUND: i32 = -1; + pub fn coin_change(coins: Vec, amount: i32) -> i32 { + if amount == 0 { + return 0; + } + if amount < 0 { + return -1; + } + if coins.iter().any(|coin| *coin < 0) { + panic!("coins must be contains only positive values"); + } + + let mut coins = coins + .into_iter() + .filter(|coin| *coin <= amount) + .collect::>(); + coins.sort(); + coins.reverse(); + + let mut visited_amount = vec![false; (amount + 1) as usize]; + let mut frontiers = VecDeque::new(); + frontiers.push_back((0, 0)); + + while let Some((previous_amount, change_count)) = frontiers.pop_front() { + for coin in &coins { + let current_amount = previous_amount + *coin; + + if current_amount == amount { + return change_count + 1; + } + if amount < current_amount { + continue; + } + if visited_amount + .get(current_amount as usize) + .is_some_and(|is_visited| *is_visited) + { + continue; + } + visited_amount[current_amount as usize] = true; + + frontiers.push_back((current_amount, change_count + 1)); + } + } + + Self::COMBINATION_NOT_FOUND + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step3_test() { + let coins = vec![1, 2, 5]; + let amount = 11; + assert_eq!(Solution::coin_change(coins, amount), 3); + + let coins = vec![2]; + let amount = 3; + assert_eq!(Solution::coin_change(coins, amount), -1); + + let coins = vec![1]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![1, 2, 3, 4, 5]; + let amount = 7; + assert_eq!(Solution::coin_change(coins, amount), 2); + + let coins = vec![186, 419, 83, 408]; + let amount = 6249; + assert_eq!(Solution::coin_change(coins, amount), 20); + + let coins = vec![186, 419, 83, 408]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![]; + let amount = 0; + assert_eq!(Solution::coin_change(coins, amount), 0); + + let coins = vec![]; + let amount = 5; + assert_eq!(Solution::coin_change(coins, amount), -1); + } + + #[test] + fn step3_minus_amount_test() { + let coins = vec![1, 2, 3, 5]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), -1); + } + + #[test] + #[should_panic] + fn step3_have_minus_coin_test() { + let coins = vec![1, 2, 3, -6]; + let amount = -5; + assert_eq!(Solution::coin_change(coins, amount), 2); + } +}