leetcode
leetcode 2351 ~ 2400
字符串连接删减字母

字符串连接删减字母

难度:

标签:

题目描述

You are given a 0-indexed array words containing n strings.

Let's define a join operation join(x, y) between two strings x and y as concatenating them into xy. However, if the last character of x is equal to the first character of y, one of them is deleted.

For example join("ab", "ba") = "aba" and join("ab", "cde") = "abcde".

You are to perform n - 1 join operations. Let str0 = words[0]. Starting from i = 1 up to i = n - 1, for the ith operation, you can do one of the following:

  • Make stri = join(stri - 1, words[i])
  • Make stri = join(words[i], stri - 1)

Your task is to minimize the length of strn - 1.

Return an integer denoting the minimum possible length of strn - 1.

 

Example 1:

Input: words = ["aa","ab","bc"]
Output: 4
Explanation: In this example, we can perform join operations in the following order to minimize the length of str2: 
str0 = "aa"
str1 = join(str0, "ab") = "aab"
str2 = join(str1, "bc") = "aabc" 
It can be shown that the minimum possible length of str2 is 4.

Example 2:

Input: words = ["ab","b"]
Output: 2
Explanation: In this example, str0 = "ab", there are two ways to get str1: 
join(str0, "b") = "ab" or join("b", str0) = "bab". 
The first string, "ab", has the minimum length. Hence, the answer is 2.

Example 3:

Input: words = ["aaa","c","aba"]
Output: 6
Explanation: In this example, we can perform join operations in the following order to minimize the length of str2: 
str0 = "aaa"
str1 = join(str0, "c") = "aaac"
str2 = join("aba", str1) = "abaaac"
It can be shown that the minimum possible length of str2 is 6.
 

 

Constraints:

  • 1 <= words.length <= 1000
  • 1 <= words[i].length <= 50
  • Each character in words[i] is an English lowercase letter

代码结果

运行时间: 302 ms, 内存: 27.2 MB


/* 思路:使用 Java Stream API 实现相同的逻辑。我们通过计算每次 join 操作后的字符串长度,并在流操作中选择最小的长度。 */

import java.util.stream.IntStream;

public class SolutionStream {
    public int minimizeStringLength(String[] words) {
        int n = words.length;
        return IntStream.range(1, n)
                .map(i -> Math.min(words[i - 1].length() + words[i].length() - (words[i - 1].charAt(words[i - 1].length() - 1) == words[i].charAt(0) ? 1 : 0),
                                  words[i].length() + words[i - 1].length() - (words[i].charAt(words[i].length() - 1) == words[i - 1].charAt(0) ? 1 : 0)))
                .reduce(words[0].length(), Integer::sum);
    }
}

解释

方法:

这个题解使用了递归的动态规划方法来解决问题。首先将words数组反转,然后从后向前进行动态规划。使用带缓存的深度优先搜索(DFS)来计算每一步连接操作的最小可能长度。定义dfs函数,其中x表示当前处理的words数组的索引,l和r分别表示当前字符串连接的左侧和右侧可能的匹配字符。如果当前字符串的末尾字符与l匹配,或者起始字符与r匹配,则在连接时可以减少一个字符。对于每个字符串,计算两种连接方式(当前字符串连接到已有字符串的左边或右边)的最小长度,返回这两种方式的最小值。

时间复杂度:

O(n * 2500)

空间复杂度:

O(n * 2500)

代码细节讲解

🦆
为什么题解中选择将words数组进行反转再进行动态规划?反转前后在算法实现上有什么不同?
数组反转的目的在于简化动态规划过程中的状态转移。通常,动态规划需要从一个基本状态逐步构建到最终状态。在这个问题中,如果不进行反转,我们需要从前向后考虑字符串的连接,这将涉及到更复杂的状态追踪,特别是在字符串较多时。反转后,我们可以从后向前计算,使得每次递归调用都是基于已经计算过的结果,简化了问题的复杂度。
🦆
在动态规划的解法中,函数`dfs`的参数`l`和`r`具体代表什么意义?如何理解它们在连接操作中的作用?
`l`和`r`在`dfs`函数中代表当前连接字符串序列的最左侧和最右侧的字符。这两个参数帮助确定当前处理的字符串是否可以与已有的字符串序列在左端或右端减少字符进行连接。例如,如果当前字符串的末尾字符与`l`相匹配,那么在连接到序列左端时可以少计算一个字符长度,因为这两个字符重叠。同理适用于`r`。这样的设计使得算法在连接时能够有效地考虑减少重复字符,优化整体字符串的长度。
🦆
题解中提到如果`words[x][0] == r`或`words[x][-1] == l`可以减少一个字符,这种情况是如何被计算到最终长度的?
在`dfs`函数中,如果`words[x][0] == r`或`words[x][-1] == l`,则表示当前字符串可以在连接点与另一字符串重叠一个字符。具体到代码实现,这会导致在计算连接长度时直接减去一个字符(长度减1)。例如,如果`words[x][-1] == l`,当将`words[x]`连接到左侧时,不需要重复计算`l`字符,因此实际添加到序列的长度是`len(words[x]) - 1`。这一机制在动态规划中被用于寻找可能的最短总长度。
🦆
动态规划缓存中,`l`和`r`的可能值为何是最多50种?这是否意味着每个字符串的长度不能超过50,或者是字符种类限制?
`l`和`r`的可能值最多50种通常指的是字符种类的限制,而非字符串的长度。这意味着假设在使用的字符集中,字符的种类不超过50种(如英文字母的大小写共52种)。在算法中,每个字符的位置可以由一个固定种类的字符来表示,所以`l`和`r`作为单个字符,其可能的值受到字符集大小的限制。这不限制单个字符串的长度,而是限制了考虑的字符种类,确保缓存的有效和实用性。

相关问题