0955-删列造序 II
给定由 n
个字符串组成的数组 strs
,其中每个字符串长度相等。
选取一个删除索引序列,对于 strs
中的每个字符串,删除对应每个索引处的字符。
比如,有 strs = ["abcdef", "uvwxyz"]
,删除索引序列 {0, 2, 3}
,删除后 strs
为["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:贪心
想法
针对该问题,我们考虑保留哪些列去获得最终的有序结果,而不是删除哪些列。
如果第一列不是字典序排列的,我们就必须删除它。
否则,我们需要讨论是否保留第一列。会出现以下两种情况:
如果我们不保留第一列,则最后答案的行需要保证有序;
如果我们保留了第一列,那么最终答案的行(除去第一列)只需要在第一个字母相同的情况下需要保证有序。
这个描述很难理解,看下面的例子:
假设我们有
A = ["axx", "ayy", "baa", "bbb", "bcc"]
,当我们保留第一列之后,最终行变成R = ["xx", "yy", "aa", "bb", "cc"]
,对于这些行,并不要求所有有序(R[0] <= R[1] <= R[2] <= R[3] <= R[4]
),只需要达到一个较弱的要求:对于第一个字母相同的行保证有序(R[0] <= R[1]
和R[2] <= R[3] <= R[4]
)。
现在,我们只将结论应用到第一列,但实际上这个结论对每列都合适。如果我们不能取用第一列,就删除它。否则,我们就取用第一列,因为无论如何都可以使要求更简单。
算法
首先没有任意列保留,对于每一列:如果保留后结果保持有序,就保留这一列;否则删除它。
1 | class Solution { |
1 | class Solution(object): |
复杂度分析
- 时间复杂度:O(NW^2),其中 N 是
A
的长度,W 是A[i]
的长度。 - 空间复杂度:O(NW)。
方法 2:优化贪心
解释
方法 1 可以用更少的空间和时间。
核心思路是记录每一列的”割“信息。在第一个例子中,A = ["axx","ayy","baa","bbb","bcc"]
(R
也是相同的定义),第一列将条件 R[0] <= R[1] <= R[2] <= R[3] <= R[4]
切成了 R[0] <= R[1]
和 R[2] <= R[3] <= R[4]
。也就是说,"a" == column[1] != column[2] == "b"
”切割“了 R
中的一个条件。
从更高层面上说,我们的算法只需要考虑新加进的列是否保证有序。通过维护”割“的信息,只需要比较新列的字符。
1 | class Solution { |
1 | class Solution(object): |
复杂度分析
- 时间复杂度:O(NW),其中 N 是
A
的长度,W 是A[i]
的长度。 - 空间复杂度:额外空间开销 O(N)(在 Python 中,
zip(*A)
需要 O(NW) 的空间)。