krahets 6 months ago
parent e434a3343c
commit 6bac0db1c4

@ -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}
[class]{}-[func]{record_solution}
[class]{}-[func]{is_valid}
[class]{}-[func]{make_choice}
[class]{}-[func]{undo_choice}
[class]{}-[func]{backtrack}
### 判断当前状态是否为解 ###
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
```
=== "Zig"

@ -710,9 +710,45 @@ comments: true
=== "Ruby"
```ruby title="n_queens.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{n_queens}
### 回溯算法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
```
=== "Zig"

@ -506,9 +506,36 @@ comments: true
=== "Ruby"
```ruby title="permutations_i.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{permutations_i}
### 回溯算法:全排列 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
```
=== "Zig"
@ -1032,9 +1059,38 @@ comments: true
=== "Ruby"
```ruby title="permutations_ii.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{permutations_ii}
### 回溯算法:全排列 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
```
=== "Zig"

@ -470,9 +470,35 @@ comments: true
=== "Ruby"
```ruby title="subset_sum_i_naive.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{subset_sum_i_naive}
### 回溯算法:子集和 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
```
=== "Zig"
@ -1011,9 +1037,37 @@ comments: true
=== "Ruby"
```ruby title="subset_sum_i.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{subset_sum_i}
### 回溯算法:子集和 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
```
=== "Zig"
@ -1598,9 +1652,41 @@ comments: true
=== "Ruby"
```ruby title="subset_sum_ii.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{subset_sum_ii}
### 回溯算法:子集和 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
```
=== "Zig"

@ -331,7 +331,7 @@ comments: true
if i > j {
return -1;
}
let m: i32 = (i + j) / 2;
let m: i32 = i + (j - i) / 2;
if nums[m as usize] < target {
// 递归子问题 f(m+1, j)
return dfs(nums, target, m + 1, j);

@ -2896,7 +2896,7 @@ comments: true
hashMap->capacity = 4;
hashMap->loadThres = 2.0 / 3.0;
hashMap->extendRatio = 2;
hashMap->buckets = (Pair **)malloc(sizeof(Pair *) * hashMap->capacity);
hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));
hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair));
hashMap->TOMBSTONE->key = -1;
hashMap->TOMBSTONE->val = "-1";
@ -3015,7 +3015,7 @@ comments: true
int oldCapacity = hashMap->capacity;
// 初始化扩容后的新哈希表
hashMap->capacity *= hashMap->extendRatio;
hashMap->buckets = (Pair **)malloc(sizeof(Pair *) * hashMap->capacity);
hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));
hashMap->size = 0;
// 将键值对从原哈希表搬运至新哈希表
for (int i = 0; i < oldCapacity; i++) {

@ -99,7 +99,7 @@ comments: true
if left >= right:
return # 当子数组长度为 1 时终止递归
# 划分阶段
mid = (left + right) // 2 # 计算中点
mid = (left + right) // 2 # 计算中点
merge_sort(nums, left, mid) # 递归左子数组
merge_sort(nums, mid + 1, right) # 递归右子数组
# 合并阶段
@ -142,7 +142,7 @@ comments: true
if (left >= right)
return; // 当子数组长度为 1 时终止递归
// 划分阶段
int mid = (left + right) / 2; // 计算中点
int mid = left + (right - left) / 2; // 计算中点
mergeSort(nums, left, mid); // 递归左子数组
mergeSort(nums, mid + 1, right); // 递归右子数组
// 合并阶段
@ -186,7 +186,7 @@ comments: true
if (left >= right)
return; // 当子数组长度为 1 时终止递归
// 划分阶段
int mid = (left + right) / 2; // 计算中点
int mid = left + (right - left) / 2; // 计算中点
mergeSort(nums, left, mid); // 递归左子数组
mergeSort(nums, mid + 1, right); // 递归右子数组
// 合并阶段
@ -229,7 +229,7 @@ comments: true
// 终止条件
if (left >= right) return; // 当子数组长度为 1 时终止递归
// 划分阶段
int mid = (left + right) / 2; // 计算中点
int mid = left + (right - left) / 2; // 计算中点
MergeSort(nums, left, mid); // 递归左子数组
MergeSort(nums, mid + 1, right); // 递归右子数组
// 合并阶段
@ -282,7 +282,7 @@ comments: true
return
}
// 划分阶段
mid := (left + right) / 2
mid := left + (right - left) / 2
mergeSort(nums, left, mid)
mergeSort(nums, mid+1, right)
// 合并阶段
@ -335,7 +335,7 @@ comments: true
return
}
// 划分阶段
let mid = (left + right) / 2 // 计算中点
let mid = left + (right - left) / 2 // 计算中点
mergeSort(nums: &nums, left: left, right: mid) // 递归左子数组
mergeSort(nums: &nums, left: mid + 1, right: right) // 递归右子数组
// 合并阶段
@ -381,7 +381,7 @@ comments: true
// 终止条件
if (left >= right) return; // 当子数组长度为 1 时终止递归
// 划分阶段
let mid = Math.floor((left + right) / 2); // 计算中点
let mid = Math.floor(left + (right - left) / 2); // 计算中点
mergeSort(nums, left, mid); // 递归左子数组
mergeSort(nums, mid + 1, right); // 递归右子数组
// 合并阶段
@ -427,7 +427,7 @@ comments: true
// 终止条件
if (left >= right) return; // 当子数组长度为 1 时终止递归
// 划分阶段
let mid = Math.floor((left + right) / 2); // 计算中点
let mid = Math.floor(left + (right - left) / 2); // 计算中点
mergeSort(nums, left, mid); // 递归左子数组
mergeSort(nums, mid + 1, right); // 递归右子数组
// 合并阶段
@ -470,7 +470,7 @@ comments: true
// 终止条件
if (left >= right) return; // 当子数组长度为 1 时终止递归
// 划分阶段
int mid = (left + right) ~/ 2; // 计算中点
int mid = left + (right - left) ~/ 2; // 计算中点
mergeSort(nums, left, mid); // 递归左子数组
mergeSort(nums, mid + 1, right); // 递归右子数组
// 合并阶段
@ -525,7 +525,7 @@ comments: true
}
// 划分阶段
let mid = (left + right) / 2; // 计算中点
let mid = left + (right - left) / 2; // 计算中点
merge_sort(nums, left, mid); // 递归左子数组
merge_sort(nums, mid + 1, right); // 递归右子数组
@ -574,7 +574,7 @@ comments: true
if (left >= right)
return; // 当子数组长度为 1 时终止递归
// 划分阶段
int mid = (left + right) / 2; // 计算中点
int mid = left + (right - left) / 2; // 计算中点
mergeSort(nums, left, mid); // 递归左子数组
mergeSort(nums, mid + 1, right); // 递归右子数组
// 合并阶段
@ -598,7 +598,7 @@ comments: true
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++]
else
else
tmp[k++] = nums[j++]
}
// 将左子数组和右子数组的剩余元素复制到临时数组中
@ -619,7 +619,7 @@ comments: true
// 终止条件
if (left >= right) return // 当子数组长度为 1 时终止递归
// 划分阶段
val mid = (left + right) / 2 // 计算中点
val mid = left + (right - left) / 2 // 计算中点
mergeSort(nums, left, mid) // 递归左子数组
mergeSort(nums, mid + 1, right) // 递归右子数组
// 合并阶段
@ -671,7 +671,7 @@ comments: true
# 当子数组长度为 1 时终止递归
return if left >= right
# 划分阶段
mid = (left + right) / 2 # 计算中点
mid = left + (right - left) / 2 # 计算中点
merge_sort(nums, left, mid) # 递归左子数组
merge_sort(nums, mid + 1, right) # 递归右子数组
# 合并阶段
@ -692,10 +692,10 @@ comments: true
const mem_allocator = mem_arena.allocator();
var tmp = try mem_allocator.alloc(i32, right + 1 - left);
std.mem.copy(i32, tmp, nums[left..right+1]);
// 左子数组的起始索引和结束索引
// 左子数组的起始索引和结束索引
var leftStart = left - left;
var leftEnd = mid - left;
// 右子数组的起始索引和结束索引
// 右子数组的起始索引和结束索引
var rightStart = mid + 1 - left;
var rightEnd = right - left;
// i, j 分别指向左子数组、右子数组的首元素
@ -725,7 +725,7 @@ comments: true
// 终止条件
if (left >= right) return; // 当子数组长度为 1 时终止递归
// 划分阶段
var mid = (left + right) / 2; // 计算中点
var mid = left + (right - left) / 2; // 计算中点
try mergeSort(nums, left, mid); // 递归左子数组
try mergeSort(nums, mid + 1, right); // 递归右子数组
// 合并阶段

@ -861,7 +861,7 @@ comments: true
/* 哨兵划分(三数取中值) */
func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int {
// 选取三个候选元素的中位数
let med = medianThree(nums: nums, left: left, mid: (left + right) / 2, right: right)
let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right)
// 将中位数交换至数组最左端
nums.swapAt(left, med)
return partition(nums: &nums, left: left, right: right)

@ -545,9 +545,7 @@ $$
counter[d] -= 1; // 将 d 的数量减 1
}
// 使用结果覆盖原数组 nums
for i in 0..n {
nums[i] = res[i];
}
nums.copy_from_slice(&res);
}
/* 基数排序 */

@ -83,7 +83,7 @@ In the implementation code, we declare a recursive function `dfs()` to solve the
return -1;
}
// Calculate midpoint index m
int m = (i + j) / 2;
int m = i + (j - i) / 2;
if (nums[m] < target) {
// Recursive subproblem f(m+1, j)
return dfs(nums, target, m + 1, j);
@ -114,7 +114,7 @@ In the implementation code, we declare a recursive function `dfs()` to solve the
return -1;
}
// Calculate midpoint index m
int m = (i + j) / 2;
int m = i + (j - i) / 2;
if (nums[m] < target) {
// Recursive subproblem f(m+1, j)
return dfs(nums, target, m + 1, j);

@ -63,7 +63,7 @@ The code is as follows:
# Loop until the search interval is empty (when i > j, it is empty)
while i <= j:
# Theoretically, Python's numbers can be infinitely large (depending on memory size), so there is no need to consider large number overflow
m = (i + j) // 2 # Calculate midpoint index m
m = i + (j - i) // 2 # Calculate midpoint index m
if nums[m] < target:
i = m + 1 # This situation indicates that target is in the interval [m+1, j]
elif nums[m] > target:
@ -202,7 +202,7 @@ We can implement a binary search algorithm with the same functionality based on
i, j = 0, len(nums)
# Loop until the search interval is empty (when i = j, it is empty)
while i < j:
m = (i + j) // 2 # Calculate midpoint index m
m = i + (j - i) // 2 # Calculate midpoint index m
if nums[m] < target:
i = m + 1 # This situation indicates that target is in the interval [m+1, j)
elif nums[m] > target:

@ -35,7 +35,7 @@ Therefore, at the end of the binary, it is certain that: $i$ points to the first
"""Binary search for insertion point (no duplicate elements)"""
i, j = 0, len(nums) - 1 # Initialize double closed interval [0, n-1]
while i <= j:
m = (i + j) // 2 # Calculate midpoint index m
m = i + (j - i) // 2 # Calculate midpoint index m
if nums[m] < target:
i = m + 1 # Target is in interval [m+1, j]
elif nums[m] > target:
@ -217,7 +217,7 @@ Even so, we can still keep the conditions expanded, as their logic is clearer an
"""Binary search for insertion point (with duplicate elements)"""
i, j = 0, len(nums) - 1 # Initialize double closed interval [0, n-1]
while i <= j:
m = (i + j) // 2 # Calculate midpoint index m
m = i + (j - i) // 2 # Calculate midpoint index m
if nums[m] < target:
i = m + 1 # Target is in interval [m+1, j]
elif nums[m] > target:

@ -99,7 +99,7 @@ The implementation of merge sort is shown in the following code. Note that the i
if left >= right:
return # Terminate recursion when subarray length is 1
# Partition stage
mid = (left + right) // 2 # Calculate midpoint
mid = left + (right - left) // 2 # Calculate midpoint
merge_sort(nums, left, mid) # Recursively process the left subarray
merge_sort(nums, mid + 1, right) # Recursively process the right subarray
# Merge stage
@ -142,7 +142,7 @@ The implementation of merge sort is shown in the following code. Note that the i
if (left >= right)
return; // Terminate recursion when subarray length is 1
// Partition stage
int mid = (left + right) / 2; // Calculate midpoint
int mid = left + (right - left) / 2; // Calculate midpoint
mergeSort(nums, left, mid); // Recursively process the left subarray
mergeSort(nums, mid + 1, right); // Recursively process the right subarray
// Merge stage
@ -186,7 +186,7 @@ The implementation of merge sort is shown in the following code. Note that the i
if (left >= right)
return; // Terminate recursion when subarray length is 1
// Partition stage
int mid = (left + right) / 2; // Calculate midpoint
int mid = left + (right - left) / 2; // Calculate midpoint
mergeSort(nums, left, mid); // Recursively process the left subarray
mergeSort(nums, mid + 1, right); // Recursively process the right subarray
// Merge stage

@ -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}
[class]{}-[func]{record_solution}
[class]{}-[func]{is_valid}
[class]{}-[func]{make_choice}
[class]{}-[func]{undo_choice}
[class]{}-[func]{backtrack}
### 判斷當前狀態是否為解 ###
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
```
=== "Zig"

@ -710,9 +710,45 @@ comments: true
=== "Ruby"
```ruby title="n_queens.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{n_queens}
### 回溯演算法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
```
=== "Zig"

@ -506,9 +506,36 @@ comments: true
=== "Ruby"
```ruby title="permutations_i.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{permutations_i}
### 回溯演算法:全排列 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
```
=== "Zig"
@ -1032,9 +1059,38 @@ comments: true
=== "Ruby"
```ruby title="permutations_ii.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{permutations_ii}
### 回溯演算法:全排列 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
```
=== "Zig"

@ -470,9 +470,35 @@ comments: true
=== "Ruby"
```ruby title="subset_sum_i_naive.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{subset_sum_i_naive}
### 回溯演算法:子集和 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
```
=== "Zig"
@ -1011,9 +1037,37 @@ comments: true
=== "Ruby"
```ruby title="subset_sum_i.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{subset_sum_i}
### 回溯演算法:子集和 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
```
=== "Zig"
@ -1598,9 +1652,41 @@ comments: true
=== "Ruby"
```ruby title="subset_sum_ii.rb"
[class]{}-[func]{backtrack}
[class]{}-[func]{subset_sum_ii}
### 回溯演算法:子集和 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
```
=== "Zig"

@ -331,7 +331,7 @@ comments: true
if i > j {
return -1;
}
let m: i32 = (i + j) / 2;
let m: i32 = i + (j - i) / 2;
if nums[m as usize] < target {
// 遞迴子問題 f(m+1, j)
return dfs(nums, target, m + 1, j);

@ -2896,7 +2896,7 @@ comments: true
hashMap->capacity = 4;
hashMap->loadThres = 2.0 / 3.0;
hashMap->extendRatio = 2;
hashMap->buckets = (Pair **)malloc(sizeof(Pair *) * hashMap->capacity);
hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));
hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair));
hashMap->TOMBSTONE->key = -1;
hashMap->TOMBSTONE->val = "-1";
@ -3015,7 +3015,7 @@ comments: true
int oldCapacity = hashMap->capacity;
// 初始化擴容後的新雜湊表
hashMap->capacity *= hashMap->extendRatio;
hashMap->buckets = (Pair **)malloc(sizeof(Pair *) * hashMap->capacity);
hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));
hashMap->size = 0;
// 將鍵值對從原雜湊表搬運至新雜湊表
for (int i = 0; i < oldCapacity; i++) {

@ -99,7 +99,7 @@ comments: true
if left >= right:
return # 當子陣列長度為 1 時終止遞迴
# 劃分階段
mid = (left + right) // 2 # 計算中點
mid = (left + right) // 2 # 計算中點
merge_sort(nums, left, mid) # 遞迴左子陣列
merge_sort(nums, mid + 1, right) # 遞迴右子陣列
# 合併階段
@ -142,7 +142,7 @@ comments: true
if (left >= right)
return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) / 2; // 計算中點
int mid = left + (right - left) / 2; // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
@ -186,7 +186,7 @@ comments: true
if (left >= right)
return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) / 2; // 計算中點
int mid = left + (right - left) / 2; // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
@ -229,7 +229,7 @@ comments: true
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) / 2; // 計算中點
int mid = left + (right - left) / 2; // 計算中點
MergeSort(nums, left, mid); // 遞迴左子陣列
MergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
@ -282,7 +282,7 @@ comments: true
return
}
// 劃分階段
mid := (left + right) / 2
mid := left + (right - left) / 2
mergeSort(nums, left, mid)
mergeSort(nums, mid+1, right)
// 合併階段
@ -335,7 +335,7 @@ comments: true
return
}
// 劃分階段
let mid = (left + right) / 2 // 計算中點
let mid = left + (right - left) / 2 // 計算中點
mergeSort(nums: &nums, left: left, right: mid) // 遞迴左子陣列
mergeSort(nums: &nums, left: mid + 1, right: right) // 遞迴右子陣列
// 合併階段
@ -381,7 +381,7 @@ comments: true
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
let mid = Math.floor((left + right) / 2); // 計算中點
let mid = Math.floor(left + (right - left) / 2); // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
@ -427,7 +427,7 @@ comments: true
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
let mid = Math.floor((left + right) / 2); // 計算中點
let mid = Math.floor(left + (right - left) / 2); // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
@ -470,7 +470,7 @@ comments: true
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) ~/ 2; // 計算中點
int mid = left + (right - left) ~/ 2; // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
@ -525,7 +525,7 @@ comments: true
}
// 劃分階段
let mid = (left + right) / 2; // 計算中點
let mid = left + (right - left) / 2; // 計算中點
merge_sort(nums, left, mid); // 遞迴左子陣列
merge_sort(nums, mid + 1, right); // 遞迴右子陣列
@ -574,7 +574,7 @@ comments: true
if (left >= right)
return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
int mid = (left + right) / 2; // 計算中點
int mid = left + (right - left) / 2; // 計算中點
mergeSort(nums, left, mid); // 遞迴左子陣列
mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段
@ -598,7 +598,7 @@ comments: true
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++]
else
else
tmp[k++] = nums[j++]
}
// 將左子陣列和右子陣列的剩餘元素複製到臨時陣列中
@ -619,7 +619,7 @@ comments: true
// 終止條件
if (left >= right) return // 當子陣列長度為 1 時終止遞迴
// 劃分階段
val mid = (left + right) / 2 // 計算中點
val mid = left + (right - left) / 2 // 計算中點
mergeSort(nums, left, mid) // 遞迴左子陣列
mergeSort(nums, mid + 1, right) // 遞迴右子陣列
// 合併階段
@ -671,7 +671,7 @@ comments: true
# 當子陣列長度為 1 時終止遞迴
return if left >= right
# 劃分階段
mid = (left + right) / 2 # 計算中點
mid = left + (right - left) / 2 # 計算中點
merge_sort(nums, left, mid) # 遞迴左子陣列
merge_sort(nums, mid + 1, right) # 遞迴右子陣列
# 合併階段
@ -692,10 +692,10 @@ comments: true
const mem_allocator = mem_arena.allocator();
var tmp = try mem_allocator.alloc(i32, right + 1 - left);
std.mem.copy(i32, tmp, nums[left..right+1]);
// 左子陣列的起始索引和結束索引
// 左子陣列的起始索引和結束索引
var leftStart = left - left;
var leftEnd = mid - left;
// 右子陣列的起始索引和結束索引
// 右子陣列的起始索引和結束索引
var rightStart = mid + 1 - left;
var rightEnd = right - left;
// i, j 分別指向左子陣列、右子陣列的首元素
@ -725,7 +725,7 @@ comments: true
// 終止條件
if (left >= right) return; // 當子陣列長度為 1 時終止遞迴
// 劃分階段
var mid = (left + right) / 2; // 計算中點
var mid = left + (right - left) / 2; // 計算中點
try mergeSort(nums, left, mid); // 遞迴左子陣列
try mergeSort(nums, mid + 1, right); // 遞迴右子陣列
// 合併階段

@ -861,7 +861,7 @@ comments: true
/* 哨兵劃分(三數取中值) */
func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int {
// 選取三個候選元素的中位數
let med = medianThree(nums: nums, left: left, mid: (left + right) / 2, right: right)
let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right)
// 將中位數交換至陣列最左端
nums.swapAt(left, med)
return partition(nums: &nums, left: left, right: right)

@ -545,9 +545,7 @@ $$
counter[d] -= 1; // 將 d 的數量減 1
}
// 使用結果覆蓋原陣列 nums
for i in 0..n {
nums[i] = res[i];
}
nums.copy_from_slice(&res);
}
/* 基數排序 */

Loading…
Cancel
Save