Merge branch 'krahets:main' into main
@ -90,11 +90,11 @@ int main() {
|
||||
|
||||
/* 获取栈的长度 */
|
||||
int size = stack->size;
|
||||
printf("栈的长度 size = %d\n", size);
|
||||
printf("栈的长度 size = %d\n", size);
|
||||
|
||||
/* 判断是否为空 */
|
||||
bool empty = isEmpty(stack);
|
||||
printf("栈是否为空 = %stack\n", empty ? "true" : "false");
|
||||
printf("栈是否为空 = %s\n", empty ? "true" : "false");
|
||||
|
||||
// 释放内存
|
||||
delArrayStack(stack);
|
||||
|
@ -72,6 +72,9 @@ func (q *arrayDeque) pushLast(num int) {
|
||||
/* 队首出队 */
|
||||
func (q *arrayDeque) popFirst() any {
|
||||
num := q.peekFirst()
|
||||
if num == nil {
|
||||
return nil
|
||||
}
|
||||
// 队首指针向后移动一位
|
||||
q.front = q.index(q.front + 1)
|
||||
q.queSize--
|
||||
@ -81,6 +84,9 @@ func (q *arrayDeque) popFirst() any {
|
||||
/* 队尾出队 */
|
||||
func (q *arrayDeque) popLast() any {
|
||||
num := q.peekLast()
|
||||
if num == nil {
|
||||
return nil
|
||||
}
|
||||
q.queSize--
|
||||
return num
|
||||
}
|
||||
|
@ -49,6 +49,10 @@ func (q *arrayQueue) push(num int) {
|
||||
/* 出队 */
|
||||
func (q *arrayQueue) pop() any {
|
||||
num := q.peek()
|
||||
if num == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 队首指针向后移动一位,若越过尾部,则返回到数组头部
|
||||
q.front = (q.front + 1) % q.queCapacity
|
||||
q.queSize--
|
||||
|
@ -46,9 +46,13 @@ func TestQueue(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestArrayQueue(t *testing.T) {
|
||||
|
||||
// 初始化队列,使用队列的通用接口
|
||||
capacity := 10
|
||||
queue := newArrayQueue(capacity)
|
||||
if queue.pop() != nil {
|
||||
t.Errorf("want:%v,got:%v", nil, queue.pop())
|
||||
}
|
||||
|
||||
// 元素入队
|
||||
queue.push(1)
|
||||
|
@ -19,7 +19,7 @@ fun merge(nums: IntArray, left: Int, mid: Int, right: Int) {
|
||||
while (i <= mid && j <= right) {
|
||||
if (nums[i] <= nums[j])
|
||||
tmp[k++] = nums[i++]
|
||||
else
|
||||
else
|
||||
tmp[k++] = nums[j++]
|
||||
}
|
||||
// 将左子数组和右子数组的剩余元素复制到临时数组中
|
||||
|
@ -97,7 +97,9 @@ def linear_log_recur(n: int) -> int:
|
||||
"""线性对数阶"""
|
||||
if n <= 1:
|
||||
return 1
|
||||
count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2)
|
||||
# 一分为二,子问题的规模减小一半
|
||||
count = linear_log_recur(n // 2) + linear_log_recur(n // 2)
|
||||
# 当前子问题包含 n 个操作
|
||||
for _ in range(n):
|
||||
count += 1
|
||||
return count
|
||||
@ -120,32 +122,32 @@ if __name__ == "__main__":
|
||||
n = 8
|
||||
print("输入数据大小 n =", n)
|
||||
|
||||
count: int = constant(n)
|
||||
count = constant(n)
|
||||
print("常数阶的操作数量 =", count)
|
||||
|
||||
count: int = linear(n)
|
||||
count = linear(n)
|
||||
print("线性阶的操作数量 =", count)
|
||||
count: int = array_traversal([0] * n)
|
||||
count = array_traversal([0] * n)
|
||||
print("线性阶(遍历数组)的操作数量 =", count)
|
||||
|
||||
count: int = quadratic(n)
|
||||
count = quadratic(n)
|
||||
print("平方阶的操作数量 =", count)
|
||||
nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1]
|
||||
count: int = bubble_sort(nums)
|
||||
count = bubble_sort(nums)
|
||||
print("平方阶(冒泡排序)的操作数量 =", count)
|
||||
|
||||
count: int = exponential(n)
|
||||
count = exponential(n)
|
||||
print("指数阶(循环实现)的操作数量 =", count)
|
||||
count: int = exp_recur(n)
|
||||
count = exp_recur(n)
|
||||
print("指数阶(递归实现)的操作数量 =", count)
|
||||
|
||||
count: int = logarithmic(n)
|
||||
count = logarithmic(n)
|
||||
print("对数阶(循环实现)的操作数量 =", count)
|
||||
count: int = log_recur(n)
|
||||
count = log_recur(n)
|
||||
print("对数阶(递归实现)的操作数量 =", count)
|
||||
|
||||
count: int = linear_log_recur(n)
|
||||
count = linear_log_recur(n)
|
||||
print("线性对数阶(递归实现)的操作数量 =", count)
|
||||
|
||||
count: int = factorial_recur(n)
|
||||
count = factorial_recur(n)
|
||||
print("阶乘阶(递归实现)的操作数量 =", count)
|
||||
|
61
codes/ruby/chapter_backtracking/n_queens.rb
Normal file
@ -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
|
46
codes/ruby/chapter_backtracking/permutations_i.rb
Normal file
@ -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
|
48
codes/ruby/chapter_backtracking/permutations_ii.rb
Normal file
@ -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
|
47
codes/ruby/chapter_backtracking/subset_sum_i.rb
Normal file
@ -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
|
46
codes/ruby/chapter_backtracking/subset_sum_i_naive.rb
Normal file
@ -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
|
51
codes/ruby/chapter_backtracking/subset_sum_ii.rb
Normal file
@ -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
|
@ -0,0 +1,37 @@
|
||||
=begin
|
||||
File: climbing_stairs_backtrack.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 回溯 ###
|
||||
def backtrack(choices, state, n, res)
|
||||
# 当爬到第 n 阶时,方案数量加 1
|
||||
res[0] += 1 if state == n
|
||||
# 遍历所有选择
|
||||
for choice in choices
|
||||
# 剪枝:不允许越过第 n 阶
|
||||
next if state + choice > n
|
||||
|
||||
# 尝试:做出选择,更新状态
|
||||
backtrack(choices, state + choice, n, res)
|
||||
end
|
||||
# 回退
|
||||
end
|
||||
|
||||
### 爬楼梯:回溯 ###
|
||||
def climbing_stairs_backtrack(n)
|
||||
choices = [1, 2] # 可选择向上爬 1 阶或 2 阶
|
||||
state = 0 # 从第 0 阶开始爬
|
||||
res = [0] # 使用 res[0] 记录方案数量
|
||||
backtrack(choices, state, n, res)
|
||||
res.first
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
n = 9
|
||||
|
||||
res = climbing_stairs_backtrack(n)
|
||||
puts "爬 #{n} 阶楼梯共有 #{res} 种方案"
|
||||
end
|
@ -0,0 +1,31 @@
|
||||
=begin
|
||||
File: climbing_stairs_constraint_dp.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 带约束爬楼梯:动态规划 ###
|
||||
def climbing_stairs_constraint_dp(n)
|
||||
return 1 if n == 1 || n == 2
|
||||
|
||||
# 初始化 dp 表,用于存储子问题的解
|
||||
dp = Array.new(n + 1) { Array.new(3, 0) }
|
||||
# 初始状态:预设最小子问题的解
|
||||
dp[1][1], dp[1][2] = 1, 0
|
||||
dp[2][1], dp[2][2] = 0, 1
|
||||
# 状态转移:从较小子问题逐步求解较大子问题
|
||||
for i in 3...(n + 1)
|
||||
dp[i][1] = dp[i - 1][2]
|
||||
dp[i][2] = dp[i - 2][1] + dp[i - 2][2]
|
||||
end
|
||||
|
||||
dp[n][1] + dp[n][2]
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
n = 9
|
||||
|
||||
res = climbing_stairs_constraint_dp(n)
|
||||
puts "爬 #{n} 阶楼梯共有 #{res} 种方案"
|
||||
end
|
@ -0,0 +1,26 @@
|
||||
=begin
|
||||
File: climbing_stairs_dfs.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 搜索 ###
|
||||
def dfs(i)
|
||||
# 已知 dp[1] 和 dp[2] ,返回之
|
||||
return i if i == 1 || i == 2
|
||||
# dp[i] = dp[i-1] + dp[i-2]
|
||||
dfs(i - 1) + dfs(i - 2)
|
||||
end
|
||||
|
||||
### 爬楼梯:搜索 ###
|
||||
def climbing_stairs_dfs(n)
|
||||
dfs(n)
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
n = 9
|
||||
|
||||
res = climbing_stairs_dfs(n)
|
||||
puts "爬 #{n} 阶楼梯共有 #{res} 种方案"
|
||||
end
|
@ -0,0 +1,33 @@
|
||||
=begin
|
||||
File: climbing_stairs_dfs_mem.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 记忆化搜索 ###
|
||||
def dfs(i, mem)
|
||||
# 已知 dp[1] 和 dp[2] ,返回之
|
||||
return i if i == 1 || i == 2
|
||||
# 若存在记录 dp[i] ,则直接返回之
|
||||
return mem[i] if mem[i] != -1
|
||||
|
||||
# dp[i] = dp[i-1] + dp[i-2]
|
||||
count = dfs(i - 1, mem) + dfs(i - 2, mem)
|
||||
# 记录 dp[i]
|
||||
mem[i] = count
|
||||
end
|
||||
|
||||
### 爬楼梯:记忆化搜索 ###
|
||||
def climbing_stairs_dfs_mem(n)
|
||||
# mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录
|
||||
mem = Array.new(n + 1, -1)
|
||||
dfs(n, mem)
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
n = 9
|
||||
|
||||
res = climbing_stairs_dfs_mem(n)
|
||||
puts "爬 #{n} 阶楼梯共有 #{res} 种方案"
|
||||
end
|
40
codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb
Normal file
@ -0,0 +1,40 @@
|
||||
=begin
|
||||
File: climbing_stairs_dp.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 爬楼梯:动态规划 ###
|
||||
def climbing_stairs_dp(n)
|
||||
return n if n == 1 || n == 2
|
||||
|
||||
# 初始化 dp 表,用于存储子问题的解
|
||||
dp = Array.new(n + 1, 0)
|
||||
# 初始状态:预设最小子问题的解
|
||||
dp[1], dp[2] = 1, 2
|
||||
# 状态转移:从较小子问题逐步求解较大子问题
|
||||
(3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }
|
||||
|
||||
dp[n]
|
||||
end
|
||||
|
||||
### 爬楼梯:空间优化后的动态规划 ###
|
||||
def climbing_stairs_dp_comp(n)
|
||||
return n if n == 1 || n == 2
|
||||
|
||||
a, b = 1, 2
|
||||
(3...(n + 1)).each { a, b = b, a + b }
|
||||
|
||||
b
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
n = 9
|
||||
|
||||
res = climbing_stairs_dp(n)
|
||||
puts "爬 #{n} 阶楼梯共有 #{res} 种方案"
|
||||
|
||||
res = climbing_stairs_dp_comp(n)
|
||||
puts "爬 #{n} 阶楼梯共有 #{res} 种方案"
|
||||
end
|
65
codes/ruby/chapter_dynamic_programming/coin_change.rb
Normal file
@ -0,0 +1,65 @@
|
||||
=begin
|
||||
File: coin_change.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 零钱兑换:动态规划 ###
|
||||
def coin_change_dp(coins, amt)
|
||||
n = coins.length
|
||||
_MAX = amt + 1
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
|
||||
# 状态转移:首行首列
|
||||
(1...(amt + 1)).each { |a| dp[0][a] = _MAX }
|
||||
# 状态转移:其余行和列
|
||||
for i in 1...(n + 1)
|
||||
for a in 1...(amt + 1)
|
||||
if coins[i - 1] > a
|
||||
# 若超过目标金额,则不选硬币 i
|
||||
dp[i][a] = dp[i - 1][a]
|
||||
else
|
||||
# 不选和选硬币 i 这两种方案的较小值
|
||||
dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min
|
||||
end
|
||||
end
|
||||
end
|
||||
dp[n][amt] != _MAX ? dp[n][amt] : -1
|
||||
end
|
||||
|
||||
### 零钱兑换:空间优化后的动态规划 ###
|
||||
def coin_change_dp_comp(coins, amt)
|
||||
n = coins.length
|
||||
_MAX = amt + 1
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(amt + 1, _MAX)
|
||||
dp[0] = 0
|
||||
# 状态转移
|
||||
for i in 1...(n + 1)
|
||||
# 正序遍历
|
||||
for a in 1...(amt + 1)
|
||||
if coins[i - 1] > a
|
||||
# 若超过目标金额,则不选硬币 i
|
||||
dp[a] = dp[a]
|
||||
else
|
||||
# 不选和选硬币 i 这两种方案的较小值
|
||||
dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min
|
||||
end
|
||||
end
|
||||
end
|
||||
dp[amt] != _MAX ? dp[amt] : -1
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
coins = [1, 2, 5]
|
||||
amt = 4
|
||||
|
||||
# 动态规划
|
||||
res = coin_change_dp(coins, amt)
|
||||
puts "凑到目标金额所需的最少硬币数量为 #{res}"
|
||||
|
||||
# 空间优化后的动态规划
|
||||
res = coin_change_dp_comp(coins, amt)
|
||||
puts "凑到目标金额所需的最少硬币数量为 #{res}"
|
||||
end
|
63
codes/ruby/chapter_dynamic_programming/coin_change_ii.rb
Normal file
@ -0,0 +1,63 @@
|
||||
=begin
|
||||
File: coin_change_ii.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 零钱兑换 II:动态规划 ###
|
||||
def coin_change_ii_dp(coins, amt)
|
||||
n = coins.length
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(n + 1) { Array.new(amt + 1, 0) }
|
||||
# 初始化首列
|
||||
(0...(n + 1)).each { |i| dp[i][0] = 1 }
|
||||
# 状态转移
|
||||
for i in 1...(n + 1)
|
||||
for a in 1...(amt + 1)
|
||||
if coins[i - 1] > a
|
||||
# 若超过目标金额,则不选硬币 i
|
||||
dp[i][a] = dp[i - 1][a]
|
||||
else
|
||||
# 不选和选硬币 i 这两种方案之和
|
||||
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]
|
||||
end
|
||||
end
|
||||
end
|
||||
dp[n][amt]
|
||||
end
|
||||
|
||||
### 零钱兑换 II:空间优化后的动态规划 ###
|
||||
def coin_change_ii_dp_comp(coins, amt)
|
||||
n = coins.length
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(amt + 1, 0)
|
||||
dp[0] = 1
|
||||
# 状态转移
|
||||
for i in 1...(n + 1)
|
||||
# 正序遍历
|
||||
for a in 1...(amt + 1)
|
||||
if coins[i - 1] > a
|
||||
# 若超过目标金额,则不选硬币 i
|
||||
dp[a] = dp[a]
|
||||
else
|
||||
# 不选和选硬币 i 这两种方案之和
|
||||
dp[a] = dp[a] + dp[a - coins[i - 1]]
|
||||
end
|
||||
end
|
||||
end
|
||||
dp[amt]
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
coins = [1, 2, 5]
|
||||
amt = 5
|
||||
|
||||
# 动态规划
|
||||
res = coin_change_ii_dp(coins, amt)
|
||||
puts "凑出目标金额的硬币组合数量为 #{res}"
|
||||
|
||||
# 空间优化后的动态规划
|
||||
res = coin_change_ii_dp_comp(coins, amt)
|
||||
puts "凑出目标金额的硬币组合数量为 #{res}"
|
||||
end
|
115
codes/ruby/chapter_dynamic_programming/edit_distance.rb
Normal file
@ -0,0 +1,115 @@
|
||||
=begin
|
||||
File: edit_distance.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 编辑距离:暴力搜索 ###
|
||||
def edit_distance_dfs(s, t, i, j)
|
||||
# 若 s 和 t 都为空,则返回 0
|
||||
return 0 if i == 0 && j == 0
|
||||
# 若 s 为空,则返回 t 长度
|
||||
return j if i == 0
|
||||
# 若 t 为空,则返回 s 长度
|
||||
return i if j == 0
|
||||
# 若两字符相等,则直接跳过此两字符
|
||||
return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1]
|
||||
# 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||||
insert = edit_distance_dfs(s, t, i, j - 1)
|
||||
delete = edit_distance_dfs(s, t, i - 1, j)
|
||||
replace = edit_distance_dfs(s, t, i - 1, j - 1)
|
||||
# 返回最少编辑步数
|
||||
[insert, delete, replace].min + 1
|
||||
end
|
||||
|
||||
def edit_distance_dfs_mem(s, t, mem, i, j)
|
||||
# 若 s 和 t 都为空,则返回 0
|
||||
return 0 if i == 0 && j == 0
|
||||
# 若 s 为空,则返回 t 长度
|
||||
return j if i == 0
|
||||
# 若 t 为空,则返回 s 长度
|
||||
return i if j == 0
|
||||
# 若已有记录,则直接返回之
|
||||
return mem[i][j] if mem[i][j] != -1
|
||||
# 若两字符相等,则直接跳过此两字符
|
||||
return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1]
|
||||
# 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||||
insert = edit_distance_dfs_mem(s, t, mem, i, j - 1)
|
||||
delete = edit_distance_dfs_mem(s, t, mem, i - 1, j)
|
||||
replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1)
|
||||
# 记录并返回最少编辑步数
|
||||
mem[i][j] = [insert, delete, replace].min + 1
|
||||
end
|
||||
|
||||
### 编辑距离:动态规划 ###
|
||||
def edit_distance_dp(s, t)
|
||||
n, m = s.length, t.length
|
||||
dp = Array.new(n + 1) { Array.new(m + 1, 0) }
|
||||
# 状态转移:首行首列
|
||||
(1...(n + 1)).each { |i| dp[i][0] = i }
|
||||
(1...(m + 1)).each { |j| dp[0][j] = j }
|
||||
# 状态转移:其余行和列
|
||||
for i in 1...(n + 1)
|
||||
for j in 1...(m +1)
|
||||
if s[i - 1] == t[j - 1]
|
||||
# 若两字符相等,则直接跳过此两字符
|
||||
dp[i][j] = dp[i - 1][j - 1]
|
||||
else
|
||||
# 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||||
dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
dp[n][m]
|
||||
end
|
||||
|
||||
### 编辑距离:空间优化后的动态规划 ###
|
||||
def edit_distance_dp_comp(s, t)
|
||||
n, m = s.length, t.length
|
||||
dp = Array.new(m + 1, 0)
|
||||
# 状态转移:首行
|
||||
(1...(m + 1)).each { |j| dp[j] = j }
|
||||
# 状态转移:其余行
|
||||
for i in 1...(n + 1)
|
||||
# 状态转移:首列
|
||||
leftup = dp.first # 暂存 dp[i-1, j-1]
|
||||
dp[0] += 1
|
||||
# 状态转移:其余列
|
||||
for j in 1...(m + 1)
|
||||
temp = dp[j]
|
||||
if s[i - 1] == t[j - 1]
|
||||
# 若两字符相等,则直接跳过此两字符
|
||||
dp[j] = leftup
|
||||
else
|
||||
# 最少编辑步数 = 插入、删除、替换这三种操作的最少编辑步数 + 1
|
||||
dp[j] = [dp[j - 1], dp[j], leftup].min + 1
|
||||
end
|
||||
leftup = temp # 更新为下一轮的 dp[i-1, j-1]
|
||||
end
|
||||
end
|
||||
dp[m]
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
s = 'bag'
|
||||
t = 'pack'
|
||||
n, m = s.length, t.length
|
||||
|
||||
# 暴力搜索
|
||||
res = edit_distance_dfs(s, t, n, m)
|
||||
puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步"
|
||||
|
||||
# 记忆化搜索
|
||||
mem = Array.new(n + 1) { Array.new(m + 1, -1) }
|
||||
res = edit_distance_dfs_mem(s, t, mem, n, m)
|
||||
puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步"
|
||||
|
||||
# 动态规划
|
||||
res = edit_distance_dp(s, t)
|
||||
puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步"
|
||||
|
||||
# 空间优化后的动态规划
|
||||
res = edit_distance_dp_comp(s, t)
|
||||
puts "将 #{s} 更改为 #{t} 最少需要编辑 #{res} 步"
|
||||
end
|
99
codes/ruby/chapter_dynamic_programming/knapsack.rb
Normal file
@ -0,0 +1,99 @@
|
||||
=begin
|
||||
File: knapsack.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 0-1 背包:暴力搜索 ###
|
||||
def knapsack_dfs(wgt, val, i, c)
|
||||
# 若已选完所有物品或背包无剩余容量,则返回价值 0
|
||||
return 0 if i == 0 || c == 0
|
||||
# 若超过背包容量,则只能选择不放入背包
|
||||
return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c
|
||||
# 计算不放入和放入物品 i 的最大价值
|
||||
no = knapsack_dfs(wgt, val, i - 1, c)
|
||||
yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]
|
||||
# 返回两种方案中价值更大的那一个
|
||||
[no, yes].max
|
||||
end
|
||||
|
||||
### 0-1 背包:记忆化搜索 ###
|
||||
def knapsack_dfs_mem(wgt, val, mem, i, c)
|
||||
# 若已选完所有物品或背包无剩余容量,则返回价值 0
|
||||
return 0 if i == 0 || c == 0
|
||||
# 若已有记录,则直接返回
|
||||
return mem[i][c] if mem[i][c] != -1
|
||||
# 若超过背包容量,则只能选择不放入背包
|
||||
return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c
|
||||
# 计算不放入和放入物品 i 的最大价值
|
||||
no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)
|
||||
yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]
|
||||
# 记录并返回两种方案中价值更大的那一个
|
||||
mem[i][c] = [no, yes].max
|
||||
end
|
||||
|
||||
### 0-1 背包:动态规划 ###
|
||||
def knapsack_dp(wgt, val, cap)
|
||||
n = wgt.length
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
|
||||
# 状态转移
|
||||
for i in 1...(n + 1)
|
||||
for c in 1...(cap + 1)
|
||||
if wgt[i - 1] > c
|
||||
# 若超过背包容量,则不选物品 i
|
||||
dp[i][c] = dp[i - 1][c]
|
||||
else
|
||||
# 不选和选物品 i 这两种方案的较大值
|
||||
dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max
|
||||
end
|
||||
end
|
||||
end
|
||||
dp[n][cap]
|
||||
end
|
||||
|
||||
### 0-1 背包:空间优化后的动态规划 ###
|
||||
def knapsack_dp_comp(wgt, val, cap)
|
||||
n = wgt.length
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(cap + 1, 0)
|
||||
# 状态转移
|
||||
for i in 1...(n + 1)
|
||||
# 倒序遍历
|
||||
for c in cap.downto(1)
|
||||
if wgt[i - 1] > c
|
||||
# 若超过背包容量,则不选物品 i
|
||||
dp[c] = dp[c]
|
||||
else
|
||||
# 不选和选物品 i 这两种方案的较大值
|
||||
dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max
|
||||
end
|
||||
end
|
||||
end
|
||||
dp[cap]
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
wgt = [10, 20, 30, 40, 50]
|
||||
val = [50, 120, 150, 210, 240]
|
||||
cap = 50
|
||||
n = wgt.length
|
||||
|
||||
# 暴力搜索
|
||||
res = knapsack_dfs(wgt, val, n, cap)
|
||||
puts "不超过背包容量的最大物品价值为 #{res}"
|
||||
|
||||
# 记忆化搜索
|
||||
mem = Array.new(n + 1) { Array.new(cap + 1, -1) }
|
||||
res = knapsack_dfs_mem(wgt, val, mem, n, cap)
|
||||
puts "不超过背包容量的最大物品价值为 #{res}"
|
||||
|
||||
# 动态规划
|
||||
res = knapsack_dp(wgt, val, cap)
|
||||
puts "不超过背包容量的最大物品价值为 #{res}"
|
||||
|
||||
# 空间优化后的动态规划
|
||||
res = knapsack_dp_comp(wgt, val, cap)
|
||||
puts "不超过背包容量的最大物品价值为 #{res}"
|
||||
end
|
@ -0,0 +1,39 @@
|
||||
=begin
|
||||
File: min_cost_climbing_stairs_dp.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 爬楼梯最小代价:动态规划 ###
|
||||
def min_cost_climbing_stairs_dp(cost)
|
||||
n = cost.length - 1
|
||||
return cost[n] if n == 1 || n == 2
|
||||
# 初始化 dp 表,用于存储子问题的解
|
||||
dp = Array.new(n + 1, 0)
|
||||
# 初始状态:预设最小子问题的解
|
||||
dp[1], dp[2] = cost[1], cost[2]
|
||||
# 状态转移:从较小子问题逐步求解较大子问题
|
||||
(3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }
|
||||
dp[n]
|
||||
end
|
||||
|
||||
# 爬楼梯最小代价:空间优化后的动态规划
|
||||
def min_cost_climbing_stairs_dp_comp(cost)
|
||||
n = cost.length - 1
|
||||
return cost[n] if n == 1 || n == 2
|
||||
a, b = cost[1], cost[2]
|
||||
(3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] }
|
||||
b
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]
|
||||
puts "输入楼梯的代价列表为 #{cost}"
|
||||
|
||||
res = min_cost_climbing_stairs_dp(cost)
|
||||
puts "爬完楼梯的最低代价为 #{res}"
|
||||
|
||||
res = min_cost_climbing_stairs_dp_comp(cost)
|
||||
puts "爬完楼梯的最低代价为 #{res}"
|
||||
end
|
93
codes/ruby/chapter_dynamic_programming/min_path_sum.rb
Normal file
@ -0,0 +1,93 @@
|
||||
=begin
|
||||
File: min_path_sum.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 最小路径和:暴力搜索 ###
|
||||
def min_path_sum_dfs(grid, i, j)
|
||||
# 若为左上角单元格,则终止搜索
|
||||
return grid[i][j] if i == 0 && j == 0
|
||||
# 若行列索引越界,则返回 +∞ 代价
|
||||
return Float::INFINITY if i < 0 || j < 0
|
||||
# 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价
|
||||
up = min_path_sum_dfs(grid, i - 1, j)
|
||||
left = min_path_sum_dfs(grid, i, j - 1)
|
||||
# 返回从左上角到 (i, j) 的最小路径代价
|
||||
[left, up].min + grid[i][j]
|
||||
end
|
||||
|
||||
### 最小路径和:记忆化搜索 ###
|
||||
def min_path_sum_dfs_mem(grid, mem, i, j)
|
||||
# 若为左上角单元格,则终止搜索
|
||||
return grid[0][0] if i == 0 && j == 0
|
||||
# 若行列索引越界,则返回 +∞ 代价
|
||||
return Float::INFINITY if i < 0 || j < 0
|
||||
# 若已有记录,则直接返回
|
||||
return mem[i][j] if mem[i][j] != -1
|
||||
# 左边和上边单元格的最小路径代价
|
||||
up = min_path_sum_dfs_mem(grid, mem, i - 1, j)
|
||||
left = min_path_sum_dfs_mem(grid, mem, i, j - 1)
|
||||
# 记录并返回左上角到 (i, j) 的最小路径代价
|
||||
mem[i][j] = [left, up].min + grid[i][j]
|
||||
end
|
||||
|
||||
### 最小路径和:动态规划 ###
|
||||
def min_path_sum_dp(grid)
|
||||
n, m = grid.length, grid.first.length
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(n) { Array.new(m, 0) }
|
||||
dp[0][0] = grid[0][0]
|
||||
# 状态转移:首行
|
||||
(1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }
|
||||
# 状态转移:首列
|
||||
(1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }
|
||||
# 状态转移:其余行和列
|
||||
for i in 1...n
|
||||
for j in 1...m
|
||||
dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j]
|
||||
end
|
||||
end
|
||||
dp[n -1][m -1]
|
||||
end
|
||||
|
||||
### 最小路径和:空间优化后的动态规划 ###
|
||||
def min_path_sum_dp_comp(grid)
|
||||
n, m = grid.length, grid.first.length
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(m, 0)
|
||||
# 状态转移:首行
|
||||
dp[0] = grid[0][0]
|
||||
(1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }
|
||||
# 状态转移:其余行
|
||||
for i in 1...n
|
||||
# 状态转移:首列
|
||||
dp[0] = dp[0] + grid[i][0]
|
||||
# 状态转移:其余列
|
||||
(1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] }
|
||||
end
|
||||
dp[m - 1]
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]]
|
||||
n, m = grid.length, grid.first.length
|
||||
|
||||
# 暴力搜索
|
||||
res = min_path_sum_dfs(grid, n - 1, m - 1)
|
||||
puts "从左上角到右下角的做小路径和为 #{res}"
|
||||
|
||||
# 记忆化搜索
|
||||
mem = Array.new(n) { Array.new(m, - 1) }
|
||||
res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1)
|
||||
puts "从左上角到右下角的做小路径和为 #{res}"
|
||||
|
||||
# 动态规划
|
||||
res = min_path_sum_dp(grid)
|
||||
puts "从左上角到右下角的做小路径和为 #{res}"
|
||||
|
||||
# 空间优化后的动态规划
|
||||
res = min_path_sum_dp_comp(grid)
|
||||
puts "从左上角到右下角的做小路径和为 #{res}"
|
||||
end
|
61
codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb
Normal file
@ -0,0 +1,61 @@
|
||||
=begin
|
||||
File: unbounded_knapsack.rb
|
||||
Created Time: 2024-05-29
|
||||
Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
|
||||
=end
|
||||
|
||||
### 完全背包:动态规划 ###
|
||||
def unbounded_knapsack_dp(wgt, val, cap)
|
||||
n = wgt.length
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(n + 1) { Array.new(cap + 1, 0) }
|
||||
# 状态转移
|
||||
for i in 1...(n + 1)
|
||||
for c in 1...(cap + 1)
|
||||
if wgt[i - 1] > c
|
||||
# 若超过背包容量,则不选物品 i
|
||||
dp[i][c] = dp[i - 1][c]
|
||||
else
|
||||
# 不选和选物品 i 这两种方案的较大值
|
||||
dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max
|
||||
end
|
||||
end
|
||||
end
|
||||
dp[n][cap]
|
||||
end
|
||||
|
||||
### 完全背包:空间优化后的动态规划 ##3
|
||||
def unbounded_knapsack_dp_comp(wgt, val, cap)
|
||||
n = wgt.length
|
||||
# 初始化 dp 表
|
||||
dp = Array.new(cap + 1, 0)
|
||||
# 状态转移
|
||||
for i in 1...(n + 1)
|
||||
# 正序遍历
|
||||
for c in 1...(cap + 1)
|
||||
if wgt[i -1] > c
|
||||
# 若超过背包容量,则不选物品 i
|
||||
dp[c] = dp[c]
|
||||
else
|
||||
# 不选和选物品 i 这两种方案的较大值
|
||||
dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max
|
||||
end
|
||||
end
|
||||
end
|
||||
dp[cap]
|
||||
end
|
||||
|
||||
### Driver Code ###
|
||||
if __FILE__ == $0
|
||||
wgt = [1, 2, 3]
|
||||
val = [5, 11, 15]
|
||||
cap = 4
|
||||
|
||||
# 动态规划
|
||||
res = unbounded_knapsack_dp(wgt, val, cap)
|
||||
puts "不超过背包容量的最大物品价值为 #{res}"
|
||||
|
||||
# 空间优化后的动态规划
|
||||
res = unbounded_knapsack_dp_comp(wgt, val, cap)
|
||||
puts "不超过背包容量的最大物品价值为 #{res}"
|
||||
end
|
@ -74,7 +74,6 @@ fn find(nums: &[i32], target: i32) -> Option<usize> {
|
||||
fn main() {
|
||||
/* 初始化数组 */
|
||||
let arr: [i32; 5] = [0; 5];
|
||||
let slice: &[i32] = &[0; 5];
|
||||
print!("数组 arr = ");
|
||||
print_util::print_array(&arr);
|
||||
// 在 Rust 中,指定长度时([i32; 5])为数组,不指定长度时(&[i32])为切片
|
||||
|
@ -10,7 +10,7 @@ use std::{cell::RefCell, rc::Rc};
|
||||
use tree_node::{vec_to_tree, TreeNode};
|
||||
|
||||
/* 前序遍历:例题一 */
|
||||
fn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<Rc<RefCell<TreeNode>>>) {
|
||||
fn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<&Rc<RefCell<TreeNode>>>) {
|
||||
if root.is_none() {
|
||||
return;
|
||||
}
|
||||
@ -19,8 +19,8 @@ fn pre_order(res: &mut Vec<Rc<RefCell<TreeNode>>>, root: Option<Rc<RefCell<TreeN
|
||||
// 记录解
|
||||
res.push(node.clone());
|
||||
}
|
||||
pre_order(res, node.borrow().left.clone());
|
||||
pre_order(res, node.borrow().right.clone());
|
||||
pre_order(res, node.borrow().left.as_ref());
|
||||
pre_order(res, node.borrow().right.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ pub fn main() {
|
||||
|
||||
// 前序遍历
|
||||
let mut res = Vec::new();
|
||||
pre_order(&mut res, root);
|
||||
pre_order(&mut res, root.as_ref());
|
||||
|
||||
println!("\n输出所有值为 7 的节点");
|
||||
let mut vals = Vec::new();
|
||||
|
@ -13,7 +13,7 @@ use tree_node::{vec_to_tree, TreeNode};
|
||||
fn pre_order(
|
||||
res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,
|
||||
path: &mut Vec<Rc<RefCell<TreeNode>>>,
|
||||
root: Option<Rc<RefCell<TreeNode>>>,
|
||||
root: Option<&Rc<RefCell<TreeNode>>>,
|
||||
) {
|
||||
if root.is_none() {
|
||||
return;
|
||||
@ -25,10 +25,10 @@ fn pre_order(
|
||||
// 记录解
|
||||
res.push(path.clone());
|
||||
}
|
||||
pre_order(res, path, node.borrow().left.clone());
|
||||
pre_order(res, path, node.borrow().right.clone());
|
||||
pre_order(res, path, node.borrow().left.as_ref());
|
||||
pre_order(res, path, node.borrow().right.as_ref());
|
||||
// 回退
|
||||
path.remove(path.len() - 1);
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ pub fn main() {
|
||||
// 前序遍历
|
||||
let mut path = Vec::new();
|
||||
let mut res = Vec::new();
|
||||
pre_order(&mut res, &mut path, root);
|
||||
pre_order(&mut res, &mut path, root.as_ref());
|
||||
|
||||
println!("\n输出所有根节点到节点 7 的路径");
|
||||
for path in res {
|
||||
|
@ -13,7 +13,7 @@ use tree_node::{vec_to_tree, TreeNode};
|
||||
fn pre_order(
|
||||
res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,
|
||||
path: &mut Vec<Rc<RefCell<TreeNode>>>,
|
||||
root: Option<Rc<RefCell<TreeNode>>>,
|
||||
root: Option<&Rc<RefCell<TreeNode>>>,
|
||||
) {
|
||||
// 剪枝
|
||||
if root.is_none() || root.as_ref().unwrap().borrow().val == 3 {
|
||||
@ -26,10 +26,10 @@ fn pre_order(
|
||||
// 记录解
|
||||
res.push(path.clone());
|
||||
}
|
||||
pre_order(res, path, node.borrow().left.clone());
|
||||
pre_order(res, path, node.borrow().right.clone());
|
||||
pre_order(res, path, node.borrow().left.as_ref());
|
||||
pre_order(res, path, node.borrow().right.as_ref());
|
||||
// 回退
|
||||
path.remove(path.len() - 1);
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ pub fn main() {
|
||||
// 前序遍历
|
||||
let mut path = Vec::new();
|
||||
let mut res = Vec::new();
|
||||
pre_order(&mut res, &mut path, root);
|
||||
pre_order(&mut res, &mut path, root.as_ref());
|
||||
|
||||
println!("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点");
|
||||
for path in res {
|
||||
|
@ -11,7 +11,7 @@ use tree_node::{vec_to_tree, TreeNode};
|
||||
|
||||
/* 判断当前状态是否为解 */
|
||||
fn is_solution(state: &mut Vec<Rc<RefCell<TreeNode>>>) -> bool {
|
||||
return !state.is_empty() && state.get(state.len() - 1).unwrap().borrow().val == 7;
|
||||
return !state.is_empty() && state.last().unwrap().borrow().val == 7;
|
||||
}
|
||||
|
||||
/* 记录解 */
|
||||
@ -23,8 +23,8 @@ fn record_solution(
|
||||
}
|
||||
|
||||
/* 判断在当前状态下,该选择是否合法 */
|
||||
fn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Rc<RefCell<TreeNode>>) -> bool {
|
||||
return choice.borrow().val != 3;
|
||||
fn is_valid(_: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Option<&Rc<RefCell<TreeNode>>>) -> bool {
|
||||
return choice.is_some() && choice.unwrap().borrow().val != 3;
|
||||
}
|
||||
|
||||
/* 更新状态 */
|
||||
@ -34,13 +34,13 @@ fn make_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, choice: Rc<RefCell<TreeNo
|
||||
|
||||
/* 恢复状态 */
|
||||
fn undo_choice(state: &mut Vec<Rc<RefCell<TreeNode>>>, _: Rc<RefCell<TreeNode>>) {
|
||||
state.remove(state.len() - 1);
|
||||
state.pop();
|
||||
}
|
||||
|
||||
/* 回溯算法:例题三 */
|
||||
fn backtrack(
|
||||
state: &mut Vec<Rc<RefCell<TreeNode>>>,
|
||||
choices: &mut Vec<Rc<RefCell<TreeNode>>>,
|
||||
choices: &Vec<Option<&Rc<RefCell<TreeNode>>>>,
|
||||
res: &mut Vec<Vec<Rc<RefCell<TreeNode>>>>,
|
||||
) {
|
||||
// 检查是否为解
|
||||
@ -49,22 +49,22 @@ fn backtrack(
|
||||
record_solution(state, res);
|
||||
}
|
||||
// 遍历所有选择
|
||||
for choice in choices {
|
||||
for &choice in choices.iter() {
|
||||
// 剪枝:检查选择是否合法
|
||||
if is_valid(state, choice.clone()) {
|
||||
if is_valid(state, choice) {
|
||||
// 尝试:做出选择,更新状态
|
||||
make_choice(state, choice.clone());
|
||||
make_choice(state, choice.unwrap().clone());
|
||||
// 进行下一轮选择
|
||||
backtrack(
|
||||
state,
|
||||
&mut vec![
|
||||
choice.borrow().left.clone().unwrap(),
|
||||
choice.borrow().right.clone().unwrap(),
|
||||
&vec![
|
||||
choice.unwrap().borrow().left.as_ref(),
|
||||
choice.unwrap().borrow().right.as_ref(),
|
||||
],
|
||||
res,
|
||||
);
|
||||
// 回退:撤销选择,恢复到之前的状态
|
||||
undo_choice(state, choice.clone());
|
||||
undo_choice(state, choice.unwrap().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,7 +77,7 @@ pub fn main() {
|
||||
|
||||
// 回溯算法
|
||||
let mut res = Vec::new();
|
||||
backtrack(&mut Vec::new(), &mut vec![root.unwrap()], &mut res);
|
||||
backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res);
|
||||
|
||||
println!("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点");
|
||||
for path in res {
|
||||
|
BIN
docs/assets/avatar/avatar_khoaxuantu.jpg
Normal file
After Width: | Height: | Size: 9.5 KiB |
@ -73,4 +73,4 @@
|
||||
|
||||
**Q**:初始化列表 `res = [0] * self.size()` 操作,会导致 `res` 的每个元素引用相同的地址吗?
|
||||
|
||||
不会。但二维数组会有这个问题,例如初始化二维列表 `res = [[0] * self.size()]` ,则多次引用了同一个列表 `[0]` 。
|
||||
不会。但二维数组会有这个问题,例如初始化二维列表 `res = [[0]] * self.size()` ,则多次引用了同一个列表 `[0]` 。
|
||||
|
@ -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"
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
也就是说,在能够解决问题的前提下,算法效率已成为衡量算法优劣的主要评价指标,它包括以下两个维度。
|
||||
|
||||
- **时间效率**:算法运行速度的快慢。
|
||||
- **时间效率**:算法运行时间的长短。
|
||||
- **空间效率**:算法占用内存空间的大小。
|
||||
|
||||
简而言之,**我们的目标是设计“既快又省”的数据结构与算法**。而有效地评估算法效率至关重要,因为只有这样,我们才能将各种算法进行对比,进而指导算法设计与优化过程。
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
假设我们现在有算法 `A` 和算法 `B` ,它们都能解决同一问题,现在需要对比这两个算法的效率。最直接的方法是找一台计算机,运行这两个算法,并监控记录它们的运行时间和内存占用情况。这种评估方式能够反映真实情况,但也存在较大的局限性。
|
||||
|
||||
一方面,**难以排除测试环境的干扰因素**。硬件配置会影响算法的性能。比如在某台计算机中,算法 `A` 的运行时间比算法 `B` 短;但在另一台配置不同的计算机中,可能得到相反的测试结果。这意味着我们需要在各种机器上进行测试,统计平均效率,而这是不现实的。
|
||||
一方面,**难以排除测试环境的干扰因素**。硬件配置会影响算法的性能表现。比如一个算法的并行度较高,那么它就更适合在多核 CPU 上运行,一个算法的内存操作密集,那么它在高性能内存上的表现就会更好。也就是说,算法在不同的机器上的测试结果可能是不一致的。这意味着我们需要在各种机器上进行测试,统计平均效率,而这是不现实的。
|
||||
|
||||
另一方面,**展开完整测试非常耗费资源**。随着输入数据量的变化,算法会表现出不同的效率。例如,在输入数据量较小时,算法 `A` 的运行时间比算法 `B` 短;而在输入数据量较大时,测试结果可能恰恰相反。因此,为了得到有说服力的结论,我们需要测试各种规模的输入数据,而这需要耗费大量的计算资源。
|
||||
|
||||
@ -32,8 +32,9 @@
|
||||
- “随着输入数据大小的增加”意味着复杂度反映了算法运行效率与输入数据体量之间的关系。
|
||||
- “时间和空间的增长趋势”表示复杂度分析关注的不是运行时间或占用空间的具体值,而是时间或空间增长的“快慢”。
|
||||
|
||||
**复杂度分析克服了实际测试方法的弊端**,体现在以下两个方面。
|
||||
**复杂度分析克服了实际测试方法的弊端**,体现在以下几个方面。
|
||||
|
||||
- 它无需实际运行代码,更加绿色节能。
|
||||
- 它独立于测试环境,分析结果适用于所有运行平台。
|
||||
- 它可以体现不同数据量下的算法效率,尤其是在大数据量下的算法性能。
|
||||
|
||||
|
@ -534,7 +534,7 @@ $$
|
||||
|
||||
- **时间复杂度能够有效评估算法效率**。例如,算法 `B` 的运行时间呈线性增长,在 $n > 1$ 时比算法 `A` 更慢,在 $n > 1000000$ 时比算法 `C` 更慢。事实上,只要输入数据大小 $n$ 足够大,复杂度为“常数阶”的算法一定优于“线性阶”的算法,这正是时间增长趋势的含义。
|
||||
- **时间复杂度的推算方法更简便**。显然,运行平台和计算操作类型都与算法运行时间的增长趋势无关。因此在时间复杂度分析中,我们可以简单地将所有计算操作的执行时间视为相同的“单位时间”,从而将“计算操作运行时间统计”简化为“计算操作数量统计”,这样一来估算难度就大大降低了。
|
||||
- **时间复杂度也存在一定的局限性**。例如,尽管算法 `A` 和 `C` 的时间复杂度相同,但实际运行时间差别很大。同样,尽管算法 `B` 的时间复杂度比 `C` 高,但在输入数据大小 $n$ 较小时,算法 `B` 明显优于算法 `C` 。在这些情况下,我们很难仅凭时间复杂度判断算法效率的高低。当然,尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。
|
||||
- **时间复杂度也存在一定的局限性**。例如,尽管算法 `A` 和 `C` 的时间复杂度相同,但实际运行时间差别很大。同样,尽管算法 `B` 的时间复杂度比 `C` 高,但在输入数据大小 $n$ 较小时,算法 `B` 明显优于算法 `C` 。对于此类情况,我们时常难以仅凭时间复杂度判断算法效率的高低。当然,尽管存在上述问题,复杂度分析仍然是评判算法效率最有效且常用的方法。
|
||||
|
||||
## 函数渐近上界
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
|
||||
**Q**:原码转补码的方法是“先取反后加 1”,那么补码转原码应该是逆运算“先减 1 后取反”,而补码转原码也一样可以通过“先取反后加 1”得到,这是为什么呢?
|
||||
|
||||
**A**:这是因为原码和补码的相互转换实际上是计算“补数”的过程。我们先给出补数的定义:假设 $a + b = c$ ,那么我们称 $a$ 是 $b$ 到 $c$ 的补数,反之也称 $b$ 是 $a$ 到 $c$ 的补数。
|
||||
这是因为原码和补码的相互转换实际上是计算“补数”的过程。我们先给出补数的定义:假设 $a + b = c$ ,那么我们称 $a$ 是 $b$ 到 $c$ 的补数,反之也称 $b$ 是 $a$ 到 $c$ 的补数。
|
||||
|
||||
给定一个 $n = 4$ 位长度的二进制数 $0010$ ,如果将这个数字看作原码(不考虑符号位),那么它的补码需通过“先取反后加 1”得到:
|
||||
|
||||
@ -63,4 +63,4 @@ $$
|
||||
|
||||
本质上看,“取反”操作实际上是求到 $1111$ 的补数(因为恒有 `原码 + 反码 = 1111`);而在反码基础上再加 1 得到的补码,就是到 $10000$ 的补数。
|
||||
|
||||
上述 $n = 4$ 为例,其可推广至任意位数的二进制数。
|
||||
上述以 $n = 4$ 为例,其可被推广至任意位数的二进制数。
|
||||
|
@ -122,17 +122,17 @@
|
||||
Queue<Integer> minHeap = new PriorityQueue<>();
|
||||
// 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可)
|
||||
Queue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
|
||||
|
||||
|
||||
/* 元素入堆 */
|
||||
maxHeap.offer(1);
|
||||
maxHeap.offer(3);
|
||||
maxHeap.offer(2);
|
||||
maxHeap.offer(5);
|
||||
maxHeap.offer(4);
|
||||
|
||||
|
||||
/* 获取堆顶元素 */
|
||||
int peek = maxHeap.peek(); // 5
|
||||
|
||||
|
||||
/* 堆顶元素出堆 */
|
||||
// 出堆元素会形成一个从大到小的序列
|
||||
peek = maxHeap.poll(); // 5
|
||||
@ -140,13 +140,13 @@
|
||||
peek = maxHeap.poll(); // 3
|
||||
peek = maxHeap.poll(); // 2
|
||||
peek = maxHeap.poll(); // 1
|
||||
|
||||
|
||||
/* 获取堆大小 */
|
||||
int size = maxHeap.size();
|
||||
|
||||
|
||||
/* 判断堆是否为空 */
|
||||
boolean isEmpty = maxHeap.isEmpty();
|
||||
|
||||
|
||||
/* 输入列表并建堆 */
|
||||
minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4));
|
||||
```
|
||||
@ -337,7 +337,7 @@
|
||||
max_heap.push(2);
|
||||
max_heap.push(5);
|
||||
max_heap.push(4);
|
||||
|
||||
|
||||
/* 获取堆顶元素 */
|
||||
let peek = max_heap.peek().unwrap(); // 5
|
||||
|
||||
@ -373,17 +373,17 @@
|
||||
var minHeap = PriorityQueue<Int>()
|
||||
// 初始化大顶堆(使用 lambda 表达式修改 Comparator 即可)
|
||||
val maxHeap = PriorityQueue { a: Int, b: Int -> b - a }
|
||||
|
||||
|
||||
/* 元素入堆 */
|
||||
maxHeap.offer(1)
|
||||
maxHeap.offer(3)
|
||||
maxHeap.offer(2)
|
||||
maxHeap.offer(5)
|
||||
maxHeap.offer(4)
|
||||
|
||||
|
||||
/* 获取堆顶元素 */
|
||||
var peek = maxHeap.peek() // 5
|
||||
|
||||
|
||||
/* 堆顶元素出堆 */
|
||||
// 出堆元素会形成一个从大到小的序列
|
||||
peek = maxHeap.poll() // 5
|
||||
@ -391,13 +391,13 @@
|
||||
peek = maxHeap.poll() // 3
|
||||
peek = maxHeap.poll() // 2
|
||||
peek = maxHeap.poll() // 1
|
||||
|
||||
|
||||
/* 获取堆大小 */
|
||||
val size = maxHeap.size
|
||||
|
||||
|
||||
/* 判断堆是否为空 */
|
||||
val isEmpty = maxHeap.isEmpty()
|
||||
|
||||
|
||||
/* 输入列表并建堆 */
|
||||
minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))
|
||||
```
|
||||
@ -405,7 +405,7 @@
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title="heap.rb"
|
||||
|
||||
# Ruby 未提供内置 Heap 类
|
||||
```
|
||||
|
||||
=== "Zig"
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
## 数据结构定义
|
||||
|
||||
<u>数据结构(data structure)</u>是计算机中组织和存储数据的方式,具有以下设计目标。
|
||||
<u>数据结构(data structure)</u>是组织和存储数据的方式,涵盖数据内容、数据之间关系和数据操作方法,它具有以下设计目标。
|
||||
|
||||
- 空间占用尽量少,以节省计算机内存。
|
||||
- 数据操作尽可能快速,涵盖数据访问、添加、删除、更新等。
|
||||
|
@ -322,6 +322,13 @@
|
||||
<br><sub>JS, TS</sub>
|
||||
</a>
|
||||
</div>
|
||||
<div class="profile-cell">
|
||||
<a href="https://github.com/khoaxuantu">
|
||||
<img class="profile-img" src="assets/avatar/avatar_khoaxuantu.jpg" alt="Reviewer: khoaxuantu" />
|
||||
<br><b>khoaxuantu</b>
|
||||
<br><sub>Ruby</sub>
|
||||
</a>
|
||||
</div>
|
||||
<div class="profile-cell">
|
||||
<a href="https://github.com/krahets">
|
||||
<img class="profile-img" src="assets/avatar/avatar_krahets.jpg" alt="Reviewer: krahets" />
|
||||
|
@ -74,7 +74,7 @@ On the other hand, linked lists are primarily necessary for binary trees and gra
|
||||
|
||||
**Q**: Does initializing a list `res = [0] * self.size()` result in each element of `res` referencing the same address?
|
||||
|
||||
No. However, this issue arises with two-dimensional arrays, for example, initializing a two-dimensional list `res = [[0] * self.size()]` would reference the same list `[0]` multiple times.
|
||||
No. However, this issue arises with two-dimensional arrays, for example, initializing a two-dimensional list `res = [[0]] * self.size()` would reference the same list `[0]` multiple times.
|
||||
|
||||
**Q**: In deleting a node, is it necessary to break the reference to its successor node?
|
||||
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
@ -2,19 +2,19 @@
|
||||
|
||||
<u>Backtracking algorithm</u> is a method to solve problems by exhaustive search, where the core idea is to start from an initial state and brute force all possible solutions, recording the correct ones until a solution is found or all possible choices are exhausted without finding a solution.
|
||||
|
||||
Backtracking typically employs "depth-first search" to traverse the solution space. In the "Binary Tree" chapter, we mentioned that preorder, inorder, and postorder traversals are all depth-first searches. Next, we use preorder traversal to construct a backtracking problem to gradually understand the workings of the backtracking algorithm.
|
||||
Backtracking typically employs "depth-first search" to traverse the solution space. In the "Binary Tree" chapter, we mentioned that pre-order, in-order, and post-order traversals are all depth-first searches. Next, we use pre-order traversal to construct a backtracking problem to gradually understand the workings of the backtracking algorithm.
|
||||
|
||||
!!! question "Example One"
|
||||
|
||||
Given a binary tree, search and record all nodes with a value of $7$, please return a list of nodes.
|
||||
|
||||
For this problem, we traverse this tree in preorder and check if the current node's value is $7$. If it is, we add the node's value to the result list `res`. The relevant process is shown in the figure below:
|
||||
For this problem, we traverse this tree in pre-order and check if the current node's value is $7$. If it is, we add the node's value to the result list `res`. The relevant process is shown in the figure below:
|
||||
|
||||
```src
|
||||
[file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
## Trying and retreating
|
||||
|
||||
@ -425,7 +425,7 @@ As per the requirements, after finding a node with a value of $7$, the search sh
|
||||
|
||||

|
||||
|
||||
Compared to the implementation based on preorder traversal, the code implementation based on the backtracking algorithm framework seems verbose, but it has better universality. In fact, **many backtracking problems can be solved within this framework**. We just need to define `state` and `choices` according to the specific problem and implement the methods in the framework.
|
||||
Compared to the implementation based on pre-order traversal, the code implementation based on the backtracking algorithm framework seems verbose, but it has better universality. In fact, **many backtracking problems can be solved within this framework**. We just need to define `state` and `choices` according to the specific problem and implement the methods in the framework.
|
||||
|
||||
## Common terminology
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
@ -2,7 +2,7 @@
|
||||
|
||||
!!! question
|
||||
|
||||
Given the preorder traversal `preorder` and inorder traversal `inorder` of a binary tree, construct the binary tree and return the root node of the binary tree. Assume that there are no duplicate values in the nodes of the binary tree (as shown in the figure below).
|
||||
Given the pre-order traversal `preorder` and in-order traversal `inorder` of a binary tree, construct the binary tree and return the root node of the binary tree. Assume that there are no duplicate values in the nodes of the binary tree (as shown in the figure below).
|
||||
|
||||

|
||||
|
||||
@ -11,25 +11,25 @@
|
||||
The original problem of constructing a binary tree from `preorder` and `inorder` is a typical divide and conquer problem.
|
||||
|
||||
- **The problem can be decomposed**: From the perspective of divide and conquer, we can divide the original problem into two subproblems: building the left subtree and building the right subtree, plus one operation: initializing the root node. For each subtree (subproblem), we can still use the above division method, dividing it into smaller subtrees (subproblems), until the smallest subproblem (empty subtree) is reached.
|
||||
- **The subproblems are independent**: The left and right subtrees are independent of each other, with no overlap. When building the left subtree, we only need to focus on the parts of the inorder and preorder traversals that correspond to the left subtree. The same applies to the right subtree.
|
||||
- **The subproblems are independent**: The left and right subtrees are independent of each other, with no overlap. When building the left subtree, we only need to focus on the parts of the in-order and pre-order traversals that correspond to the left subtree. The same applies to the right subtree.
|
||||
- **Solutions to subproblems can be combined**: Once the solutions for the left and right subtrees (solutions to subproblems) are obtained, we can link them to the root node to obtain the solution to the original problem.
|
||||
|
||||
### How to divide the subtrees
|
||||
|
||||
Based on the above analysis, this problem can be solved using divide and conquer, **but how do we use the preorder traversal `preorder` and inorder traversal `inorder` to divide the left and right subtrees?**
|
||||
Based on the above analysis, this problem can be solved using divide and conquer, **but how do we use the pre-order traversal `preorder` and in-order traversal `inorder` to divide the left and right subtrees?**
|
||||
|
||||
By definition, `preorder` and `inorder` can be divided into three parts.
|
||||
|
||||
- Preorder traversal: `[ Root | Left Subtree | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 3 | 9 | 2 1 7 ]`.
|
||||
- Inorder traversal: `[ Left Subtree | Root | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 9 | 3 | 1 2 7 ]`.
|
||||
- Pre-order traversal: `[ Root | Left Subtree | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 3 | 9 | 2 1 7 ]`.
|
||||
- In-order traversal: `[ Left Subtree | Root | Right Subtree ]`, for example, the tree in the figure corresponds to `[ 9 | 3 | 1 2 7 ]`.
|
||||
|
||||
Using the data in the figure above, we can obtain the division results as shown in the figure below.
|
||||
|
||||
1. The first element 3 in the preorder traversal is the value of the root node.
|
||||
1. The first element 3 in the pre-order traversal is the value of the root node.
|
||||
2. Find the index of the root node 3 in `inorder`, and use this index to divide `inorder` into `[ 9 | 3 | 1 2 7 ]`.
|
||||
3. Based on the division results of `inorder`, it is easy to determine the number of nodes in the left and right subtrees as 1 and 3, respectively, thus dividing `preorder` into `[ 3 | 9 | 2 1 7 ]`.
|
||||
|
||||

|
||||

|
||||
|
||||
### Describing subtree intervals based on variables
|
||||
|
||||
@ -41,7 +41,7 @@ Based on the above division method, **we have now obtained the index intervals o
|
||||
|
||||
As shown in the table below, the above variables can represent the index of the root node in `preorder` as well as the index intervals of the subtrees in `inorder`.
|
||||
|
||||
<p align="center"> Table <id> Indexes of the root node and subtrees in preorder and inorder traversals </p>
|
||||
<p align="center"> Table <id> Indexes of the root node and subtrees in pre-order and in-order traversals </p>
|
||||
|
||||
| | Root node index in `preorder` | Subtree index interval in `inorder` |
|
||||
| ------------- | ----------------------------- | ----------------------------------- |
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
@ -7,5 +7,5 @@
|
||||
- Divide and conquer can solve many algorithm problems and is widely used in data structure and algorithm design, where its presence is ubiquitous.
|
||||
- Compared to brute force search, adaptive search is more efficient. Search algorithms with a time complexity of $O(\log n)$ are usually based on the divide and conquer strategy.
|
||||
- Binary search is another typical application of the divide and conquer strategy, which does not include the step of merging the solutions of subproblems. We can implement binary search through recursive divide and conquer.
|
||||
- In the problem of constructing binary trees, building the tree (original problem) can be divided into building the left and right subtree (subproblems), which can be achieved by partitioning the index intervals of the preorder and inorder traversals.
|
||||
- In the problem of constructing binary trees, building the tree (original problem) can be divided into building the left and right subtree (subproblems), which can be achieved by partitioning the index intervals of the pre-order and in-order traversals.
|
||||
- In the Tower of Hanoi problem, a problem of size $n$ can be divided into two subproblems of size $n-1$ and one subproblem of size $1$. By solving these three subproblems in sequence, the original problem is consequently resolved.
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@ -127,7 +127,7 @@ To deepen the understanding, it is suggested to combine the figure below with th
|
||||
|
||||
Similar to breadth-first traversal, the order of the depth-first traversal sequence is also not unique. Given a certain vertex, exploring in any direction first is possible, that is, the order of adjacent vertices can be arbitrarily shuffled, all being part of depth-first traversal.
|
||||
|
||||
Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", "left $\rightarrow$ right $\rightarrow$ root" correspond to preorder, inorder, and postorder traversals, respectively. They showcase three types of traversal priorities, yet all three are considered depth-first traversal.
|
||||
Taking tree traversal as an example, "root $\rightarrow$ left $\rightarrow$ right", "left $\rightarrow$ root $\rightarrow$ right", "left $\rightarrow$ right $\rightarrow$ root" correspond to pre-order, in-order, and post-order traversals, respectively. They showcase three types of traversal priorities, yet all three are considered depth-first traversal.
|
||||
|
||||
### Complexity analysis
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |