Medium

题目描述

给定由 n 个字符串组成的数组 strs,所有字符串都具有相同的长度。

我们可以选择任意删除索引,并删除每个字符串中这些索引处的字符。

例如,如果我们有 strs = [“abcdef”,“uvwxyz”] 和删除索引 {0, 2, 3},那么删除后的最终数组是 [“bef”, “vyz”]。

假设我们选择了一组删除索引 answer,使得删除后,最终数组的元素按字典序排列(即 strs[0] <= strs[1] <= strs[2] <= … <= strs[n - 1])。返回 answer.length 的最小可能值。

示例 1:

输入:strs = ["ca","bb","ac"]
输出:1
解释:
删除第一列后,strs = ["a", "b", "c"]。
现在 strs 按字典序排列(即 strs[0] <= strs[1] <= strs[2])。
我们至少需要 1 次删除,因为最初 strs 不是按字典序排列的,所以答案是 1。

示例 2:

输入:strs = ["xc","yb","za"]
输出:0
解释:
strs 已经按字典序排列,所以我们不需要删除任何内容。
注意 strs 的行不一定按字典序排列:
即,不一定满足 (strs[0][0] <= strs[0][1] <= ...)

示例 3:

输入:strs = ["zyx","wvu","tsr"]
输出:3
解释:我们必须删除每一列。

提示:

  • n == strs.length
  • 1 <= n <= 100
  • 1 <= strs[i].length <= 100
  • strs[i] 由小写英文字母组成

解题思路

这道题的核心思想是贪心策略:我们逐列检查,如果当前列能够保持或增强字典序,就保留它;否则就删除。

关键洞察:

  1. 已确定顺序的字符串对:一旦某两个字符串在前面的列中已经确定了字典序关系(如 str[i] < str[i+1]),那么后续列对这两个字符串的顺序就不再重要了
  2. 未确定顺序的字符串对:只有那些在前面所有保留列中都相等的字符串对,才需要继续考虑它们在当前列的大小关系

算法流程:

  1. 维护一个 sorted 数组,标记哪些相邻字符串对已经确定了字典序关系
  2. 对每一列,先检查是否会破坏已有的字典序:对于未确定顺序的相邻字符串对,如果当前列使得前面的字符大于后面的字符,则必须删除当前列
  3. 如果当前列不会破坏字典序,则保留它,并更新 sorted 数组:对于那些在当前列中前面字符小于后面字符的未确定字符串对,将其标记为已确定

这是一个典型的贪心问题,每次都做局部最优选择(能保留就保留),最终得到全局最优解。

推荐解法:贪心算法,时间复杂度最优。

代码实现

class Solution {
public:
    int minDeletionSize(vector<string>& strs) {
        int n = strs.size();
        int m = strs[0].size();
        vector<bool> sorted(n - 1, false);
        int deletions = 0;
        
        for (int j = 0; j < m; j++) {
            bool needDelete = false;
            
            // 检查当前列是否破坏字典序
            for (int i = 0; i < n - 1; i++) {
                if (!sorted[i] && strs[i][j] > strs[i + 1][j]) {
                    needDelete = true;
                    break;
                }
            }
            
            if (needDelete) {
                deletions++;
            } else {
                // 更新已确定顺序的字符串对
                for (int i = 0; i < n - 1; i++) {
                    if (!sorted[i] && strs[i][j] < strs[i + 1][j]) {
                        sorted[i] = true;
                    }
                }
            }
        }
        
        return deletions;
    }
};
class Solution:
    def minDeletionSize(self, strs: List[str]) -> int:
        n = len(strs)
        m = len(strs[0])
        sorted_pairs = [False] * (n - 1)
        deletions = 0
        
        for j in range(m):
            need_delete = False
            
            # 检查当前列是否破坏字典序
            for i in range(n - 1):
                if not sorted_pairs[i] and strs[i][j] > strs[i + 1][j]:
                    need_delete = True
                    break
            
            if need_delete:
                deletions += 1
            else:
                # 更新已确定顺序的字符串对
                for i in range(n - 1):
                    if not sorted_pairs[i] and strs[i][j] < strs[i + 1][j]:
                        sorted_pairs[i] = True
        
        return deletions
public class Solution {
    public int MinDeletionSize(string[] strs) {
        int n = strs.Length;
        int m = strs[0].Length;
        bool[] sorted = new bool[n - 1];
        int deletions = 0;
        
        for (int j = 0; j < m; j++) {
            bool needDelete = false;
            
            // 检查当前列是否破坏字典序
            for (int i = 0; i < n - 1; i++) {
                if (!sorted[i] && strs[i][j] > strs[i + 1][j]) {
                    needDelete = true;
                    break;
                }
            }
            
            if (needDelete) {
                deletions++;
            } else {
                // 更新已确定顺序的字符串对
                for (int i = 0; i < n - 1; i++) {
                    if (!sorted[i] && strs[i][j] < strs[i + 1][j]) {
                        sorted[i] = true;
                    }
                }
            }
        }
        
        return deletions;
    }
}
var minDeletionSize = function(strs) {
    const n = strs.length;
    const m = strs[0].length;
    const sorted = new Array(n - 1).fill(false);
    let deletions = 0;
    
    for (let j = 0; j < m; j++) {
        let needDelete = false;
        
        // 检查当前列是否破坏字典序
        for (let i = 0; i < n - 1; i++) {
            if (!sorted[i] && strs[i][j] > strs[i + 1][j]) {
                needDelete = true;
                break;
            }
        }
        
        if (needDelete) {
            deletions++;
        } else {
            // 更新已确定顺序的字符串对
            for (let i = 0; i < n - 1; i++) {
                if (!sorted[i] && strs[i][j] < strs[i + 1][j]) {
                    sorted[i] = true;
                }
            }
        }
    }
    
    return deletions;
};

复杂度分析

复杂度类型说明
时间复杂度O(n × m)n 为字符串个数,m 为字符串长度,需要遍历每一列
空间复杂度O(n)需要 sorted 数组记录相邻字符串对的状态