주어진 jenres 문자열 배열을 위 딕셔너리 두개로 먼저 파싱한 뒤, 총 재생수가 많은 순으로 정렬하여 해당 장르를 접근하였습니다.
[장르: (인덱스: Int, 재생수: Int)]의 value에서 재생수가 많은 순으로 정렬한 다음, 장르 key값으로 (인덱스: Int, 재생수: Int) 튜플 value에 접근하여 가장 재생수가 높은 두개의 곡을 뽑아서 배열에 저장한 뒤 리턴하였습니다.
코드로 구현하면 다음과 같습니다.
import Foundation
funcsolution(_genres:[String], _plays:[Int]) -> [Int] {
var played = [String: [(index: Int, plays: Int)]]()
var totalPlayedGenres = [String: Int]()
var album = [Int]()
for i in0..<genres.count {
let genre = genres[i]
let playCount = plays[i]
if played[genre] ==nil {
played[genre] = [(index: i, plays: playCount)]
totalPlayedGenres[genre] = playCount
} else {
played[genre]!.append((index: i, plays: playCount))
totalPlayedGenres[genre]!+= playCount
}
}
let sortedGenres = totalPlayedGenres.sorted { $0.value >$1.value }
sortedGenres.forEach { element inlet songs = played[element.key]!.sorted{ $0.plays >$1.plays}
for i in0..<songs.count {
album.append(songs[i].index)
if i ==1 { break }
}
}
return album
}
당신은 온라인 게임을 운영하고 있습니다. 같은 시간대에 게임을 이용하는 사람이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. 게임진행], [2. 이전기록확인], [3.종료하기] 로 세가지 동작을 실행할 수 있습니다.
이를 열거형을 사용하여 didSet 관찰자를 사용해 입력받은 값에 따라 설정하여 didSet에 선언된 메서드가 실행되도록 구현하였습니다.
enumGameState: Int{
case play =1case record =2case exit =3case none
}
privatevar currentState: GameState= .none {
didSet {
switch currentState {
case .play:
playBaseball()
case .record:
openRecord()
case .exit:
exitGame()
default:
print("올바른 숫자를 입력해주세요!")
}
}
}
funcplay() {
while currentState != .exit {
selectMode()
}
}
privatefuncselectMode() {
print("환영합니다! 원하시는 번호를 입력해주세요\n1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기")
guardlet input =readLine(),
let inputInteger =Int(input),
let state =GameState(rawValue: inputInteger) else { return }
currentState = state
}
종합해보면 아래와 같습니다.
classNumberBaseballGame{
privatevar answer: [Int] = []
privatevar records: [(game: Int, tryCount: Int)] = []
privatevar currentGameIndex =0privatevar currentState: GameState= .none {
didSet {
switch currentState {
case .play:
playBaseball()
case .record:
openRecord()
case .exit:
exitGame()
default:
print("올바른 숫자를 입력해주세요!")
}
}
}
enumGameState: Int{
case play =1case record =2case exit =3case none
}
init() { }
funcplay() {
while currentState != .exit {
selectMode()
}
}
privatefuncselectMode() {
print("환영합니다! 원하시는 번호를 입력해주세요\n1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기")
guardlet input =readLine(),
let inputInteger =Int(input),
let state =GameState(rawValue: inputInteger) else { return }
currentState = state
}
privatefuncexitGame() {
records.removeAll()
currentGameIndex =0
}
privatefuncopenRecord() {
records.forEach { (game, count) inprint("\(game)번째 게임 : 시도 횟수 - \(count)\n")
}
}
privatefuncplayBaseball() {
var currentTryCount =0var isFindAnswer =false
currentGameIndex +=1
initAnswer()
print("<게임을 시작합니다.>")
while!isFindAnswer {
print("숫자를 입력하세요")
guardlet input =readLine() else { continue }
if!isCorrectInput(input: input) {
print("올바르지 않은 입력값입니다.\n")
continue
}
currentTryCount +=1let inputIntegerArray = input.map{Int(String($0))!}
let result = compareAnswer(with: inputIntegerArray)
if result.equalPoint ==3 {
print("정답입니다!")
isFindAnswer =true
records.append((game: currentGameIndex, tryCount: currentTryCount))
} elseif result.equalPoint ==0&& result.containsPoint ==0 {
print("Nothing")
} else {
print("\(result.equalPoint) Strike \(result.containsPoint) Ball")
}
}
}
privatefuncisCorrectInput(input: String) -> Bool {
if input.prefix(1) =="0" { returnfalse }
ifInt(input) ==nil||Set(input.map{$0}).count !=3 {
returnfalse
}
returntrue
}
privatefuncinitAnswer() {
var answerArray = [Int]()
funcrandomNumber(range: Range<Int>) -> Int {
returnInt.random(in: range)
}
while answerArray.count <3 {
let num = randomNumber(range: 1..<10)
if!answerArray.contains(num) { answerArray.append(num)}
}
answer = answerArray
}
privatefunccompareAnswer(withnumArray: [Int]) -> (equalPoint: Int, containsPoint: Int) {
var equalPoint =0var containsPoint =0for i in0..<numArray.count {
if answer[i] == numArray[i] {
equalPoint +=1
} elseif answer.contains(numArray[i]) {
containsPoint +=1
}
}
return (equalPoint: equalPoint, containsPoint: containsPoint)
}
}
RGB거리에는 집이 N개 있다. 거리는 선분으로 나타낼 수 있고, 1번 집부터 N번 집이 순서대로 있다.
집은 빨강, 초록, 파랑 중 하나의 색으로 칠해야 한다. 각각의 집을 빨강, 초록, 파랑으로 칠하는 비용이 주어졌을 때, 아래 규칙을 만족하면서 모든 집을 칠하는 비용의 최솟값을 구해보자.
1번 집의 색은 2번, N번 집의 색과 같지 않아야 한다.
N번 집의 색은 N-1번, 1번 집의 색과 같지 않아야 한다.
i(2 ≤ i ≤ N-1)번 집의 색은 i-1, i+1번 집의 색과 같지 않아야 한다.
입력
첫째 줄에 집의 수 N(2 ≤ N ≤ 1,000)이 주어진다. 둘째 줄부터 N개의 줄에는 각 집을 빨강, 초록, 파랑으로 칠하는 비용이 1번 집부터 한 줄에 하나씩 주어진다. 집을 칠하는 비용은 1,000보다 작거나 같은 자연수이다.
출력
첫째 줄에 모든 집을 칠하는 비용의 최솟값을 출력한다.
접근방법: DP
해당문제를 풀기전 규칙을 먼저 이해해야한다. 규칙은 단순하다.
직선 위 N개의 집이 주어졌을 때, 서로 인접한 집과의 색이 같지 않고, 첫번째와 마지막의 집의 색이 같으면 안된다.
직선을 원으로 만들어 첫번째와 마지막 집을 인접시키면 모든 집은 인접한 집과 다른 색을 갖는다는 점과 같지만
이 문제에선 이 방식은 사용하지않지만 규칙을 이해하는데 쉬웠다.
첫번째 집부터 마지막집까지 색칠하는 과정을 거치는데 첫번째와 마지막집의 색이 같으면 안되므로 첫번째집의 색을 알고 있어야한다.
이를 DP방식을 이용하여 풀려면 이전까지 색칠한 비용을 알고 이용하여 그다음 집까지 색칠비용을 구한다.
인접한 집과 색이 같으면 안되므로
첫번째 집이 빨강이면 두번째 집은 파랑과 초록이 가능하다
두번째 집이 파랑이라면 세번째 집은 빨강과 초록이 가능하다.
세번째 집이 초록이라면 네번째 집은 빨강과 파랑이 가능하다.
주어진 비용을 2차원 배열로 저장하면 빨강: 0, 초록: 1, 파랑: 2 인덱스를 갖는 배열이 된다.
위 방식을 사용하면 dp배열을 아래와 같이 정의할 수 있다.
dp[n][0]: n번째 집을 빨강으로 색칠한 최소비용
dp[n][1]: n번째 집을 초록으로 색칠한 최소비용
dp[n][2]: n번째 집을 파랑으로 색칠한 최소비용
dp[n][0] = arr[n][0] + min(dp[n-1][1], dp[n-1][2]) (arr: 색칠비용이 담긴 2차원배열)
이렇게 마지막 집까지 모두 색칠했을 때, 마지막 집과 첫번째 집의 색이 다른 경우의 비용을 갱신하여 값을 구한다.
코드로 구현하면 다음과 같다.
import Foundation
// 입력받기letN=Int(readLine()!)!var arr = [[0]]
letINF=100000000var ans =INFfor_in1...N {
arr.append(readLine()!.split(separator: " ").map{Int(String($0))!})
}
// 첫번째 집이 어떤색을 칠하는지? 가 기준이 된다.// 집이 나열된 선분을 순환구조로 나타낸다면, 인접한 집과의 색은 같으면 안된다.// 첫번째 집을 세가지 색 모두 칠해보고 색칠비용의 최솟값을 구한다.for i in0...2 {
// dp[i][j]: i번째집을 j색으로 칠했을 때의 최소비용var dp =Array(repeating: Array(repeating: 0, count: 3), count: N+1)
// 첫번째 집과 마지막집의 색이 같으면 안되므로, 우리는 첫번째 집의 색을 기억하고 있어야한다.// 따라서 첫번째 집의 색 이외의 경우의수는 임의의 최댓값을 넣는다.for j in0..<3 {
if i == j {
dp[1][j] = arr[1][j]
} else {
dp[1][j] =INF
}
}
// 규칙에따라 현재집은 이전의 집과 다른색을 칠해야한다.// n번째 집까지 색칠한 최소비용: n-1번째까지 칠한 최소비용 + n번째 집의 색칠비용for j in2...N {
dp[j][0] = arr[j][0] +min(dp[j-1][1], dp[j-1][2])
dp[j][1] = arr[j][1] +min(dp[j-1][0], dp[j-1][2])
dp[j][2] = arr[j][2] +min(dp[j-1][0], dp[j-1][1])
}
// 첫번째 집과 마지막번째 집의 색이 같지 않다면, 최소비용을 갱신한다.for k in0..<3 {
if i != k {
ans =min(ans, dp[N][k])
}
}
}
print(ans)