leetcode
leetcode 2451 ~ 2500
最短且字典序最小的美丽子字符串

最短且字典序最小的美丽子字符串

难度:

标签:

题目描述

You are given a binary string s and a positive integer k.

A substring of s is beautiful if the number of 1's in it is exactly k.

Let len be the length of the shortest beautiful substring.

Return the lexicographically smallest beautiful substring of string s with length equal to len. If s doesn't contain a beautiful substring, return an empty string.

A string a is lexicographically larger than a string b (of the same length) if in the first position where a and b differ, a has a character strictly larger than the corresponding character in b.

  • For example, "abcd" is lexicographically larger than "abcc" because the first position they differ is at the fourth character, and d is greater than c.

 

Example 1:

Input: s = "100011001", k = 3
Output: "11001"
Explanation: There are 7 beautiful substrings in this example:
1. The substring "100011001".
2. The substring "100011001".
3. The substring "100011001".
4. The substring "100011001".
5. The substring "100011001".
6. The substring "100011001".
7. The substring "100011001".
The length of the shortest beautiful substring is 5.
The lexicographically smallest beautiful substring with length 5 is the substring "11001".

Example 2:

Input: s = "1011", k = 2
Output: "11"
Explanation: There are 3 beautiful substrings in this example:
1. The substring "1011".
2. The substring "1011".
3. The substring "1011".
The length of the shortest beautiful substring is 2.
The lexicographically smallest beautiful substring with length 2 is the substring "11".

Example 3:

Input: s = "000", k = 1
Output: ""
Explanation: There are no beautiful substrings in this example.

 

Constraints:

  • 1 <= s.length <= 100
  • 1 <= k <= s.length

代码结果

运行时间: 26 ms, 内存: 15.9 MB


/*
 * 思路:
 * 使用Java Stream遍历字符串,筛选出包含恰好k个'1'的子字符串,
 * 并在这些子字符串中找到长度最短且字典序最小的子字符串。
 */
import java.util.Comparator;
import java.util.stream.IntStream;

public class SolutionStream {
    public String minBeautifulSubstring(String s, int k) {
        return IntStream.range(0, s.length())
                .boxed()
                .flatMap(i -> IntStream.range(i + k, s.length() + 1)
                        .mapToObj(j -> s.substring(i, j)))
                .filter(sub -> sub.chars().filter(c -> c == '1').count() == k)
                .min(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()))
                .orElse("");
    }
}

解释

方法:

该解法使用了一个滑动窗口的策略来找到所有包含恰好 k 个 '1' 的子字符串。首先,定义两个指针 c (窗口左边界) 和 x (窗口右边界) 来扫描整个字符串。变量 t 用于记录当前窗口中 '1' 的数量。当 t 大于 k 时,移动左边界 c 直到 t 等于 k。随后检查左边界的 '0',若左边界是 '0' 则向右移动以找到最短的子字符串。每次当 t 等于 k 时,记录当前的子字符串。最后,在记录的所有满足条件的子字符串中找到长度最短且字典序最小的字符串返回。

时间复杂度:

O(n + m log m)

空间复杂度:

O(m * n)

代码细节讲解

🦆
如何确保处理的子字符串始终包含恰好k个'1',而不是超过k个?
在算法中,通过维护一个计数变量 t 来记录窗口内 '1' 的数量。每当 t 大于 k,就移动窗口的左边界 c,直到 t 减少至 k。这个过程通过检查并调整左边界 c 的位置,确保每次当窗口内的 '1' 数量超过 k 时,能够通过移动窗口左边界来减少 '1' 的数量至恰好 k。这样,每次记录子字符串时,都确保了其包含的 '1' 数量恰好为 k。
🦆
在移动左边界以跳过开头的'0'时,为什么没有检查是否还保持有k个'1'?是否可能错过了一些有效的子字符串?
在代码中,移动左边界 c 跳过 '0' 是在确认窗口内已经有 k 个 '1' 之后进行的。这一步只是为了缩短子字符串的长度,并不会改变窗口内 '1' 的数量。因此,这一步并不会导致错过任何有效的子字符串。左边界的移动只发生在 '1' 的数量已经正确匹配为 k 之后,所以不会影响窗口内 '1' 的计数。
🦆
解法中提到当t等于k时记录子字符串,但如果这时候子字符串的开头是'0',这是否会影响到最终的子字符串长度和字典序的判断?
如果子字符串的开头是 '0', 算法中已经包含了逻辑来移动左边界 c 跳过这些 '0'。这样做的目的是确保记录的子字符串长度尽可能短,同时确保字典序尽可能小。因此,这种处理会有助于找到最短且字典序最小的子字符串,而不会影响到最终的长度和字典序判断。
🦆
为什么在找到所有满足条件的子字符串后还需要进行一次排序?这是否影响算法的效率?
在找到所有满足条件的子字符串后,需要进行排序以确保可以选择出字典序最小的字符串。虽然这一步增加了额外的时间复杂度,但是通常情况下,满足条件的子字符串数量并不会非常大,因此排序的代价相对较小。此外,排序是必要的,因为只有这样才能确保结果是字典序最小的。这是一种权衡效率和正确性的实用做法。

相关问题