https://school.programmers.co.kr/learn/courses/30/lessons/178870
비내림차순으로 정렬된 수열이 주어질 때, 다음 조건을 만족하는 부분 수열을 찾으려고 합니다.
수열을 나타내는 정수 배열 sequence와 부분 수열의 합을 나타내는 정수 k가 매개변수로 주어질 때, 위 조건을 만족하는 부분 수열의 시작 인덱스와 마지막 인덱스를 배열에 담아 return 하는 solution 함수를 완성해주세요. 이때 수열의 인덱스는 0부터 시작합니다.
이 문제는 누적합과 구간합과 관련된 문제이다.
배열을 한번 순회하며 누적된 합을 구하고 새로운 배열에 저장한다.
예를들어 [1, 2, 3, 4, 5] 배열이 있다면 해당 배열에 대해서 [0, 1, 3, 6, 10, 15] 배열이 생성되도록 한다.
누적합으로 구해진 배열에서 예를 들어 인덱스 5 - 3을 진행하면 3번째 인덱스에서 4번째(5-1) 인덱스까지의 구간합을 구할 수 있어진다.
가장 간단한 방법으로는 누적합 배열을 이중 반복문을 진행하며 전체에 대해서 부분 수열을 가지고 문제 조건에 가장 가까운 부분 수열을 구하는 방식이 있지만 해당 알고리즘으로 문제를 풀어나가면 시간 복잡도가 O(n) = n^2이 걸려 시간초과에 걸리게 된다.
따라서 구간합의 크기가 주어진 크기와 같은 부분 수열 중에서 최소 길이를 구하는 과정에서 시간복잡도를 줄여야한다.
문제를 읽어보면 sequence 배열이 비내림차순, 즉 오름차순으로 정렬이 되어있기 때문에 투포인트로 부분 수열을 구하는 방식으로 풀이가 가능하다.
구간의 왼쪽과 오른쪽을 지정할 포인터를 설정하고 해당 구간의 부분 수열에서 구간합이 k보다 작으면 오른쪽 포인터를 한칸 오른쪽으로 이동하고, 그 외의 경우에는 왼쪽 포인터를 한칸 오른쪽으로 이동한다. 해당 문제는 오름차순으로 sequence 배열이 정렬 되어있기 때문에 오른쪽으로 포인터를 이동시키는 방식으로 문제 풀이가 가능하다.
// 1. 누적합, 구간합에 관련된 문제이다.
// 2. 구간합을 구하기 위해서는 누적합 배열을 먼저 만들고 누적합 배열 요소의 값의 차이를 통해 구간합을 구한다.
// 3. 구간에서 전체를 확인하게 되면 O(n) = n^2 으로 시간초과로 실패.
// 따라서 앞에서부터 확인하며 구간의 합이 k와 같으면 해당 구간의 길이를 비교하여 저장해놓은 것과 바꿀지 결정한다.
// 4. 구간합이 k보다 작으면 tail포인터를 뒤로 이동, 그 외에는 head포인터를 뒤로 이동한다.
// prefixSum : 누적합 배열, 0번째 요소는 0으로 시작 점점 더해가며 배열을 채움
function solution(sequence, k) {
let seqLen = sequence.length
let prefixSum = new Array(seqLen + 1).fill(0)
let result = [0, 0]
let [headIdx, tailIdx] = [0, 0]
let arrLen = Infinity
for(let i = 0; i < seqLen; i++) {
prefixSum[i + 1] = prefixSum[i] + sequence[i]
}
while(headIdx <= tailIdx) {
const sum = prefixSum[tailIdx] - prefixSum[headIdx]
if(sum === k) {
const nowLen = (tailIdx - 1) - headIdx
if(nowLen < arrLen) {
arrLen = nowLen
result = [headIdx, tailIdx - 1]
}
}
if(sum < k) {
tailIdx++
} else {
headIdx++
}
}
return result
}