一緒に創造し、成長するために一緒に働きましょう!「ナゲッツデイリー新プラン・8月アップデートチャレンジ」参加30日目、イベント詳細はこちら
少なくとも K 個の繰り返し文字を含む最長部分文字列
文字列 s と整数 k が与えられた場合、s 内の最長の部分文字列を見つけ、部分文字列内の各文字が k 以上出現する必要があります。この部分文字列の長さを返します。
- 例 1:
- 入力: s = "aaabb"、k = 3
- 出力: 3
- 説明: 最長のサブストリングは「aaa」で、「a」が 3 回繰り返されます。
- 例 2:
-
入力: s = "ababbc"、k = 2
-
出力: 5
-
説明: 最長のサブストリングは「ababb」で、「a」が 2 回繰り返され、「b」が 3 回繰り返されます。
-
方法 1: 分割統治 (Java)
文字列 s の場合、出現回数が 0 より大きく k より小さい文字 ch がある場合、ch を含む部分文字列が要件を満たすことは不可能です。
つまり、文字列を ch に従っていくつかのセグメントに分割する場合、要件を満たす最長の部分文字列は、セグメント化されるセグメントに出現する必要があり、1 つ以上のセグメントにまたがることはできません。
- 具体的なアイデア:
- 全体として考えると、文字が文字列全体で < k 回出現する場合、その文字は正当な部分文字列に出現してはなりません。
- s: aaabbaa,k: 3, b は 2 回しか現れず、正当な部分文字列には現れないので、両側で見つけることができます。
- aaa と aa を考えて、それをより小さな部分問題に変えて、aaa と aa で有効な部分文字列の最長の長さを再帰的に見つけます。
- 部分問題への再帰のサイズが十分に小さい場合、つまり、部分文字列の長さが k 未満の場合、部分文字列の文字が同じであっても、文字の出現回数は k 未満です。であるため、有効な部分文字列がなく、0 が返されます。
class Solution {
public int longestSubstring(String s, int k) {
int n = s.length();
return dfs(s, 0, n - 1, k);
}
public int dfs(String s, int l, int r, int k) {
int[] cnt = new int[26];
for (int i = l; i <= r; i++) {
cnt[s.charAt(i) - 'a']++;
}
char split = 0;
for (int i = 0; i < 26; i++) {
if (cnt[i] > 0 && cnt[i] < k) {
split = (char) (i + 'a');
break;
}
}
if (split == 0) {
return r - l + 1;
}
int i = l;
int ret = 0;
while (i <= r) {
while (i <= r && s.charAt(i) == split) {
i++;
}
if (i > r) {
break;
}
int start = i;
while (i <= r && s.charAt(i) != split) {
i++;
}
int length = dfs(s, start, i - 1, k);
ret = Math.max(ret, length);
}
return ret;
}
}
复制代码
N: 文字列の長さ
Σは文字セットです
時間計算量: O(N⋅∣Σ∣)
空間複雑度: O(∣Σ∣^2)
方法 2: スライド ウィンドウ (行く)
特定の数の文字タイプ t に対して、スライディング ウィンドウの左右の境界 l、r、スライディング ウィンドウ内の各文字の出現回数、およびスライディング ウィンドウ内の文字タイプの総数を維持します。
total>t の場合、左の境界 l を右にシフトし続け、それに応じて cnt と total を total≤t まで更新します。このようにして、任意の右境界 r に対して、最小の l (l_{min} として示される) を見つけることができるため、s[l_{min}...r] 間の文字タイプの数は t を超えません。
追加のカウンターを少なく維持することで、cnt 配列をトラバースしなくても、各文字が少なくとも k 回出現するかどうかを知ることができ、ループするたびに一定時間でカウンター値を更新できます。
func longestSubstring(s string, k int) (ans int) {
for t := 1; t <= 26; t++ {
cnt := [26]int{}
total := 0
lessK := 0
l := 0
for r, ch := range s {
ch -= 'a'
if cnt[ch] == 0 {
total++
lessK++
}
cnt[ch]++
if cnt[ch] == k {
lessK--
}
for total > t {
ch := s[l] - 'a'
if cnt[ch] == k {
lessK++
}
cnt[ch]--
if cnt[ch] == 0 {
total--
lessK--
}
l++
}
if lessK == 0 {
ans = max(ans, r-l+1)
}
}
}
return ans
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
复制代码
N: 文字列の長さ
Σは文字セットです
時間計算量: O(N⋅∣Σ∣+∣Σ∣^2)
空間複雑度: O(∣Σ∣)