This commit is contained in:
krahets
2024-05-24 16:12:17 +08:00
parent e434a3343c
commit 6bac0db1c4
22 changed files with 635 additions and 99 deletions
@@ -220,7 +220,16 @@ comments: true
=== "Ruby"
```ruby title="preorder_traversal_i_compact.rb"
[class]{}-[func]{pre_order}
### 前序走訪:例題一 ###
def pre_order(root)
return unless root
# 記錄解
$res << root if root.val == 7
pre_order(root.left)
pre_order(root.right)
end
```
=== "Zig"
@@ -522,7 +531,22 @@ comments: true
=== "Ruby"
```ruby title="preorder_traversal_ii_compact.rb"
[class]{}-[func]{pre_order}
### 前序走訪:例題二 ###
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
```
=== "Zig"
@@ -866,7 +890,23 @@ comments: true
=== "Ruby"
```ruby title="preorder_traversal_iii_compact.rb"
[class]{}-[func]{pre_order}
### 前序走訪:例題三 ###
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
```
=== "Zig"
@@ -1203,7 +1243,27 @@ comments: true
=== "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"
@@ -1843,17 +1903,49 @@ comments: true
=== "Ruby"
```ruby title="preorder_traversal_iii_template.rb"
[class]{}-[func]{is_solution}
### 判斷當前狀態是否為解 ###
def is_solution?(state)
!state.empty? && state.last.val == 7
end
[class]{}-[func]{record_solution}
### 記錄解 ###
def record_solution(state, res)
res << state.dup
end
[class]{}-[func]{is_valid}
### 判斷在當前狀態下,該選擇是否合法 ###
def is_valid?(state, choice)
choice && choice.val != 3
end
[class]{}-[func]{make_choice}
### 更新狀態 ###
def make_choice(state, choice)
state << choice
end
[class]{}-[func]{undo_choice}
### 恢復狀態 ###
def undo_choice(state, choice)
state.pop
end
[class]{}-[func]{backtrack}
### 回溯演算法:例題三 ###
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
```
=== "Zig"
@@ -710,9 +710,45 @@ comments: true
=== "Ruby"
```ruby title="n_queens.rb"
[class]{}-[func]{backtrack}
### 回溯演算法:n 皇后 ###
def backtrack(row, n, state, res, cols, diags1, diags2)
# 當放置完所有行時,記錄解
if row == n
res << state.map { |row| row.dup }
return
end
[class]{}-[func]{n_queens}
# 走訪所有列
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
```
=== "Zig"
@@ -506,9 +506,36 @@ comments: true
=== "Ruby"
```ruby title="permutations_i.rb"
[class]{}-[func]{backtrack}
### 回溯演算法:全排列 I ###
def backtrack(state, choices, selected, res)
# 當狀態長度等於元素數量時,記錄解
if state.length == choices.length
res << state.dup
return
end
[class]{}-[func]{permutations_i}
# 走訪所有選擇
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
```
=== "Zig"
@@ -1032,9 +1059,38 @@ comments: true
=== "Ruby"
```ruby title="permutations_ii.rb"
[class]{}-[func]{backtrack}
### 回溯演算法:全排列 II ###
def backtrack(state, choices, selected, res)
# 當狀態長度等於元素數量時,記錄解
if state.length == choices.length
res << state.dup
return
end
[class]{}-[func]{permutations_ii}
# 走訪所有選擇
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
```
=== "Zig"
@@ -470,9 +470,35 @@ comments: true
=== "Ruby"
```ruby title="subset_sum_i_naive.rb"
[class]{}-[func]{backtrack}
### 回溯演算法:子集和 I ###
def backtrack(state, target, total, choices, res)
# 子集和等於 target 時,記錄解
if total == target
res << state.dup
return
end
[class]{}-[func]{subset_sum_i_naive}
# 走訪所有選擇
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
```
=== "Zig"
@@ -1011,9 +1037,37 @@ comments: true
=== "Ruby"
```ruby title="subset_sum_i.rb"
[class]{}-[func]{backtrack}
### 回溯演算法:子集和 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
[class]{}-[func]{subset_sum_i}
### 求解子集和 I ###
def subset_sum_i(nums, target)
state = [] # 狀態(子集)
nums.sort! # 對 nums 進行排序
start = 0 # 走訪起始點
res = [] # 結果串列(子集串列)
backtrack(state, target, nums, start, res)
res
end
```
=== "Zig"
@@ -1598,9 +1652,41 @@ comments: true
=== "Ruby"
```ruby title="subset_sum_ii.rb"
[class]{}-[func]{backtrack}
### 回溯演算法:子集和 II ###
def backtrack(state, target, choices, start, res)
# 子集和等於 target 時,記錄解
if target.zero?
res << state.dup
return
end
[class]{}-[func]{subset_sum_ii}
# 走訪所有選擇
# 剪枝二:從 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
```
=== "Zig"