당신은 온라인 게임을 운영하고 있습니다. 같은 시간대에 게임을 이용하는 사람이m명 늘어날 때마다 서버 1대가 추가로 필요합니다. 어느 시간대의 이용자가m명 미만이라면, 서버 증설이 필요하지 않습니다. 어느 시간대의 이용자가n x m명 이상(n + 1) x m명 미만이라면 최소n대의 증설된 서버가 운영 중이어야 합니다. 한 번 증설한 서버는k시간 동안 운영하고 그 이후에는 반납합니다. 예를 들어,k= 5 일 때 10시에 증설한 서버는 10 ~ 15시에만 운영됩니다.
하루 동안 모든 게임 이용자가 게임을 하기 위해 서버를 최소 몇 번 증설해야 하는지 알고 싶습니다. 같은 시간대에 서버를 x대 증설했다면 해당 시간대의 증설 횟수는 x회입니다.
다음은m= 3,k= 5 일 때의 시간대별 증설된 서버의 수와 증설 횟수 예시입니다.
시각
게임 이용자의 수
증설된 서버의 수
증설된 서버의 수증설 횟수
0 ~ 1
0
0
0
1 ~ 2
2
0
0
2 ~ 3
3
1
1
3 ~ 4
3
1
0
4 ~ 5
1
1
0
5 ~ 6
2
1
0
6 ~ 7
0
1
0
7 ~ 8
0
0
0
8 ~ 9
0
0
0
9 ~ 10
0
0
0
10 ~ 11
4
1
1
11 ~ 12
2
1
0
12 ~ 13
0
1
0
13 ~ 14
6
2
1
14 ~ 15
0
2
0
15 ~ 16
4
1
0
16 ~ 17
2
1
0
17 ~ 18
13
4
3
18 ~ 19
3
3
0
19 ~ 20
5
3
0
20 ~ 21
10
3
0
21 ~ 22
0
3
0
22 ~ 23
1
0
0
23 ~ 24
5
1
1
모든 게임 이용자를 감당하기 위해 최소 7번 서버를 증설해야 하며, 이보다 적은 수의 서버 증설로는 모든 게임 이용자를 감당할 수 없습니다.
0시에서 23시까지의 시간대별 게임 이용자의 수를 나타내는 1차원 정수 배열players, 서버 한 대로 감당할 수 있는 최대 이용자의 수를 나타내는 정수m, 서버 한 대가 운영 가능한 시간을 나타내는 정수k가 주어집니다. 이때, 모든 게임 이용자를 감당하기 위한 최소 서버 증설 횟수를 return 하도록 solution을 완성해 주세요.
제한사항
players의 길이 = 24
0 ≤players의 원소 ≤ 1,000
players[i]는i시 ~i+1시 사이의 게임 이용자의 수를 나타냅니다.
1 ≤m≤ 1,000
1 ≤k≤ 24
내가 푼 풀이
주어진 players배열과 크기가 같은 servers 배열을 만들었습니다.
players배열을 반복문으로 순회하면서 각 시간마다 서버 증설이 필요한지 조건을 달았습니다.
증설이 필요하다면 그리디 기법으로 현재 시간의 인원을 수용할 만큼만의 서버를 증설하고 k시간까지 최대 이용자 수를 증가시켰습니다.
증설이 필요할 때 서버를 증설하고 증설된 수를 count 변수에 담아서 리턴했습니다.
import Foundation
funcsolution(_players:[Int], _m:Int, _k:Int) -> Int {
var servers =Array(repeating: 0, count: players.count)
var count =0for i in0..<players.count {
let allowPlayerCount = (servers[i] * m) + m
if players[i] >= allowPlayerCount {
let needServerCount =abs(servers[i] - (players[i] / m))
count += needServerCount
for j in i..<i+k {
if j >= players.count { continue }
servers[j] += needServerCount
}
}
}
return count
}
A도둑과 B도둑이 팀을 이루어 모든 물건을 훔치려고 합니다. 단, 각 도둑이 물건을 훔칠 때 남기는 흔적이 누적되면 경찰에 붙잡히기 때문에, 두 도둑 중 누구도 경찰에 붙잡히지 않도록 흔적을 최소화해야 합니다.
물건을 훔칠 때 조건은 아래와 같습니다.
물건 i를 훔칠 때,
A도둑이 훔치면info[i][0]개의 A에 대한 흔적을 남깁니다.
B도둑이 훔치면info[i][1]개의 B에 대한 흔적을 남깁니다.
각 물건에 대해 A도둑과 B도둑이 남기는 흔적의 개수는 1 이상 3 이하입니다.
경찰에 붙잡히는 조건은 아래와 같습니다.
A도둑은 자신이 남긴 흔적의 누적 개수가n개 이상이면 경찰에 붙잡힙니다.
B도둑은 자신이 남긴 흔적의 누적 개수가m개 이상이면 경찰에 붙잡힙니다.
각 물건을 훔칠 때 생기는 흔적에 대한 정보를 담은 2차원 정수 배열info, A도둑이 경찰에 붙잡히는 최소 흔적 개수를 나타내는 정수n, B도둑이 경찰에 붙잡히는 최소 흔적 개수를 나타내는 정수m이 매개변수로 주어집니다. 두 도둑 모두 경찰에 붙잡히지 않도록 모든 물건을 훔쳤을 때,A도둑이 남긴 흔적의 누적 개수의 최솟값을 return 하도록 solution 함수를 완성해 주세요. 만약 어떠한 방법으로도 두 도둑 모두 경찰에 붙잡히지 않게 할 수 없다면 -1을 return해 주세요.
제한사항
1 ≤info의 길이 ≤ 40
info[i]는 물건i를 훔칠 때 생기는 흔적의 개수를 나타내며, [A에 대한 흔적 개수,B에 대한 흔적 개수]의 형태입니다.
1 ≤흔적 개수≤ 3
1 ≤n≤ 120
1 ≤m≤ 120
테스트 케이스 구성 안내
아래는 테스트 케이스 구성을 나타냅니다. 각 그룹 내의 테스트 케이스를 모두 통과하면 해당 그룹에 할당된 점수를 획득할 수 있습니다.
그룹총점테스트 케이스 그룹 설명
#1
15%
info[i][1]= 1
#2
40%
info의 길이 ≤ 20
#3
45%
추가 제한 사항 없음
입출력 예infonmresult
[[1, 2], [2, 3], [2, 1]]
4
4
2
[[1, 2], [2, 3], [2, 1]]
1
7
0
[[3, 3], [3, 3]]
7
1
6
[[3, 3], [3, 3]]
6
1
-1
내가 푼 풀이
첫번째 방법 - DFS
한개의 물건이 주어졌을 때, 두가지 경우의 수가 있습니다.
1. A가 훔치기
2. B가 훔치기
또한 경찰에게 걸리지 않게 모든 물건을 훔쳤기에 물건을 훔치지 않는 옵션은 없습니다.
결국 A가 최소로 도둑질을 하면 흔적도 자연스럽게 최솟값을 가질 것이라 생각하고, B에게 먼저 조건을 걸어주었고, B가 최대한 훔치도록 구현하였습니다.
import Foundation
funcsolution(_info:[[Int]], _n:Int, _m:Int) -> Int {
var min =Int.max
var informations = info
funcdfs(count: [Int], currentIndex: Int) {
if currentIndex >= info.count {
min >= count[0] ? min = count[0] : nilreturn
}
let theifACount = [count[0] + info[currentIndex][0], count[1]]
let theifBCount = [count[0], count[1] + info[currentIndex][1]]
if theifACount[0] < n && theifACount[0] < min {
dfs(count: [count[0] + info[currentIndex][0], count[1]],
currentIndex: currentIndex +1)
}
if theifBCount[1] < m {
dfs(count: [count[0], count[1] + info[currentIndex][1]],
currentIndex: currentIndex +1)
}
}
dfs(count: [0, 0], currentIndex: 0)
return min ==Int.max ?-1 : min
}
결과는 시간초과가 발생하였습니다.
이유를 확인해보면, 만약 m,n이 매우 넉넉하다면, 큰 수라면 bfs의 실행은 240 번 실행되어 시간초과가 발생하였습니다.
그러면 시간만 줄여서 될까 해서 테스트케이스를 몇 개 추가했더니, 해당 케이스도 실패하는걸 볼 수 있었습니다.
info = [[1, 2], [3, 1], [3, 2]]
n =5
m =3// 결과는 4가 나와야 하지만 1이 나왔습니다.
이를 통해 매번 탐색할 때 최적해를 갱신해야 할 필요가 있구나 싶었습니다.
두번째 방법 - DP
이전까지의 최적해를 저장하여 그다음 최적해를 구하기 위해 DP를 사용했습니다.
info배열을 돌면서 한번으로 최적해가 구해질 수 있을까? 해서 작성해보았는데 위 테스트 케이스도 통과하지 못했습니다.
B도둑이 알뜰살뜰하게 m을 넘지 않으면서 m과 가까운 수만큼 흔적을 남기는 방법이 뭐가 있을까 하다가 배낭문제가 생각났습니다.
(도둑이 배낭에 보석을 담을 때 정해진 배낭무게, 보석무게와 가치에 맞춰서 가장 가치있는 보석만 골라담는 문제)
B도둑이 최대한 흔적을 많이 남길 수 있게 배낭문제처럼 m값을 갱신하고 A흔적의 최솟값을 구하려고 합니다.
이를 1번 입력을 예시로 2차원배열로 나타내보겠습니다.
행은 m, B가 남길 수 있는 흔적 입니다. 열은 index로 info의 물건을 접근하기위한 인덱스입니다.
조건을 다시 살펴보자면,
1. 모든 물건을 훔쳤다.(물건을 훔치지 않고 건너뛰는건 불가능)
2. A는 n, B는 m 보다 같거나 클 수 없다. 만약 같거나 크다면 훔칠 수 없다고 판단한다.
위 조건을 바탕으로 배낭문제 처럼 2차원 배열을 채워가며 A의 최솟값을 구하겠습니다.
dp[i][j] = 0 은 물건을 하나도 훔치지 않을 때 값입니다.
행은 무게를 뜻하고, 열은 물건의 순서를 뜻하고, 해당 위치의 원소는 A의 흔적입니다.
첫번째는 아무것도 훔치지 않았기때문에 dp[0][1...3] = 0 입니다.
이제 첫번째 물건을 훔치겠습니다. 물건의 정보는 [1, 2] 입니다.
이때, 두가지 경우의 수가 있습니다. (1. A가 훔치기 2. B가 훔치기)
해당 DP인덱스의 원소는 A의 흔적을 의미하기에 A가 훔친다면 이전 값에서 A의 흔적을 더한값이 될 것입니다.
B가 훔친다면, (현재열 + 2) 인덱스에 이전 값을 넣게 됩니다.
이렇게 이전의 저장된 값을 바탕으로 최적해를 구하기위해 갱신하면 다음과 같습니다.
그 다음 두번째 물건을 훔치겠습니다. 물건의 정보는 [2,3] 입니다.
두번째 물건도 두가지 경우가 있습니다.
dp[2][0]에는 3이 들어가고 이 의미는 두번째 물건까지 A가 훔치는 경우입니다.
두번째 물건을 B가 훔치는 경우는 (현재 열 + 3) 인덱스에 넣게 되는데, 이 때 첫번째 물건을 넣은 값과 비교를 해야합니다.
import Foundation
funcsolution(_info:[[Int]], _n:Int, _m:Int) -> Int {
letINF=100000000// DPvar dp =Array(repeating: Array(repeating: INF, count: m),
count: info.count+1)
for i in0..<m {
dp[0][i] =0
}
for i in1...info.count {
// 현재 물건에 대한 A의 흔적과 B의 흔적let aThief = info[i-1][0]
let bThief = info[i-1][1]
for j in0..<m {
// A가 훔친 경우 값 갱신
dp[i][j] =min(dp[i][j], dp[i-1][j] + aThief)
// B가 훔친 경우 값 갱신if j + bThief < m {
dp[i][j + bThief] =min(dp[i][j + bThief], dp[i-1][j])
}
}
}
// 마지막 물건과 B의 흔적이 m보다 크거나 같지 않게 훔쳤을 때의 최소값 리턴return dp[info.count][m-1] >= n ?-1 : dp[info.count][m-1]
}
1 x 1 크기의 칸들로 이루어진 직사각형 격자 형태의 미로에서 탈출하려고 합니다. 각 칸은 통로 또는 벽으로 구성되어 있으며, 벽으로 된 칸은 지나갈 수 없고 통로로 된 칸으로만 이동할 수 있습니다. 통로들 중 한 칸에는 미로를 빠져나가는 문이 있는데, 이 문은 레버를 당겨서만 열 수 있습니다. 레버 또한 통로들 중 한 칸에 있습니다. 따라서, 출발 지점에서 먼저 레버가 있는 칸으로 이동하여 레버를 당긴 후 미로를 빠져나가는 문이 있는 칸으로 이동하면 됩니다. 이때 아직 레버를 당기지 않았더라도 출구가 있는 칸을 지나갈 수 있습니다. 미로에서 한 칸을 이동하는데 1초가 걸린다고 할 때, 최대한 빠르게 미로를 빠져나가는데 걸리는 시간을 구하려 합니다.
미로를 나타낸 문자열 배열 maps가 매개변수로 주어질 때, 미로를 탈출하는데 필요한 최소 시간을 return 하는 solution 함수를 완성해주세요. 만약, 탈출할 수 없다면 -1을 return 해주세요.
제한사항
5 ≤maps의 길이 ≤ 100
5 ≤maps[i]의 길이 ≤ 100
maps[i]는 다음 5개의 문자들로만 이루어져 있습니다.
S : 시작 지점
E : 출구
L : 레버
O : 통로
X : 벽
시작 지점과 출구, 레버는 항상 다른 곳에 존재하며 한 개씩만 존재합니다.
출구는 레버가 당겨지지 않아도 지나갈 수 있으며, 모든 통로, 출구, 레버, 시작점은 여러 번 지나갈 수 있습니다.
입출력 예
maps
result
["SOOOL","XXXXO","OOOOO","OXXXX","OOOOE"]
16
["LOOXS","OOOOX","OOOOO","OOOOO","EOOOO"]
-1
내가 푼 풀이
접근방법 1. DFS 백트래킹 (시간초과)
최단시간을 구하는 문제는 BFS와 맞다..
1. 미로를 진입해 레버를 찾기까지 걸린시간을 구한다.
2. 레버를 찾았다면 레버위치에서 출구까지의 걸린시간을 더한다.
3. 레버를 못찾거나, 탈출구를 못찾았다면 -1출력
-> 이방법을 처음에 생각해서 사용해봤는데, 시간초과를 피할 수 없었다.
최악의 경우 모든 길을 찾아야하므로 BFS를 이용하기로 했다.
접근방법 2. BFS
DFS를 실패하고 바로 BFS를 설계했지만, BFS도 시간초과가 좀 났었다.
BFS는 인접한 지점 먼저 탐색이지만, 이미 탐색한곳은 재탐색을 하지 않게 해줘야한다.
재탐색여부를 저장하는 visited[Bool]에 값을 넣는 시점에따라 완전탐색, 조건에맞는 부분탐색을 하게 되었다.
rows x columns 크기인 행렬이 있습니다. 행렬에는 1부터 rows x columns까지의 숫자가 한 줄씩 순서대로 적혀있습니다. 이 행렬에서 직사각형 모양의 범위를 여러 번 선택해, 테두리 부분에 있는 숫자들을 시계방향으로 회전시키려 합니다. 각 회전은 (x1, y1, x2, y2)인 정수 4개로 표현하며, 그 의미는 다음과 같습니다.
x1 행 y1 열부터 x2 행 y2 열까지의 영역에 해당하는 직사각형에서 테두리에 있는 숫자들을 한 칸씩 시계방향으로 회전합니다.
다음은 6 x 6 크기 행렬의 예시입니다.
이 행렬에 (2, 2, 5, 4) 회전을 적용하면, 아래 그림과 같이 2행 2열부터 5행 4열까지 영역의 테두리가 시계방향으로 회전합니다. 이때, 중앙의 15와 21이 있는 영역은 회전하지 않는 것을 주의하세요.
행렬의 세로 길이(행 개수) rows, 가로 길이(열 개수) columns, 그리고 회전들의 목록 queries가 주어질 때, 각 회전들을 배열에 적용한 뒤, 그 회전에 의해 위치가 바뀐 숫자들 중가장 작은 숫자들을 순서대로 배열에 담아return 하도록 solution 함수를 완성해주세요.
제한사항
rows는 2 이상 100 이하인 자연수입니다.
columns는 2 이상 100 이하인 자연수입니다.
처음에 행렬에는 가로 방향으로 숫자가 1부터 하나씩 증가하면서 적혀있습니다.
즉, 아무 회전도 하지 않았을 때, i 행 j 열에 있는 숫자는 ((i-1) x columns + j)입니다.
예를 들어, 두 번째 회전에 대한 답은 첫 번째 회전을 실행한 다음, 그 상태에서 두 번째 회전을 실행했을 때 이동한 숫자 중 최솟값을 구하면 됩니다.
입출력 예시
rows
columns
queries
result
6
6
[[2,2,5,4],[3,3,6,6],[5,1,6,3]]
[8, 10, 25]
3
3
[[1,1,2,2],[1,2,2,3],[2,1,3,2],[2,2,3,3]]
[1, 1, 5, 3]
100
97
[[1,1,100,97]]
[1]
내가 푼 풀이
- 회전을 구현하여 해당 범위를 교체하고, 최솟값을 저장한다.
- 따로 다른 알고리즘 없이 회전하는 동작을 구현하면 된다.
import Foundation
funcsolution(_rows:Int, _columns:Int, _queries:[[Int]]) -> [Int] {
var board =Array(repeating:Array(repeating: 0, count: columns+1), count: rows+1)
var answer = [Int]()
// 입력for i in1...rows {
for j in1...columns {
board[i][j] = ((i-1) * columns + j)
}
}
// 쿼리 돌리기var b = board
for query in queries {
var a = moveNums(query, b)
var minNum = a.removeLast()
answer += minNum
b = a
}
return answer
}
funcmoveNums(_query: [Int],_arr: [[Int]]) -> [[Int]] {
var dx = [1, 0, -1, 0]
var dy = [0, 1, 0, -1]
var a = arr
var mx = query[1]
var my = query[0]
var rc = query[3] - query[1]
var cc = query[2] - query[0]
var previous = a[my][mx]
var minNum = a[my][mx]
// 회전하며, 저장된 값과 그 다음의 값을 비교해 최솟값 갱신for i in0..<4 {
if i ==0|| i ==2 {
for j in0..<rc {
mx += dx[i]
my += dy[i]
minNum =min(minNum, a[my][mx])
var tmp = a[my][mx]
a[my][mx] = previous
previous = tmp
}
} else {
for j in0..<cc {
mx += dx[i]
my += dy[i]
minNum =min(minNum, a[my][mx])
var tmp = a[my][mx]
a[my][mx] = previous
previous = tmp
}
}
}
a.append([minNum])
return a
}
IT 벤처 회사를 운영하고 있는라이언은 매년 사내 해커톤 대회를 개최하여 우승자에게 상금을 지급하고 있습니다. 이번 대회에서는 우승자에게 지급되는 상금을 이전 대회와는 다르게 다음과 같은 방식으로 결정하려고 합니다. 해커톤 대회에 참가하는 모든 참가자들에게는 숫자들과 3가지의 연산문자(+, -, *) 만으로 이루어진 연산 수식이 전달되며, 참가자의 미션은 전달받은 수식에 포함된 연산자의 우선순위를 자유롭게 재정의하여 만들 수 있는 가장 큰 숫자를 제출하는 것입니다. 단, 연산자의 우선순위를 새로 정의할 때, 같은 순위의 연산자는 없어야 합니다. 즉,+>->*또는->*>+등과 같이 연산자 우선순위를 정의할 수 있으나+,*>-또는*>+,-처럼 2개 이상의 연산자가 동일한 순위를 가지도록 연산자 우선순위를 정의할 수는 없습니다. 수식에 포함된 연산자가 2개라면 정의할 수 있는 연산자 우선순위 조합은 2! = 2가지이며, 연산자가 3개라면 3! = 6가지 조합이 가능합니다. 만약 계산된 결과가 음수라면 해당 숫자의 절댓값으로 변환하여 제출하며 제출한 숫자가 가장 큰 참가자를 우승자로 선정하며, 우승자가 제출한 숫자를 우승상금으로 지급하게 됩니다.
예를 들어, 참가자 중네오가 아래와 같은 수식을 전달받았다고 가정합니다.
"100-200*300-500+20"
일반적으로 수학 및 전산학에서 약속된 연산자 우선순위에 따르면 더하기와 빼기는 서로 동등하며 곱하기는 더하기, 빼기에 비해 우선순위가 높아*>+,-로 우선순위가 정의되어 있습니다. 대회 규칙에 따라+>->*또는->*>+등과 같이 연산자 우선순위를 정의할 수 있으나+,*>-또는*>+,-처럼 2개 이상의 연산자가 동일한 순위를 가지도록 연산자 우선순위를 정의할 수는 없습니다. 수식에 연산자가 3개 주어졌으므로 가능한 연산자 우선순위 조합은 3! = 6가지이며, 그 중+>->*로 연산자 우선순위를 정한다면 결괏값은 22,000원이 됩니다. 반면에*>+>-로 연산자 우선순위를 정한다면 수식의 결괏값은 -60,420 이지만, 규칙에 따라 우승 시 상금은 절댓값인 60,420원이 됩니다.
참가자에게 주어진 연산 수식이 담긴 문자열 expression이 매개변수로 주어질 때, 우승 시 받을 수 있는 가장 큰 상금 금액을 return 하도록 solution 함수를 완성해주세요.
[제한사항]
expression은 길이가 3 이상 100 이하인 문자열입니다.
expression은 공백문자, 괄호문자 없이 오로지 숫자와 3가지의 연산자(+, -, *) 만으로 이루어진 올바른 중위표기법(연산의 두 대상 사이에 연산기호를 사용하는 방식)으로 표현된 연산식입니다. 잘못된 연산식은 입력으로 주어지지 않습니다.
즉,"402+-561*"처럼 잘못된 수식은 올바른 중위표기법이 아니므로 주어지지 않습니다.
expression의 피연산자(operand)는 0 이상 999 이하의 숫자입니다.
즉,"100-2145*458+12"처럼 999를 초과하는 피연산자가 포함된 수식은 입력으로 주어지지 않습니다.
"-56+100"처럼 피연산자가 음수인 수식도 입력으로 주어지지 않습니다.
expression은 적어도 1개 이상의 연산자를 포함하고 있습니다.
연산자 우선순위를 어떻게 적용하더라도, expression의 중간 계산값과 최종 결괏값은 절댓값이 263- 1 이하가 되도록 입력이 주어집니다.
같은 연산자끼리는 앞에 있는 것의 우선순위가 더 높습니다.
입출력 예
expression
result
"100-200*300-500+20"
60420
"50*6-3*2"
300
내가 푼 풀이
- 주어진 연산자의 우선순위를 모든 경우의수로 탐색해본다.
import Foundation
funcsolution(_expression:String) -> Int64 {
var operation = ["+", "-", "*"]
var arr = [String]()
var oop = [[String]]()
var num =""var answer =Int.min
// 우선순위의 모든 경우의수 뽑기funccombi(_targetArr: [String],_arr: [String]) {
if arr.count ==3 {
oop.append(arr)
}
for i in0..<targetArr.count {
if!arr.contains(targetArr[i]) {
combi(targetArr, arr + [targetArr[i]])
}
}
}
combi(operation, [])
// 연산자와 피연산자 구분하여 저장for i in expression {
if operation.contains(String(i)) {
arr.append(num)
arr.append(String(i))
num =""
} else {
num +=String(i)
}
}
arr.append(num)
// 모든경우의수 탐색하여 최댓값 구하기for i in oop {
var a = arr
for j in i {
a = operate(String(j), a)
}
answer =max(answer, abs(Int(a[0])!))
}
returnInt64(answer)
}
// 주어진 연산자로 계산funcoperate(_op: String,_expression: [String]) -> [String] {
var ex = [String]()
var index =-1for i in0..<expression.count {
if i == index {
index =-1continue
}
if expression[i] != op {
ex.append(expression[i])
} else {
var n1 =Int(ex.removeLast())!var n2 =Int(expression[i+1])!switch op {
case"*":
ex.append(String(n1 * n2))
case"-":
ex.append(String(n1 - n2))
case"+":
ex.append(String(n1 + n2))
default :
continue
}
index = i+1
}
}
return ex
}
카카오에 신입 개발자로 입사한"콘"은 선배 개발자로부터 개발역량 강화를 위해 다른 개발자가 작성한 소스 코드를 분석하여 문제점을 발견하고 수정하라는 업무 과제를 받았습니다. 소스를 컴파일하여 로그를 보니 대부분 소스 코드 내 작성된 괄호가 개수는 맞지만 짝이 맞지 않은 형태로 작성되어 오류가 나는 것을 알게 되었습니다. 수정해야 할 소스 파일이 너무 많아서 고민하던 "콘"은 소스 코드에 작성된 모든 괄호를 뽑아서 올바른 순서대로 배치된 괄호 문자열을 알려주는 프로그램을 다음과 같이 개발하려고 합니다.
용어의 정의
'('와')'로만 이루어진 문자열이 있을 경우, '(' 의 개수와 ')' 의 개수가 같다면 이를균형잡힌 괄호 문자열이라고 부릅니다. 그리고 여기에 '('와 ')'의 괄호의 짝도 모두 맞을 경우에는 이를올바른 괄호 문자열이라고 부릅니다. 예를 들어,"(()))("와 같은 문자열은 "균형잡힌 괄호 문자열" 이지만 "올바른 괄호 문자열"은 아닙니다. 반면에"(())()"와 같은 문자열은 "균형잡힌 괄호 문자열" 이면서 동시에 "올바른 괄호 문자열" 입니다.
'(' 와 ')' 로만 이루어진 문자열 w가 "균형잡힌 괄호 문자열" 이라면 다음과 같은 과정을 통해 "올바른 괄호 문자열"로 변환할 수 있습니다.
1. 입력이 빈 문자열인 경우, 빈 문자열을 반환합니다.
2. 문자열 w를 두 "균형잡힌 괄호 문자열" u, v로 분리합니다. 단, u는 "균형잡힌 괄호 문자열"로 더 이상 분리할 수 없어야 하며, v는 빈 문자열이 될 수 있습니다.
3. 문자열 u가 "올바른 괄호 문자열" 이라면 문자열 v에 대해 1단계부터 다시 수행합니다.
3-1. 수행한 결과 문자열을 u에 이어 붙인 후 반환합니다.
4. 문자열 u가 "올바른 괄호 문자열"이 아니라면 아래 과정을 수행합니다.
4-1. 빈 문자열에 첫 번째 문자로 '('를 붙입니다.
4-2. 문자열 v에 대해 1단계부터 재귀적으로 수행한 결과 문자열을 이어 붙입니다.
4-3. ')'를 다시 붙입니다.
4-4. u의 첫 번째와 마지막 문자를 제거하고, 나머지 문자열의 괄호 방향을 뒤집어서 뒤에 붙입니다.
4-5. 생성된 문자열을 반환합니다.
"균형잡힌 괄호 문자열"p가 매개변수로 주어질 때, 주어진 알고리즘을 수행해"올바른 괄호 문자열"로 변환한 결과를 return 하도록 solution 함수를 완성해 주세요.
매개변수 설명
p는 '(' 와 ')' 로만 이루어진 문자열이며 길이는 2 이상 1,000 이하인 짝수입니다.
문자열 p를 이루는 '(' 와 ')' 의 개수는 항상 같습니다.
만약 p가 이미 "올바른 괄호 문자열"이라면 그대로 return 하면 됩니다.
입출력 예
p
result
"(()())()"
"(()())()"
")("
"()"
"()))((()"
"()(())()"
내가 푼 풀이
- 주어진 동작들을 함수로 구현한 뒤, 재귀호출 형식으로 구현한다.
import Foundation
funcsolution(_p:String) -> String {
return correctSameString(p)
}
// 전체적인 동작funccorrectSameString(_str: String) -> String {
// 올바른문자열이라면 재귀 탈출if isCorrectString(str) {
return str
} else {
var arr = divideString(s: str)
// 분리한 문자열 u가 올바른 문자열이면 v는 다시 재귀호출if isCorrectString(arr[0]) {
return"\(arr[0])"+"\(correctSameString(arr[1]))"
} else {
// 분리한 문자열 u가 올바른 문자열이 아니라면, v는 재귀호출하고, (v)u 형식으로 출력var s =""var cs =""var sarr = arr[0]
// u문자열의 앞뒤 자르기if sarr.count !=0 {
s =String(sarr.prefix(sarr.count-1))
s.remove(at: s.startIndex)
}
// u문자열 뒤집기for i in s {
if i =="(" {
cs +=")"
} else {
cs +="("
}
}
// 형식에 맞게 재귀호출return"("+ correctSameString(arr[1]) +")"+"\(cs)"
}
}
}
// 문자열을 두개의 올바른 괄호문자열로 나눈다.funcdivideString(s: String) -> [String] {
var str = s
var leftStr =""var rightStr =""for i in1...str.count/2 {
var index = i *2
leftStr =String(str.prefix(index))
rightStr =String(str.suffix(str.count - index))
if isSameCountString(leftStr) && isSameCountString(rightStr) {
return [leftStr, rightStr]
}
}
return [leftStr, rightStr]
}
// 균형잡힌 문자열인지 판단funcisSameCountString(_p: String) -> Bool {
var str = p
var count =0for i in str {
if i =="(" {
count +=1
} elseif i ==")" {
count -=1
}
}
if count !=0 {
returnfalse
} else {
returntrue
}
}
// 올바른 문자열인지 판단funcisCorrectString(_p: String) -> Bool {
var stack = [String]()
for i in p {
switch i {
case"(":
stack.append("(")
case")":
if!stack.isEmpty {
stack.removeLast()
}
default:
continue
}
}
if stack.isEmpty {
returntrue
} else {
returnfalse
}
}