연속된 부분 수열의 합

원용현·2023년 4월 9일
0

프로그래머스

목록 보기
43/49

링크

https://school.programmers.co.kr/learn/courses/30/lessons/178870

문제

비내림차순으로 정렬된 수열이 주어질 때, 다음 조건을 만족하는 부분 수열을 찾으려고 합니다.

  • 기존 수열에서 임의의 두 인덱스의 원소와 그 사이의 원소를 모두 포함하는 부분 수열이어야 합니다.
  • 부분 수열의 합은 k입니다.
  • 합이 k인 부분 수열이 여러 개인 경우 길이가 짧은 수열을 찾습니다.
  • 길이가 짧은 수열이 여러 개인 경우 앞쪽(시작 인덱스가 작은)에 나오는 수열을 찾습니다.

수열을 나타내는 정수 배열 sequence와 부분 수열의 합을 나타내는 정수 k가 매개변수로 주어질 때, 위 조건을 만족하는 부분 수열의 시작 인덱스와 마지막 인덱스를 배열에 담아 return 하는 solution 함수를 완성해주세요. 이때 수열의 인덱스는 0부터 시작합니다.

  • 5 ≤ sequence의 길이 ≤ 1,000,000
    • 1 ≤ sequence의 원소 ≤ 1,000
    • sequence는 비내림차순으로 정렬되어 있습니다.
  • 5 ≤ k ≤ 1,000,000,000
    • k는 항상 sequence의 부분 수열로 만들 수 있는 값입니다.

풀이

이 문제는 누적합과 구간합과 관련된 문제이다.
배열을 한번 순회하며 누적된 합을 구하고 새로운 배열에 저장한다.
예를들어 [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. 부분 수열에서 구간합이 k보다 작으면 오른쪽 포인터를 한칸 오른쪽으로 이동
    • 구간합이 작다는 것을 전체 구간의 크기를 키워야하기 때문에 오른쪽 포인터를 이동시켜 부분 수열을 더 크게 만들어 구간합을 증가시킨다.
  2. 그 외의 경우에는 왼쪽 포인터를 한칸 오른쪽으로 이동
    • 구간합이 k보다 크거나 같다면 전체구간의 크기를 줄여야하기 때문에 왼쪽 포인터를 이동시킨다.
    • 구간합이 k와 같은 경우에는 부분 수열의 길이를 비교해서 더 짧은 것으로 결과값을 바꾼다.
    • 같은 경우에도 이동시키는 이유는 부분 수열의 길이가 더 작은 경우가 뒤에 있을 수 있기 때문에 적어도 누적합 배열의 오른쪽 끝까지 이동시키며 비교를 해야한다.

코드

// 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
}

0개의 댓글