feat: Add Ruby code - chapter "Backtracking" (#1373)

* [feat] add ruby code - chapter backtracking

* feat: add ruby code block - chapter backtracking
pull/1378/head
khoaxuantu 6 months ago committed by GitHub
parent 21be3fdaf8
commit aa818945f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,61 @@
=begin
File: n_queens.rb
Created Time: 2024-05-21
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### 回溯算法n 皇后 ###
def backtrack(row, n, state, res, cols, diags1, diags2)
# 当放置完所有行时,记录解
if row == n
res << state.map { |row| row.dup }
return
end
# 遍历所有列
for col in 0...n
# 计算该格子对应的主对角线和次对角线
diag1 = row - col + n - 1
diag2 = row + col
# 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后
if !cols[col] && !diags1[diag1] && !diags2[diag2]
# 尝试:将皇后放置在该格子
state[row][col] = "Q"
cols[col] = diags1[diag1] = diags2[diag2] = true
# 放置下一行
backtrack(row + 1, n, state, res, cols, diags1, diags2)
# 回退:将该格子恢复为空位
state[row][col] = "#"
cols[col] = diags1[diag1] = diags2[diag2] = false
end
end
end
### 求解 n 皇后 ###
def n_queens(n)
# 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位
state = Array.new(n) { Array.new(n, "#") }
cols = Array.new(n, false) # 记录列是否有皇后
diags1 = Array.new(2 * n - 1, false) # 记录主对角线上是否有皇后
diags2 = Array.new(2 * n - 1, false) # 记录次对角线上是否有皇后
res = []
backtrack(0, n, state, res, cols, diags1, diags2)
res
end
### Driver Code ###
if __FILE__ == $0
n = 4
res = n_queens(n)
puts "输入棋盘长宽为 #{n}"
puts "皇后放置方案共有 #{res.length}"
for state in res
puts "--------------------"
for row in state
p row
end
end
end

@ -0,0 +1,46 @@
=begin
File: permutations_i.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### 回溯算法:全排列 I ###
def backtrack(state, choices, selected, res)
# 当状态长度等于元素数量时,记录解
if state.length == choices.length
res << state.dup
return
end
# 遍历所有选择
choices.each_with_index do |choice, i|
# 剪枝:不允许重复选择元素
unless selected[i]
# 尝试:做出选择,更新状态
selected[i] = true
state << choice
# 进行下一轮选择
backtrack(state, choices, selected, res)
# 回退:撤销选择,恢复到之前的状态
selected[i] = false
state.pop
end
end
end
### 全排列 I ###
def permutations_i(nums)
res = []
backtrack([], nums, Array.new(nums.length, false), res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [1, 2, 3]
res = permutations_i(nums)
puts "输入数组 nums = #{nums}"
puts "所有排列 res = #{res}"
end

@ -0,0 +1,48 @@
=begin
File: permutations_ii.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### 回溯算法:全排列 II ###
def backtrack(state, choices, selected, res)
# 当状态长度等于元素数量时,记录解
if state.length == choices.length
res << state.dup
return
end
# 遍历所有选择
duplicated = Set.new
choices.each_with_index do |choice, i|
# 剪枝:不允许重复选择元素 且 不允许重复选择相等元素
if !selected[i] && !duplicated.include?(choice)
# 尝试:做出选择,更新状态
duplicated.add(choice)
selected[i] = true
state << choice
# 进行下一轮选择
backtrack(state, choices, selected, res)
# 回退:撤销选择,恢复到之前的状态
selected[i] = false
state.pop
end
end
end
### 全排列 II ###
def permutations_ii(nums)
res = []
backtrack([], nums, Array.new(nums.length, false), res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [1, 2, 2]
res = permutations_ii(nums)
puts "输入数组 nums = #{nums}"
puts "所有排列 res = #{res}"
end

@ -0,0 +1,33 @@
=begin
File: preorder_traversal_i_compact.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### 前序遍历:例题一 ###
def pre_order(root)
return unless root
# 记录解
$res << root if root.val == 7
pre_order(root.left)
pre_order(root.right)
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\n初始化二叉树"
print_tree(root)
# 前序遍历
$res = []
pre_order(root)
puts "\n输出所有值为 7 的节点"
p $res.map { |node| node.val }
end

@ -0,0 +1,41 @@
=begin
File: preorder_traversal_ii_compact.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### 前序遍历:例题二 ###
def pre_order(root)
return unless root
# 尝试
$path << root
# 记录解
$res << $path.dup if root.val == 7
pre_order(root.left)
pre_order(root.right)
# 回退
$path.pop
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\n初始化二叉树"
print_tree(root)
# 前序遍历
$path, $res = [], []
pre_order(root)
puts "\n输出所有根节点到节点 7 的路径"
for path in $res
p path.map { |node| node.val }
end
end

@ -0,0 +1,42 @@
=begin
File: preorder_traversal_iii_compact.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### 前序遍历:例题三 ###
def pre_order(root)
# 剪枝
return if !root || root.val == 3
# 尝试
$path.append(root)
# 记录解
$res << $path.dup if root.val == 7
pre_order(root.left)
pre_order(root.right)
# 回退
$path.pop
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\n初始化二叉树"
print_tree(root)
# 前序遍历
$path, $res = [], []
pre_order(root)
puts "\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点"
for path in $res
p path.map { |node| node.val }
end
end

@ -0,0 +1,68 @@
=begin
File: preorder_traversal_iii_template.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
require_relative '../utils/tree_node'
require_relative '../utils/print_util'
### 判断当前状态是否为解 ###
def is_solution?(state)
!state.empty? && state.last.val == 7
end
### 记录解 ###
def record_solution(state, res)
res << state.dup
end
### 判断在当前状态下,该选择是否合法 ###
def is_valid?(state, choice)
choice && choice.val != 3
end
### 更新状态 ###
def make_choice(state, choice)
state << choice
end
### 恢复状态 ###
def undo_choice(state, choice)
state.pop
end
### 回溯算法:例题三 ###
def backtrack(state, choices, res)
# 检查是否为解
record_solution(state, res) if is_solution?(state)
# 遍历所有选择
for choice in choices
# 剪枝:检查选择是否合法
if is_valid?(state, choice)
# 尝试:做出选择,更新状态
make_choice(state, choice)
# 进行下一轮选择
backtrack(state, [choice.left, choice.right], res)
# 回退:撤销选择,恢复到之前的状态
undo_choice(state, choice)
end
end
end
### Driver Code ###
if __FILE__ == $0
root = arr_to_tree([1, 7, 3, 4, 5, 6, 7])
puts "\n初始化二叉树"
print_tree(root)
# 回溯算法
res = []
backtrack([], [root], res)
puts "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点"
for path in res
p path.map { |node| node.val }
end
end

@ -0,0 +1,47 @@
=begin
File: subset_sum_i.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### 回溯算法:子集和 I ###
def backtrack(state, target, choices, start, res)
# 子集和等于 target 时,记录解
if target.zero?
res << state.dup
return
end
# 遍历所有选择
# 剪枝二:从 start 开始遍历,避免生成重复子集
for i in start...choices.length
# 剪枝一:若子集和超过 target ,则直接结束循环
# 这是因为数组已排序,后边元素更大,子集和一定超过 target
break if target - choices[i] < 0
# 尝试:做出选择,更新 target, start
state << choices[i]
# 进行下一轮选择
backtrack(state, target - choices[i], choices, i, res)
# 回退:撤销选择,恢复到之前的状态
state.pop
end
end
### 求解子集和 I ###
def subset_sum_i(nums, target)
state = [] # 状态(子集)
nums.sort! # 对 nums 进行排序
start = 0 # 遍历起始点
res = [] # 结果列表(子集列表)
backtrack(state, target, nums, start, res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [3, 4, 5]
target = 9
res = subset_sum_i(nums, target)
puts "输入数组 = #{nums}, target = #{target}"
puts "所有和等于 #{target} 的子集 res = #{res}"
end

@ -0,0 +1,46 @@
=begin
File: subset_sum_i_naive.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### 回溯算法:子集和 I ###
def backtrack(state, target, total, choices, res)
# 子集和等于 target 时,记录解
if total == target
res << state.dup
return
end
# 遍历所有选择
for i in 0...choices.length
# 剪枝:若子集和超过 target ,则跳过该选择
next if total + choices[i] > target
# 尝试:做出选择,更新元素和 total
state << choices[i]
# 进行下一轮选择
backtrack(state, target, total + choices[i], choices, res)
# 回退:撤销选择,恢复到之前的状态
state.pop
end
end
### 求解子集和 I包含重复子集###
def subset_sum_i_naive(nums, target)
state = [] # 状态(子集)
total = 0 # 子集和
res = [] # 结果列表(子集列表)
backtrack(state, target, total, nums, res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [3, 4, 5]
target = 9
res = subset_sum_i_naive(nums, target)
puts "输入数组 nums = #{nums}, target = #{target}"
puts "所有和等于 #{target} 的子集 res = #{res}"
puts "请注意,该方法输出的结果包含重复集合"
end

@ -0,0 +1,51 @@
=begin
File: subset_sum_ii.rb
Created Time: 2024-05-22
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
=end
### 回溯算法:子集和 II ###
def backtrack(state, target, choices, start, res)
# 子集和等于 target 时,记录解
if target.zero?
res << state.dup
return
end
# 遍历所有选择
# 剪枝二:从 start 开始遍历,避免生成重复子集
# 剪枝三:从 start 开始遍历,避免重复选择同一元素
for i in start...choices.length
# 剪枝一:若子集和超过 target ,则直接结束循环
# 这是因为数组已排序,后边元素更大,子集和一定超过 target
break if target - choices[i] < 0
# 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过
next if i > start && choices[i] == choices[i - 1]
# 尝试:做出选择,更新 target, start
state << choices[i]
# 进行下一轮选择
backtrack(state, target - choices[i], choices, i + 1, res)
# 回退:撤销选择,恢复到之前的状态
state.pop
end
end
### 求解子集和 II ###
def subset_sum_ii(nums, target)
state = [] # 状态(子集)
nums.sort! # 对 nums 进行排序
start = 0 # 遍历起始点
res = [] # 结果列表(子集列表)
backtrack(state, target, nums, start, res)
res
end
### Driver Code ###
if __FILE__ == $0
nums = [4, 4, 5]
target = 9
res = subset_sum_ii(nums, target)
puts "输入数组 nums = #{nums}, target = #{target}"
puts "所有和等于 #{target} 的子集 res = #{res}"
end

@ -406,7 +406,27 @@
=== "Ruby"
```ruby title=""
### 回溯算法框架 ###
def backtrack(state, choices, res)
# 判断是否为解
if is_solution?(state)
# 记录解
record_solution(state, res)
return
end
# 遍历所有选择
for choice in choices
# 剪枝:判断选择是否合法
if is_valid?(state, choice)
# 尝试:做出选择,更新状态
make_choice(state, choice)
backtrack(state, choices, res)
# 回退:撤销选择,恢复到之前的状态
undo_choice(state, choice)
end
end
end
```
=== "Zig"

Loading…
Cancel
Save