1. 项目概述
作为一名长期奋战在编程竞赛一线的选手,我深知解题训练的重要性。最近我完成了第34、35、36三道编程题的解答,这三道题虽然编号相邻,但涉及的知识点和解题思路却各有特点。今天我就来详细拆解这三道题的解题过程,分享其中用到的核心算法和调试技巧。
2. 题目分析与解题思路
2.1 第34题解析
第34题是一个典型的动态规划问题,要求在一个二维矩阵中找到从左上角到右下角的最短路径。这道题的核心在于状态转移方程的设计。
我采用的解题步骤是:
- 定义dp[i][j]表示到达(i,j)位置的最小路径和
- 初始化第一行和第一列,因为它们的路径是唯一的
- 状态转移方程:dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
- 最终结果是dp[m-1][n-1]
在实际编码中,我发现边界条件的处理特别重要。比如当矩阵只有一行或一列时,需要单独处理。
2.2 第35题解析
第35题是一个二分查找的变种题,要求在旋转排序数组中搜索目标值。这道题的难点在于数组被旋转后,不再是完全有序的。
我的解题思路是:
- 首先找到旋转点(即数组中的最小值位置)
- 根据目标值与数组首尾元素的大小关系,决定在旋转点的哪一侧进行二分查找
- 执行标准的二分查找算法
在调试过程中,我遇到了几个边界情况:
- 数组完全有序(未旋转)
- 数组所有元素相同
- 目标值正好等于旋转点
2.3 第36题解析
第36题是验证数独有效性的问题。需要检查给定的9x9数独是否符合规则,但不要求有解。
我采用了三种不同的验证方法:
- 行验证:检查每一行是否有重复数字
- 列验证:检查每一列是否有重复数字
- 3x3子格验证:检查每个3x3的小格子是否有重复数字
为了提高效率,我使用了位运算来记录数字是否出现过。具体是用一个整数作为位掩码,每个数字对应一个bit位。
3. 代码实现细节
3.1 第34题实现
python复制def minPathSum(grid):
m, n = len(grid), len(grid[0])
dp = [[0]*n for _ in range(m)]
dp[0][0] = grid[0][0]
# 初始化第一列
for i in range(1, m):
dp[i][0] = dp[i-1][0] + grid[i][0]
# 初始化第一行
for j in range(1, n):
dp[0][j] = dp[0][j-1] + grid[0][j]
# 填充dp表
for i in range(1, m):
for j in range(1, n):
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
return dp[m-1][n-1]
3.2 第35题实现
python复制def search(nums, target):
left, right = 0, len(nums)-1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 左半部分有序
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
# 右半部分有序
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
3.3 第36题实现
python复制def isValidSudoku(board):
rows = [0] * 9
cols = [0] * 9
boxes = [0] * 9
for i in range(9):
for j in range(9):
if board[i][j] == '.':
continue
num = int(board[i][j])
pos = 1 << (num - 1)
# 检查行
if rows[i] & pos:
return False
rows[i] |= pos
# 检查列
if cols[j] & pos:
return False
cols[j] |= pos
# 检查3x3格子
box_idx = (i // 3) * 3 + j // 3
if boxes[box_idx] & pos:
return False
boxes[box_idx] |= pos
return True
4. 调试经验与优化技巧
4.1 动态规划的空间优化
在第34题的实现中,我最初使用了O(mn)的空间来存储dp表。后来发现可以优化到O(n)的空间复杂度,因为每次计算只需要上一行的数据。
优化后的实现:
python复制def minPathSum(grid):
m, n = len(grid), len(grid[0])
dp = [0] * n
dp[0] = grid[0][0]
# 初始化第一行
for j in range(1, n):
dp[j] = dp[j-1] + grid[0][j]
for i in range(1, m):
dp[0] += grid[i][0]
for j in range(1, n):
dp[j] = min(dp[j], dp[j-1]) + grid[i][j]
return dp[-1]
4.2 二分查找的边界处理
在第35题的调试过程中,我发现当数组包含重复元素时,原来的算法会失效。因此增加了一个预处理步骤来跳过重复元素:
python复制while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
4.3 位运算的巧妙应用
第36题中使用位运算来记录数字出现情况是一个效率很高的方法。每个数字对应一个bit位,通过位与运算可以快速判断是否重复。
5. 测试用例设计
为了确保代码的正确性,我为每道题设计了多组测试用例:
5.1 第34题测试用例
- 常规矩阵
- 单行矩阵
- 单列矩阵
- 大矩阵(性能测试)
5.2 第35题测试用例
- 正常旋转数组
- 未旋转的有序数组
- 包含重复元素的数组
- 目标值不存在的情况
5.3 第36题测试用例
- 有效数独
- 行重复
- 列重复
- 3x3子格重复
- 包含多个错误的数独
6. 性能分析与优化
6.1 时间复杂度分析
- 第34题:O(mn),必须遍历整个矩阵
- 第35题:O(logn),标准的二分查找复杂度
- 第36题:O(1),因为数独大小固定为9x9
6.2 空间复杂度优化
- 第34题:从O(mn)优化到O(n)
- 第35题:O(1),不需要额外空间
- 第36题:O(1),使用了固定大小的数组
6.3 实际运行测试
在LeetCode平台上,优化后的实现都达到了运行时间的前90%百分位,证明了这些优化的有效性。