hyelie
hyelie
Hyeil Jeong
       
글쓰기    관리    수식입력
  • 전체보기 (495)
    • PS (283)
      • Algorithm (28)
      • PS Log (244)
      • Contest (6)
      • Tips (5)
    • Development (52)
      • Java (14)
      • Spring (23)
      • SQL (2)
      • Node.js (2)
      • Socket.io (3)
      • Study (4)
      • Utils (4)
    • DevOps (36)
      • Git (5)
      • Docker (4)
      • Kubernetes (2)
      • GCP (3)
      • Environment Set Up (8)
      • Tutorial (12)
      • Figma (2)
    • CS (74)
      • OOP (7)
      • OS (24)
      • DB (2)
      • Network (24)
      • Architecture (0)
      • Security (2)
      • Software Design (0)
      • Parallel Computing (15)
    • Project (15)
      • Project N2T (5)
      • Project ASG (0)
      • Project Meerkat (1)
      • Model Checking (7)
      • Ideas (2)
    • 내가 하고싶은 것! (34)
      • Plan (16)
      • Software Maestro (10)
      • 취준 (8)
hELLO · Designed By 정상우.
hyelie

hyelie

PS/PS Log

23.04.22. 풀었던 문제들

Leetcode 1312. Minimum Insertion Steps to Make a String Palindrome

 longest palindrome subsequence length(x라고 하자)를 알아내면 되는 문제이다.
 왜냐하면, 전체 길이를 n이라 할 때, n - x를 하면 되기 때문이다.longest palindrome subsequence는 이미 palindrome이기 때문에 나머지 부분인 n - x인 부분만만 채워 넣으면 palindrome이 되기 때문이다.

 그렇다면 쉬운 문제다. longest palindrome subsequence를 찾는 건 LCS와 유사한 DP로 쉽게 풀 수 있다.

 

 dp[i][j]를 index i to j까지 longest palindrome subsequence length로 정의하면 아래와 같다.

  • s[i] == s[j] then dp[i][j] = dp[i+1][j-1] + 2 / else max(dp[i][j-1], dp[i+1][j]
  • 거꾸로는 dp[i-1][j+1] = dp[i][j] + 2 when s[i-1] == s[j+1] / else dp[i-1][j+1] = max(dp[i-1][j], dp[i][j+1])

 이 두 식 중에 구현하기 편리한 것은 위의 것이다. 아래 것은 범위를 신경써 주어야 하기도 하고 여러 번 연산해 줘야 하는 반면 위의 것은 원큐에 dp[i][j]를 바로 계산할 수 있기 때문이다.

 

 base case는 아래와 같다.

  • dp[i][i] = 1 for all i

 

첫 번째 접근 : bottom-up DP

 DP 문제를 풀며 느낀 건데, 가능하면 for문을 사용하는 bottom-up과 recurse를 사용하는 top-down 모두에 익숙해여야 하는 것 같다. 그래서 이 문제부터는 두 가지 방식으로 풀려 한다.

 사실 이 문제는 몇 번 풀어본 문제라 좀 쉽게 풀었다. 특히 대각선 접근은 distance를 1부터 n까지 설정하고, i값으로 for문을 돌린 후 j값을 만들어 주는 게 제일 직관적이고 빠르고 이상적인 것 같다. 이제부터 이렇게 쓰려 의식해 보자.

class Solution {
public:
    int minInsertions(string s) {
        int n = s.length();
        // dp[i][j] : index i to j까지 max palindrome subsequence length
        vector<vector<int>> dp(n, vector<int>(n, 0));
        for(int i = 0; i<n; i++) dp[i][i] = 1;

        for(int d = 1; d<n; d++){
            for(int i = 0; i<n-d; i++){
                int j = i+d;
                if(s[i] == s[j]) dp[i][j] = dp[i+1][j-1] + 2;
                dp[i][j] = max(dp[i][j], max(dp[i+1][j], dp[i][j-1]));                
            }
        }

        return n - dp[0][n-1];
    }
};

 

 

두 번째 접근 : top-down DP

 recurse 사용하는 게 항상 귀찮고, 어렵다. 종료조건 설정하는 게 까다로워서...

 일단 재귀 식은 동일하다. 그리고 top-down dp는 함수 자체가 DP의 정의를 나타내기 때문에 좀 더 직관적?인 부분도 있긴 하다. 

 여기서 종료 조건은 크게 3가지이다.

  • memoization이 된 경우(이미 탐색한 경우)
  • i == j인 경우 return 1
  • i > j인 경우 return 0

 위 3가지 경우를 제외한 경우에는 점화식을 따라 탐색하면 된다.

class Solution {
public:
    int recurse(vector<vector<int>> &dp, string &s, int i, int j){
        if(dp[i][j] != 0) return dp[i][j];
        if(i == j) return 1;
        if(i > j) return 0;
        if(s[i] == s[j]){
            dp[i][j] = recurse(dp, s, i+1, j-1) + 2;
            return dp[i][j];
        }
        else{
            dp[i][j] = max(recurse(dp, s, i+1, j), recurse(dp, s, i, j-1));
            return dp[i][j];
        }
    }
    int minInsertions(string s) {
        int n = s.length();
        // dp[i][j] : index i to j까지 max palindrome subsequence length
        vector<vector<int>> dp(n, vector<int>(n, 0));

        return n - recurse(dp, s, 0, n-1);
    }
};

 

시간복잡도

 n by n size의 2D array를 순회하며 탐색하므로 O($n^2$)이다.

 

공간복잡도

 n by n size의 2D array를 정의하고 탐색하므로 O($n^2$)이다. top-down의 경우 worst case stack이 O(n)만큼 쌓인다.

 

후기

 top-down에 익숙해지자.

 

 

 

 

저작자표시 (새창열림)

'PS > PS Log' 카테고리의 다른 글

23.04.24. 풀었던 문제들  (0) 2023.04.24
23.04.23. 풀었던 문제들  (0) 2023.04.23
23.04.21. 풀었던 문제들  (0) 2023.04.21
23.04.20. 풀었던 문제들  (0) 2023.04.20
23.04.19. 풀었던 문제들  (0) 2023.04.19
    hyelie
    hyelie

    티스토리툴바