오늘은 99클럽 코테 스터디 38일차 입니다. 이제 4일정도 남았네요.
꾸준히 했지만 여전히 전 부족합니다. 하지만 포기하면 안돼죠?
오늘 문제를 살펴보겠습니다!
Given a string s
which consists of lowercase or uppercase letters, return the length of the longest
palindrome
that can be built with those letters.
Letters are case sensitive, for example, "Aa"
is not considered a palindrome.
Example 1:
Input: s = "abccccdd"
Output: 7
Explanation: One longest palindrome that can be built is "dccaccd", whose length is 7.
Example 2:
Input: s = "a"
Output: 1
Explanation: The longest palindrome that can be built is "a", whose length is 1.
Constraints:
1 <= s.length <= 2000
s
consists of lowercase and/or uppercase English letters only.99클럽 문제를 풀기전 아래 그림처럼 이 문제가 오늘 어떤 유형인지가 나와있습니다.
greedy 유형이라고 해서 어떻게 greedy로 접근할까? 하고 문제를 들어가보았는데 그리디가 전혀 생각이 안났습니다,,,
하지만, 문제를 다 풀고나니, 아! 이렇게 생각하면 이 문제가 그리디 알고리즘 유형이라고 할 수 있겠구나 생각이 났습니다.
그럼, 문제 한 번 접근해보겠습니다.
주어진 문자열 s
에서 만들 수 있는 가장 긴 팰린드롬의 길이를 찾기 위해서는, 팰린드롬의 특성을 고려해야 합니다.
n-1
개의 인스턴스를 사용할 수 있습니다).s
에서 각 문자의 빈도를 세어봅니다.count - 1
을 길이에 더해 최대 짝수를 포함시킵니다.s = "abccccdd"
a: 1, b: 1, c: 4, d: 2
c: 4, d: 2
→ 4 + 2 = 6
a: 1, b: 1
→ 하나의 홀수 문자를 중앙에 배치할 수 있습니다.6 + 1 = 7
.그럼 코드로 들어가볼까요?
from collections import Counter
class Solution1(object):
def longestPalindrome(self, s):
cnt = Counter(s) # 문자열 s에서 각 문자의 빈도를 세는 Counter 객체 생성
odd_cnt = 0 # 홀수 빈도의 문자가 있는지 확인하기 위한 변수 초기화
for key, value in cnt.items(): # 각 문자와 그 빈도에 대해 반복
if value % 2 != 0: # 빈도가 홀수인 경우
cnt[key] = value - 1 # 해당 문자의 빈도를 짝수로 만듦 (1을 뺌)
odd_cnt = 1 # 홀수 빈도 문자가 있음을 표시
return sum(list(cnt.values())) + odd_cnt # 짝수 빈도의 문자 합 + 홀수 문자가 있었다면 1 추가
Counter 사용:
cnt = Counter(s)
Counter
는 문자열 s
에서 각 문자의 빈도를 세어주는 Python의 내장 클래스입니다.s = "abccccdd"
라면 cnt
는 {'a': 1, 'b': 1, 'c': 4, 'd': 2}
와 같은 구조를 가집니다.홀수 빈도 처리:
odd_cnt = 0
for key, value in cnt.items():
if value % 2 != 0:
cnt[key] = value - 1
odd_cnt = 1
odd_cnt
는 홀수 빈도를 가진 문자가 있는지 여부를 나타내는 변수입니다.cnt.items()
는 cnt
에 저장된 각 문자와 그 빈도를 반복합니다.value % 2 != 0
)인 경우, 그 값을 짝수로 만들기 위해 value - 1
로 수정합니다.odd_cnt
를 1
로 설정합니다.팰린드롬 길이 계산:
return sum(list(cnt.values())) + odd_cnt
cnt.values()
는 각 문자의 수정된 빈도 값들을 반환합니다.odd_cnt
가 1
이라면, 이는 홀수 빈도를 가진 문자가 존재한다는 것을 의미하며, 그 문자를 팰린드롬의 중앙에 배치할 수 있으므로 길이에 1
을 추가로 더합니다.이 코드는 주어진 문자열에서 만들 수 있는 가장 긴 팰린드롬의 길이를 계산합니다. 문자열 내 각 문자의 빈도를 확인한 후, 홀수 빈도를 가진 문자의 빈도를 짝수로 변환하고, 홀수 빈도가 존재할 경우 추가로 중앙에 배치할 수 있는 문자를 고려해 최종 길이를 반환합니다. 이 과정에서 Counter
를 활용하여 각 문자의 빈도를 계산하고, 이를 통해 최적의 팰린드롬 길이를 구하는 방식입니다.
Counter 생성:
cnt = Counter(s)
Counter(s)
는 문자열 s
를 한 번 순회하며 각 문자의 빈도를 계산합니다.s
의 길이를 n
이라고 하면, 이 부분의 시간 복잡도는 O(n)
입니다.for 루프:
for key, value in cnt.items():
if value % 2 != 0:
cnt[key] = value - 1
odd_cnt = 1
cnt.items()
는 각 문자의 빈도에 대해 순회합니다.cnt
의 최대 크기는 문자열에 포함된 고유 문자의 수입니다. 영어 대소문자만 포함된다고 가정하면 최대 52개의 문자가 될 수 있습니다. 따라서 이 루프의 복잡도는 O(1)
에 가깝지만, 일반적으로는 O(k)
로, 여기서 k
는 고유 문자의 수입니다. 그러나 k
는 상수로 간주되므로 시간 복잡도에 큰 영향을 주지 않습니다.O(1)
입니다.합 계산 및 반환:
return sum(list(cnt.values())) + odd_cnt
cnt.values()
의 합을 계산하는 것은 최대 52개의 값을 합산하는 것입니다.O(1)
입니다.Counter(s)
를 만드는 부분이므로, 최종 시간 복잡도는 O(n)
입니다.cnt = Counter(s)
는 문자열의 문자를 세는 딕셔너리입니다.cnt
는 최대 52개의 키-값 쌍을 가질 수 있습니다. 이는 상수로 간주될 수 있으므로, 이 부분의 공간 복잡도는 O(1)
입니다.odd_cnt
, key
, value
등 추가 변수들은 상수 개수의 메모리만 차지하므로 공간 복잡도에 큰 영향을 미치지 않습니다.O(1)
입니다. 이는 입력 문자열의 길이에 상관없이 코드가 사용하는 추가 메모리가 일정하다는 것을 의미합니다.O(n)
(문자열의 길이 n
에 비례)O(1)
(입력 문자열의 길이와 무관한 상수 공간 사용)https://leetcode.com/problems/longest-palindrome/submissions/1370818515
다른 풀이로는 Counter 모듈을 사용하지 않고, 문자열의 문자들을 직접 카운트해서, 해시 테이블을 만드는 코드입니다. 또한, 홀수 개수를 처리하는 방법이 가장 보편적인 방법을 사용했습니다.
def longestPalindrome(sekf,s):
# 각 문자의 빈도를 세기 위한 딕셔너리 생성
freq = {}
for char in s:
freq[char] = freq.get(char, 0) + 1
length = 0
odd_found = False
for count in freq.values():
if count % 2 == 0:
length += count
else:
length += count - 1
odd_found = True
# 홀수 빈도의 문자가 있다면 중앙에 하나 배치
if odd_found:
length += 1
return length
O(n)
(문자열을 한 번 훑어보면서 빈도를 계산하기 때문에, 문자열의 길이를 n
이라고 할 때)O(1)
(빈도 딕셔너리는 최대 52개의 키를 가지므로, 26개의 소문자와 26개의 대문자를 고려)https://leetcode.com/problems/longest-palindrome/submissions/1370819042
이 문제를 해결하는 방법이 그리디 알고리즘(Greedy Algorithm)으로 간주되는 이유는, 문제를 해결하는 과정에서 "각 단계에서 가장 좋은 선택을 하여 최종적으로 최적의 결과를 얻는다"는 그리디 알고리즘의 핵심 개념을 따르고 있기 때문입니다.
count - 1
을 사용하여 가능한 많은 문자를 포함시키는 것이 최적의 선택입니다. (이 역시 현재 최적 선택)이 알고리즘이 그리디 알고리즘인 이유는, 각 문자에 대해 별도로 빈도를 고려하고 그 빈도에 따라 최선의 선택을 즉각적으로 내리는 방식으로 문제를 해결하기 때문입니다. 이 과정에서 나중에 더 나은 선택이 있을 수 있다는 가능성에 대한 고민 없이, 항상 현재 상황에서 가장 좋은 선택을 합니다. 이렇게 해서 전체적으로 최적의 해를 도출하게 됩니다.
오늘 문제는 문자열이 주어졌을때, 주어진 문자열을 활용해서 만들 수 있는 펠린드롬 문자열 중 가장 큰 길이를 리턴하는 문제였습니다.
펠린드롬 문자열을 어떻게 다루고, 해시 테이블을 형성하는지를 학습할 수 있는 문제였습니다.
두 가지 방법으로 코드를 작성했는데, 사실상 크게 다르지 않았습니다. 다만, Counter 모듈이 기억나지 않을때, 두번째 방법처럼 직접 해시 테이블을 작성할 수 있습니다.
전체 코드와 테스트 케이스 평가 및 시간 측정을 진행한 코드는 저의 깃허브에서 확인하실 수 있습니다.
읽어주셔서 감사합니다!