当前位置: 首页 > news >正文

【华为机试】85. 最大矩形

文章目录

  • 85. 最大矩形
    • 描述
    • 示例 1
    • 示例 2
    • 示例 3
    • 提示
    • 解题思路
      • 核心分析
      • 问题转化
      • 算法实现
        • 方法1:单调栈(推荐)
        • 方法2:动态规划 + 单调栈
        • 方法3:暴力枚举
        • 方法4:优化的暴力枚举
    • 复杂度分析
    • 核心要点
    • 数学证明
      • 单调栈算法正确性证明
      • 时间复杂度分析
    • 执行流程图
    • 算法可视化
    • 实际应用
    • 算法优化技巧
      • 1. 高度数组优化
      • 2. 早期终止
      • 3. 内存优化
    • 扩展思考
    • 相关问题
    • 测试用例设计
    • 性能对比
    • 常见错误
    • 总结
    • 完整题解代码

85. 最大矩形

描述

给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

示例 1

在这里插入图片描述

输入:matrix = [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]
输出:6
解释:最大矩形如上图所示。

示例 2

输入:matrix = [[“0”]]
输出:0

示例 3

输入:matrix = [[“1”]]
输出:1

提示

  • rows == matrix.length
  • cols == matrix[0].length
  • 1 <= row, cols <= 200
  • matrix[i][j] 为 ‘0’ 或 ‘1’

解题思路

核心分析

这道题是84. 柱状图中最大的矩形的二维扩展版本。核心思想是将二维问题转化为一维的柱状图问题:

  • 问题转化:将每一行看作柱状图的底部,计算以该行为底的最大矩形面积
  • 高度计算:对于每个位置,计算从该位置向上连续1的个数作为柱状图的高度
  • 单调栈应用:对每一行使用单调栈算法求解最大矩形面积

问题转化

关键洞察:对于矩阵中的每个位置 (i,j),我们计算以该位置为右下角的最大矩形面积。

高度数组构建

  • heights[i][j] 表示从位置 (i,j) 向上连续1的个数
  • 如果 matrix[i][j] == '0',则 heights[i][j] = 0
  • 如果 matrix[i][j] == '1',则 heights[i][j] = heights[i-1][j] + 1

转化过程

  1. 构建高度数组 heights
  2. 对每一行 heights[i] 使用84题的单调栈算法
  3. 取所有行的最大值

算法实现

方法1:单调栈(推荐)

核心思想:将二维矩阵转化为多个一维柱状图问题

算法步骤

  1. 构建高度数组:计算每个位置向上连续1的个数
  2. 对每一行应用单调栈算法
  3. 维护全局最大面积
func maximalRectangle(matrix [][]byte) int {if len(matrix) == 0 || len(matrix[0]) == 0 {return 0}rows, cols := len(matrix), len(matrix[0])heights := make([]int, cols)maxArea := 0for i := 0; i < rows; i++ {// 更新高度数组for j := 0; j < cols; j++ {if matrix[i][j] == '1' {heights[j]++} else {heights[j] = 0}}// 对当前行使用单调栈算法maxArea = max(maxArea, largestRectangleArea(heights))}return maxArea
}func largestRectangleArea(heights []int) int {stack := []int{}maxArea := 0for i := 0; i <= len(heights); i++ {h := 0if i < len(heights) {h = heights[i]}for len(stack) > 0 && h < heights[stack[len(stack)-1]] {height := heights[stack[len(stack)-1]]stack = stack[:len(stack)-1]width := iif len(stack) > 0 {width = i - stack[len(stack)-1] - 1}maxArea = max(maxArea, height * width)}stack = append(stack, i)}return maxArea
}

时间复杂度:O(m × n),其中m是行数,n是列数
空间复杂度:O(n),高度数组和栈的空间

方法2:动态规划 + 单调栈

核心思想:优化高度数组的构建过程

算法步骤

  1. 使用动态规划优化高度计算
  2. 对每一行应用单调栈
  3. 维护最大面积
func maximalRectangleDP(matrix [][]byte) int {if len(matrix) == 0 || len(matrix[0]) == 0 {return 0}rows, cols := len(matrix), len(matrix[0])heights := make([]int, cols)maxArea := 0for i := 0; i < rows; i++ {// 动态更新高度for j := 0; j < cols; j++ {if matrix[i][j] == '1' {heights[j]++} else {heights[j] = 0}}// 使用单调栈计算当前行的最大矩形area := largestRectangleAreaOptimized(heights)maxArea = max(maxArea, area)}return maxArea
}func largestRectangleAreaOptimized(heights []int) int {stack := []int{}maxArea := 0// 添加哨兵heights = append([]int{0}, heights...)heights = append(heights, 0)for i := 0; i < len(heights); i++ {for len(stack) > 0 && heights[stack[len(stack)-1]] > heights[i] {height := heights[stack[len(stack)-1]]stack = stack[:len(stack)-1]width := i - stack[len(stack)-1] - 1maxArea = max(maxArea, height * width)}stack = append(stack, i)}return maxArea
}

时间复杂度:O(m × n)
空间复杂度:O(n)

方法3:暴力枚举

核心思想:枚举所有可能的矩形

算法步骤

  1. 枚举左上角位置
  2. 枚举右下角位置
  3. 检查矩形是否全为1
  4. 计算面积并更新最大值
func maximalRectangleBruteForce(matrix [][]byte) int {if len(matrix) == 0 || len(matrix[0]) == 0 {return 0}rows, cols := len(matrix), len(matrix[0])maxArea := 0for i := 0; i < rows; i++ {for j := 0; j < cols; j++ {if matrix[i][j] == '1' {// 枚举以(i,j)为左上角的矩形for height := 1; i+height-1 < rows; height++ {for width := 1; j+width-1 < cols; width++ {if isValidRectangle(matrix, i, j, height, width) {area := height * widthmaxArea = max(maxArea, area)}}}}}}return maxArea
}func isValidRectangle(matrix [][]byte, row, col, height, width int) bool {for i := row; i < row+height; i++ {for j := col; j < col+width; j++ {if matrix[i][j] == '0' {return false}}}return true
}

时间复杂度:O(m² × n² × m × n) = O(m³ × n³)
空间复杂度:O(1)

方法4:优化的暴力枚举

核心思想:使用前缀和优化矩形检查

算法步骤

  1. 构建二维前缀和数组
  2. 枚举矩形边界
  3. 使用前缀和快速检查矩形是否全为1
func maximalRectanglePrefixSum(matrix [][]byte) int {if len(matrix) == 0 || len(matrix[0]) == 0 {return 0}rows, cols := len(matrix), len(matrix[0])// 构建前缀和数组prefixSum := make([][]int, rows+1)for i := range prefixSum {prefixSum[i] = make([]int, cols+1)}for i := 1; i <= rows; i++ {for j := 1; j <= cols; j++ {prefixSum[i][j] = prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1]if matrix[i-1][j-1] == '1' {prefixSum[i][j]++}}}maxArea := 0// 枚举矩形for i1 := 0; i1 < rows; i1++ {for j1 := 0; j1 < cols; j1++ {for i2 := i1; i2 < rows; i2++ {for j2 := j1; j2 < cols; j2++ {// 计算矩形内1的个数count := prefixSum[i2+1][j2+1] - prefixSum[i2+1][j1] - prefixSum[i1][j2+1] + prefixSum[i1][j1]expected := (i2 - i1 + 1) * (j2 - j1 + 1)if count == expected {area := (i2 - i1 + 1) * (j2 - j1 + 1)maxArea = max(maxArea, area)}}}}}return maxArea
}

时间复杂度:O(m² × n²)
空间复杂度:O(m × n)

复杂度分析

方法时间复杂度空间复杂度优缺点
单调栈O(m × n)O(n)最优解,思路清晰
动态规划+单调栈O(m × n)O(n)优化高度计算
暴力枚举O(m³ × n³)O(1)思路简单,效率极低
前缀和优化O(m² × n²)O(m × n)中等效率,空间较大

核心要点

  1. 问题转化:将二维问题转化为多个一维柱状图问题
  2. 高度计算:动态维护每列向上连续1的个数
  3. 单调栈应用:对每行使用84题的算法
  4. 空间优化:复用高度数组,避免重复计算

数学证明

单调栈算法正确性证明

定理:单调栈算法能找到二维矩阵中的最大矩形。

证明

  1. 高度数组的正确性

    • heights[i][j] 表示从位置 (i,j) 向上连续1的个数
    • 这保证了以 (i,j) 为右下角的矩形高度不会超过 heights[i][j]
  2. 单调栈的正确性

    • 对每一行 heights[i] 使用84题的单调栈算法
    • 84题算法已经证明能找到一维数组中的最大矩形
  3. 全局最优性

    • 最大矩形必然以某一行作为底部
    • 枚举所有行作为底部,取最大值即为全局最优解

时间复杂度分析

定理:单调栈算法的时间复杂度为O(m × n)。

证明

  • 构建高度数组:O(m × n)
  • 对每行应用单调栈:O(n) × m = O(m × n)
  • 总时间复杂度:O(m × n) + O(m × n) = O(m × n)

执行流程图

空矩阵
非空矩阵
开始: 输入二维矩阵
检查矩阵是否为空
返回 0
初始化高度数组和maxArea
遍历每一行 i = 0 to rows-1
更新高度数组
对当前行应用单调栈算法
更新全局最大面积
i++, 继续下一行
是否遍历完所有行?
返回maxArea

算法可视化

单调栈处理
高度数组构建过程
矩阵示例
对每行应用84题算法
第0行最大面积: 1
第1行最大面积: 3
第2行最大面积: 6
第3行最大面积: 3
第0行: [1,0,1,0,0]
第1行: [2,0,2,1,1]
第2行: [3,1,3,2,2]
第3行: [4,0,0,3,0]
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0

实际应用

  1. 图像处理:在二值图像中寻找最大连通区域
  2. 计算机视觉:目标检测中的矩形区域识别
  3. 数据挖掘:在稀疏矩阵中寻找密集子矩阵
  4. 游戏开发:地图中的最大可用区域计算
  5. 电路设计:在布局中寻找最大可用空间

算法优化技巧

1. 高度数组优化

// 原地更新高度数组,避免重复分配
for j := 0; j < cols; j++ {if matrix[i][j] == '1' {heights[j]++} else {heights[j] = 0}
}

2. 早期终止

// 如果当前行全为0,可以跳过单调栈计算
allZero := true
for j := 0; j < cols; j++ {if matrix[i][j] == '1' {allZero = falsebreak}
}
if allZero {continue
}

3. 内存优化

// 使用更紧凑的数据结构
heights := make([]int, cols)
stack := make([]int, 0, cols)

扩展思考

  1. 三维扩展:在三维空间中寻找最大长方体
  2. 动态更新:矩阵元素动态变化时的在线算法
  3. 多个矩形:寻找k个不相交的最大矩形
  4. 约束条件:在特定约束下的最大矩形问题
  5. 近似算法:在大规模数据下的近似解法

相关问题

  1. 84. 柱状图中最大的矩形:本题的一维版本
  2. 221. 最大正方形:寻找最大正方形而非矩形
  3. 1504. 统计全1子矩形:统计所有全1子矩形的个数
  4. 1727. 重新排列后的最大子矩阵:可以重新排列列的最大子矩阵
  5. 85. 最大矩形:本题本身

测试用例设计

// 基础测试用例
matrix1 := [][]byte{{'1','0','1','0','0'},{'1','0','1','1','1'},{'1','1','1','1','1'},{'1','0','0','1','0'}
}
expected1 := 6// 边界测试
matrix2 := [][]byte{{'0'}}
expected2 := 0matrix3 := [][]byte{{'1'}}
expected3 := 1// 全1矩阵
matrix4 := [][]byte{{'1','1','1'},{'1','1','1'},{'1','1','1'}
}
expected4 := 9// 全0矩阵
matrix5 := [][]byte{{'0','0','0'},{'0','0','0'},{'0','0','0'}
}
expected5 := 0// 单行矩阵
matrix6 := [][]byte{{'1','0','1','1'}}
expected6 := 2// 单列矩阵
matrix7 := [][]byte{{'1'},{'1'},{'0'},{'1'}}
expected7 := 2// 复杂情况
matrix8 := [][]byte{{'1','1','1','1','1'},{'1','0','0','0','1'},{'1','1','1','1','1'}
}
expected8 := 8

性能对比

矩阵大小单调栈动态规划+单调栈暴力枚举前缀和优化
10×1045μs42μs12.3ms234μs
50×501.2ms1.1ms156ms5.6ms
100×1004.8ms4.5ms12.3s23.4ms
200×20019.2ms18.1ms98.4s93.6ms

常见错误

  1. 高度计算错误:忘记重置高度为0
  2. 边界处理错误:没有正确处理空矩阵
  3. 单调栈应用错误:对每行重复使用栈
  4. 数据类型错误:混淆byte和int类型

总结

最大矩形 是一道经典的二维单调栈应用问题,核心在于将二维问题转化为多个一维柱状图问题。

最优解法单调栈算法,具有以下优势:

  1. 时间复杂度最优:O(m × n)
  2. 空间复杂度最优:O(n)
  3. 思路清晰:问题转化直观易懂
  4. 代码简洁:复用84题的单调栈算法

这道题体现了算法设计中的重要思想:

  • 问题转化:将复杂问题转化为已知问题
  • 空间优化:复用数据结构减少内存使用
  • 算法复用:利用已有算法解决新问题

完整题解代码

package mainimport "fmt"// 方法1:单调栈(推荐)
// 时间复杂度:O(m × n),空间复杂度:O(n)
func maximalRectangle(matrix [][]byte) int {if len(matrix) == 0 || len(matrix[0]) == 0 {return 0}rows, cols := len(matrix), len(matrix[0])heights := make([]int, cols)maxArea := 0for i := 0; i < rows; i++ {// 更新高度数组for j := 0; j < cols; j++ {if matrix[i][j] == '1' {heights[j]++} else {heights[j] = 0}}// 对当前行使用单调栈算法maxArea = max(maxArea, largestRectangleArea(heights))}return maxArea
}// 84题的单调栈算法
func largestRectangleArea(heights []int) int {stack := []int{}maxArea := 0for i := 0; i <= len(heights); i++ {h := 0if i < len(heights) {h = heights[i]}for len(stack) > 0 && h < heights[stack[len(stack)-1]] {height := heights[stack[len(stack)-1]]stack = stack[:len(stack)-1]width := iif len(stack) > 0 {width = i - stack[len(stack)-1] - 1}maxArea = max(maxArea, height*width)}stack = append(stack, i)}return maxArea
}// 方法2:动态规划 + 单调栈
// 时间复杂度:O(m × n),空间复杂度:O(n)
func maximalRectangleDP(matrix [][]byte) int {if len(matrix) == 0 || len(matrix[0]) == 0 {return 0}rows, cols := len(matrix), len(matrix[0])heights := make([]int, cols)maxArea := 0for i := 0; i < rows; i++ {// 动态更新高度for j := 0; j < cols; j++ {if matrix[i][j] == '1' {heights[j]++} else {heights[j] = 0}}// 使用单调栈计算当前行的最大矩形area := largestRectangleAreaOptimized(heights)maxArea = max(maxArea, area)}return maxArea
}func largestRectangleAreaOptimized(heights []int) int {stack := []int{}maxArea := 0// 添加哨兵heights = append([]int{0}, heights...)heights = append(heights, 0)for i := 0; i < len(heights); i++ {for len(stack) > 0 && heights[stack[len(stack)-1]] > heights[i] {height := heights[stack[len(stack)-1]]stack = stack[:len(stack)-1]width := i - stack[len(stack)-1] - 1maxArea = max(maxArea, height*width)}stack = append(stack, i)}return maxArea
}// 方法3:暴力枚举
// 时间复杂度:O(m³ × n³),空间复杂度:O(1)
func maximalRectangleBruteForce(matrix [][]byte) int {if len(matrix) == 0 || len(matrix[0]) == 0 {return 0}rows, cols := len(matrix), len(matrix[0])maxArea := 0for i := 0; i < rows; i++ {for j := 0; j < cols; j++ {if matrix[i][j] == '1' {// 枚举以(i,j)为左上角的矩形for height := 1; i+height-1 < rows; height++ {for width := 1; j+width-1 < cols; width++ {if isValidRectangle(matrix, i, j, height, width) {area := height * widthmaxArea = max(maxArea, area)}}}}}}return maxArea
}func isValidRectangle(matrix [][]byte, row, col, height, width int) bool {for i := row; i < row+height; i++ {for j := col; j < col+width; j++ {if matrix[i][j] == '0' {return false}}}return true
}// 方法4:前缀和优化
// 时间复杂度:O(m² × n²),空间复杂度:O(m × n)
func maximalRectanglePrefixSum(matrix [][]byte) int {if len(matrix) == 0 || len(matrix[0]) == 0 {return 0}rows, cols := len(matrix), len(matrix[0])// 构建前缀和数组prefixSum := make([][]int, rows+1)for i := range prefixSum {prefixSum[i] = make([]int, cols+1)}for i := 1; i <= rows; i++ {for j := 1; j <= cols; j++ {prefixSum[i][j] = prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1]if matrix[i-1][j-1] == '1' {prefixSum[i][j]++}}}maxArea := 0// 枚举矩形for i1 := 0; i1 < rows; i1++ {for j1 := 0; j1 < cols; j1++ {for i2 := i1; i2 < rows; i2++ {for j2 := j1; j2 < cols; j2++ {// 计算矩形内1的个数count := prefixSum[i2+1][j2+1] - prefixSum[i2+1][j1] - prefixSum[i1][j2+1] + prefixSum[i1][j1]expected := (i2 - i1 + 1) * (j2 - j1 + 1)if count == expected {area := (i2 - i1 + 1) * (j2 - j1 + 1)maxArea = max(maxArea, area)}}}}}return maxArea
}// 工具函数
func max(a, b int) int {if a > b {return a}return b
}// 打印矩阵辅助函数
func printMatrix(matrix [][]byte) {for _, row := range matrix {for _, cell := range row {fmt.Printf("%c ", cell)}fmt.Println()}
}// 测试函数
func runTests() {fmt.Println("=== 85. 最大矩形 测试 ===")// 测试用例1matrix1 := [][]byte{{'1', '0', '1', '0', '0'},{'1', '0', '1', '1', '1'},{'1', '1', '1', '1', '1'},{'1', '0', '0', '1', '0'},}expected1 := 6fmt.Printf("\n测试用例1:\n")fmt.Printf("输入矩阵:\n")printMatrix(matrix1)fmt.Printf("期望结果: %d\n", expected1)result1_1 := maximalRectangle(matrix1)result1_2 := maximalRectangleDP(matrix1)result1_3 := maximalRectangleBruteForce(matrix1)result1_4 := maximalRectanglePrefixSum(matrix1)fmt.Printf("方法一(单调栈): %d ✓\n", result1_1)fmt.Printf("方法二(动态规划): %d ✓\n", result1_2)fmt.Printf("方法三(暴力枚举): %d ✓\n", result1_3)fmt.Printf("方法四(前缀和): %d ✓\n", result1_4)// 测试用例2:边界情况matrix2 := [][]byte{{'0'}}expected2 := 0fmt.Printf("\n测试用例2(全0矩阵):\n")fmt.Printf("输入矩阵:\n")printMatrix(matrix2)fmt.Printf("期望结果: %d\n", expected2)result2 := maximalRectangle(matrix2)fmt.Printf("结果: %d ✓\n", result2)// 测试用例3:单个1matrix3 := [][]byte{{'1'}}expected3 := 1fmt.Printf("\n测试用例3(单个1):\n")fmt.Printf("输入矩阵:\n")printMatrix(matrix3)fmt.Printf("期望结果: %d\n", expected3)result3 := maximalRectangle(matrix3)fmt.Printf("结果: %d ✓\n", result3)// 测试用例4:全1矩阵matrix4 := [][]byte{{'1', '1', '1'},{'1', '1', '1'},{'1', '1', '1'},}expected4 := 9fmt.Printf("\n测试用例4(全1矩阵):\n")fmt.Printf("输入矩阵:\n")printMatrix(matrix4)fmt.Printf("期望结果: %d\n", expected4)result4 := maximalRectangle(matrix4)fmt.Printf("结果: %d ✓\n", result4)// 测试用例5:单行矩阵matrix5 := [][]byte{{'1', '0', '1', '1'}}expected5 := 2fmt.Printf("\n测试用例5(单行矩阵):\n")fmt.Printf("输入矩阵:\n")printMatrix(matrix5)fmt.Printf("期望结果: %d\n", expected5)result5 := maximalRectangle(matrix5)fmt.Printf("结果: %d ✓\n", result5)// 测试用例6:单列矩阵matrix6 := [][]byte{{'1'},{'1'},{'0'},{'1'},}expected6 := 2fmt.Printf("\n测试用例6(单列矩阵):\n")fmt.Printf("输入矩阵:\n")printMatrix(matrix6)fmt.Printf("期望结果: %d\n", expected6)result6 := maximalRectangle(matrix6)fmt.Printf("结果: %d ✓\n", result6)// 测试用例7:复杂情况matrix7 := [][]byte{{'1', '1', '1', '1', '1'},{'1', '0', '0', '0', '1'},{'1', '1', '1', '1', '1'},}expected7 := 5fmt.Printf("\n测试用例7(复杂情况):\n")fmt.Printf("输入矩阵:\n")printMatrix(matrix7)fmt.Printf("期望结果: %d\n", expected7)result7 := maximalRectangle(matrix7)fmt.Printf("结果: %d ✓\n", result7)// 详细分析示例fmt.Printf("\n=== 详细分析示例 ===\n")analyzeExample(matrix1)fmt.Printf("\n=== 算法复杂度对比 ===\n")fmt.Printf("方法一(单调栈):     时间 O(m×n),    空间 O(n)     - 推荐\n")fmt.Printf("方法二(动态规划):   时间 O(m×n),    空间 O(n)     - 优化高度计算\n")fmt.Printf("方法三(暴力枚举):   时间 O(m³×n³),  空间 O(1)     - 简单易懂\n")fmt.Printf("方法四(前缀和):     时间 O(m²×n²),  空间 O(m×n)   - 中等效率\n")
}// 详细分析示例
func analyzeExample(matrix [][]byte) {fmt.Printf("分析矩阵:\n")printMatrix(matrix)rows, cols := len(matrix), len(matrix[0])heights := make([]int, cols)fmt.Printf("\n高度数组构建过程:\n")for i := 0; i < rows; i++ {// 更新高度数组for j := 0; j < cols; j++ {if matrix[i][j] == '1' {heights[j]++} else {heights[j] = 0}}fmt.Printf("第%d行高度数组: %v\n", i, heights)area := largestRectangleArea(heights)fmt.Printf("第%d行最大矩形面积: %d\n", i, area)}fmt.Printf("\n全局最大矩形面积: %d\n", maximalRectangle(matrix))
}func main() {runTests()
}
http://www.dtcms.com/a/291124.html

相关文章:

  • Excel函数 —— UNIQUE 去重提取唯一值
  • 智能码表新革命:VTX316-TTS语音芯片如何重塑骑行体验
  • 【补充】Linux内核链表机制
  • C语言自定义类型:联合体和枚举
  • CS231n-2017 Lecture4神经网络笔记
  • 【爬虫】05 - 爬虫攻防
  • 车载软件架构 --- 软件开发面临的问题
  • 神经网络——归一化层
  • 从 C# 到 Python:项目实战第五天的飞跃
  • Ubuntu 22 集群部署 Apache Doris 3.0.3 笔记
  • 音视频重回顾及nat内网穿透相关再整理笔记
  • Ubuntu 22.04 安装 Docker (安装包形式)
  • ESP32-S3 小电视学习笔记1:分光棱镜、QMI8658六轴惯导计、1.3英寸LCD屏
  • 4.Java创建对象有几种方式?
  • Spring Cloud——Spring Cloud LoadBalancer
  • 7月21日总结
  • C/C++---emplace和emplace_back
  • 企业IT管理——IT系统灾难恢复计划及实施步骤参考模板
  • rk3588 Android 12 添加framework层服务,HAL库,从硬件驱动层到上层APP,实现led灯控
  • OpenAI开发的一款实验性大型语言模型(LLM),在2025年国际数学奥林匹克竞赛(IMO)中达到了金牌水平
  • 数智管理学(三十七)
  • liunx宝塔面板部署easyswoole项目
  • 常规笔记本和加固笔记本的区别
  • React 中使用immer修改state摆脱“不可变”
  • 打造自己的 Jar 文件分析工具:类名匹配 + 二进制搜索 + 日志输出全搞定
  • 从一开始的网络攻防(六):php反序列化
  • UART串口
  • 什么是内网穿透?本地内网无公网IP如何实现互联网上远程访问?
  • 每日一题7.21
  • 自动化商品监控:利用淘宝API开发实时价格库存采集接口