알고리즘 문제 풀이

[LeetCode] 76. Minimum Window Substring

_OB1N 2021. 8. 16. 15:31

문체 출처: https://leetcode.com/problems/minimum-window-substring/

 

Minimum Window Substring - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com


문제

길이 m과 n의 두 문자열 s와 t가 주어진다. 윈도우 내에 t의 문자(중복 포함)가 모두 포함되도록 s의 최소 운도우 부분 문자열을 반환하라. 만약 부분 문자열이 없다면, 빈 문자열인 ""을 반환하라.

 

테스트케이스에는 유일한 답이 존재한다.

 

부분 문자열은 문자열 안에서 문자의 연속적인 시퀀스를 말한다.

 

Follow up: O(m + n) 이내에 동작하는 알고리즘을 작성할 수 있는가? 

예제

Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"

Explanation: The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t.
Input: s = "a", t = "a"
Output: "a"

Explanation: The entire string s is the minimum window.
Input: s = "a", t = "aa"
Output: ""

Explanation: Both 'a's from t must be included in the window.
Since the largest window of s only has one 'a', return empty string.

풀이

처음에 O(m + n)이라는 조항에 구애받지 않고 문제를 풀려고 접근을 하였고, 이에 이진 탐색(Binary Search)을 사용하여 문제에 접근하였다. 이때 이진탐색의 대상은 윈도우의 사이즈이다.

 

찾아야 하는 하위 문자열의 길이(l)를 생각해보면, 최소 t 와 같은 크기이거나 최대 s 자신의 크기일 것이다(n ≤ l < m). 이를 이용하여 다음과 같이 이진탐색의 코드 구조를 만들 수 있다.

var minWindow = function(s, t) {
    const m = s.length;
    const n = t.length;
    if (m < n) { return ""; }
    
    // tmap: t의 구성 문자를 카운트하는 맵 변수
    // 하위 문자열에 t 의 모든 문자가 들어있는지 확인 용도
    const tLetters = new Map();
    t.split('').forEach(char => {
        if (!tLetters.has(char)) { tLetters.set(char, 0); }
        tLetters.set(char, tLetters.get(char) + 1);
    });
    
    // 본격적인 이진탐색 알고리즘
    let ans = "";
    let lo = n, hi = m;
    while (lo <= hi) {
        const mid = lo + Math.floor((hi - lo) / 2);
        
        // findSubstr(): mid크기의 윈도우에 t의 문자가 모두 존재할 수 있는지 확인하는 함수
        // - 존재할 수 있다면, 해당 하위 문자열을 반환
        // - 존재하지 않는다면, 빈 문자열 ""을 반환
        const tmp = findSubstr(s, new Map(tLetters), mid);
        if (tmp == '') {
            lo = mid + 1;
        } else {
            ans = tmp;
            hi = mid - 1;
        }
    }
    return ans;
};

위 코드에서 구현해야 하는 부분은 findSubstr() 함수 이다. findSubstr()함수는 mid 크기의 윈도우를 s에서 움직이면서 t의 문자를 모두 갖는 부분을 체크해 반환하는 함수로 다음과 같이 구현할 수 있다.

var findSubstr = function(s, tMap, windowSize) {
    // 초기 윈도우 설정
    for (let i = 0; i < windowSize; i++) {
        if (!tMap.has(s[i])) { continue; }
        tMap.set(s[i], tMap.get(s[i]) - 1 );
    }
   	
    if (checkWindow(tMap)) { return s.substring(0, windowSize); }
    
    // 윈도우를 움직이면서 t 문자열의 모든 문자를 갖는 곳이 있는지 체크
    for (let i = windowSize; i < s.length; i++) {
        if (tMap.has(s[i-windowSize])) { tMap.set(s[i-windowSize], tMap.get(s[i-windowSize]) + 1); }
        if (tMap.has(s[i])) { tMap.set(s[i], tMap.get(s[i]) - 1); }
        
        if (checkWindow(tMap)) { return s.substring(i-windowSize+1, i+1); }
    }
    
    return "";
}

var checkWindow = function(tMap) {
    for (let [_, v] of tMap) {
        if (v > 0) { return false; }
    }
    return true;
}

이 알고리즘의 시간 복잡도에 대해 생각해보자. 이진 탐색에서 O(logM), findSubStr()에서 O(M), 그리고 checkWindow() 에서 O(N)의 시간 복잡도를 갖을 것이기 때문에 전체 알고리즘은 O(N * M * logM)의 시간 복잡도를 갖는다.

 

Github: 76.js

 

GitHub - opwe37/Algorithm-Study

Contribute to opwe37/Algorithm-Study development by creating an account on GitHub.

github.com