3403 - Find the Lexicographically Largest String From the Box I
info
- 문제 보기: 3403 - Find the Lexicographically Largest String From the Box I
- 소요 시간: 25분 49초
- 풀이 언어:
java
- 체감 난이도: 3️⃣
- 리뷰 횟수: ✅
풀이 키워드
스포주의
문자열
풀이 코드
info
- 메모리: 52700 KB
- 시간: 10 ms
class Solution {
public String answerString(String word, int numFriends) {
if (numFriends == 1) return word; // corner case
// lex order first, desc length order then
final int n = word.length();
final int mxLen = word.length() - (numFriends-1);
Set<String> candSet = new TreeSet<>(Collections.reverseOrder());
char mxChar = 'a';
char[] wordArr = word.toCharArray();
for (char ch : wordArr) mxChar = mxChar < ch ? ch : mxChar;
for (int i = 0; i < n; ++i) {
if (wordArr[i] != mxChar) continue;
int j = Math.min(i+mxLen, n);
candSet.add(word.substring(i, j));
}
return candSet.iterator().next();
}
}
풀이 해설
문자열을 친구 수만큼 빈 문자열이 되지 않도록 쪼개고,
그 문자열들 중에서 lexicographically largest한 문자열을 반환해야 하는 문제이다.
...길어서 lex라고 하겠다. lex largest string을 구하려면
- 사전순으로 뒤에 있는 것 우선
- 비교 가능한 범위에서 문자 구성이 모두 동일하면 길이가 긴 문자열 우선
이 2가지 기준으로 문자열을 필터링해야 한다.
이는 결국 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 보다 "z" 가 더 lex largest 하다는 뜻이라서,
mxChar
을 사전에 결정하고 해당 문자로 시작하는 인덱스에서만 후보군을 탐색하도록 그리디한 접근 방식을 떠올렸다.
자바의 TreeSet 사용해보기
TreeSet은 RBT로 구현된, '자동정렬 unique 우선순위 큐' 같은 것이라고 생각하면 된다.
그냥 set이 필요한데 정렬도 필요하다 싶으면 TreeSet 쓰면 된다.
Set<String> candSet = new TreeSet<>(Collections.reverseOrder());
root에 lex largest한 문자열이 놓이도록 내림차순으로 생성했고, iterator().next()
로 root에 접근했다.
TreeSet을 선택한 이유는 다음과 같다.
- 후보 문자열의 중복을 제거하여 후보군 탐색시 복잡도를 최적화할 수 있기 때문
- 자동 정렬 조아용 🤗💕
caution
corner case 조심 ❗
public String answerString(String word, int numFriends) {
if (numFriends == 1) return word; // corner case
"gh"
1
답: "gh"
상단의 corner case가 별도로 처리되지 않는다면 어떻게 로직이 돌아갈지 분석해보자.
final int mxLen = word.length() - (numFriends-1);
char mxChar = 'a';
char[] wordArr = word.toCharArray();
for (char ch : wordArr) mxChar = mxChar < ch ? ch : mxChar;
별다른 처리가 없을 시 mxLen
은 정상적으로 2로 설정되지만
그리디하게 가장 사전순으로 높은 알파벳을 추리는 과정에서 mxChar
이 h로 설정되고
for (int i = 0; i < n; ++i) {
if (wordArr[i] != mxChar) continue;
int j = Math.min(i+mxLen, n);
candSet.add(word.substring(i, j));
}
결국 for문 순회에서 i = 0일때 g로 시작하는 문자열은 스킵되며
i = 1일때 h로 시작하는 문자열 중에서 j
가 min(1+2, 2) = 2로 설정되어 "h"가 candSet
에 들어가게 된다.
하지만 numFriends
가 1이었기에 사실 "gh"는 쪼개질 수가 없었다 (!)
완전히 예외 케이스인 것이다.
✨ 다른 풀이 방법
TreeSet
을 사용하지 않고 compareTo
메소드를 사용하여 더 나은 문자열 발견시 그때그때 바꿔주면
수행시간이 5ms로 줄어든다. (대다수가 이렇게 풀이한듯?)
class Solution {
public String answerString(String word, int numFriends) {
if (numFriends == 1) return word; // corner case
// lex order first, desc length order then
final int n = word.length();
final int mxLen = word.length() - (numFriends-1);
char mxChar = 'a';
char[] wordArr = word.toCharArray();
for (char ch : wordArr) mxChar = mxChar < ch ? ch : mxChar;
String ans = "";
for (int i = 0; i < n; ++i) {
if (wordArr[i] != mxChar) continue;
int j = Math.min(i+mxLen, n);
String cand = word.substring(i, j);
if (ans.compareTo(cand) < 0) ans = cand;
}
return ans;
}
}
메모
- 코테 연습용으로도 괜춘한듯
- corner case 말고는 전반적으로 무난한 난이도
- 기본적으로 입력의 최소값은 좀 테스트해보십쇼 🙂...