leetcode
leetcode 2251 ~ 2300
最少得分子序列

最少得分子序列

难度:

标签:

题目描述

You are given two strings s and t.

You are allowed to remove any number of characters from the string t.

The score of the string is 0 if no characters are removed from the string t, otherwise:

  • Let left be the minimum index among all removed characters.
  • Let right be the maximum index among all removed characters.

Then the score of the string is right - left + 1.

Return the minimum possible score to make t a subsequence of s.

A subsequence of a string is a new string that is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (i.e., "ace" is a subsequence of "abcde" while "aec" is not).

 

Example 1:

Input: s = "abacaba", t = "bzaa"
Output: 1
Explanation: In this example, we remove the character "z" at index 1 (0-indexed).
The string t becomes "baa" which is a subsequence of the string "abacaba" and the score is 1 - 1 + 1 = 1.
It can be proven that 1 is the minimum score that we can achieve.

Example 2:

Input: s = "cde", t = "xyz"
Output: 3
Explanation: In this example, we remove characters "x", "y" and "z" at indices 0, 1, and 2 (0-indexed).
The string t becomes "" which is a subsequence of the string "cde" and the score is 2 - 0 + 1 = 3.
It can be proven that 3 is the minimum score that we can achieve.

 

Constraints:

  • 1 <= s.length, t.length <= 105
  • s and t consist of only lowercase English letters.

代码结果

运行时间: 87 ms, 内存: 20.2 MB


/*
 * 思路:
 * 1. 使用Java Stream API来处理前缀和后缀匹配。
 * 2. 我们需要在字符串 t 中删除字符,使得 t 成为 s 的子序列。
 * 3. 使用双指针的方法,一个从 t 的开头向后遍历,另一个从 t 的结尾向前遍历,来确定删除区间。
 */

import java.util.stream.IntStream;

public class Solution {
    public int minScore(String s, String t) {
        int n = s.length(), m = t.length();
        int[] prefix = new int[m + 1];
        int[] suffix = new int[m + 1];
        
        IntStream.range(0, m).forEach(i -> {
            if (i < n && t.charAt(i) == s.charAt(i)) {
                prefix[i + 1] = prefix[i] + 1;
            } else {
                prefix[i + 1] = prefix[i];
            }
        });
        
        IntStream.range(0, m).forEach(i -> {
            if (i < n && t.charAt(m - 1 - i) == s.charAt(n - 1 - i)) {
                suffix[i + 1] = suffix[i] + 1;
            } else {
                suffix[i + 1] = suffix[i];
            }
        });
        
        return IntStream.rangeClosed(0, m)
                .filter(i -> prefix[i] + suffix[m - i] >= n)
                .map(i -> Math.max(0, m - n))
                .min().orElse(Integer.MAX_VALUE);
    }
}

解释

方法:

该题解采用了一种双指针和后缀数组的混合策略,来确定最小得分。首先,从后向前遍历字符串s,同时使用指针j跟踪字符串t,寻找t作为s的子序列的最后一个字符的位置。这个位置存储在suf数组中,suf[i]表示s从i开始,t需要删除到哪个位置才能成为s的子序列。然后,从前向后遍历字符串s,同时更新指针j来检查s的每个字符是否与t的当前字符匹配,如果匹配,则移动j。对于每个匹配,计算得分suf[i + 1] - j,这是当前i的最小得分。最后,返回所有可能得分中的最小值。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在题解中,后缀数组suf的具体作用是什么?能不能详细解释一下它如何帮助确定t需要删除到哪个位置?
在题解中,后缀数组suf用于记录对于字符串s中的每个位置i,字符串t要成为s的子序列,需要删除到的位置。具体来说,suf[i]存储的是字符串t的索引位置,表示从t的这个位置往前(包括此位置),所有字符都已经在s的索引i及之前找到匹配。这样,suf数组帮助我们理解为了让t成为s从位置i开始的子序列,t需要保留哪些字符,从而可以计算出需要删除的字符数量。通过这种方式,我们可以针对每个s的位置,快速得到t需要调整的程度,进而计算出每个位置的得分。
🦆
题解中提到,通过一次反向遍历s来建立suf数组。为什么选择反向遍历,而不是正向遍历s字符串来构建suf数组?
选择反向遍历s字符串来构建suf数组的原因是,这样可以更自然地处理t的字符与s的匹配顺序。当我们从s的末尾开始向前遍历时,我们逐渐构建t作为s子序列的可能性。这使我们能够在遇到匹配的字符时,逐步减小j的值(即t的索引),这能够保证t的这部分是s从当前i位置到末尾的有效子序列。如果我们从前向后遍历s,则每次匹配后处理t的剩余部分会变得复杂,因为我们需要不断更新和回溯t的索引位置,这在逻辑上和实现上都更为复杂。
🦆
如何理解题解中提到的'对于每个匹配,计算得分suf[i + 1] - j',这里的suf[i + 1]和j各代表什么?
在题解中,'suf[i + 1] - j'用于计算每个匹配位置的得分。这里,suf[i + 1]表示在字符串s的位置i+1及之后,t成为s的子序列需要删除t中字符到的位置。而j是当前匹配到的t的字符位置,表示t中前j个字符已经在s中找到了匹配。因此,suf[i + 1] - j实际上表示从t的第j个位置到suf[i + 1]的位置,t需要保留的字符数量,即s[i]位置处的得分。这个得分反映了在i位置匹配之后,为了保持t作为s的子序列,t需要做出的调整。

相关问题