leetcode
leetcode 1701 ~ 1750
可移除字符的最大数目

可移除字符的最大数目

难度:

标签:

题目描述

代码结果

运行时间: 649 ms, 内存: 26.3 MB


/*
 * 思路:
 * 1. 使用二分查找法来确定最大的k值。
 * 2. 对于每一个k值,移除前k个removable中的字符,并检查p是否仍然是s的子序列。
 * 3. 为了高效地检查p是否是s的子序列,使用一个辅助函数isSubsequence。
 * 4. 使用Java Stream API来实现这一逻辑。
 */
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

public class Solution {
    public int maximumRemovals(String s, String p, int[] removable) {
        int left = 0, right = removable.length;
        while (left < right) {
            int mid = (left + right + 1) / 2;
            if (isSubsequence(s, p, removable, mid)) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
    
    private boolean isSubsequence(String s, String p, int[] removable, int k) {
        Set<Integer> removedIndices = Arrays.stream(removable, 0, k).boxed().collect(Collectors.toSet());
        String filteredS = s.chars()
                            .filter(i -> !removedIndices.contains(i))
                            .mapToObj(c -> String.valueOf((char)c))
                            .collect(Collectors.joining());
        return isSubsequence(filteredS, p);
    }
    
    private boolean isSubsequence(String s, String p) {
        int j = 0;
        for (int i = 0; i < s.length() && j < p.length(); i++) {
            if (s.charAt(i) == p.charAt(j)) {
                j++;
            }
        }
        return j == p.length();
    }
}

解释

方法:

本题解采用二分查找来确定最大的 k 值。我们定义两个边界:left 和 right,初始化 left 为 0,right 为 removable.length + 1。我们在这个范围内进行二分搜索,检查每一个中间值 mid。对于每个 mid,我们尝试从字符串 s 中移除前 mid 个在 removable 数组中指定的字符。移除后,我们检查 p 是否仍然是新字符串的子序列。如果是,我们将搜索范围的左边界移动到 mid + 1;如果不是,我们将右边界设置为 mid。继续这个过程,直到 left 和 right 相遇。最终,left - 1 将是满足条件的最大 k 值。

时间复杂度:

O(removable.length * s.length * log(removable.length))

空间复杂度:

O(s.length)

代码细节讲解

🦆
在二分查找中,为什么初始设置`right`为`removable.length + 1`而不是`removable.length`?这样的设置对算法有何影响?
在二分查找中,设置`right`为`removable.length + 1`是为了包含全部移除`removable`数组中所有元素的情况。二分查找的过程中,通常`right`是不被包括在搜索区间中的。如果设置`right`为`removable.length`,那么最大的`mid`值将是`removable.length - 1`,这意味着最后一个元素永远不会被考虑是否可以移除。通过设置`right`为`removable.length + 1`,可以确保在二分搜索时能够测试到所有可能的移除数量,从0到`removable.length`个元素都能被考虑。
🦆
在二分查找过程中,每次修改字符串后都要检查`p`是否是新字符串`s2`的子序列。这个检查过程是否可以优化以减少不必要的计算?
是的,可以优化这个检查过程。一种方法是使用一个布尔数组来标记哪些字符被移除,而不是真的从字符串中删除字符并重新组合。这样,我们可以在不修改原字符串的情况下,直接通过跳过被标记为移除的字符来检查`p`是否为子序列,这将显著减少字符串操作的开销。另一种可能的优化是使用动态规划或者其他高级数据结构(如线段树或树状数组)来更快地检查子序列的存在性。
🦆
题解采用了直接修改字符串并重新检查子序列的方法,这种方法在实际应用中是否最优?是否存在其他可能的策略来提高算法的效率?
直接修改字符串并重新检查子序列的方法虽然可行,但不是最优。这种方法涉及频繁的字符串操作,可能导致较高的时间复杂度。更优的策略可能包括使用额外的数据结构来避免字符串的频繁修改。例如,可以使用布尔数组或位向量来追踪哪些字符被移除,然后通过忽略这些字符来检查子序列。这减少了字符串操作的需要,可以更快地执行。
🦆
在算法最后返回`left - 1`作为结果,这种处理方式是否可能在某些边界情况下返回错误的结果?请解释这种情况可能发生的原因。
返回`left - 1`通常是正确的,因为在二分查找的过程中,当`mid`使`p`不再是子序列时,`right`会设置为`mid`,这意味着最后一次`p`作为子序列成功的`mid`值实际上是`left - 1`。然而,在极端情况下,如果`p`一开始就不是`s`的子序列,或者没有任何移除操作可以使`p`成为子序列,那么最终的`left`可能会是0,此时`left - 1`会返回-1,这在逻辑上是不正确的。因此,在实际应用中可能需要在返回前加入检查,确保返回值非负。

相关问题