Letter Combinations of a Phone Number
전화 키패드에서는 2부터 9까지의 각 숫자에 몇 개의 문자가 연결되어 있습니다. 2는 abc, 3은 def, 4는 ghi, 5는 jkl, 6은 mno, 7은 pqrs, 8은 tuv, 9는 wxyz입니다.
문자열 digits가 주어집니다. 숫자의 순서를 유지하면서 각 숫자에 대해 문자 하나를 선택하면 키패드로 입력할 수 있는 문자열 하나를 얻습니다. 가능한 모든 문자열을 사전 순으로 정렬하여 반환하세요. "23"의 경우 "ad"부터 "cf"까지 총 9개의 문자열이 있습니다.
함수
- digitsstring
- 누른 숫자는 각각 2에서 9까지입니다
- 반환값string-array
- 키로 입력할 수 있는 모든 문자열을 사전식 순서로
제약 조건
1 ≤ digits.length ≤ 4-
digits의 각 문자는2부터9까지의 숫자입니다. - 정답은 최대
44 = 256개의 문자열을 포함합니다.
예제
- 입력
- digits = "23"
- 출력
- ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
- 설명
- 2는
a,b,c를 제공하고 3은d,e,f를 제공합니다. 첫 번째 각 문자는 두 번째 각 문자와 짝을 이루므로 문자열은 3 × 3 = 9개이며, 첫 번째 문자가 가장 느리게 바뀌도록 나열하면 정렬된 상태를 유지합니다.
- 입력
- digits = "7"
- 출력
- ["p", "q", "r", "s"]
- 설명
- 한 자리 숫자에서는 각 문자가 하나의 완전한 답입니다. 7은 네 개의 문자가 있는 두 키 중 하나이므로 답은 문자열 네 개입니다.
- 입력
- digits = "94"
- 출력
- ["wg", "wh", "wi", "xg", "xh", "xi", "yg", "yh", "yi", "zg", "zh", "zi"]
- 설명
- 9는 글자가 네 개이고 4는 세 개이므로 문자열은 4 × 3 = 12개입니다.
w로 시작하는 세 문자열은 모두x로 시작하는 첫 번째 문자열보다 앞에 옵니다.
제출 시 숨은 테스트 +14개
후속 질문
사전에 있는 실제 단어의 조합만 원한다고 가정해 봅시다. 모든 4^n 문자열을 먼저 만들지 않으려면 어떻게 해야 할까요?
힌트
하나씩 열어 보세요. 열 때마다 조금 더 알려 줍니다.
선택지를 트리로 그려 보세요. 첫 번째 단계에서는 첫 번째 숫자에 사용할 문자를 고르고, 두 번째 단계에서는 두 번째 숫자에 사용할 문자를 고르는 식으로 이어집니다. 루트에서 리프까지의 경로는 무엇을 나타내나요?
각 잎은 하나의 답이고, 각 답은 하나의 잎입니다. 트리를 깊이 우선으로 탐색하며 각 키의 문자를 왼쪽에서 오른쪽으로 시도하면 사전순으로 잎을 만나게 됩니다.
계속 늘어나는 문자열 하나를 유지하세요. 위치
i에서digits[i]의 각 문자를 차례로 덧붙이고, 위치i+1로 이동한 다음 문자를 다시 제거하세요.i가digits의 끝에 도달하면 문자열의 복사본을 저장하세요.
풀이
여기서는 아무것도 건너뛸 수 없습니다. 답 자체에 최대 4^n개의 문자열이 들어 있으므로, 올바른 모든 풀이에서는 이를 작성하는 데 적어도 그만큼의 작업이 필요합니다. 이 문제에서 확인하는 것은 선택지를 하나도 빠뜨리거나 반복하지 않고 체계적으로 생성할 수 있는지입니다. 이것이 가장 기본적인 형태의 백트래킹입니다. 각 숫자마다 한 단계씩 있는 결정 트리를 깊이 우선으로 탐색하며, 모든 리프가 답이 됩니다.
문자열을 한 번에 한 자리씩 만드세요
핵심 아이디어
답을 한 번에 한 자리씩 만드세요. 빈 문자열 하나를 담은 목록으로 시작합니다. "23"의 경우, 숫자 2는 이를 a, b, c로 바꿉니다. 그런 다음 숫자 3은 이 세 문자열 각각에 d, e, f를 붙여 길이가 2인 문자열 아홉 개를 만듭니다. 마지막 숫자를 처리한 뒤에는 목록에 모든 답이 담깁니다.
결과는 별도의 작업 없이 정렬된 순서로 나옵니다. 숫자를 처리하기 전의 목록이 정렬되어 있다고 가정해 보세요. 접두사들을 같은 순서로 확장하고, 각 접두사에는 해당 키의 문자를 왼쪽에서 오른쪽 순서로 붙입니다. 앞선 접두사를 가진 문자열이 여전히 먼저 오고, 접두사가 같은 두 문자열은 새 문자에 따라 정렬되는데, 이는 사전순입니다.
비용은 답의 크기만큼 듭니다. 숫자가 n개라면 마지막 목록에는 길이가 n인 문자열이 최대 4^n개 들어가며, 이전 목록 전체에는 이보다 최대 절반 정도의 문자열이 들어가고, 그 문자열은 모두 더 짧습니다. 단점은 메모리입니다. 한 단계의 목록을 만드는 동안 이전 단계의 목록 전체도 메모리에 남아 있으며, 여기에는 버리게 될 짧은 접두사도 모두 포함됩니다.
알고리즘
combos = [""], 즉 빈 접두사 하나로 시작합니다.- 각 숫자마다 새 목록을 만듭니다.
combos의 각 접두사와 해당 숫자 키의 각 문자에 대해prefix + letter를 추가합니다. combos를 새 목록으로 바꿉니다.- 마지막 숫자를 처리한 후
combos를 반환합니다.
KEYPAD = {"2": "abc", "3": "def", "4": "ghi", "5": "jkl",
"6": "mno", "7": "pqrs", "8": "tuv", "9": "wxyz"}
def letterCombinations(digits):
combos = [""] # every prefix built so far; one empty prefix to start
for digit in digits:
# Each old prefix grows by each letter of this digit, in order.
combos = [prefix + letter for prefix in combos for letter in KEYPAD[digit]]
return combos의사 결정 트리에서 백트래킹하기
핵심 아이디어
정답을 의사 결정 트리로 생각해 보세요. 루트는 빈 문자열입니다. "23"에는 자식이 세 개 있으며, 2의 각 문자에 해당하는 a, b, c입니다. 이 자식들은 각각 또 세 개의 자식을 가지며, 3의 각 문자에 하나씩 해당합니다. 트리에는 숫자 하나당 한 레벨이 있고, ad부터 cf까지의 아홉 개 잎 노드가 바로 정답입니다.
백트래킹은 하나의 버퍼 path를 사용해 트리를 깊이 우선으로 순회합니다. 레벨 i에서는 digits[i]의 문자를 하나 선택해 추가하고, i+1을 재귀 호출해 그 아래의 모든 경우를 탐색한 다음, 문자를 제거해 선택을 되돌립니다. 선택을 되돌리기 때문에 버퍼 하나로 트리 전체를 처리할 수 있습니다. ad, ae, af를 저장한 뒤 문자를 꺼내면 path는 a로 돌아가고, 이어서 빈 문자열로 돌아가 b를 처리할 준비가 됩니다. i가 digits의 길이와 같아지면 버퍼는 완성된 정답이므로, 복사본을 저장합니다.
각 레벨에서 왼쪽부터 문자를 시도하면 잎 노드를 사전 순서로 방문하므로 결과를 정렬할 필요가 없습니다. 이 문제에서는 모든 분기가 정답으로 끝나므로 가지치기할 부분이 없습니다. 트리는 깊이가 4에 불과하고 잎 노드는 최대 256개입니다. 정답을 쓰는 데 드는 작업량은 여전히 O(4^n · n)이지만, 추가 메모리는 전체 접두사 레벨이 아니라 버퍼와 호출 스택에 사용하는 O(n)입니다. 같은 선택, 탐색, 되돌리기 반복문으로 부분 집합, 순열, 조합 합, 단어 찾기 문제도 해결할 수 있습니다.
알고리즘
- 빈
path와 빈result를 유지합니다. backtrack(i)를 정의합니다.i가digits의 길이와 같으면path의 복사본을 저장하고 반환합니다.- 그렇지 않으면
digits[i]의 키에 있는 각 문자를 순서대로 처리합니다. 해당 문자를path에 추가하고,backtrack(i+1)을 호출한 다음, 해당 문자를 제거합니다. backtrack(0)을 호출하고result를 반환합니다.
KEYPAD = {"2": "abc", "3": "def", "4": "ghi", "5": "jkl",
"6": "mno", "7": "pqrs", "8": "tuv", "9": "wxyz"}
def letterCombinations(digits):
result = []
path = [] # the letters chosen so far, one per digit
def backtrack(i):
if i == len(digits):
# Every digit has a letter: this leaf is one finished string.
result.append("".join(path))
return
for letter in KEYPAD[digits[i]]:
path.append(letter) # choose
backtrack(i + 1) # explore the digits after this one
path.pop() # undo, so the next letter can take its place
backtrack(0)
return result
함정과 경계 사례
탐색 자체는 짧으므로, 대부분의 버그는 키패드나 공유 버퍼에서 발생합니다.
- 모든 키에 글자가 세 개씩 있다고 가정하는 경우. 7에는
pqrs, 9에는wxyz가 있으므로, 알파벳에서 인덱스(d-2)*3부터 글자 세 개를 가져오면 7의s가 누락되고 8은t가 아닌s부터 시작합니다. 키패드를 표로 작성하세요. - 되돌리기를 잊는 경우. 재귀 호출 후 글자를 제거하지 않으면
path가 계속 늘어나고,"23"의 두 번째 답이ae가 아니라ade로 나옵니다. - 복사본 대신 버퍼를 저장하는 경우. Python에서
result.append(path)는 같은 리스트를 아홉 번 저장하고, 마지막에는 리스트가 비어 있습니다. 저장할 때 새로운 문자열로 결합하세요. - 순서를 놓치는 경우. 키의 글자를 오른쪽에서 왼쪽으로 시도하거나 반복 버전에서 스택을 사용해 문자열을 늘리면, 문제에서 요구하는 정렬된 순서와 다른 순서로 답이 나옵니다.
- 숫자로 읽히는 숫자 문자열. PHP와 R처럼 형식이 느슨한 언어에서는
"23"이 숫자 23으로 전달될 수 있습니다. 문자를 인덱싱하기 전에 텍스트로 변환하세요.
자주 묻는 질문4
전화번호의 문자 조합 문제의 시간 복잡도는 얼마인가요?
n자리 숫자의 경우 시간 복잡도는 O(4^n · n)입니다. 모든 숫자가 7 또는 9일 때 문자열은 4^n개가 될 수 있고, 각 문자열을 작성하는 데 n단계가 걸립니다. 세 글자 키만 사용하는 경우에는 O(3^n · n)입니다. 출력의 크기가 이만큼이므로 어떤 해법도 이보다 더 빠를 수 없습니다. 백트래킹은 출력 공간 외에 추가로 O(n)의 공간이 필요합니다.
재귀 없이 Letter Combinations를 풀 수 있나요?
네. 답을 단계별로 만드세요. 빈 문자열 하나에서 시작해 각 숫자마다 그 키의 모든 문자로 현재 문자열을 확장합니다. 작업량은 같으며, 같은 트리를 깊이 우선이 아니라 너비 우선으로 순회합니다. 메모리에는 접두사의 한 레벨 전체를 저장하지만, 재귀는 숫자 개수만큼의 깊이를 가진 스택만 필요합니다.
백트래킹은 왜 조합을 정렬된 순서로 반환하나요?
모든 답은 길이가 같고, 깊이 우선 탐색은 첫 번째 단계에서 a를 선택한 뒤 b를 선택하기 전에 a로 시작하는 모든 문자열을 끝낸다. 각 키의 문자를 왼쪽에서 오른쪽으로 시도하는 한, 이는 모든 단계에서 동일하게 적용된다. 이것이 바로 사전순이므로 정렬할 필요가 없다.
숫자 0과 1은 어떨까요?
전화 키패드에서는 0과 1에 해당하는 문자가 없으며, 이 문제 버전에서는 2부터 9까지만 사용합니다. 0과 1이 나올 수 있다면, 선택할 문자가 없으므로 해당 숫자를 건너뛸지 아니면 답을 빈 결과로 만들지 결정해야 합니다. 면접에서는 코드를 작성하기 전에 원하는 방식이 무엇인지 물어보세요.
비슷한 문제
같은 아이디어를 쓰는 문제입니다. 두세 개를 풀면 패턴이 몸에 익습니다.
Python
def letterCombinations(digits):
# 여기에 코드를 작성하세요케이스 1
케이스 2
케이스 3
입력
digits = "23"
기대값
["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]