0955-删列造序 II

Raphael Liu Lv10

给定由 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
public int minDeletionSize(String[] A) {
int N = A.length;
int W = A[0].length();
int ans = 0;

// cur : all rows we have written
// For example, with A = ["abc","def","ghi"] we might have
// cur = ["ab", "de", "gh"].
String[] cur = new String[N];
for (int j = 0; j < W; ++j) {
// cur2 : What we potentially can write, including the
// newest column col = [A[i][j] for i]
// Eg. if cur = ["ab","de","gh"] and col = ("c","f","i"),
// then cur2 = ["abc","def","ghi"].
String[] cur2 = Arrays.copyOf(cur, N);
for (int i = 0; i < N; ++i)
cur2[i] += A[i].charAt(j);

if (isSorted(cur2))
cur = cur2;
else
ans++;
}

return ans;
}

public boolean isSorted(String[] A) {
for (int i = 0; i < A.length - 1; ++i)
if (A[i].compareTo(A[i+1]) > 0)
return false;

return true;
}
}
[]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution(object):
def minDeletionSize(self, A):
def is_sorted(A):
return all(A[i] <= A[i+1] for i in xrange(len(A) - 1))

ans = 0
# cur : all rows we have written
# For example, with A = ["abc","def","ghi"] we might have
# cur = ["ab", "de", "gh"].
cur = [""] * len(A)

for col in zip(*A):
# cur2 : What we potentially can write, including the
# newest column 'col'.
# Eg. if cur = ["ab","de","gh"] and col = ("c","f","i"),
# then cur2 = ["abc","def","ghi"].
cur2 = cur[:]
for i, letter in enumerate(col):
cur2[i] = cur2[i] + letter

if is_sorted(cur2):
cur = cur2
else:
ans += 1

return ans

复杂度分析

  • 时间复杂度: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public int minDeletionSize(String[] A) {
int N = A.length;
int W = A[0].length();
// cuts[j] is true : we don't need to check any new A[i][j] <= A[i][j+1]
boolean[] cuts = new boolean[N-1];

int ans = 0;
search: for (int j = 0; j < W; ++j) {
// Evaluate whether we can keep this column
for (int i = 0; i < N-1; ++i)
if (!cuts[i] && A[i].charAt(j) > A[i+1].charAt(j)) {
// Can't keep the column - delete and continue
ans++;
continue search;
}

// Update 'cuts' information
for (int i = 0; i < N-1; ++i)
if (A[i].charAt(j) < A[i+1].charAt(j))
cuts[i] = true;
}

return ans;
}
}

[]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution(object):
def minDeletionSize(self, A):
# cuts[i] is True : we don't need to check col[i] <= col[i+1]
cuts = [False] * (len(A) - 1)

ans = 0
for col in zip(*A):
if all(cuts[i] or col[i] <= col[i+1] for i in xrange(len(col) - 1)):
for i in xrange(len(col) - 1):
if col[i] < col[i+1]:
cuts[i] = True
else:
ans += 1
return ans

复杂度分析

  • 时间复杂度:O(NW),其中 N 是 A 的长度,W 是 A[i] 的长度。
  • 空间复杂度:额外空间开销 O(N)(在 Python 中,zip(*A) 需要 O(NW) 的空间)。
 Comments
On this page
0955-删列造序 II