Go&Java アルゴリズムで少なくとも K 個の文字が繰り返される最長の部分文字列

一緒に創造し、成長するために一緒に働きましょう!「ナゲッツデイリー新プラン・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(∣Σ∣)

おすすめ

転載: juejin.im/post/7136203034983399432