347. Top K Frequent Elements
#Algorithm #Algorithm-Divide_N_Conquer #Algorithm-Bucket_Sort
1. 문제
1-1. 원문
Given an integer array nums
and an integer k
, return the k
most frequent elements. You may return the answer in any order.
Example 1:
Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]
Example 2:
Input: nums = [1], k = 1
Output: [1]
Constraints:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
k
is in the range[1, the number of unique elements in the array]
.- It is guaranteed that the answer is unique.
1-2. 내용 번역
주어진 배열이 있을때, 배열 안의 숫자들의 출현빈도가 가장 높은 k개의 숫자를 리턴해라. 숫자의 출력 순서는 상관없다. (숫자 Set은 유일함을 보장한다.)
2. 문제 이해
2-1. 내용 이해
nums=[1,1,1,2,2,3], k=2
이면 nums 배열안의 숫자중에 출현 빈도가 가장 높은 2개의 수를 구하는 것이다. 1은 3번, 2는 2번, 3은 1번 나타나기 때문에 가장 많이 출현하는 수는 1, 그 다음은 2가 되어서 output은 [1, 2]
혹은 [2, 1]
가 되는 것이다.
2-2. 접근법 생각
우선 어떤 수가 얼마나 나왔는지를 먼저 구해야겠네.
-> 그럼 루프 돌리면서 해시맵에다가 출현 빈도를 더해 나가야겠다.
...
그리고나서는.. 이 맵을 출현빈도대로 정렬해야겠는데..
-> sortedWith에 Comparable을 넣어서 정렬시켜버릴 수 있겠지만 이 정렬 알고리즘을 짜라는게 문제니까 이건 생략하자.
-> 그럼 해시맵을 array로 만들고 내가 아는 정렬 알고리즘을 사용해야겠네. 지금 익숙한건 머지소트랑 퀵소트니까 이걸 써봐야겠다. 으.. Int가 아니라 Pair를 가지고 정렬을 해야하니까 코드가 좀 복잡해지네..
2-3. 적용한 풀이
머지소트, 퀵소트
전체를 모두 정렬해야 한다. 의도하는 바를 구현하기에는 닭잡는데 소칼 쓰는 격이다.
Bucket Sort
추후에 여러 풀이를 보다가 이게 문제가 원하는 모범 솔루션인 것 같다는 생각이 들었다.
3. 구현
머지소트
class Solution {
fun topKFrequent(nums: IntArray, k: Int): IntArray {
val resultArr = IntArray(k)
val countingMap = HashMap<Int, Int>()
for (i in 0..nums.size-1) {
val key = nums[i]
val updatingCount = countingMap.getOrPut(key){0} + 1
countingMap.put(key, updatingCount)
}
val targetArr: Array<Pair<Int, Int>> = Array<Pair<Int, Int>>(countingMap.size) { Pair(0,0) }
var idx = 0
for ((num, counting) in countingMap) {
targetArr[idx++] = Pair<Int, Int>(counting, num)
}
val sortedArr = mergeSort(targetArr)
for (i in 0..k-1) {
resultArr[i] = sortedArr[i].second
}
return resultArr
}
fun mergeSort(targetArr: Array<Pair<Int,Int>>): Array<Pair<Int,Int>> {
val targetArrSize = targetArr.size
if (targetArrSize == 1) return arrayOf(Pair(targetArr[0].first, targetArr[0].second))
val mid = targetArrSize/2
val leftArr = mergeSort(targetArr.sliceArray(IntRange(0, mid-1)))
val rightArr = mergeSort(targetArr.sliceArray(IntRange(mid, targetArrSize - 1)))
var leftIdx = 0
var rightIdx = 0
var sortedIdx = 0
val sortedArr: Array<Pair<Int, Int>> = Array<Pair<Int, Int>>(targetArrSize) { Pair(0,0) }
while(leftIdx < leftArr.size && rightIdx < rightArr.size) {
if (leftArr[leftIdx].first > rightArr[rightIdx].first) {
sortedArr[sortedIdx++] = leftArr[leftIdx++]
} else {
sortedArr[sortedIdx++] = rightArr[rightIdx++]
}
}
while(leftIdx < leftArr.size) {
sortedArr[sortedIdx++] = leftArr[leftIdx++]
}
while(rightIdx < rightArr.size) {
sortedArr[sortedIdx++] = rightArr[rightIdx++]
}
return sortedArr
}
}
퀵소트
class Solution {
fun topKFrequent(nums: IntArray, k: Int): IntArray {
val countingMap = HashMap<Int, Int>()
for(num in nums) {
countingMap.put(num, countingMap.getOrPut(num){0}+1)
}
val countingValArr = IntArray(countingMap.size)
var idx = 0
for((key, counting) in countingMap) {
countingValArr[idx++] = counting
}
val limitVal = quickSort(countingValArr)[k-1]
val resultArr = IntArray(k)
idx = 0
for((key, counting) in countingMap) {
if (counting >= limitVal) {
resultArr[idx++] = key
}
}
return resultArr
}
fun quickSort(countingArr: IntArray): IntArray {
if (countingArr.size == 1) return countingArr
val arrSize = countingArr.size
val pivot = countingArr[arrSize-1]
var leftIdx = 0
var rightIdx = arrSize-2
while(leftIdx < rightIdx) {
while (countingArr[leftIdx] > pivot && leftIdx < rightIdx) {
leftIdx++
}
while(countingArr[rightIdx] <= pivot && leftIdx < rightIdx) {
rightIdx--
}
val temp = countingArr[rightIdx]
countingArr[rightIdx] = countingArr[leftIdx]
countingArr[leftIdx] = temp
}
if (countingArr[leftIdx] <= pivot) {
countingArr[arrSize-1] = countingArr[leftIdx]
countingArr[leftIdx] = pivot
}
if (arrSize-1 == leftIdx) {
return quickSort(countingArr.sliceArray(IntRange(0, leftIdx)))
}
return quickSort(countingArr.sliceArray(IntRange(0, leftIdx))) + quickSort(countingArr.sliceArray(IntRange(leftIdx+1, arrSize-1)))
}
}
버켓소트
class Solution {
fun topKFrequent(nums: IntArray, k: Int): IntArray {
val countingMap = HashMap<Int, Int>()
for(num in nums) {
countingMap.put(num, countingMap.getOrPut(num){0}+1)
}
val bucket = Array<ArrayList<Int>>(nums.size+1){ArrayList<Int>()}
for((key, counting) in countingMap) {
bucket[counting].add(key)
}
val resultArr = IntArray(k)
var count = 0
for(idx in nums.size downTo 0) {
if(bucket[idx].size > 0) {
for(bucketNum in bucket[idx]) {
resultArr[count++] = bucketNum
}
}
if (count == k) break
}
return resultArr
}
}
4. 복잡도
머지소트
- 시간복잡도: nlogn
퀵소트
- 시간복잡도: nlogn
버켓소트
- 시간복잡도: m^2
- 버켓소트의 원래 평균 복잡도는 n^2 이라고 되어있는데, 이는 마지막에 머지하는 과정에서 이중 루프를 돌려야하기 때문이다. 이 부분이 없으면 버켓 내부를 소팅하는 알고리즘에 의해 시간이 결정되고 버켓 안을 정렬할 필요도 없어지면 시간복잡도는 n이 된다.
- 마지막에 머지하는 과정이 있기 때문에 이것도 m^2 이지만, 다른 소트에 비해 실제 실행 시간이 현저히 적었다. 정렬을 위한 비교를 하지 않기 때문에 연산과정이 거의 필요하지 않은게 그 이유이다.