zoukankan      html  css  js  c++  java
  • [LeetCode] 340. Longest Substring with At Most K Distinct Characters

    Given a string, find the length of the longest substring T that contains at most k distinct characters.

    Example 1:

    Input: s = "eceba", k = 2
    Output: 3
    Explanation: T is "ece" which its length is 3.

    Example 2:

    Input: s = "aa", k = 1
    Output: 2
    Explanation: T is "aa" which its length is 2.


    Solution 1. Brute force, O(N^3) runtime

    For each possible substring, check if it contains at most k distinct characters, then get the longest.

     1 class Solution {
     2     public int lengthOfLongestSubstringKDistinct(String s, int k) {
     3         int max = 0;
     4         for(int i = 0; i < s.length(); i++) {
     5             for(int j = i + 1; j <= s.length(); j++) {
     6                 String sub = s.substring(i, j);
     7                 Set<Character> chars = new HashSet<>();
     8                 for(int t = i; t < j; t++) {
     9                     chars.add(s.charAt(t));
    10                 }
    11                 if(chars.size() <= k) {
    12                     max = Math.max(max, sub.length());
    13                 }
    14             }
    15         }
    16         return max;
    17 }

    Solution 2. O(N^2) runtime

    One thing that can be optimized in solution 1 is to use checked substring's characters counts to determine if the next substring of the same length satisfies the condition, from O(N) to O(1). This is done by maintaining a hashmap of each distinct character's frequency. 

    1. Initialize a hash map of each distinct character's frequency.

    2. Starting from the possible maximum length n = s.length(), do the following.

      a. from left to right, slide a window of length n to check if a substring meets the required condition. If it does, return; otherwise keep sliding one character at a time until reaching the right end.

      b. reduce sliding window length by 1 and from right to left, slide a window of length n to check if a substring meets the required condition. If it does, return; otherwise keep sliding one character at a time until reaching the left end.

      c. repeat a and b until n = 0.

     1 class Solution {
     2     public int lengthOfLongestSubstringKDistinct(String s, int k) {        
     3         int[] counts = new int[256];
     4         for(int i = 0; i < s.length(); i++) {
     5             counts[s.charAt(i) - '']++;
     6         }
     7         int uniqueChars = 0;
     8         for(int i = 0; i < 256; i++) {
     9             if(counts[i] > 0) {
    10                 uniqueChars++;
    11             }
    12         }
    13         int maxLen = s.length();
    14         boolean leftToRight = false;
    15          
    16         for(; maxLen > 0; maxLen--) {
    17             leftToRight = !leftToRight;
    18             if(leftToRight) {
    19                 int leftIdx = 0, rightIdx = maxLen - 1;
    20                 if(rightIdx + 1 < s.length()) {
    21                     counts[s.charAt(rightIdx + 1) - '']--;
    22                     if(counts[s.charAt(rightIdx + 1) - ''] == 0) {
    23                        uniqueChars--; 
    24                     }
    25                 }
    26                 if(uniqueChars <= k) {
    27                     return maxLen;
    28                 }
    29                 rightIdx ++;
    30                 while(rightIdx < s.length()) {  
    31                     counts[s.charAt(leftIdx) - '']--;
    32                     if(counts[s.charAt(leftIdx) - ''] == 0) {
    33                         uniqueChars--;
    34                     }
    35                     leftIdx++;
    36                     
    37                     if(counts[s.charAt(rightIdx) - ''] == 0) {
    38                         uniqueChars++;
    39                     }
    40                     
    41                     counts[s.charAt(rightIdx) - '']++;                  
    42                     if(uniqueChars <= k) {
    43                         return maxLen;
    44                     }
    45                     rightIdx++;
    46                 }               
    47             }
    48             else {
    49                 int rightIdx = s.length() - 1, leftIdx = s.length() - maxLen;
    50                 if(leftIdx >= 1) {
    51                     counts[s.charAt(leftIdx - 1) - '']--;
    52                     if(counts[s.charAt(leftIdx - 1) - ''] == 0) {
    53                        uniqueChars--; 
    54                     }                    
    55                 }
    56                 if(uniqueChars <= k) {
    57                     return maxLen;
    58                 }
    59                 leftIdx--;
    60                 while(leftIdx >= 0) {  
    61                     counts[s.charAt(rightIdx) - '']--;
    62                     if(counts[s.charAt(rightIdx) - ''] == 0) {
    63                         uniqueChars--;
    64                     }
    65                     rightIdx--;
    66                     
    67                     if(counts[s.charAt(leftIdx) - ''] == 0) {
    68                         uniqueChars++;
    69                     }
    70                     counts[s.charAt(leftIdx) - '']++;                    
    71                     if(uniqueChars <= k) {
    72                         return maxLen;
    73                     }
    74                     leftIdx--;
    75                 }               
    76             }
    77         }
    78         return maxLen;   
    79 }

    Solution 3. O(N) runtime

    To further optimize solution 2, we have the this observation: for a substring that starts at index i, s[i, j - 1], if it meets the condition while s[i, j] does not, we do not need to backtrack j to i + 1.This is true because all the substring from index i + 1 to j - 1 are a smaller set of s[i, j - 1]. If s[i, j - 1] is a possible solution, it eliminates the need of checking a smaller solution. We just need to increment i by 1 and pick up where j stopped. 

    For substrings that start at index i, there are two cases when we will stop incrementing j.

    1. s[i, j] has more than k distinct characters; In this case, we need to update the frequency of s.charAt(i), increment i by 1 then repeat the same process.

    2. j is out of bound, j == s.length();  In this case, we've found the answer as no other qualified substrings will have a longer length(i can only be incremented).

    The runtime is O(N) as both the start index i and end index j only move forward, which means at any given iteration, either i or j is incremented. This takes linear time to finish.

     1 class Solution {
     2     public int lengthOfLongestSubstringKDistinct(String s, int k) {       
     3         if(s == null || s.length() == 0 || k <= 0){
     4             return 0;
     5         }        
     6         int[] counts = new int[256];
     7         int distinctChars = 0;
     8         int endIdx = 0, maxLen = 0;
     9         for(int startIdx = 0; startIdx < s.length(); startIdx++) {
    10             while(endIdx < s.length()) {
    11                 if(counts[s.charAt(endIdx) - ''] > 0) {
    12                     counts[s.charAt(endIdx) - '']++;
    13                 }
    14                 else if(distinctChars == k){
    15                     break;
    16                 }
    17                 else {
    18                     counts[s.charAt(endIdx) - '']++;
    19                     distinctChars++;
    20                 }
    21                 endIdx++;
    22             }
    23             maxLen = Math.max(maxLen, endIdx - startIdx);
    24             if(endIdx == s.length()) {
    25                 break;
    26             }
    27             counts[s.charAt(startIdx) - '']--;
    28             if(counts[s.charAt(startIdx) - ''] == 0) {
    29                 distinctChars--;
    30             }
    31         }
    32         return maxLen;
    33     }
    34 }






  • 相关阅读:
    LeetCode 842. Split Array into Fibonacci Sequence
    LeetCode 1087. Brace Expansion
    LeetCode 1219. Path with Maximum Gold
    LeetCode 1079. Letter Tile Possibilities
    LeetCode 1049. Last Stone Weight II
    LeetCode 1046. Last Stone Weight
    LeetCode 1139. Largest 1-Bordered Square
    LeetCode 764. Largest Plus Sign
    LeetCode 1105. Filling Bookcase Shelves
    LeetCode 1027. Longest Arithmetic Sequence
  • 原文地址:https://www.cnblogs.com/lz87/p/10095363.html
Copyright © 2011-2022 走看看