diff --git a/longest-common-subsequence/hi-rachel.py b/longest-common-subsequence/hi-rachel.py new file mode 100644 index 000000000..0c6dbbfe9 --- /dev/null +++ b/longest-common-subsequence/hi-rachel.py @@ -0,0 +1,34 @@ +""" +두 문자열 text1, text2가 주어졌을 때, 순서를 유지하는 공통 부분 수열 중 가장 긴 길이를 반환해라 +- 없으면 0 반환 +- 순서는 일치해야 하지만, 문자열 삭제 가능 +- 소문자로만 이루어져 있음. +- 1 <= text1.length, text2.length <= 1000 + +# LCS DP 풀이 +- dp[i][j]: 문자열 text1[:i]와 text2[:j]까지의 LCS 길이 + +1. text1[i - 1] == text2[j - 1], 두 문자열이 같은 경우 + LCS 길이 증가 + dp[i][j] = dp[i - 1][j - 1] + 1 +2. 다른 경우 + text1[0...i] 또는 text2[0...j] 중 하나를 줄인 LCS 중 더 긴 쪽 선택 + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + + +TC: O(m * n) +SC: O(m * n) +""" + +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + m, n = len(text1), len(text2) + dp = [[0] * (n + 1) for _ in range(m + 1)] # 0번째 인덱스를 비워둬서 문자열이 ""일 때를 기본값으로 처리 + + for i in range(1, m + 1): # text1의 1 ~ m 인덱스 + for j in range(1, n + 1): # text2의 1 ~ n 인덱스 + if text1[i - 1] == text2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 # 두 문자가 같으면, 이전 상태 + 1 + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) # 다르면, 하나 줄인 상태 중 최댓값 선택 + return dp[m][n] diff --git a/longest-repeating-character-replacement/hi-rachel.py b/longest-repeating-character-replacement/hi-rachel.py new file mode 100644 index 000000000..6f0b220ae --- /dev/null +++ b/longest-repeating-character-replacement/hi-rachel.py @@ -0,0 +1,46 @@ +""" +주어진 문자열 s에서 최대 k번의 문자 교체를 통해 +동일한 문자가 반복된 가장 긴 부분 문자열의 길이를 구해라 + +풀이: 슬라이딩 윈도우 + 빈도수 추적 +"윈도우 [left, right] 구간에 대해, +해당 구간에서 최대 등장하는 문자 하나를 기준으로 나머지 문자들을 최대 k번까지 바꿔서 동일 문자로 만들 수 있나?" +-> 가능하면 윈도우 넓히기 +-> 안되면 left를 줄여 윈도우 유지 + +현재 윈도우 길이: right - left + 1 +윈도우 내 가장 자주 나온 문자 개수: max_cnt + +if (right - left + 1) - max_cnt <= k: + # 이 윈도우는 k번 교체로 모두 동일 문자 가능 + # -> 윈도우 확장 +else: + # 불가능 -> left를 오른쪽으로 줄여 윈도우 축소 (left + 1) + +TC: O(N) +SC: O(1) -> dict는 최대 A-Z 26개의 키를 가짐 (상수 개수 제한) +""" + +from collections import defaultdict + +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + english_char_dict = defaultdict(int) + max_len = 0 + max_cnt = 0 # 현재 윈도우에서 가장 많이 나온 문자 수 + left = 0 + + for right in range(len(s)): # 0 ~ len(s) + english_char_dict[s[right]] += 1 # 문자 등장 횟수 계산 + max_cnt = max(max_cnt, english_char_dict[s[right]]) # 최대 등장 횟수 갱신 + + # 현재 윈도우의 길이 = (right - left + 1) + # 윈도우 내에서 바꿔야 할 문자 수가 k보다 크면 교체할 문자수가 너무 많으므로 윈도우 축소 + if (right - left + 1) - max_cnt > k: + english_char_dict[s[left]] -= 1 # 왼쪽 문자 제거 + left += 1 # 윈도우 축소 + + # 윈도우가 유효한 경우, 그 윈도우 길이로 최대 길이 갱신 + max_len = max(max_len, right - left + 1) + + return max_len diff --git a/palindromic-substrings/hi-rachel.py b/palindromic-substrings/hi-rachel.py new file mode 100644 index 000000000..b397fa3b2 --- /dev/null +++ b/palindromic-substrings/hi-rachel.py @@ -0,0 +1,57 @@ +""" +string s가 주어졌을 때, s에서 나올 수 있는 palindrome의 조건을 만족하는 substring 개수를 구하라. +(s는 전부 소문자, 1이상 1000이하) + +1. 완전 탐색 +- 나올 수 있는 모든 경우의 수 substring을 만들어 palindrome의 조건을 만족하는지 계산 + +TC: O(N^3) +SC: O(N) + +2. 최적화 - 중심 확장 +- 모든 palindrome은 어떤 중심을 기준으로 좌우 대칭인 원리를 이용 +=> 문자열의 모든 위치를 중심으로 삼고, 양쪽으로 좌우를 확장하며 검사하면 됨 +- 중심 개수: 2n - 1 + +TC: O(N^2) +SC: O(1) + +3. 최적화 - DP + 1. 길이 1인 문자열은 항상 팰린드롬 + dp[i][i] = True + 2. 길이 2인 문자열은 두 문자가 같으면 팰린드롬 + s[i] == s[i+1] -> dp[i][i+1] = True + 3. 길이 3 이상인 문자열은 끝 두 문자열이 같고 안에 문자열도 모두 같아야 팰린드롬 + s[i] == s[j] and dp[i+1][j-1] == True -> dp[i][j] = True + (dp[i+1][j-1] == True시, s[i+1...j-1] 구간의 문자열이 이미 팰린드롬이라는 뜻) + +TC: O(N^2) +SC: O(N^2) +""" + +class Solution: + def countSubstrings(self, s: str) -> int: + cnt = 0 + n = len(s) + dp = [[False] * n for _ in range(n)] + + # 길이 1 => 항상 팰린드롬 + for i in range(n): + dp[i][i] = True + cnt += 1 + + # 길이 2 => 같은 문자면 팰린드롬 + for i in range(n-1): + if s[i] == s[i+1]: # 서로 같은 문자면 + dp[i][i+1] = True # 팰린드롬 + cnt += 1 + + # 길이 3 이상 + for length in range(3, n+1): # length는 부분 문자열의 길이 + for i in range(n - length + 1): + j = i + length - 1 # 끝 인덱스 + if s[i] == s[j] and dp[i+1][j-1]: + dp[i][j] = True + cnt += 1 + + return cnt diff --git a/reverse-bits/hi-rachel.py b/reverse-bits/hi-rachel.py new file mode 100644 index 000000000..682b6211d --- /dev/null +++ b/reverse-bits/hi-rachel.py @@ -0,0 +1,69 @@ +""" +1. 입력받은 정수 n을 32비트 이진수로 바꾼다 +2. 이진수를 좌우로 뒤집는다 -> stack 활용 +2. 뒤집은 이진수의 정수값을 반환한다 + +항상 32비트이므로 상수 시간, 상수 공간 +TC: O(1) +SC: O(1) +""" + +class Solution: + def reverseBits(self, n: int) -> int: + stack = [] + while len(stack) < 32: + stack.append(n % 2) + n //= 2 + + output, scale = 0, 1 # 결과, 2^0 = 1 시작 + while stack: + output += stack.pop() * scale + scale *= 2 + + return output + +""" +비트 연산자 + +쉬프트 연산자 - 정수 타입에만 사용 가능, 내부적으로 이진수로 작동 +<< 비트를 왼쪽으로 이동 +x << 1 == x * 2 +ex) 00001101 → 00011010 + +>> 비트를 오른쪽으로 이동 +x >> 1 == x // 2 +ex) 00001101 → 00000110 + +n & 1 +현재 n의 가장 오른쪽 비트 확인 +n & 1이 1이면 홀수, 0이면 짝수 +""" + +class Solution: + def reverseBits(self, n: int) -> int: + stack = [] + while len(stack) < 32: + stack.append(n & 1) # 마지막 비트 1이면 1, 0이면 0 + n >>= 1 # %= 2 와 같은 효과, 오른쪽 쉬프트 + + output, scale = 0, 1 # 결과, 2^0 = 1 시작 + while stack: + output += stack.pop() * scale + scale <<= 1 # *= 2 와 같은 효과 + + return output + +# stack 공간 절약 풀이 +class Solution: + def reverseBits(self, n: int) -> int: + output = 0 + for _ in range(32): + output <<= 1 # 왼쪽 쉬프트 + output |= (n & 1) # 논리 연산자 사용 (제일 마지막 비트가 1이라면 1, 0이라면 0) + n >>= 1 + return output + +# int, format 활용 풀이 +class Solution: + def reverseBits(self, n: int) -> int: + return int(format(n, "032b")[::-1], 2)