diff --git a/README.md b/README.md index 2c382cf5..639fd189 100644 --- a/README.md +++ b/README.md @@ -645,3 +645,4 @@ Profile on LeetCode: [fartem](https://leetcode.com/fartem/). | 32. Longest Valid Parentheses | [Link](https://leetcode.com/problems/longest-valid-parentheses/) | [Link](./lib/hard/32_longest_valid_parentheses.rb) | [Link](./test/hard/test_32_longest_valid_parentheses.rb) | | 41. First Missing Positive | [Link](https://leetcode.com/problems/first-missing-positive/) | [Link](./lib/hard/41_first_missing_positive.rb) | [Link](./test/hard/test_41_first_missing_positive.rb) | | 115. Distinct Subsequences | [Link](https://leetcode.com/problems/distinct-subsequences/) | [Link](./lib/hard/115_distinct_subsequences.rb) | [Link](./test/hard/test_115_distinct_subsequences.rb) | +| 126. Word Ladder II | [Link](https://leetcode.com/problems/word-ladder-ii/) | [Link](./lib/hard/126_word_ladder_ii.rb) | [Link](./test/hard/test_126_word_ladder_ii.rb) | diff --git a/leetcode-ruby.gemspec b/leetcode-ruby.gemspec index 44357f68..a1565372 100644 --- a/leetcode-ruby.gemspec +++ b/leetcode-ruby.gemspec @@ -5,7 +5,7 @@ require 'English' ::Gem::Specification.new do |s| s.required_ruby_version = '>= 3.0' s.name = 'leetcode-ruby' - s.version = '7.7.5' + s.version = '7.7.6' s.license = 'MIT' s.files = ::Dir['lib/**/*.rb'] + %w[README.md] s.executable = 'leetcode-ruby' diff --git a/lib/hard/126_word_ladder_ii.rb b/lib/hard/126_word_ladder_ii.rb new file mode 100644 index 00000000..055e1a8a --- /dev/null +++ b/lib/hard/126_word_ladder_ii.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +# https://leetcode.com/problems/word-ladder-ii/ +# @param {String} begin_word +# @param {String} end_word +# @param {String[]} word_list +# @return {String[][]} +def find_ladders(begin_word, end_word, word_list) + return [] unless (unvisited = word_list.to_set).include?(end_word) + + transformations = ::Hash.new { |h, k| h[k] = ::Set.new } + + begin_layer = ::Set[begin_word] + end_layer = ::Set[end_word] + unvisited.delete(end_word) + transforms = Array('a'..'z').product(Array(0...begin_word.size)) + + current_levels = begin_layer + previous_levels = end_layer + loop do + current_levels, previous_levels = previous_levels, current_levels + + unvisited.subtract(previous_levels) + + next_layer = ::Set.new + current_levels.each do |word| + transforms.each do |c, i| + next if word[i] == c + + transform = word.dup.tap { |w| w[i] = c } + + is_neighbor = unvisited.include?(transform) + + next_layer << transform if is_neighbor + + if is_neighbor || previous_levels.include?(transform) + if current_levels.equal?(begin_layer) + transformations[word] << transform + else + transformations[transform] << word + end + end + end + end + + break if next_layer.empty? || begin_layer.any? { |w| transformations[w].intersect?(end_layer) } + + current_levels.replace(next_layer) + end + + dfs_paths(begin_word, end_word, transformations) +end + +private + +# @param {String} begin_word +# @param {String} end_word +# @param {Hash} word_graph +# @return {String[][]} +def dfs_paths(begin_word, end_word, word_graph) + paths = [] + path = [] + stack = [[begin_word, 1]] + + until stack.empty? + word, level = stack.pop + + path.pop until path.size < level + path << word + + if word == end_word + paths << path.dup + else + word_graph[word].each { |w| stack << [w, level + 1] } + end + end + + paths +end diff --git a/test/hard/test_126_word_ladder_ii.rb b/test/hard/test_126_word_ladder_ii.rb new file mode 100644 index 00000000..2070f0cb --- /dev/null +++ b/test/hard/test_126_word_ladder_ii.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require_relative '../test_helper' +require_relative '../../lib/hard/126_word_ladder_ii' +require 'minitest/autorun' + +class WordLadderIITest < ::Minitest::Test + def test_default_one + assert_equal( + [ + %w[hit hot lot log cog], + %w[hit hot dot dog cog] + ], + find_ladders( + 'hit', + 'cog', + %w[hot dot dog lot log cog] + ) + ) + end + + def test_default_two + assert_equal( + [], + find_ladders( + 'hit', + 'cog', + %w[hot dot dog lot log] + ) + ) + end +end