Valid Palindrome
/LeetCodePalindrome이 뭐여... 팔린드롬... 찾아보니 회문(回文)이란다. 회문이란 앞에서부터 읽든, 뒤에서부터 읽든 동일한 단어나 문장을 의미한다. 짧게는 기러기, 이효리(?), 다시 합창합시다... 이런게 있다.
아, 로꾸거 들어보면 많이 나온다해서 들어봤는데 무슨일이야 이게...
🤦♀️ 나만 볼 수 없지
문제 해석
요약
회문인지 아닌지 판단하기! 영문과 숫자만을 고려, 대소문자 구분 없음.
전문
🇰🇷 번역
문자열이 주어지면, 회문인지 판단내려라. 오직 영숫자만을 고려하며, 대소문자는 상관하지 않는다.
주의:
이 문제를 풀기위해, 우리는 빈 문자열도 유효한 회문으로 간주한다.
Input | Output |
---|---|
"A man, a plan, a canal: Panama" | true |
"race a car" | false |
제한 사항:
s는 글로 옮길 수 있는 ASCII 문자로만 구성된다.
🇺🇸 원문
Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.
Note: For the purpose of this problem, we define empty string as valid palindrome.
Input | Output |
---|---|
"A man, a plan, a canal: Panama" | true |
"race a car" | false |
Constraints:
s consists only of printable ASCII characters.
🤔 문제 해결 방법 구상
간단하게 드는 생각으론, 하나의 포인터는 처음부터, 나머지 하나는 끝부터 시작을 하여 문자가 같으면 한칸씩 이동하면서 비교, 다르면 바로 false를 리턴하는 것이다.
- 주어진 문자열을 모두 소문자로 변경하고, 숫자와 문자를 제외한 문자들은 제거하여 문자열 정리(정규표현식 사용)
- 앞,뒤 부터 진행하는 2개의 포인터를 정의
- while문을 작성. 두 포인터가 만나거나 교차하기 전까지.
- 포인터가 가리키는 문자열을 비교하여 다르면 false 리턴, 같다면 포인터 이동
- while문 통과하면 true 리턴
코드 구현
/**
* @param {string} s
* @return {boolean}
*/
const isPalindrome = function(s) {
s = s.replace(/[^a-zA-Z0-9]/g,'').toLowerCase();
let forward = 0;
let backward = s.length - 1;
while (forward < backward) {
if (s[forward] != s[backward]) return false;
forward++;
backward--;
}
return true;
};
결과 분석
Runtime: 84 ms
Memory Usage: 38.7 MB
시간복잡도 O(n), 공간복잡도 O(1)이다. 대체로 조건에 만족한 듯 한데, 한가지 걸리는 점은 정규표현식을 사용하며, replace
메소드를 쓴 것이다. 조건에 숫자와 영문자만을 고려하며 모두 ASCII 문자인 점을 이용해야 할 것 같다.
정규표현식은 모두 설명하기엔 힘들고, 여기서 사용한 표현만 정리하려한다.
우선 정규식 리터럴을 사용하는 방법은 슬래쉬로 감싸고 있는 /어쩌구/
형태이다. 정규식에서 쓰이는 특수문자 중 [^]
의 의미는 제외한 문자를 찾는 것이다. g
는 전역검색을 의미한다. 즉, /[^a-zA-Z0-9]/g
는 문자열 전체(g
)에서 소문자 전체(a-z
), 대문자 전체(A-Z
), 숫자 0-9(0-9
)를 제외(^
)한 문자를 찾는다는 의미의 정규표현식(//
)이다.
🧐 개선
ASCII는 유니코드와 마찬가지로 텍스트 이코딩의 표준이다.(유니코드가 ASCII를 본따 만들었다.) ASCII는 알파벳 대소문자와 숫자, 그리고 소량의 특수문자와 공백으로 이루어져있다. 또한 각 문자들의 ASCII 값은 특정 크기의 숫자로 이루어져 있으며, 특히 알파벳은 순서에 따라 크기가 커진다. 즉 순서 비교가 가능하다는 것이다.('a' < 'b' < 'c' < ...
)
정규표현식을 쓰지 않기 때문에 따로 문자와 숫자 범위를 체크하는 함수를 만들고, 범위를 벗어나면 포인터를 이동시키도록 해야한다.
개선 코드
- 문자열이 2개 미만이면 무조건 true
- s를 소문자로 변환
- 문자인지 숫자이 판단하는 함수 정의
- 한 포인터는 앞에서부터, 나머지 포인터는 뒤에서부터 while문으로 순회하면서 3번 함수 확인해서 아니면 다음것으로 넘김
- 각 순서의 문자열이 다르면 바로 false 리턴
- while문을 통과하면 true 리턴
const isPalindrome = function(s) {
if (s.length < 2) return true;
s = s.toLowerCase();
const isAlnum = (str) => {
return ((str >= 'a' && str <= 'z') || (str >= '0' && str <= '9'));
};
let f = 0;
let b = s.length - 1;
while (f < b) {
if (!isAlnum(s[f])) {
f++;
continue;
}
if (!isAlnum(s[b])) {
b--;
continue;
}
if(s[f] !== s[b]) return false;
f++;
b--;
}
return true;
}
개선 결과
Runtime: 88 ms
Memory Usage: 39 MB
마찬가지로 시간복잡도 O(n), 공간복잡도 O(1)이다. 복잡도만 본다면 큰 의미는 없지만, replace
메소드를 사용하지 않고, ASCII 순서를 이용하여 문제를 풀어보았다.
마무리
ASCII도 볼때마다 초면같음... 쫌 어색...ㅋㅋㅋ 저번에 풀어본 Valid Anagram비슷한가 했지만 달랐다. Valid Anagram 문제는 순서가 중요치 않아서 Map으로 구현이 가능했지만 이번엔 존재 여부보다 순서가 더 중요했기 때문이다. 또한 저번에 살펴본 유니코드와 ASCII의 차이를 살펴보았는데, 둘 다 특정 숫자로 매칭된다는 점과 순서가 있어서 비교할 수 있다는 점을 이용하여 풀 수 있다는 공통점이 있었다. 문자열 비교문제는 ASCII와 Unicode를 고려해볼 수 있다는 점을 기억하자.