diff --git a/avl/avl.go b/avl/avl.go new file mode 100644 index 0000000..427d5e7 --- /dev/null +++ b/avl/avl.go @@ -0,0 +1,243 @@ +package avl + +import "cmp" + +// avl.go +// 泛型 AVL 自平衡二叉搜索树。 +// +// 约定:空子树高度为 0,叶子高度为 1;不插入重复值。 +// 每次插入或删除后,都要更新 Height,再按 LL/LR/RR/RL 四种情况重平衡。 + +// AVLNode AVL 树节点。Height 是以该节点为根的子树高度。 +type AVLNode[T cmp.Ordered] struct { + Val T + Left *AVLNode[T] + Right *AVLNode[T] + Height int +} + +// AVLTree AVL 自平衡二叉搜索树。 +type AVLTree[T cmp.Ordered] struct { + Root *AVLNode[T] +} + +// NewAVLTree 创建一棵空 AVL 树。 +func NewAVLTree[T cmp.Ordered]() *AVLTree[T] { + return &AVLTree[T]{} +} + +// Insert 插入 v;重复值不插入。 +func (t *AVLTree[T]) Insert(v T) { + var insert func(node *AVLNode[T]) *AVLNode[T] + insert = func(node *AVLNode[T]) *AVLNode[T] { + if node == nil { + return &AVLNode[T]{Val: v, Height: 1} + } + if v == node.Val { + return node + } else if v < node.Val { + node.Left = insert(node.Left) + } else { + node.Right = insert(node.Right) + } + node.Height = t.calculateHeight(node) + return t.balancing(node) + } + t.Root = insert(t.Root) +} + +func (t *AVLTree[T]) balancing(node *AVLNode[T]) *AVLNode[T] { + bf := t.calculateBF(node) + if bf < -1 { + // 右重 R + _bf := t.calculateBF(node.Right) + if _bf <= 0 { + // RR:左旋自身 + return t.rotateLeft(node) + } + if _bf > 0 { + // RL: 右旋右孩子再左旋自身 + node.Right = t.rotateRight(node.Right) + return t.rotateLeft(node) + } + } + if bf > 1 { + // 左重 L + _bf := t.calculateBF(node.Left) + if _bf >= 0 { + // LL: 右旋自身 + return t.rotateRight(node) + } + if _bf < 0 { + // LR: 左旋左孩子再右旋自身 + node.Left = t.rotateLeft(node.Left) + return t.rotateRight(node) + } + } + return node +} + +func (t *AVLTree[T]) calculateHeight(node *AVLNode[T]) int { + if node == nil { + return 0 + } + l, r := 0, 0 + if node.Left != nil { + l = node.Left.Height + } + if node.Right != nil { + r = node.Right.Height + } + return max(l, r) + 1 +} + +func (t *AVLTree[T]) calculateBF(node *AVLNode[T]) int { + if node == nil { + return 0 + } + leftHeight, rightHeight := 0, 0 + if node.Left != nil { + leftHeight = node.Left.Height + } + if node.Right != nil { + rightHeight = node.Right.Height + } + bf := leftHeight - rightHeight + return bf +} + +// rotateLeft 对 node 左旋,返回旋转后的新子树根。 +// +// node right +// / \ / \ +// A right → node C +// / \ / \ +// B C A B +// +// B 的值介于 node 和 right 之间,因此旋转后要接到 node.Right。 +func (t *AVLTree[T]) rotateLeft(node *AVLNode[T]) *AVLNode[T] { + rNode := node.Right + node.Right = rNode.Left + rNode.Left = node + node.Height = t.calculateHeight(node) + rNode.Height = t.calculateHeight(rNode) + return rNode +} + +// rotateRight 对 node 右旋,返回旋转后的新子树根。 +// +// node left +// / \ / \ +// left C → A node +// / \ / \ +// A B B C +// +// B 的值介于 left 和 node 之间,因此旋转后要接到 node.Left。 +func (t *AVLTree[T]) rotateRight(node *AVLNode[T]) *AVLNode[T] { + lNode := node.Left + node.Left = lNode.Right + lNode.Right = node + node.Height = t.calculateHeight(node) + lNode.Height = t.calculateHeight(lNode) + return lNode +} + +func (t *AVLTree[T]) Inorder() []T { + res := []T{} + var helper func(node *AVLNode[T]) + helper = func(node *AVLNode[T]) { + if node == nil { + return + } + helper(node.Left) + res = append(res, node.Val) + helper(node.Right) + } + helper(t.Root) + return res +} + +// Search 查找 v 是否存在。只读操作,不需要更新高度或旋转。 +func (t *AVLTree[T]) Search(v T) bool { + node := t.Root + for node != nil { + if node.Val == v { + return true + } + if v < node.Val { + node = node.Left + } else { + node = node.Right + } + } + return false +} + +// Height 返回整棵树的高度;空树为 0。 +func (t *AVLTree[T]) Height() int { + if t.Root == nil { + return 0 + } + return t.Root.Height +} + +// Min 返回最小值及其是否存在。 +func (t *AVLTree[T]) Min() (T, bool) { + node := t.Root + if node == nil { + var zero T + return zero, false + } + for node.Left != nil { + node = node.Left + } + return node.Val, true +} + +// Max 返回最大值及其是否存在。 +func (t *AVLTree[T]) Max() (T, bool) { + node := t.Root + if node == nil { + var zero T + return zero, false + } + for node.Right != nil { + node = node.Right + } + return node.Val, true +} + +// Delete 删除 v;若 v 不存在,树保持不变。 +func (t *AVLTree[T]) Delete(v T) { + var deleteNode func(node *AVLNode[T], target T) *AVLNode[T] + deleteNode = func(node *AVLNode[T], target T) *AVLNode[T] { + if node == nil { + return nil + } + if target < node.Val { + node.Left = deleteNode(node.Left, target) + } else if target > node.Val { + node.Right = deleteNode(node.Right, target) + } else { + if node.Left == nil && node.Right != nil { + return node.Right + } + if node.Left != nil && node.Right == nil { + return node.Left + } + if node.Left != nil && node.Right != nil { + n := node.Right + for n.Left != nil { + n = n.Left + } + node.Val = n.Val + node.Right = deleteNode(node.Right, n.Val) + } else { + return nil + } + } + node.Height = t.calculateHeight(node) + return t.balancing(node) + } + t.Root = deleteNode(t.Root, v) +} diff --git a/avl/avl_test.go b/avl/avl_test.go new file mode 100644 index 0000000..5362f43 --- /dev/null +++ b/avl/avl_test.go @@ -0,0 +1,189 @@ +package avl + +import ( + "reflect" + "testing" +) + +func TestAVLBasicOperations(t *testing.T) { + tree := NewAVLTree[int]() + + if tree.Height() != 0 { + t.Fatalf("空树 Height() = %d, want 0", tree.Height()) + } + if _, ok := tree.Min(); ok { + t.Fatal("空树 Min() 的 ok = true, want false") + } + if _, ok := tree.Max(); ok { + t.Fatal("空树 Max() 的 ok = true, want false") + } + if tree.Search(1) { + t.Fatal("空树 Search(1) = true, want false") + } + + for _, v := range []int{6, 3, 2, 1, 4, 5} { + tree.Insert(v) + } + + if got, want := tree.Inorder(), []int{1, 2, 3, 4, 5, 6}; !reflect.DeepEqual(got, want) { + t.Fatalf("Inorder() = %v, want %v", got, want) + } + if !tree.Search(1) || !tree.Search(6) || tree.Search(7) { + t.Fatalf("Search 结果不正确:Search(1)=%v, Search(6)=%v, Search(7)=%v", tree.Search(1), tree.Search(6), tree.Search(7)) + } + if got, ok := tree.Min(); !ok || got != 1 { + t.Fatalf("Min() = (%d, %v), want (1, true)", got, ok) + } + if got, ok := tree.Max(); !ok || got != 6 { + t.Fatalf("Max() = (%d, %v), want (6, true)", got, ok) + } + assertAVL(t, tree.Root, nil, nil) +} + +func TestAVLDelete(t *testing.T) { + cases := []struct { + name string + values []int + delete int + want []int + wantMin *int + wantMax *int + }{ + { + name: "delete only root leaf", + values: []int{10}, + delete: 10, + want: []int{}, + }, + { + name: "delete root with one child", + values: []int{10, 5}, + delete: 10, + want: []int{5}, + wantMin: intPtr(5), + wantMax: intPtr(5), + }, + { + name: "delete non-root leaf and update parent height", + values: []int{10, 5, 15}, + delete: 5, + want: []int{10, 15}, + wantMin: intPtr(10), + wantMax: intPtr(15), + }, + { + name: "delete non-root with one child", + values: []int{10, 5, 15, 12}, + delete: 15, + want: []int{5, 10, 12}, + wantMin: intPtr(5), + wantMax: intPtr(12), + }, + { + name: "delete root with two children successor is leaf", + values: []int{10, 5, 15, 12, 18}, + delete: 10, + want: []int{5, 12, 15, 18}, + wantMin: intPtr(5), + wantMax: intPtr(18), + }, + { + name: "delete root with two children successor has right child", + values: []int{10, 5, 20, 15, 25, 17}, + delete: 10, + want: []int{5, 15, 17, 20, 25}, + wantMin: intPtr(5), + wantMax: intPtr(25), + }, + { + name: "delete missing value leaves tree unchanged", + values: []int{10, 5, 20, 15, 25}, + delete: 99, + want: []int{5, 10, 15, 20, 25}, + wantMin: intPtr(5), + wantMax: intPtr(25), + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + tree := NewAVLTree[int]() + for _, v := range tc.values { + tree.Insert(v) + } + + tree.Delete(tc.delete) + + if got := tree.Inorder(); !reflect.DeepEqual(got, tc.want) { + t.Fatalf("Delete(%d) 后 Inorder() = %v, want %v", tc.delete, got, tc.want) + } + if tree.Search(tc.delete) && tc.delete != 99 { + t.Fatalf("Delete(%d) 后 Search(%d) = true, want false", tc.delete, tc.delete) + } + if tc.wantMin == nil { + if tree.Height() != 0 { + t.Fatalf("删空树后 Height() = %d, want 0", tree.Height()) + } + if _, ok := tree.Min(); ok { + t.Fatal("删空树后 Min() 的 ok = true, want false") + } + if _, ok := tree.Max(); ok { + t.Fatal("删空树后 Max() 的 ok = true, want false") + } + } else { + if got, ok := tree.Min(); !ok || got != *tc.wantMin { + t.Fatalf("Min() = (%d, %v), want (%d, true)", got, ok, *tc.wantMin) + } + if got, ok := tree.Max(); !ok || got != *tc.wantMax { + t.Fatalf("Max() = (%d, %v), want (%d, true)", got, ok, *tc.wantMax) + } + } + assertAVL(t, tree.Root, nil, nil) + }) + } +} + +func TestAVLDeleteRebalancesOnPathToRoot(t *testing.T) { + tree := NewAVLTree[int]() + for _, v := range []int{4, 2, 6, 1, 3, 5, 7} { + tree.Insert(v) + } + + for _, v := range []int{7, 6, 5} { + tree.Delete(v) + assertAVL(t, tree.Root, nil, nil) + } + + if got, want := tree.Inorder(), []int{1, 2, 3, 4}; !reflect.DeepEqual(got, want) { + t.Fatalf("连续删除后 Inorder() = %v, want %v", got, want) + } +} + +func assertAVL(t *testing.T, node *AVLNode[int], lower, upper *int) int { + t.Helper() + if node == nil { + return 0 + } + if lower != nil && node.Val <= *lower { + t.Fatalf("BST 性质被破坏:节点 %d <= 下界 %d", node.Val, *lower) + } + if upper != nil && node.Val >= *upper { + t.Fatalf("BST 性质被破坏:节点 %d >= 上界 %d", node.Val, *upper) + } + + leftHeight := assertAVL(t, node.Left, lower, &node.Val) + rightHeight := assertAVL(t, node.Right, &node.Val, upper) + if balanceFactor := leftHeight - rightHeight; balanceFactor < -1 || balanceFactor > 1 { + t.Fatalf("节点 %d 的 BF = %d, want [-1, 1]", node.Val, balanceFactor) + } + + wantHeight := max(leftHeight, rightHeight) + 1 + if node.Height != wantHeight { + t.Fatalf("节点 %d 的 Height = %d, want %d", node.Val, node.Height, wantHeight) + } + return wantHeight +} + +func intPtr(v int) *int { + return &v +} diff --git a/avl/avlref.go b/avl/avlref.go new file mode 100644 index 0000000..0909cc2 --- /dev/null +++ b/avl/avlref.go @@ -0,0 +1,240 @@ +package avl + +import "fmt" + +// AVL 自平衡二叉搜索树 —— 正确实现参考 +// +// 核心设计点(对比你原文件里的 4 个 bug): +// 1. 节点维护 Height,平衡因子 BF = height(Left) - height(Right);合法范围 {-1,0,1} +// 2. 旋转函数【返回新子树根】,调用方用返回值接回父节点的 Left/Right +// —— 这解决了"旋转接不回去"的根本问题 +// 3. 失衡判定看【子节点的 BF】,区分 LL/RR/LR/RL 四种 case,不是只看自己 BF +// 4. 不用全局 depthIncre flag,递归返回新根,height 直接从子节点算 + +type Node struct { + Val int + Left *Node + Right *Node + Height int // 以该节点为根的子树高度;空节点=0,叶子=1 +} + +func height(n *Node) int { + if n == nil { + return 0 + } + return n.Height +} + +func balanceFactor(n *Node) int { + if n == nil { + return 0 + } + return height(n.Left) - height(n.Right) +} + +func updateHeight(n *Node) { + l, r := height(n.Left), height(n.Right) + if l > r { + n.Height = l + 1 + } else { + n.Height = r + 1 + } +} + +// 左旋:右子太重时,把右子 y 提上来,x 沉为 y 的左子 +// +// x y +// / \ / \ +// A y → x C +// / \ / \ +// B C A B +// +// B 的值在 x、y 之间,必须留在中间 → 挂到 x 的右侧 +func rotateLeft(x *Node) *Node { + y := x.Right + x.Right = y.Left + y.Left = x + updateHeight(x) // 先更新成为孩子的 x + updateHeight(y) // 再更新成为根的 y + return y +} + +// 右旋:左子太重时,把左子 x 提上来,y 沉为 x 的右子 +// +// y x +// / \ / \ +// x C → A y +// / \ / \ +// A B B C +// +// B 的值在 x、y 之间 → 挂到 y 的左侧 +func rotateRight(y *Node) *Node { + x := y.Left + y.Left = x.Right + x.Right = y + updateHeight(y) // 先更新成为孩子的 y + updateHeight(x) // 再更新成为根的 x + return x +} + +// 插入:递归返回新子树根 +func Insert(root *Node, val int) *Node { + if root == nil { + return &Node{Val: val, Height: 1} + } + if val < root.Val { + root.Left = Insert(root.Left, val) + } else if val > root.Val { + root.Right = Insert(root.Right, val) + } else { + return root // 不允许重复值 + } + updateHeight(root) + bf := balanceFactor(root) + + // LL: 左子左重(左子 BF >= 0)→ 右旋一次 + if bf > 1 && balanceFactor(root.Left) >= 0 { + return rotateRight(root) + } + // LR: 左子右重(左子 BF < 0)→ 先左旋左子,再右旋自己 + if bf > 1 && balanceFactor(root.Left) < 0 { + root.Left = rotateLeft(root.Left) + return rotateRight(root) + } + // RR: 右子右重(右子 BF <= 0)→ 左旋一次 + if bf < -1 && balanceFactor(root.Right) <= 0 { + return rotateLeft(root) + } + // RL: 右子左重(右子 BF > 0)→ 先右旋右子,再左旋自己 + if bf < -1 && balanceFactor(root.Right) > 0 { + root.Right = rotateRight(root.Right) + return rotateLeft(root) + } + return root +} + +// 删除:递归返回新子树根 +func Delete(root *Node, val int) *Node { + if root == nil { + return nil + } + if val < root.Val { + root.Left = Delete(root.Left, val) + } else if val > root.Val { + root.Right = Delete(root.Right, val) + } else { + // 命中要删的节点 + if root.Left == nil { + return root.Right + } + if root.Right == nil { + return root.Left + } + // 两子都在:用右子树最小值替换当前值,再删右子树里的那个最小值 + minNode := root.Right + for minNode.Left != nil { + minNode = minNode.Left + } + root.Val = minNode.Val + root.Right = Delete(root.Right, minNode.Val) + } + updateHeight(root) + bf := balanceFactor(root) + // 重平衡逻辑与 Insert 完全一致(看子节点 BF 判 case) + if bf > 1 && balanceFactor(root.Left) >= 0 { + return rotateRight(root) + } + if bf > 1 && balanceFactor(root.Left) < 0 { + root.Left = rotateLeft(root.Left) + return rotateRight(root) + } + if bf < -1 && balanceFactor(root.Right) <= 0 { + return rotateLeft(root) + } + if bf < -1 && balanceFactor(root.Right) > 0 { + root.Right = rotateRight(root.Right) + return rotateLeft(root) + } + return root +} + +func Search(root *Node, val int) bool { + for root != nil { + if val < root.Val { + root = root.Left + } else if val > root.Val { + root = root.Right + } else { + return true + } + } + return false +} + +// 中序遍历 → 应得到升序序列(验证 BST 性质没被破坏) +func Inorder(root *Node, out *[]int) { + if root == nil { + return + } + Inorder(root.Left, out) + *out = append(*out, root.Val) + Inorder(root.Right, out) +} + +func runAVLDemo() { + var root *Node + + // 测试 1: 顺序插入 1..7(普通 BST 会退化成高度=7 的链表) + for _, v := range []int{1, 2, 3, 4, 5, 6, 7} { + root = Insert(root, v) + } + var arr []int + Inorder(root, &arr) + fmt.Println("顺序插入 1..7 中序遍历:", arr) + fmt.Printf("树高 = %d (普通BST会=7, AVL 应 ≤ 4)\n", height(root)) + fmt.Println("Search 5:", Search(root, 5), "Search 99:", Search(root, 99)) + + // 测试 2: 乱序插入 + root = nil + for _, v := range []int{5, 2, 8, 1, 9, 3, 7, 6, 4} { + root = Insert(root, v) + } + arr = nil + Inorder(root, &arr) + fmt.Println("\n乱序插入中序遍历:", arr) + fmt.Printf("树高 = %d\n", height(root)) + + // 测试 3: 删除 + root = Delete(root, 5) + arr = nil + Inorder(root, &arr) + fmt.Println("删 5 后中序遍历:", arr) + fmt.Println("Search 5:", Search(root, 5)) + fmt.Printf("树高 = %d\n", height(root)) + + // 测试 4: 验证所有 BF ∈ {-1,0,1} + if checkAllBF(root) { + fmt.Println("\n所有节点 |BF| ≤ 1 ✓") + } +} + +// 验证整棵树每个节点的 |BF| ≤ 1 +func checkAllBF(root *Node) bool { + var walk func(*Node) bool + walk = func(n *Node) bool { + if n == nil { + return true + } + bf := balanceFactor(n) + if bf < -1 || bf > 1 { + fmt.Printf("违反! 节点 %d 的 BF = %d\n", n.Val, bf) + return false + } + return walk(n.Left) && walk(n.Right) + } + return walk(root) +} + +func main() { + runAVLDemo() +} diff --git a/data-structure/ds-btree.go b/data-structure/ds-btree.go new file mode 100644 index 0000000..b59a618 --- /dev/null +++ b/data-structure/ds-btree.go @@ -0,0 +1,151 @@ +package ds + +// ds-btree.go +// 普通二叉树(不强制 BST 性质)+ 层序构造。 +// 贴合 leetcode 题目里 [1,2,3,null,null,4,5] 这种层序数组表示。 +// 复用 ds-tree.go 里定义的 TreeNode[T] 和 Ordered 约束,避免重复定义。 + +// BuildTree 从 leetcode 风格的层序数组构造二叉树。 +// vals 中 *T 为 nil 的位置表示空节点;数组末尾的 nil 可省略。 +// 示例:[1,2,3,nil,nil,4,5] → +// +// 1 +// / \ +// 2 3 +// / \ +// 4 5 +func BuildTree[T Ordered](vals []*T) *TreeNode[T] { + if len(vals) == 0 || vals[0] == nil { + return nil + } + root := &TreeNode[T]{Val: *vals[0]} + q := &Queue[*TreeNode[T]]{} + q.Enqueue(root) + i := 1 + for !q.Empty() && i < len(vals) { + node, _ := q.Dequeue() + // 左孩子 + if i < len(vals) && vals[i] != nil { + node.Left = &TreeNode[T]{Val: *vals[i]} + q.Enqueue(node.Left) + } + i++ + // 右孩子 + if i < len(vals) && vals[i] != nil { + node.Right = &TreeNode[T]{Val: *vals[i]} + q.Enqueue(node.Right) + } + i++ + } + return root +} + +// TreeToLevelOrder 把树序列化回层序数组(与 BuildTree 互逆),nil 占位。 +// 末尾连续的 nil 会被裁掉,与 leetcode 表示一致。 +func TreeToLevelOrder[T any](root *TreeNode[T]) []*T { + if root == nil { + return nil + } + var out []*T + q := &Queue[*TreeNode[T]]{} + q.Enqueue(root) + for !q.Empty() { + node, _ := q.Dequeue() + if node == nil { + out = append(out, nil) + continue + } + v := node.Val + out = append(out, &v) + q.Enqueue(node.Left) + q.Enqueue(node.Right) + } + // 裁掉末尾 nil + for len(out) > 0 && out[len(out)-1] == nil { + out = out[:len(out)-1] + } + return out +} + +// MaxDepth 求二叉树最大深度(空树 0,单节点 1)。对任意二叉树都适用,不限于 BST。 +func MaxDepth[T any](root *TreeNode[T]) int { + if root == nil { + return 0 + } + l := MaxDepth(root.Left) + r := MaxDepth(root.Right) + if l > r { + return l + 1 + } + return r + 1 +} + +// InOrderAny 对任意二叉树做中序遍历(不限于 BST,结果未必有序)。 +// 复用 TreeNode[T],不依赖 BST 类型,方便裸树题目直接调用。 +func InOrderAny[T any](root *TreeNode[T]) []T { + var out []T + var walk func(*TreeNode[T]) + walk = func(n *TreeNode[T]) { + if n == nil { + return + } + walk(n.Left) + out = append(out, n.Val) + walk(n.Right) + } + walk(root) + return out +} + +// PreOrderAny 对任意二叉树做前序遍历。 +func PreOrderAny[T any](root *TreeNode[T]) []T { + var out []T + var walk func(*TreeNode[T]) + walk = func(n *TreeNode[T]) { + if n == nil { + return + } + out = append(out, n.Val) + walk(n.Left) + walk(n.Right) + } + walk(root) + return out +} + +// PostOrderAny 对任意二叉树做后序遍历。 +func PostOrderAny[T any](root *TreeNode[T]) []T { + var out []T + var walk func(*TreeNode[T]) + walk = func(n *TreeNode[T]) { + if n == nil { + return + } + walk(n.Left) + walk(n.Right) + out = append(out, n.Val) + } + walk(root) + return out +} + +// LevelOrderAny 对任意二叉树做层序遍历。裸树版,不依赖 BST。 +func LevelOrderAny[T any](root *TreeNode[T]) []T { + if root == nil { + return nil + } + var out []T + q := &Queue[*TreeNode[T]]{} + q.Enqueue(root) + for !q.Empty() { + node, _ := q.Dequeue() + out = append(out, node.Val) + if node.Left != nil { + q.Enqueue(node.Left) + } + if node.Right != nil { + q.Enqueue(node.Right) + } + } + return out +} diff --git a/data-structure/ds-btree_test.go b/data-structure/ds-btree_test.go new file mode 100644 index 0000000..64eebf6 --- /dev/null +++ b/data-structure/ds-btree_test.go @@ -0,0 +1,107 @@ +package ds + +import "testing" + +// 辅助:int 指针构造器,简化测试用例。 +func iptr(i int) *int { return &i } + +func TestBuildTreeRoundTrip(t *testing.T) { + // 1 + // / \ + // 2 3 + // / \ + // 4 5 + vals := []*int{iptr(1), iptr(2), iptr(3), nil, nil, iptr(4), iptr(5)} + root := BuildTree[int](vals) + if root == nil { + t.Fatal("BuildTree 不应返回 nil") + } + // 层序应为 [1,2,3,nil,nil,4,5] + out := TreeToLevelOrder[int](root) + want := []*int{iptr(1), iptr(2), iptr(3), nil, nil, iptr(4), iptr(5)} + if len(out) != len(want) { + t.Fatalf("TreeToLevelOrder 长度 = %d, want %d", len(out), len(want)) + } + for i := range want { + if (out[i] == nil) != (want[i] == nil) { + t.Fatalf("位置 %d: out nil=%v, want nil=%v", i, out[i] == nil, want[i] == nil) + } + if out[i] != nil && *out[i] != *want[i] { + t.Fatalf("位置 %d: out = %d, want %d", i, *out[i], *want[i]) + } + } +} + +func TestBuildTreeTraversals(t *testing.T) { + // 1 + // / \ + // 2 3 + // / \ + // 4 5 + vals := []*int{iptr(1), iptr(2), iptr(3), iptr(4), iptr(5)} + root := BuildTree[int](vals) + + pre := PreOrderAny[int](root) + wantPre := []int{1, 2, 4, 5, 3} + for i := range wantPre { + if pre[i] != wantPre[i] { + t.Fatalf("PreOrderAny[%d] = %d, want %d", i, pre[i], wantPre[i]) + } + } + + in := InOrderAny[int](root) + wantIn := []int{4, 2, 5, 1, 3} + for i := range wantIn { + if in[i] != wantIn[i] { + t.Fatalf("InOrderAny[%d] = %d, want %d", i, in[i], wantIn[i]) + } + } + + post := PostOrderAny[int](root) + wantPost := []int{4, 5, 2, 3, 1} + for i := range wantPost { + if post[i] != wantPost[i] { + t.Fatalf("PostOrderAny[%d] = %d, want %d", i, post[i], wantPost[i]) + } + } + + lvl := LevelOrderAny[int](root) + wantLvl := []int{1, 2, 3, 4, 5} + for i := range wantLvl { + if lvl[i] != wantLvl[i] { + t.Fatalf("LevelOrderAny[%d] = %d, want %d", i, lvl[i], wantLvl[i]) + } + } +} + +func TestBuildTreeEdgeCases(t *testing.T) { + // 空数组 + if r := BuildTree[int](nil); r != nil { + t.Fatal("空数组应返回 nil") + } + // 数组首元素为 nil + if r := BuildTree[int]([]*int{nil, iptr(1)}); r != nil { + t.Fatal("首元素 nil 应返回 nil") + } + // 单节点 + root := BuildTree[int]([]*int{iptr(42)}) + if root.Val != 42 || root.Left != nil || root.Right != nil { + t.Fatalf("单节点树结构错误: %+v", root) + } +} + +func TestMaxDepth(t *testing.T) { + // 1 + // / \ + // 2 3 + // / + // 4 + vals := []*int{iptr(1), iptr(2), iptr(3), iptr(4)} + root := BuildTree[int](vals) + if d := MaxDepth[int](root); d != 3 { + t.Fatalf("MaxDepth = %d, want 3", d) + } + if d := MaxDepth[int](nil); d != 0 { + t.Fatalf("空树 MaxDepth = %d, want 0", d) + } +} diff --git a/data-structure/ds-deque.go b/data-structure/ds-deque.go new file mode 100644 index 0000000..85e97fa --- /dev/null +++ b/data-structure/ds-deque.go @@ -0,0 +1,100 @@ +package ds + +// ds-deque.go +// 泛型双端队列,基于环形缓冲。PushFront/PushBack/PopFront/PopBack 全 O(1) 均摊。 +// 当容量不足时扩容为 2 倍,自动重新对齐。 + +// Deque 双端队列,零值可用。 +type Deque[T any] struct { + buf []T + head int // 队首索引,逆时针(向后)增长 + tail int // 队尾索引 +1 + n int // 元素个数 +} + +// Len 元素个数。 +func (d *Deque[T]) Len() int { return d.n } + +// Empty 是否为空。 +func (d *Deque[T]) Empty() bool { return d.n == 0 } + +// PushBack 尾部插入。 +func (d *Deque[T]) PushBack(v T) { + d.grow() + d.buf[d.tail] = v + d.tail = (d.tail + 1) % len(d.buf) + d.n++ +} + +// PushFront 头部插入。 +func (d *Deque[T]) PushFront(v T) { + d.grow() + d.head = (d.head - 1 + len(d.buf)) % len(d.buf) + d.buf[d.head] = v + d.n++ +} + +// PopBack 尾部弹出;空返回零值和 false。 +func (d *Deque[T]) PopBack() (T, bool) { + var zero T + if d.n == 0 { + return zero, false + } + d.tail = (d.tail - 1 + len(d.buf)) % len(d.buf) + v := d.buf[d.tail] + d.buf[d.tail] = zero + d.n-- + return v, true +} + +// PopFront 头部弹出;空返回零值和 false。 +func (d *Deque[T]) PopFront() (T, bool) { + var zero T + if d.n == 0 { + return zero, false + } + v := d.buf[d.head] + d.buf[d.head] = zero + d.head = (d.head + 1) % len(d.buf) + d.n-- + return v, true +} + +// Front 查看头部,不移除。 +func (d *Deque[T]) Front() (T, bool) { + var zero T + if d.n == 0 { + return zero, false + } + return d.buf[d.head], true +} + +// Back 查看尾部,不移除。 +func (d *Deque[T]) Back() (T, bool) { + var zero T + if d.n == 0 { + return zero, false + } + return d.buf[(d.tail-1+len(d.buf))%len(d.buf)], true +} + +// grow 容量不足时扩容并重新对齐。 +func (d *Deque[T]) grow() { + if d.buf == nil { + d.buf = make([]T, 8) + return + } + if d.n < len(d.buf) { + return + } + oldCap := len(d.buf) + newCap := oldCap * 2 + nb := make([]T, newCap) + // 把 head..head+n 的环形数据复制到新缓冲区前端。 + for i := 0; i < d.n; i++ { + nb[i] = d.buf[(d.head+i)%oldCap] + } + d.buf = nb + d.head = 0 + d.tail = d.n +} diff --git a/data-structure/ds-deque_test.go b/data-structure/ds-deque_test.go new file mode 100644 index 0000000..b79d348 --- /dev/null +++ b/data-structure/ds-deque_test.go @@ -0,0 +1,78 @@ +package ds + +import "testing" + +func TestDequeBothEnds(t *testing.T) { + var d Deque[int] + d.PushBack(2) + d.PushBack(3) + d.PushFront(1) + d.PushFront(0) + // 队列内容应为 [0,1,2,3] + if got, _ := d.Front(); got != 0 { + t.Fatalf("Front = %d, want 0", got) + } + if got, _ := d.Back(); got != 3 { + t.Fatalf("Back = %d, want 3", got) + } + if d.Len() != 4 { + t.Fatalf("Len = %d, want 4", d.Len()) + } + + // 交替弹出,验证两端 O(1) + if v, _ := d.PopFront(); v != 0 { + t.Fatalf("PopFront = %d, want 0", v) + } + if v, _ := d.PopBack(); v != 3 { + t.Fatalf("PopBack = %d, want 3", v) + } + if v, _ := d.PopFront(); v != 1 { + t.Fatalf("PopFront = %d, want 1", v) + } + if v, _ := d.PopBack(); v != 2 { + t.Fatalf("PopBack = %d, want 2", v) + } + if !d.Empty() { + t.Fatal("应为空") + } +} + +func TestDequeGrowAcrossBoundary(t *testing.T) { + // 跨越 head 边界触发扩容,验证数据顺序不乱。 + var d Deque[int] + for i := 0; i < 20; i++ { + d.PushBack(i) + } + // 先弹出几个,让 head 前移,再继续插入触发扩容。 + for i := 0; i < 5; i++ { + _, _ = d.PopFront() + } + for i := 20; i < 40; i++ { + d.PushBack(i) + } + // 期望顺序 5..39 + for want := 5; want < 40; want++ { + got, ok := d.PopFront() + if !ok || got != want { + t.Fatalf("PopFront = %d,%v, want %d", got, ok, want) + } + } +} + +func TestDequeString(t *testing.T) { + var d Deque[string] + d.PushFront("b") + d.PushFront("a") + d.PushBack("c") + var got []string + for !d.Empty() { + v, _ := d.PopFront() + got = append(got, v) + } + want := []string{"a", "b", "c"} + for i := range want { + if got[i] != want[i] { + t.Fatalf("结果[%d] = %q, want %q", i, got[i], want[i]) + } + } +} diff --git a/data-structure/ds-heap.go b/data-structure/ds-heap.go new file mode 100644 index 0000000..a783f25 --- /dev/null +++ b/data-structure/ds-heap.go @@ -0,0 +1,101 @@ +package ds + +// ds-heap.go +// 泛型二叉堆,手动实现 siftUp/siftDown,不依赖 container/heap。 +// 通过 less 函数决定堆性质:less(a,b)=true 表示 a 应在 b 上方。 +// - 传 a < b → 小顶堆 +// - 传 a > b → 大顶堆 +// Push/Pop 均 O(log n),Peek O(1)。 + +// Heap 泛型堆。零值不可直接用,必须用 NewHeap 创建。 +type Heap[T any] struct { + data []T + less func(a, b T) bool +} + +// NewHeap 创建堆。less(a,b) 返回 true 表示 a 优先级更高(更靠堆顶)。 +func NewHeap[T any](less func(a, b T) bool) *Heap[T] { + return &Heap[T]{less: less} +} + +// Len 元素个数。 +func (h *Heap[T]) Len() int { return len(h.data) } + +// Empty 是否为空。 +func (h *Heap[T]) Empty() bool { return len(h.data) == 0 } + +// Peek 查看堆顶,不移除。 +func (h *Heap[T]) Peek() (T, bool) { + var zero T + if len(h.data) == 0 { + return zero, false + } + return h.data[0], true +} + +// Push 插入元素:先追加到末尾,再 siftUp。 +func (h *Heap[T]) Push(v T) { + h.data = append(h.data, v) + h.siftUp(len(h.data) - 1) +} + +// Pop 弹出堆顶:把末尾元素换到堆顶,缩容,再 siftDown。 +func (h *Heap[T]) Pop() (T, bool) { + var zero T + n := len(h.data) + if n == 0 { + return zero, false + } + top := h.data[0] + if n == 1 { + h.data = h.data[:0] + return top, true + } + h.data[0] = h.data[n-1] + h.data[n-1] = zero // 置零释放引用 + h.data = h.data[:n-1] + h.siftDown(0) + return top, true +} + +// Init 从已有切片原地建堆,O(n)。调用后 h 持有该切片的引用。 +func (h *Heap[T]) Init(data []T) { + h.data = data + // 从最后一个非叶子节点开始向下调整。 + for i := len(h.data)/2 - 1; i >= 0; i-- { + h.siftDown(i) + } +} + +// siftUp 从 i 向上调整。 +func (h *Heap[T]) siftUp(i int) { + for i > 0 { + parent := (i - 1) / 2 + if !h.less(h.data[i], h.data[parent]) { + break + } + h.data[i], h.data[parent] = h.data[parent], h.data[i] + i = parent + } +} + +// siftDown 从 i 向下调整。 +func (h *Heap[T]) siftDown(i int) { + n := len(h.data) + for { + l := 2*i + 1 + r := 2*i + 2 + best := i + if l < n && h.less(h.data[l], h.data[best]) { + best = l + } + if r < n && h.less(h.data[r], h.data[best]) { + best = r + } + if best == i { + break + } + h.data[i], h.data[best] = h.data[best], h.data[i] + i = best + } +} diff --git a/data-structure/ds-heap_test.go b/data-structure/ds-heap_test.go new file mode 100644 index 0000000..99ac22d --- /dev/null +++ b/data-structure/ds-heap_test.go @@ -0,0 +1,113 @@ +package ds + +import ( + "math/rand" + "sort" + "testing" +) + +func TestHeapMinOrder(t *testing.T) { + h := NewHeap[int](func(a, b int) bool { return a < b }) + for _, v := range []int{5, 3, 8, 1, 4, 7, 9, 2, 6} { + h.Push(v) + } + var got []int + for !h.Empty() { + v, _ := h.Pop() + got = append(got, v) + } + want := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} + for i := range want { + if got[i] != want[i] { + t.Fatalf("Pop 顺序[%d] = %d, want %d", i, got[i], want[i]) + } + } +} + +func TestHeapMaxOrder(t *testing.T) { + h := NewHeap[int](func(a, b int) bool { return a > b }) // 大顶堆 + for _, v := range []int{5, 3, 8, 1, 4} { + h.Push(v) + } + top, _ := h.Peek() + if top != 8 { + t.Fatalf("大顶堆 Peek = %d, want 8", top) + } + var got []int + for !h.Empty() { + v, _ := h.Pop() + got = append(got, v) + } + want := []int{8, 5, 4, 3, 1} + for i := range want { + if got[i] != want[i] { + t.Fatalf("大顶堆 Pop[%d] = %d, want %d", i, got[i], want[i]) + } + } +} + +func TestHeapInit(t *testing.T) { + h := NewHeap[int](func(a, b int) bool { return a < b }) + data := []int{9, 5, 2, 7, 1, 8, 3} + // Init 后 h 持有 data 的底层数组,Pop 会原地修改它。 + // 所以 ref 必须在 Init 之前拷贝,避免被破坏。 + ref := make([]int, len(data)) + copy(ref, data) + sort.Ints(ref) + + h.Init(data) + if top, _ := h.Peek(); top != 1 { + t.Fatalf("Init 后 Peek = %d, want 1", top) + } + var got []int + for !h.Empty() { + v, _ := h.Pop() + got = append(got, v) + } + for i := range ref { + if got[i] != ref[i] { + t.Fatalf("Init+Pop[%d] = %d, want %d", i, got[i], ref[i]) + } + } +} + +func TestHeapEmpty(t *testing.T) { + h := NewHeap[int](func(a, b int) bool { return a < b }) + if _, ok := h.Pop(); ok { + t.Fatal("空堆 Pop 应返回 false") + } + if _, ok := h.Peek(); ok { + t.Fatal("空堆 Peek 应返回 false") + } +} + +func TestHeapRandomVsSort(t *testing.T) { + // 随机数据:堆弹出顺序应与排序结果一致。 + h := NewHeap[int](func(a, b int) bool { return a < b }) + rng := rand.New(rand.NewSource(42)) + const N = 2000 + ref := make([]int, N) + for i := 0; i < N; i++ { + v := rng.Intn(10000) + h.Push(v) + ref[i] = v + } + sort.Ints(ref) + for i := 0; i < N; i++ { + v, ok := h.Pop() + if !ok || v != ref[i] { + t.Fatalf("随机测试[%d] = %d,%v, want %d", i, v, ok, ref[i]) + } + } +} + +func TestHeapString(t *testing.T) { + h := NewHeap[string](func(a, b string) bool { return a < b }) + for _, s := range []string{"banana", "apple", "cherry"} { + h.Push(s) + } + first, _ := h.Pop() + if first != "apple" { + t.Fatalf("字符串堆 Pop = %q, want \"apple\"", first) + } +} diff --git a/data-structure/ds-list.go b/data-structure/ds-list.go new file mode 100644 index 0000000..d006712 --- /dev/null +++ b/data-structure/ds-list.go @@ -0,0 +1,99 @@ +package ds + +// ds-list.go +// 泛型单链表。带哨兵头节点(dummy head),便于在 O(1) 删除首节点时统一处理边界。 +// T 约束为 comparable,Delete 的值比较直接用 ==,类型安全且无需 reflect。 +// 所有节点指针操作都不依赖切片下标,删除/反转只改指针,不搬移数据。 + +// ListNode 单链表节点。 +type ListNode[T any] struct { + Val T + Next *ListNode[T] +} + +// LinkedList 单链表,持有哨兵头节点和尾指针。 +// head 是哨兵,不存真实数据;真实首节点是 head.Next。 +// 尾指针让 PushBack 为 O(1)。 +type LinkedList[T comparable] struct { + head *ListNode[T] // 哨兵头节点 + tail *ListNode[T] // 指向最后一个真实节点;空表时 tail == head +} + +// NewLinkedList 创建空链表。 +func NewLinkedList[T comparable]() *LinkedList[T] { + dummy := &ListNode[T]{} + return &LinkedList[T]{head: dummy, tail: dummy} +} + +// Len 链表长度,O(n)。链表无 size 字段时只能遍历;如需 O(1) 长度可自行加计数器。 +func (l *LinkedList[T]) Len() int { + n := 0 + for cur := l.head.Next; cur != nil; cur = cur.Next { + n++ + } + return n +} + +// Empty 是否为空。 +func (l *LinkedList[T]) Empty() bool { return l.head.Next == nil } + +// PushFront 头插,O(1)。 +func (l *LinkedList[T]) PushFront(v T) { + node := &ListNode[T]{Val: v, Next: l.head.Next} + l.head.Next = node + if l.tail == l.head { // 之前是空表,更新尾指针 + l.tail = node + } +} + +// PushBack 尾插,O(1)。 +func (l *LinkedList[T]) PushBack(v T) { + node := &ListNode[T]{Val: v} + l.tail.Next = node + l.tail = node +} + +// Delete 删除第一个值等于 v 的节点,O(n)。返回是否删除成功。 +func (l *LinkedList[T]) Delete(v T) bool { + prev := l.head + for cur := l.head.Next; cur != nil; prev, cur = cur, cur.Next { + if cur.Val == v { + prev.Next = cur.Next + if cur == l.tail { // 删的是尾节点,更新尾指针 + l.tail = prev + } + cur.Next = nil // 断开引用 + return true + } + } + return false +} + +// Reverse 整表反转,O(n)。 +func (l *LinkedList[T]) Reverse() { + var prev *ListNode[T] + cur := l.head.Next + // 反转后原来的首节点变成尾节点 + if cur != nil { + l.tail = cur + } + for cur != nil { + next := cur.Next + cur.Next = prev + prev = cur + cur = next + } + l.head.Next = prev +} + +// ToSlice 顺序转为切片,便于测试和遍历。 +func (l *LinkedList[T]) ToSlice() []T { + var out []T + for cur := l.head.Next; cur != nil; cur = cur.Next { + out = append(out, cur.Val) + } + return out +} + +// Head 返回第一个真实节点(非哨兵),空表返回 nil。供需要直接操作节点的场景使用。 +func (l *LinkedList[T]) Head() *ListNode[T] { return l.head.Next } diff --git a/data-structure/ds-list_test.go b/data-structure/ds-list_test.go new file mode 100644 index 0000000..f0c1c40 --- /dev/null +++ b/data-structure/ds-list_test.go @@ -0,0 +1,86 @@ +package ds + +import "testing" + +func TestLinkedListPushDelete(t *testing.T) { + l := NewLinkedList[int]() + if !l.Empty() { + t.Fatal("新建应为空") + } + l.PushBack(2) + l.PushBack(3) + l.PushFront(1) + l.PushFront(0) + // 期望 [0,1,2,3] + got := l.ToSlice() + want := []int{0, 1, 2, 3} + for i := range want { + if got[i] != want[i] { + t.Fatalf("ToSlice[%d] = %d, want %d", i, got[i], want[i]) + } + } + + // 删中间 + if !l.Delete(2) { + t.Fatal("Delete(2) 应成功") + } + if l.Delete(99) { + t.Fatal("Delete(99) 不应成功") + } + // 删头部 + if !l.Delete(0) { + t.Fatal("Delete(0) 应成功") + } + // 删尾部 + if !l.Delete(3) { + t.Fatal("Delete(3) 应成功") + } + got = l.ToSlice() + if len(got) != 1 || got[0] != 1 { + t.Fatalf("删完后剩 %v, want [1]", got) + } + // 删到空,验证 tail 回到 head + if !l.Delete(1) { + t.Fatal("Delete(1) 应成功") + } + if !l.Empty() { + t.Fatal("应为空") + } +} + +func TestLinkedListReverse(t *testing.T) { + l := NewLinkedList[string]() + for _, s := range []string{"a", "b", "c", "d"} { + l.PushBack(s) + } + l.Reverse() + got := l.ToSlice() + want := []string{"d", "c", "b", "a"} + for i := range want { + if got[i] != want[i] { + t.Fatalf("Reverse[%d] = %q, want %q", i, got[i], want[i]) + } + } + // 反转空表不应 panic + l2 := NewLinkedList[int]() + l2.Reverse() + if !l2.Empty() { + t.Fatal("空表反转后应仍为空") + } +} + +func TestLinkedListSingleNode(t *testing.T) { + l := NewLinkedList[int]() + l.PushBack(42) + if l.Len() != 1 { + t.Fatalf("Len = %d, want 1", l.Len()) + } + if !l.Delete(42) { + t.Fatal("Delete 单节点应成功") + } + // 删空后再次 PushBack,验证 tail 指针正确 + l.PushBack(7) + if got := l.ToSlice(); len(got) != 1 || got[0] != 7 { + t.Fatalf("删空后再插 = %v, want [7]", got) + } +} diff --git a/data-structure/ds-queue.go b/data-structure/ds-queue.go new file mode 100644 index 0000000..67a25ea --- /dev/null +++ b/data-structure/ds-queue.go @@ -0,0 +1,59 @@ +package ds + +// ds-queue.go +// 泛型切片队列。Enqueue O(1) 均摊,Dequeue O(1)(用 head 指针,避免每次头部搬运)。 +// 当剩余元素不足时惰性整理底层数组,控制内存增长。 + +// Queue 泛型队列,零值可用。 +type Queue[T any] struct { + data []T + head int // 队首索引,避免 O(n) 头部搬运 +} + +// Enqueue 入队。 +func (q *Queue[T]) Enqueue(v T) { + q.data = append(q.data, v) +} + +// Dequeue 出队;空队列返回零值和 false。 +func (q *Queue[T]) Dequeue() (T, bool) { + var zero T + if q.head >= len(q.data) { + return zero, false + } + v := q.data[q.head] + q.data[q.head] = zero // 置零,释放引用 + q.head++ + // 当已出队部分超过总容量一半时,整理切片回收底层内存。 + if q.head > 0 && q.head*2 >= cap(q.data) { + q.compact() + } + return v, true +} + +// Front 查看队首,不移除。 +func (q *Queue[T]) Front() (T, bool) { + var zero T + if q.head >= len(q.data) { + return zero, false + } + return q.data[q.head], true +} + +// Len 队列元素个数。 +func (q *Queue[T]) Len() int { return len(q.data) - q.head } + +// Empty 是否为空。 +func (q *Queue[T]) Empty() bool { return q.head >= len(q.data) } + +// compact 回收已出队占用的底层内存。 +func (q *Queue[T]) compact() { + n := q.Len() + if n == 0 { + q.data = q.data[:0] + } else { + copy(q.data, q.data[q.head:]) + q.data = q.data[:n] + } + q.head = 0 +} diff --git a/data-structure/ds-queue_test.go b/data-structure/ds-queue_test.go new file mode 100644 index 0000000..4a444b1 --- /dev/null +++ b/data-structure/ds-queue_test.go @@ -0,0 +1,61 @@ +package ds + +import "testing" + +func TestQueueFIFO(t *testing.T) { + var q Queue[int] + if !q.Empty() { + t.Fatal("新建队列应为空") + } + if _, ok := q.Dequeue(); ok { + t.Fatal("空队列 Dequeue 应返回 false") + } + + q.Enqueue(1) + q.Enqueue(2) + q.Enqueue(3) + if q.Len() != 3 { + t.Fatalf("Len = %d, want 3", q.Len()) + } + + // FIFO 顺序 + for _, want := range []int{1, 2, 3} { + got, ok := q.Dequeue() + if !ok { + t.Fatalf("Dequeue 失败,期望 %d", want) + } + if got != want { + t.Fatalf("Dequeue = %d, want %d", got, want) + } + } + if !q.Empty() { + t.Fatal("全部出队后应为空") + } +} + +func TestQueueFront(t *testing.T) { + var q Queue[string] + q.Enqueue("x") + q.Enqueue("y") + f, ok := q.Front() + if !ok || f != "x" { + t.Fatalf("Front = %q,%v, want \"x\",true", f, ok) + } + _, _ = q.Dequeue() + f, _ = q.Front() + if f != "y" { + t.Fatalf("第二次 Front = %q, want \"y\"", f) + } +} + +func TestQueueCompactNoGrowth(t *testing.T) { + // 大量入队/出队后,逻辑长度正确且不泄漏内存。 + var q Queue[int] + for i := 0; i < 1000; i++ { + q.Enqueue(i) + _, _ = q.Dequeue() + } + if !q.Empty() { + t.Fatal("1000 次入队+出队后应为空") + } +} diff --git a/data-structure/ds-set.go b/data-structure/ds-set.go new file mode 100644 index 0000000..7440744 --- /dev/null +++ b/data-structure/ds-set.go @@ -0,0 +1,91 @@ +package ds + +// ds-set.go +// 泛型集合,基于 map[T]struct{}。T 约束为 comparable。 +// Add/Remove/Contains 均 O(1) 均摊。支持并集/交集/差集。 +// 比 map[T]bool 更省内存(空结构体不占空间),语义也更清晰。 + +// Set 泛型集合,零值不可直接用(map 零值不能写),必须用 NewSet。 +type Set[T comparable] struct { + m map[T]struct{} +} + +// NewSet 创建空集合。 +func NewSet[T comparable]() *Set[T] { + return &Set[T]{m: make(map[T]struct{})} +} + +// NewSetFrom 从切片创建集合,自动去重。 +func NewSetFrom[T comparable](vals []T) *Set[T] { + s := NewSet[T]() + for _, v := range vals { + s.m[v] = struct{}{} + } + return s +} + +// Add 添加元素,已存在则无操作。 +func (s *Set[T]) Add(v T) { s.m[v] = struct{}{} } + +// Remove 删除元素,不存在则无操作。 +func (s *Set[T]) Remove(v T) { delete(s.m, v) } + +// Contains 是否包含元素。 +func (s *Set[T]) Contains(v T) bool { + _, ok := s.m[v] + return ok +} + +// Len 元素个数。 +func (s *Set[T]) Len() int { return len(s.m) } + +// Empty 是否为空。 +func (s *Set[T]) Empty() bool { return len(s.m) == 0 } + +// ToSlice 转为切片。map 遍历顺序无序,如需稳定顺序需自行排序。 +func (s *Set[T]) ToSlice() []T { + out := make([]T, 0, len(s.m)) + for v := range s.m { + out = append(out, v) + } + return out +} + +// Union 并集:返回新集合,包含 s 和 other 的所有元素。 +func (s *Set[T]) Union(other *Set[T]) *Set[T] { + r := NewSet[T]() + for v := range s.m { + r.m[v] = struct{}{} + } + for v := range other.m { + r.m[v] = struct{}{} + } + return r +} + +// Intersect 交集:返回新集合,仅包含 s 和 other 都有的元素。 +func (s *Set[T]) Intersect(other *Set[T]) *Set[T] { + r := NewSet[T]() + // 遍历较小集合,降低常数 + small, big := s, other + if small.Len() > big.Len() { + small, big = big, small + } + for v := range small.m { + if _, ok := big.m[v]; ok { + r.m[v] = struct{}{} + } + } + return r +} + +// Difference 差集:返回新集合,包含在 s 中但不在 other 中的元素。 +func (s *Set[T]) Difference(other *Set[T]) *Set[T] { + r := NewSet[T]() + for v := range s.m { + if _, ok := other.m[v]; !ok { + r.m[v] = struct{}{} + } + } + return r +} diff --git a/data-structure/ds-set_test.go b/data-structure/ds-set_test.go new file mode 100644 index 0000000..bffb391 --- /dev/null +++ b/data-structure/ds-set_test.go @@ -0,0 +1,90 @@ +package ds + +import ( + "reflect" + "sort" + "testing" +) + +func TestSetBasic(t *testing.T) { + s := NewSet[int]() + if !s.Empty() { + t.Fatal("新建应为空") + } + s.Add(1) + s.Add(2) + s.Add(1) // 重复 + if s.Len() != 2 { + t.Fatalf("Len = %d, want 2", s.Len()) + } + if !s.Contains(1) { + t.Fatal("应包含 1") + } + if s.Contains(3) { + t.Fatal("不应包含 3") + } + s.Remove(1) + if s.Contains(1) { + t.Fatal("Remove 后不应再包含 1") + } +} + +func TestSetFromSlice(t *testing.T) { + s := NewSetFrom([]string{"a", "b", "a", "c", "b"}) + if s.Len() != 3 { + t.Fatalf("Len = %d, want 3", s.Len()) + } + for _, want := range []string{"a", "b", "c"} { + if !s.Contains(want) { + t.Fatalf("应包含 %q", want) + } + } +} + +func TestSetUnionIntersectDiff(t *testing.T) { + a := NewSetFrom([]int{1, 2, 3, 4}) + b := NewSetFrom([]int{3, 4, 5, 6}) + + u := a.Union(b) + if !sameSet(u, NewSetFrom([]int{1, 2, 3, 4, 5, 6})) { + t.Fatalf("Union = %v, want {1,2,3,4,5,6}", sortedSlice(u)) + } + + i := a.Intersect(b) + if !sameSet(i, NewSetFrom([]int{3, 4})) { + t.Fatalf("Intersect = %v, want {3,4}", sortedSlice(i)) + } + + d := a.Difference(b) + if !sameSet(d, NewSetFrom([]int{1, 2})) { + t.Fatalf("Difference = %v, want {1,2}", sortedSlice(d)) + } +} + +func TestSetToSlice(t *testing.T) { + s := NewSetFrom([]int{3, 1, 2}) + got := sortedSlice(s) + want := []int{1, 2, 3} + if !reflect.DeepEqual(got, want) { + t.Fatalf("ToSlice(sorted) = %v, want %v", got, want) + } +} + +// sameSet 比较两个集合元素是否相同(忽略顺序)。 +func sameSet[T comparable](a, b *Set[T]) bool { + if a.Len() != b.Len() { + return false + } + for v := range a.m { + if _, ok := b.m[v]; !ok { + return false + } + } + return true +} + +func sortedSlice(s *Set[int]) []int { + out := s.ToSlice() + sort.Ints(out) + return out +} diff --git a/data-structure/ds-stack.go b/data-structure/ds-stack.go new file mode 100644 index 0000000..5227cee --- /dev/null +++ b/data-structure/ds-stack.go @@ -0,0 +1,41 @@ +package ds + +// ds-stack.go +// 泛型切片栈。Push/Pop/Peek 均 O(1)。 +// Pop 前先读值再缩切片,并把被弹出位置置零,避免底层数组持有引用导致内存泄漏。 + +// Stack 泛型栈,零值可用。 +type Stack[T any] struct { + data []T +} + +// Push 入栈。 +func (s *Stack[T]) Push(v T) { s.data = append(s.data, v) } + +// Pop 出栈;空栈返回零值和 false。 +func (s *Stack[T]) Pop() (T, bool) { + var zero T + if len(s.data) == 0 { + return zero, false + } + n := len(s.data) - 1 + v := s.data[n] + s.data[n] = zero // 置零,释放引用 + s.data = s.data[:n] + return v, true +} + +// Peek 查看栈顶,不移除;空栈返回零值和 false。 +func (s *Stack[T]) Peek() (T, bool) { + var zero T + if len(s.data) == 0 { + return zero, false + } + return s.data[len(s.data)-1], true +} + +// Len 栈元素个数。 +func (s *Stack[T]) Len() int { return len(s.data) } + +// Empty 是否为空。 +func (s *Stack[T]) Empty() bool { return len(s.data) == 0 } diff --git a/data-structure/ds-stack_test.go b/data-structure/ds-stack_test.go new file mode 100644 index 0000000..98cf4c1 --- /dev/null +++ b/data-structure/ds-stack_test.go @@ -0,0 +1,74 @@ +package ds + +import "testing" + +func TestStackBasic(t *testing.T) { + var s Stack[int] + if !s.Empty() { + t.Fatal("新建栈应为空") + } + if _, ok := s.Pop(); ok { + t.Fatal("空栈 Pop 应返回 false") + } + if _, ok := s.Peek(); ok { + t.Fatal("空栈 Peek 应返回 false") + } + + s.Push(1) + s.Push(2) + s.Push(3) + if s.Len() != 3 { + t.Fatalf("Len = %d, want 3", s.Len()) + } + + // LIFO 顺序 + for _, want := range []int{3, 2, 1} { + got, ok := s.Pop() + if !ok { + t.Fatalf("Pop 失败,期望 %d", want) + } + if got != want { + t.Fatalf("Pop = %d, want %d", got, want) + } + } + if !s.Empty() { + t.Fatal("全部弹出后应为空") + } +} + +func TestStackStringType(t *testing.T) { + var s Stack[string] + s.Push("a") + s.Push("b") + top, ok := s.Peek() + if !ok || top != "b" { + t.Fatalf("Peek = %q,%v, want \"b\",true", top, ok) + } + v, _ := s.Pop() + if v != "b" { + t.Fatalf("Pop = %q, want \"b\"", v) + } +} + +func TestStackLeakFunctional(t *testing.T) { + // 功能性验证:大量 push/pop 交替后仍正确,间接验证无状态泄漏。 + var s Stack[int] + s.Push(1) + s.Push(2) + v, _ := s.Pop() + if v != 2 { + t.Fatalf("Pop = %d, want 2", v) + } + s.Push(3) + v, _ = s.Pop() + if v != 3 { + t.Fatalf("Pop = %d, want 3", v) + } + v, _ = s.Pop() + if v != 1 { + t.Fatalf("Pop = %d, want 1", v) + } + if !s.Empty() { + t.Fatal("应为空") + } +} diff --git a/data-structure/ds-tree.go b/data-structure/ds-tree.go new file mode 100644 index 0000000..b69f04b --- /dev/null +++ b/data-structure/ds-tree.go @@ -0,0 +1,169 @@ +package ds + +// ds-tree.go +// 泛型二叉搜索树 (BST)。T 约束为 Ordered,Insert/Search 均基于比较。 +// 中序遍历天然有序;提供递归与迭代两种遍历入口。 +// 不做自平衡,退化时退化为链表;leetcode 题目里常需要这种朴素 BST 作为基础。 + +// TreeNode 二叉树节点。 +type TreeNode[T any] struct { + Val T + Left *TreeNode[T] + Right *TreeNode[T] +} + +// BST 二叉搜索树。 +type BST[T Ordered] struct { + Root *TreeNode[T] +} + +// Ordered 约束:支持 < <= > >= 比较。 +type Ordered interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | + ~float32 | ~float64 | ~string +} + +// NewBST 创建空 BST。 +func NewBST[T Ordered]() *BST[T] { return &BST[T]{} } + +// Insert 按 BST 性质插入,重复值放右子树。 +func (t *BST[T]) Insert(v T) { + t.Root = insertNode(t.Root, v) +} + +func insertNode[T Ordered](node *TreeNode[T], v T) *TreeNode[T] { + if node == nil { + return &TreeNode[T]{Val: v} + } + if v < node.Val { + node.Left = insertNode(node.Left, v) + } else { + node.Right = insertNode(node.Right, v) + } + return node +} + +// Search 查找值是否存在。 +func (t *BST[T]) Search(v T) bool { + cur := t.Root + for cur != nil { + if v == cur.Val { + return true + } + if v < cur.Val { + cur = cur.Left + } else { + cur = cur.Right + } + } + return false +} + +// InOrder 中序遍历(左→根→右),BST 上结果升序。递归版。 +func (t *BST[T]) InOrder() []T { + var out []T + var walk func(*TreeNode[T]) + walk = func(n *TreeNode[T]) { + if n == nil { + return + } + walk(n.Left) + out = append(out, n.Val) + walk(n.Right) + } + walk(t.Root) + return out +} + +// PreOrder 前序遍历(根→左→右)。递归版。 +func (t *BST[T]) PreOrder() []T { + var out []T + var walk func(*TreeNode[T]) + walk = func(n *TreeNode[T]) { + if n == nil { + return + } + out = append(out, n.Val) + walk(n.Left) + walk(n.Right) + } + walk(t.Root) + return out +} + +// PostOrder 后序遍历(左→右→根)。递归版。 +func (t *BST[T]) PostOrder() []T { + var out []T + var walk func(*TreeNode[T]) + walk = func(n *TreeNode[T]) { + if n == nil { + return + } + walk(n.Left) + walk(n.Right) + out = append(out, n.Val) + } + walk(t.Root) + return out +} + +// LevelOrder 层序遍历 (BFS),借助上面定义的 Queue。 +func (t *BST[T]) LevelOrder() []T { + if t.Root == nil { + return nil + } + var out []T + q := &Queue[*TreeNode[T]]{} + q.Enqueue(t.Root) + for !q.Empty() { + node, _ := q.Dequeue() + out = append(out, node.Val) + if node.Left != nil { + q.Enqueue(node.Left) + } + if node.Right != nil { + q.Enqueue(node.Right) + } + } + return out +} + +// Height 树高(空树为 0,单节点为 1)。 +func (t *BST[T]) Height() int { + var h func(*TreeNode[T]) int + h = func(n *TreeNode[T]) int { + if n == nil { + return 0 + } + l := h(n.Left) + r := h(n.Right) + if l > r { + return l + 1 + } + return r + 1 + } + return h(t.Root) +} + +// DFS 迭代版深度优先遍历(前序),用栈模拟。作为迭代遍历的示例。 +func (t *BST[T]) DFS() []T { + if t.Root == nil { + return nil + } + var out []T + st := &Stack[*TreeNode[T]]{} + st.Push(t.Root) + for !st.Empty() { + node, _ := st.Pop() + out = append(out, node.Val) + // 先压右再压左,弹出顺序才是 根→左→右 + if node.Right != nil { + st.Push(node.Right) + } + if node.Left != nil { + st.Push(node.Left) + } + } + return out +} diff --git a/data-structure/ds-tree_test.go b/data-structure/ds-tree_test.go new file mode 100644 index 0000000..40bb591 --- /dev/null +++ b/data-structure/ds-tree_test.go @@ -0,0 +1,95 @@ +package ds + +import "testing" + +func TestBSTInsertInOrder(t *testing.T) { + tree := NewBST[int]() + vals := []int{5, 3, 8, 1, 4, 7, 9} + for _, v := range vals { + tree.Insert(v) + } + got := tree.InOrder() + want := []int{1, 3, 4, 5, 7, 8, 9} + if len(got) != len(want) { + t.Fatalf("InOrder 长度 = %d, want %d", len(got), len(want)) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("InOrder[%d] = %d, want %d", i, got[i], want[i]) + } + } +} + +func TestBSTSearch(t *testing.T) { + tree := NewBST[int]() + for _, v := range []int{5, 3, 8, 1} { + tree.Insert(v) + } + if !tree.Search(3) { + t.Fatal("Search(3) 应为 true") + } + if tree.Search(99) { + t.Fatal("Search(99) 应为 false") + } + if tree.Search(0) { + t.Fatal("Search(0) 应为 false") + } +} + +func TestBSTTraversals(t *testing.T) { + tree := NewBST[int]() + // 构造: + // 4 + // / \ + // 2 6 + // / \ / \ + // 1 3 5 7 + for _, v := range []int{4, 2, 6, 1, 3, 5, 7} { + tree.Insert(v) + } + pre := tree.PreOrder() + wantPre := []int{4, 2, 1, 3, 6, 5, 7} + for i := range wantPre { + if pre[i] != wantPre[i] { + t.Fatalf("PreOrder[%d] = %d, want %d", i, pre[i], wantPre[i]) + } + } + post := tree.PostOrder() + wantPost := []int{1, 3, 2, 5, 7, 6, 4} + for i := range wantPost { + if post[i] != wantPost[i] { + t.Fatalf("PostOrder[%d] = %d, want %d", i, post[i], wantPost[i]) + } + } + lvl := tree.LevelOrder() + wantLvl := []int{4, 2, 6, 1, 3, 5, 7} + for i := range wantLvl { + if lvl[i] != wantLvl[i] { + t.Fatalf("LevelOrder[%d] = %d, want %d", i, lvl[i], wantLvl[i]) + } + } + dfs := tree.DFS() + // DFS 迭代版是前序,应与 PreOrder 相同 + for i := range wantPre { + if dfs[i] != wantPre[i] { + t.Fatalf("DFS[%d] = %d, want %d", i, dfs[i], wantPre[i]) + } + } +} + +func TestBSTHeightAndEmpty(t *testing.T) { + tree := NewBST[int]() + if tree.Height() != 0 { + t.Fatal("空树高度应为 0") + } + tree.Insert(1) + if tree.Height() != 1 { + t.Fatal("单节点高度应为 1") + } + tree.Insert(2) + tree.Insert(3) + // 退化为链表,高度 3 + if tree.Height() != 3 { + t.Fatalf("Height = %d, want 3 (退化链表)", tree.Height()) + } +} diff --git a/data-structure/ds-trie.go b/data-structure/ds-trie.go new file mode 100644 index 0000000..6c938f9 --- /dev/null +++ b/data-structure/ds-trie.go @@ -0,0 +1,109 @@ +package ds + +// ds-trie.go +// 前缀树 (Trie),支持任意字符串的插入/查找/前缀匹配/删除。 +// 每个节点 26 个子指针(默认按小写字母 a-z 索引),isEnd 标记单词结尾。 +// 如需支持更多字符集,可改用 map[rune]*TrieNode。 + +// TrieNode 前缀树节点。 +type TrieNode struct { + children [26]*TrieNode + isEnd bool +} + +// Trie 前缀树。默认只支持小写字母 a-z。 +type Trie struct { + root *TrieNode +} + +// NewTrie 创建空 Trie。 +func NewTrie() *Trie { + return &Trie{root: &TrieNode{}} +} + +// Insert 插入单词。仅支持 a-z,其他字符会 panic(用 rune 索引前先校验)。 +func (t *Trie) Insert(word string) { + node := t.root + for _, ch := range word { + idx := ch - 'a' + if idx < 0 || idx >= 26 { + panic("trie: 只支持小写字母 a-z,收到非法字符") + } + if node.children[idx] == nil { + node.children[idx] = &TrieNode{} + } + node = node.children[idx] + } + node.isEnd = true +} + +// Search 查找完整单词是否存在(必须 isEnd)。 +func (t *Trie) Search(word string) bool { + node := t.find(word) + return node != nil && node.isEnd +} + +// StartsWith 是否存在以 prefix 为前缀的单词。 +func (t *Trie) StartsWith(prefix string) bool { + return t.find(prefix) != nil +} + +// find 沿前缀走到对应节点,不存在返回 nil。 +func (t *Trie) find(s string) *TrieNode { + node := t.root + for _, ch := range s { + idx := ch - 'a' + if idx < 0 || idx >= 26 { + return nil + } + node = node.children[idx] + if node == nil { + return nil + } + } + return node +} + +// Delete 删除单词。仅当单词存在时删除,并回收不再被任何单词共享的节点。 +// 返回是否删除成功(单词存在且被删除)。 +func (t *Trie) Delete(word string) bool { + // 先确认单词存在,避免与回收语义混淆。 + node := t.find(word) + if node == nil || !node.isEnd { + return false + } + t.deleteNode(t.root, word, 0) + return true +} + +// deleteNode 递归回收:删除 word 沿途不再被需要的节点。 +// 调用前需保证 word 存在。返回值:当前节点是否可被父节点回收。 +func (t *Trie) deleteNode(node *TrieNode, word string, depth int) bool { + if node == nil { + return false + } + if depth == len(word) { + node.isEnd = false + return isEmpty(node) + } + idx := int(word[depth] - 'a') + if idx < 0 || idx >= 26 { + return false + } + child := node.children[idx] + if t.deleteNode(child, word, depth+1) { + node.children[idx] = nil + } + // 当前节点可回收:无子节点 且 非单词结尾 + return isEmpty(node) && !node.isEnd +} + +// isEmpty 节点是否无任何子节点。 +func isEmpty(node *TrieNode) bool { + for i := 0; i < 26; i++ { + if node.children[i] != nil { + return false + } + } + return true +} diff --git a/data-structure/ds-trie_test.go b/data-structure/ds-trie_test.go new file mode 100644 index 0000000..b23cb86 --- /dev/null +++ b/data-structure/ds-trie_test.go @@ -0,0 +1,90 @@ +package ds + +import "testing" + +func TestTrieInsertSearch(t *testing.T) { + tr := NewTrie() + words := []string{"apple", "app", "april", "banana"} + for _, w := range words { + tr.Insert(w) + } + for _, w := range words { + if !tr.Search(w) { + t.Fatalf("Search(%q) 应为 true", w) + } + } + if tr.Search("appl") { + t.Fatal("Search(\"appl\") 应为 false") + } + if tr.Search("apples") { + t.Fatal("Search(\"apples\") 应为 false") + } +} + +func TestTrieStartsWith(t *testing.T) { + tr := NewTrie() + for _, w := range []string{"apple", "app", "april"} { + tr.Insert(w) + } + if !tr.StartsWith("app") { + t.Fatal("StartsWith(\"app\") 应为 true") + } + if !tr.StartsWith("ap") { + t.Fatal("StartsWith(\"ap\") 应为 true") + } + if tr.StartsWith("b") { + t.Fatal("StartsWith(\"b\") 应为 false") + } +} + +func TestTrieDelete(t *testing.T) { + tr := NewTrie() + tr.Insert("apple") + tr.Insert("app") + // 删 apple,app 应保留(共享前缀节点不能被回收) + if !tr.Delete("apple") { + t.Fatal("Delete(\"apple\") 应成功") + } + if tr.Search("apple") { + t.Fatal("Delete 后 Search(\"apple\") 应为 false") + } + if !tr.Search("app") { + t.Fatal("Search(\"app\") 仍应为 true (共享前缀)") + } + if !tr.StartsWith("app") { + t.Fatal("StartsWith(\"app\") 仍应为 true") + } + + // 删不存在的单词 + if tr.Delete("xyz") { + t.Fatal("Delete(\"xyz\") 应返回 false") + } + + // 删最后一个单词,根的对应子树应被完全回收 + tr.Delete("app") + if tr.Search("app") { + t.Fatal("Delete(\"app\") 后应搜不到") + } +} + +func TestTrieDeleteNonWordPrefix(t *testing.T) { + tr := NewTrie() + tr.Insert("apple") + // "appl" 是前缀但不是完整单词,Delete 应返回 false + if tr.Delete("appl") { + t.Fatal("Delete(\"appl\") 非完整单词,应返回 false") + } + if !tr.Search("apple") { + t.Fatal("apple 不应受影响") + } +} + +func TestTrieEmpty(t *testing.T) { + tr := NewTrie() + if tr.Search("anything") { + t.Fatal("空 Trie Search 应为 false") + } + if tr.StartsWith("a") { + t.Fatal("空 Trie StartsWith 应为 false") + } +} diff --git a/input-scan-cheatsheet.go b/misc/input-scan-cheatsheet.go similarity index 100% rename from input-scan-cheatsheet.go rename to misc/input-scan-cheatsheet.go diff --git a/misc/unique-range-assign/leetcode_test.go b/misc/unique-range-assign/leetcode_test.go new file mode 100644 index 0000000..efe5ab1 --- /dev/null +++ b/misc/unique-range-assign/leetcode_test.go @@ -0,0 +1,109 @@ +package misc + +import "testing" + +// 题目(推测题干) +// +// 给定两个长度为 n 的 0/1 数组 a 和 b。 +// 一次操作:选择 [L, R, V],满足 1 ≤ L ≤ R ≤ n,V ∈ {0,1}, +// 将 a 中下标在 [L,R] 内的所有元素统一修改为 V(区间外不变)。 +// +// 问:是否存在恰好一种操作使 a 变为 b? +// - 存在且唯一:输出该操作 [L,R,V](0-indexed 闭区间) +// - 不存在或不唯一:输出 -1 +// +// 思路(0-indexed) +// +// 记 need = { i : a[i] != b[i] },即必须被改到的位置。 +// +// 1. 找 lo = min(need), hi = max(need)。need 为空 → a == b,特判(见下)。 +// 2. 区间 [L,R] 必须 ⊇ [lo, hi],否则 need 中落在区间外的位置不会被改。 +// 3. 区间内每个位置改完都要等于 b;而一个操作只有一个 V, +// 所以 [lo,hi] 内 b 必须全相同,V = b[lo]。否则无解 (-1)。 +// 4. L 的取值范围:L ≤ lo。向左能扩张到 b 中连续等于 V 的位置 +// (这些位置改后仍等于 b,不破坏)。 +// - L 唯一 ⟺ lo == 0(到左边界)或 b[lo-1] != V(左边紧邻不等于 V,无法扩张) +// 5. R 的取值范围:R ≥ hi。向右能扩张到 b 中连续等于 V 的位置。 +// - R 唯一 ⟺ hi == n-1(到右边界)或 b[hi+1] != V +// 6. L、R 都唯一 → 输出 [lo, hi, V];否则 -1。 +// +// 特判 a == b(need 为空): +// - 要一次操作让 a 不变:区间内 a 必须全等于 V。 +// - 任何单点 [i,i] 配 V=a[i] 都可行。 +// - n == 1:唯一解 [0, 0, a[0]]。 +// - n >= 2:有多个单点选择 → 不唯一 → -1。 + +func uniqueRangeAssign(a, b []int) (L, R, V int, ok bool) { + n := len(a) + + // 第 1 步:找 lo, hi + lo, hi := -1, -1 + for i := 0; i < n; i++ { + if a[i] != b[i] { + if lo == -1 { + lo = i + } + hi = i + } + } + + // 特判 a == b + if lo == -1 { + if n == 1 { + return 0, 0, a[0], true + } + return 0, 0, 0, false + } + + // 第 3 步:V = b[lo],检查 [lo,hi] 内 b 全等于 V + V = b[lo] + for i := lo; i <= hi; i++ { + if b[i] != V { + return 0, 0, 0, false + } + } + + // 第 4 步:L 唯一性 + lOK := lo == 0 || b[lo-1] != V + // 第 5 步:R 唯一性 + rOK := hi == n-1 || b[hi+1] != V + + if !lOK || !rOK { + return 0, 0, 0, false + } + + // 第 6 步:唯一解 + return lo, hi, V, true +} + +func TestUniqueRangeAssign(t *testing.T) { + cases := []struct { + name string + a, b []int + wantL int + wantR int + wantV int + wantOK bool + }{ + {"唯一-全改", []int{0, 0}, []int{1, 1}, 0, 1, 1, true}, + {"唯一-部分改", []int{0, 0, 0, 0, 0}, []int{1, 1, 1, 0, 0}, 0, 2, 1, true}, + {"唯一-右扩", []int{0, 0, 0, 0, 0}, []int{0, 0, 1, 1, 1}, 2, 4, 1, true}, + {"无解-内部b不一致", []int{0, 1, 0}, []int{1, 0, 1}, 0, 0, 0, false}, + {"不唯一-R可扩", []int{0, 1, 1}, []int{1, 1, 1}, 0, 0, 0, false}, + {"不唯一-L可扩", []int{1, 0, 1}, []int{1, 1, 1}, 0, 0, 0, false}, + {"不唯一-a=b,n>=2", []int{1, 1, 1}, []int{1, 1, 1}, 0, 0, 0, false}, + {"唯一-a=b,n=1", []int{0}, []int{0}, 0, 0, 0, true}, + {"唯一-单点改", []int{0}, []int{1}, 0, 0, 1, true}, + {"唯一-覆盖整个数组", []int{1, 1, 0, 1}, []int{0, 0, 0, 0}, 0, 3, 0, true}, + {"无解-need不连续且b不一致", []int{0, 0, 0, 0}, []int{1, 0, 0, 1}, 0, 0, 0, false}, + } + for _, c := range cases { + L, R, V, ok := uniqueRangeAssign(c.a, c.b) + pass := ok == c.wantOK && + (!ok || (L == c.wantL && R == c.wantR && V == c.wantV)) + if !pass { + t.Errorf("%s: a=%v b=%v -> [%d,%d,%d] ok=%v, want [%d,%d,%d] ok=%v", + c.name, c.a, c.b, L, R, V, ok, c.wantL, c.wantR, c.wantV, c.wantOK) + } + } +} \ No newline at end of file diff --git a/sort_test.go b/sort_test.go index 7320878..a1869cd 100644 --- a/sort_test.go +++ b/sort_test.go @@ -174,8 +174,7 @@ func HeapSort(nums []int) { return } // siftDown:在 [0, end] 范围内把 nums[i] 下沉到正确位置 - var siftDown func(i, end int) - siftDown = func(i, end int) { + siftDown := func(i, end int) { for 2*i+1 <= end { l, r := 2*i+1, 2*i+2 largest := i @@ -206,7 +205,39 @@ func HeapSort(nums []int) { // ShellSort 希尔排序:按递减步长分组做插入排序,最后步长为 1 退化为插入排序。 // 平均约 O(n^1.3),非稳定。代码短,比插入排序在乱序数据上快很多。 func ShellSort(nums []int) { - // TODO: 你来写 + gap := len(nums) + for gap > 1 { + gap /= 2 + for i := 0; i < len(nums); i++ { + // 插入排序 + for j := 1; j*gap+i < len(nums); j++ { + for k := j; k > 0; k-- { + if nums[k*gap+i] < nums[(k-1)*gap+i] { + nums[k*gap+i], nums[(k-1)*gap+i] = nums[(k-1)*gap+i], nums[k*gap+i] + } else { + break + } + } + } + } + } +} + +// ShellSortStandard 是更常见的希尔排序写法:不显式遍历每一个分组, +// 而是让 j 每次减 gap,使其天然只在 nums[i%gap] 这一组内移动。 +// 相比 ShellSort 的交换式插入,这里先保存 key、再右移元素,能减少写入次数。 +func ShellSortStandard(nums []int) { + for gap := len(nums) / 2; gap > 0; gap /= 2 { + for i := gap; i < len(nums); i++ { + key := nums[i] + j := i + for j-gap >= 0 && key < nums[j-gap] { + nums[j] = nums[j-gap] + j -= gap + } + nums[j] = key + } + } } // CountingSort 计数排序:元素为 [0, k] 整数,统计频次后前缀和定位。 @@ -271,9 +302,10 @@ func TestSortAlgos(t *testing.T) { // {"SelectionSort", SelectionSort}, // {"InsertionSort", InsertionSort}, // {"MergeSort", MergeSort}, - {"HeapSort", HeapSort}, - {"BucketSort", BucketSort}, + // {"HeapSort", HeapSort}, + // {"BucketSort", BucketSort}, // {"ShellSort", ShellSort}, + {"ShellSortStandard", ShellSortStandard}, // {"CountingSort", CountingSort}, } for _, s := range sorters { diff --git a/top-100-liked/74/leetcode_test.go b/top-100-liked/74/leetcode_test.go index 145ab7c..ece4ad2 100644 --- a/top-100-liked/74/leetcode_test.go +++ b/top-100-liked/74/leetcode_test.go @@ -1,6 +1,7 @@ package top100liked import ( + "container/heap" "reflect" "sort" "testing" @@ -136,6 +137,53 @@ func topKFrequentBucket(nums []int, k int) []int { return res } +// 解法三:container/heap 维护大小 k 的最小堆 — O(n log k) / O(n) +// 思路同解法一,但用标准库 container/heap 提供堆算法,自己只实现 heap.Interface。 +// +// container/heap 两层 API(最容易踩坑的地方): +// - 你实现的 Push/Pop 只是"回调":Push 追加到切片末尾,Pop 删除末尾元素 +// - 真正对外的是 heap.Push / heap.Pop:它们调你的 Push/Pop 并负责 siftUp/siftDown +// - Less 用 < 是小顶堆(堆顶最小),用 > 是大顶堆 +// - Push/Pop 必须用指针接收者(要改 slice header);Len/Less/Swap 用值接收者即可 +// +// 要点: +// - entryHeap []entry 实现 heap.Interface(Len/Less/Swap + Push/Pop) +// - Less 按 cnt 升序,堆顶是堆内频率最小者 +// - 遍历 m:heap.Push(h, entry{v,c});h.Len() > k 时 heap.Pop(h) 淘汰最小 +// - 收集堆中剩余 k 个元素返回 +type entryHeap []entry + +func (h entryHeap) Len() int { return len(h) } +func (h entryHeap) Less(i, j int) bool { return h[i].cnt < h[j].cnt } // < 小顶堆 +func (h entryHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +// Push/Pop 是给 heap 包调用的回调,只负责在切片末尾增删,不要加堆序逻辑 +func (h *entryHeap) Push(x any) { *h = append(*h, x.(entry)) } +func (h *entryHeap) Pop() any { + old := *h + n := len(old) + x := old[n-1] + *h = old[:n-1] + return x +} + +func topKFrequentContainerHeap(nums []int, k int) []int { + m := countFreq(nums) + h := &entryHeap{} + heap.Init(h) + for v, c := range m { + heap.Push(h, entry{v, c}) + if h.Len() > k { + heap.Pop(h) + } + } + res := make([]int, 0, k) + for h.Len() > 0 { + res = append(res, heap.Pop(h).(entry).val) + } + return res +} + // equalIgnoreOrder 忽略顺序比较两个 []int。 func equalIgnoreOrder(a, b []int) bool { if len(a) != len(b) { @@ -167,6 +215,7 @@ func TestTopKFrequent(t *testing.T) { }{ {"heap", topKFrequentHeap}, {"bucket", topKFrequentBucket}, + {"container-heap", topKFrequentContainerHeap}, } for _, s := range solvers { t.Run(s.name, func(t *testing.T) { diff --git a/top-100-liked/75/leetcode_test.go b/top-100-liked/75/leetcode_test.go new file mode 100644 index 0000000..ca3a812 --- /dev/null +++ b/top-100-liked/75/leetcode_test.go @@ -0,0 +1,134 @@ +package top100liked + +import ( + "fmt" + "testing" +) + +// https://leetcode.cn/problems/find-median-from-data-stream/?envType=study-plan-v2&envId=top-100-liked + +type heap[T any] struct { + data []T + compareFunc func(T, T) bool +} + +func NewHeap[T any](compareFunc func(T, T) bool) heap[T] { + return heap[T]{ + compareFunc: compareFunc, + } +} + +func (h *heap[T]) Up(idx int) { + for (idx-1)/2 >= 0 { + cur := h.data[idx] + p := h.data[(idx-1)/2] + if h.compareFunc(cur, p) { + h.data[idx], h.data[(idx-1)/2] = h.data[(idx-1)/2], h.data[idx] + idx = (idx - 1) / 2 + } else { + break + } + } +} + +func (h *heap[T]) Down(idx int) { + for { + l, r := 2*idx+1, 2*idx+2 + m := idx + if l < len(h.data) && h.compareFunc(h.data[l], h.data[m]) { + m = l + } + if r < len(h.data) && h.compareFunc(h.data[r], h.data[m]) { + m = r + } + if m == idx { + break + } + h.data[idx], h.data[m] = h.data[m], h.data[idx] + idx = m + } +} + +func (h *heap[T]) Push(val T) { + h.data = append(h.data, val) + h.Up(len(h.data) - 1) +} + +func (h *heap[T]) Pop() T { + if len(h.data) <= 0 { + var zero T + return zero + } + old := h.data[0] + h.data[0] = h.data[len(h.data)-1] + h.data = h.data[:len(h.data)-1] + h.Down(0) + return old +} + +func (h *heap[T]) Top() T { + if len(h.data) > 0 { + return h.data[0] + } + var zero T + return zero +} + +func (h *heap[T]) Len() int { + return len(h.data) +} + +type MedianFinder struct { + left heap[int] // 小 + right heap[int] // 大 +} + +func Constructor() MedianFinder { + return MedianFinder{ + left: NewHeap(func(i1, i2 int) bool { return i1 > i2 }), + right: NewHeap(func(i1, i2 int) bool { return i1 < i2 }), + } +} + +func (this *MedianFinder) AddNum(num int) { + if this.left.Len() == 0 { + this.left.Push(num) + return + } + if num >= this.left.Top() { + this.right.Push(num) + } else { + this.left.Push(num) + } + if this.left.Len() > this.right.Len()+1 { + v := this.left.Pop() + this.right.Push(v) + } + if this.right.Len() > this.left.Len()+1 { + v := this.right.Pop() + this.left.Push(v) + } +} + +func (this *MedianFinder) FindMedian() float64 { + switch (this.left.Len() + this.right.Len()) % 2 { + case 0: + return (float64(this.left.Top()) + float64(this.right.Top())) / 2 + case 1: + if this.left.Len() > this.right.Len() { + return float64(this.left.Top()) + } else { + return float64(this.right.Top()) + } + } + return 0 +} + +func Test(t *testing.T) { + medianFinder := Constructor() + medianFinder.AddNum(1) // arr = [1] + medianFinder.AddNum(2) // arr = [1, 2] + fmt.Println(medianFinder.FindMedian()) // 返回 1.5 ((1 + 2) / 2) + medianFinder.AddNum(3) // arr[1, 2, 3] + fmt.Println(medianFinder.FindMedian()) // return 2.0 +}