Algorithm/Two Pointer

[백준] 1806 : 부분합 (파이썬)

_은선_ 2024. 8. 1. 00:14
728x90
SMALL

투포인터, 누적합 문제

https://www.acmicpc.net/problem/1806


투포인터 알고리즘

 

1. 개념

투 포인터 알고리즘(Two Pointer Algorithm)은 배열이나 리스트와 같은 선형 자료 구조에서 두 개의 포인터를 사용하여 문제를 해결하는 기법이다. 이 알고리즘은 주로 다음과 같은 문제를 해결하는 데 사용된다.

  • 부분 배열 또는 부분 수열에서 특정 조건을 만족하는 경우 찾기
  • 두 배열에서의 교집합 구하기
  • 정렬된 배열에서 특정 합을 가지는 쌍 찾기

 

투 포인터 알고리즘의 기본 아이디어는 다음과 같다.

두 개의 포인터를 사용하여 배열의 시작 부분과 끝 부분, 또는 특정 지점에서 시작하여 원하는 조건을 만족할 때까지 이동한다.
포인터를 이동시키면서 조건을 체크하고, 조건에 맞는 경우를 찾으면 결과를 갱신힌다.

 

2. 핵심

 

정답을 찾기 위해 봐야하는것들 중에서 꼭 봐야 하는 부분만 추리는게 핵심이다.

  • 화살표 두 개에 의미를 부여해서 탐색 범위를 압축하는 방법
    • 2개의 포인터가 모두 왼쪽에서 시작해서 같은 방향으로 이동
    • 2개의 포인터가 양 끝에서 서로를 향해 이동
  • 관찰을 통해서 문제에 등장하는 변수 2개의 값을 두 포인터로 표현하는 경우

 

3. 꿀팁 키워드

  • 1차원 배열에서의 “연속 부분 수열” 또는 “순서를 지키며 차례대로”
  • 곱의 최소

💡 풀이코드 (성공)

import sys 
n, m = map(int, sys.stdin.readline().split())
arr = [0]
arr += list(map(int, sys.stdin.readline().split()))

def sol(n, m, arr):
    partSum = [0] * (n + 1)

    for i in range(1, n + 1):
        partSum[i] = partSum[i-1] + arr[i]

    arr.sort() # 부분합 구하고 정렬하도록 변경 

    start = end = 0
    ans = sys.maxsize

    while start <= end and end <= n:
        if (partSum[end] - partSum[start]) < m:
            end += 1
        else: 
            ans = min(ans, (end - start))
            start += 1 
    if ans == sys.maxsize:
        print(0)
    else:
        print(ans)
sol(n, m, arr)

 

0. 시간복잡도 = O(nlogn)

정렬하는 코드 때문에 O(nlogn)이다.

 

1. 누적합 구하기

 

문제를 해결하기 위해서는 수열의 부분합을 구하고, 그 합이 S 이상인지 비교하는 반복적인 작업이 필요하다. 

따라서 매번 부분수열의 합을 일일이 더하는 불필요한 반복 연산을 피하기 위해, 누적합 배열(partSum)을 만들어 활용하였다.

 

배열     : 5 1 3 5 10 7 4 9 2 8
누적합 : 5 6 9 14 24 31 35 44 46 54

 

partSum = [0] * (n + 1)

for i in range(1, n + 1):
    partSum[i] = partSum[i-1] + arr[i]

 

2. 투포인터 알고리즘

 

1) 변수 초기화

start = end = 0
ans = sys.maxsize

 

2) 메인 로직

  • 부분합이 m보다 작다면 end(끝 포인터)를 1 증가한다.
    • 부분수열의 합이 m보다 작으므로 부분수열의 길이를 1 증가시켜준다.
  • 부분합이 m 이상이라면 start(시작 포인터)를 1 증가한다.
    • 부분수열의 합의 조건을 만족한 경우이다. 가장 짧은 부분수열의 길이를 구하기 위해 ans에 기록된 값과 비교 후 업데이트를 수행한다.
    • while문의 조건을 만족할 때까지 다음 비교를 수행하여 합이 m 이상인 가장 짧은 부분수열을 구하기 위해 start를 1 증가시켜준다.
  • 합이 m 이상인 부분수열을 찾지 못했다면 0을 출력한다. 
while start <= end and end <= n:
    if (partSum[end] - partSum[start]) < m: # 부분합이 m보다 작다면
        end += 1
    else: # 부분합이 m 이상이라면 
        ans = min(ans, (end - start))
        start += 1 
if ans == sys.maxsize:
    print(0)
else:
    print(ans)

 

 

3. 디버깅

누적합 배열(partSum)을 구하기도 전에 배열 정렬하는 코드를 넣었더니 오답이 나왔었다.

따라서, 누적합을 구한 후 arr 배열을 정렬하도록 위치를 바꿔주었더니 정답을 맞췄다. 

좀 더 생각을 해보니 배열 정렬 할 필요가 없었으므로 해당 코드를 제거해주었다. (역시 정답이었다.)

 

이전 코드
10 18 5  1 3 5 10 7 4 9 2 8
정답 : 3
출력 : 2 
arr.sort() # 부분합 구하고 정렬하도록 변경
728x90
LIST