1. 问题背景与题目解析
今天我们来拆解LeetCode第944题"Delete Columns to Make Sorted"(删列造序)。这是一道关于字符串数组和字典序的经典问题,在2023年LeetCode周赛430中也出现过类似变种。题目要求我们找出需要删除的列数,使得剩下的每一列都是非递减排序的。
问题的具体描述是:给定一个由n个字符串组成的数组strs,所有字符串长度相同。这些字符串可以排列成一个n行m列的矩阵。我们需要检查每一列,如果发现某列不是按字典序非递减排列的,就需要删除这一列。最终返回需要删除的列数。
举个例子:
输入:strs = ["cba","daf","ghi"]
输出:1
解释:
矩阵排列如下:
c b a
d a f
g h i
可以看到第2列('b','a','h')不是按字典序排列的,所以需要删除这一列。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心解题思路与算法选择
2.1 暴力解法分析
最直观的解法就是暴力检查每一列:
- 将字符串数组转换为二维字符矩阵
- 逐列检查是否按字典序排列
- 统计需要删除的列数
这种解法的时间复杂度是O(n*m),其中n是字符串数量,m是字符串长度。空间复杂度是O(1)(如果不考虑输入数据本身的存储)。
2.2 优化思路
在实际编码中,我们可以进行一些优化:
- 不需要显式转换为二维矩阵,直接通过字符串索引访问字符
- 一旦发现当前列的某个字符不满足非递减条件,立即标记该列并跳出循环
- 使用位运算或布尔数组记录需要删除的列
2.3 字典序比较的细节
字典序比较需要注意:
- 比较字符的ASCII码值
- 只需要保证strs[i][j] <= strs[i+1][j]对所有i成立
- 空字符串或单字符串的情况需要特殊处理(但题目保证所有字符串长度相同)
3. 代码实现与性能优化
3.1 Python实现示例
python复制def minDeletionSize(strs):
delete_count = 0
n = len(strs)
if n == 0:
return 0
m = len(strs[0])
for j in range(m):
for i in range(n-1):
if strs[i][j] > strs[i+1][j]:
delete_count += 1
break
return delete_count
3.2 性能优化版本
python复制def minDeletionSize(strs):
return sum(any(a > b for a, b in zip(col, col[1:]))
for col in zip(*strs))
这个版本利用了Python的zip(*strs)来转置矩阵,使列变为行,然后使用生成器表达式和any()函数简洁地判断是否需要删除。
3.3 时间复杂度分析
两种实现的时间复杂度都是O(n*m),但第二种实现更简洁,利用了Python内置函数,实际运行效率可能更高。
4. 边界条件与特殊测试用例
4.1 常见边界情况
- 空输入:strs = []
- 应该返回0
- 单字符串:strs = ["a"]
- 应该返回0
- 所有列都需要删除:
- strs = ["zyx","wvu","tsr"]
- 应该返回3
- 不需要删除任何列:
- strs = ["abc","def","ghi"]
- 应该返回0
4.2 特殊字符处理
题目中的字符不限于小写字母,也可能是大写字母、数字或其他ASCII字符。字典序比较基于ASCII码值,所以:
- "A" < "a"(ASCII 65 < 97)
- "9" < "A"(ASCII 57 < 65)
5. 相关题目扩展与变种
5.1 LeetCode类似题目
- 944的变种:如果要求删除最少的列使得剩下的每一行都是唯一的
-
- Delete Columns to Make Sorted II
-
- Delete Columns to Make Sorted III
5.2 实际应用场景
这类问题在数据库索引优化、表格数据处理、基因组序列分析等领域有实际应用。例如:
- 检查CSV文件的列是否有序
- 优化数据库查询的索引列选择
- 生物信息学中DNA序列的比对
6. 解题技巧与经验分享
6.1 调试技巧
- 打印中间矩阵:在检查每列前打印当前列的所有字符
- 使用断言验证边界条件
- 对于大型输入,可以先测试小样本
6.2 常见错误
- 索引越界:忘记检查输入是否为空
- 错误理解字典序:混淆字符比较和字符串比较
- 过早优化:尝试复杂算法前应先实现简单解法
6.3 性能优化建议
- 对于非常大的输入,考虑并行处理各列
- 使用位掩码记录需要删除的列
- 在C++等语言中,使用vector
可能比bitset更高效
7. 不同语言实现对比
7.1 Java实现
java复制public int minDeletionSize(String[] strs) {
int count = 0;
for (int j = 0; j < strs[0].length(); j++) {
for (int i = 0; i < strs.length - 1; i++) {
if (strs[i].charAt(j) > strs[i + 1].charAt(j)) {
count++;
break;
}
}
}
return count;
}
7.2 C++实现
cpp复制int minDeletionSize(vector<string>& strs) {
int res = 0;
for (int j = 0; j < strs[0].size(); ++j) {
for (int i = 0; i < strs.size() - 1; ++i) {
if (strs[i][j] > strs[i + 1][j]) {
++res;
break;
}
}
}
return res;
}
7.3 JavaScript实现
javascript复制var minDeletionSize = function(strs) {
let count = 0;
for (let j = 0; j < strs[0].length; j++) {
for (let i = 0; i < strs.length - 1; i++) {
if (strs[i][j] > strs[i+1][j]) {
count++;
break;
}
}
}
return count;
};
8. 进阶思考与挑战
8.1 如果允许重新排列行
如果题目改为允许重新排列行(而不只是删除列),问题将变为寻找矩阵的最大有序子矩阵,这是一个更复杂的问题,可能需要动态规划或贪心算法。
8.2 多线程解决方案
对于非常大的矩阵,可以考虑将列检查分配给多个线程并行处理,最后汇总结果。这在Python中可以使用multiprocessing模块实现。
8.3 内存优化
对于极端大的输入(如数百万行),可能需要流式处理,不一次性加载所有数据到内存。这时需要按列读取数据并处理。
在实际面试中,写完基础解法后,可以主动讨论这些进阶思路,展示解决问题的全面思考能力。
