ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 1208. [Python]부분수열의 합 2
    Python_알고리즘/Gold I 2024. 11. 1. 22:58

    1. 문제

     

     

    2. 접근 방법

     

    • 시간 제한: 1초
    • 메모리 제한: 256MB
    • 이분 탐색
    • 중간에서 만나기

     

    3. 파이썬 코드

     

    # input 값
    N, S = map(int,input().split())
    # 숫자 리스트
    num_list = list(map(int,input().split()))
    # 정렬
    num_list.sort()
    # 딕셔너리를 통해서 몇개 나왔는지 체크
    answer_dict = {}
    # 왼쪽 부분 합
    left_sum = []
    # 오른쪽 부분 합
    right_sum = []
    # 왼쪽 부분 합 구하는 로직
    for i in range(N//2):
        left_length = len(left_sum)
        for j in range(left_length):
            left_sum.append(left_sum[j]+num_list[i])
        left_sum.append(num_list[i])
    # 오른쪽 부분 합 구하는 로직
    for i in range(N//2,N):
        right_length = len(right_sum)
        for j in range(right_length):
            right_sum.append(right_sum[j]+num_list[i])
        right_sum.append(num_list[i])
    # 총 몇개의 S 가 나오는지 체크
    cnt = 0
    
    # 왼쪽 리스트를 순회하며 S인 경우 체크 및 딕셔너리에 왼쪽 리스트 값 추가
    for i in left_sum:
        if i == S:
            cnt += 1
        if i in answer_dict:
            answer_dict[i] += 1
        else:
            answer_dict[i] = 1
    # 오른쪽 리스트를 순회하며 S인 경우 체크
    for i in right_sum:
        if i == S:
            cnt += 1
    # 왼쪽 리스트 정렬, 오른쪽 리스트 정렬
    left_sum.sort()
    right_sum.sort()
    # 왼쪽 리스트 길이 체크, 시작 인덱스, 오른쪽 리스트 시작 값 선택
    left_length = len(left_sum)
    left_index = 0
    right_index = len(right_sum)-1
    # 왼쪽 리스트 인덱스 값이 길이보다 작고 오른쪽 인덱스가 0 보다 크거나 같을때 까지
    while left_index < left_length and right_index > -1:
        # 왼쪽 리스트 값 + 오른쪽 리스트 값이 S 보다 작으면 왼쪽 리스트 인덱스 +1
        if left_sum[left_index] + right_sum[right_index] < S:
            left_index += 1
        # 왼쪽 리스트 값 + 오른쪽 리스트 값이 S 보다 큰 경우 오른쪽 리스트 인덱스 -1
        elif left_sum[left_index] + right_sum[right_index] > S:
            right_index -= 1
        # 외의 경우 같을 때
        else:
            # cnt 값에 딕셔너리에 저장된 왼쪽 리스트 값의 개수를 증가
            cnt += answer_dict[S - right_sum[right_index]]
            # 오른쪽 리스트 크기가 더 크기 떄문에 -1
            right_index -= 1
    print(cnt)

     

    4. 문제를 풀고난 후 생각

     

    • 중간에서 만나기 라는 알고리즘을 처음 접했다.
    • 같은 문제를 분할 탐색과 비슷하게 나눠서 계산하여 시간 복잡도를 줄이는 알고리즘이다.
    • 이 문제에서 2^40 의 시간 복잡도를 2^20 + 2^20 의 시간 복잡도로 나눠서 접근할 수 있도록 한 문제이다.
    • 왼쪽 리스트, 오른쪽 리스트로 나눠준 후 각각 부분수열의 합을 모두 구해준 리스트를 생성한다.
    • 생성한 리스트를 바탕으로 왼쪽 리스트 0 번인덱스 ~ 끝 과 오른쪽 리스트 끝 인덱스 ~ 0 번 까지 값을 비교해가며 S가 나오는지 체크를 해주면 된다.
    • 만약 S에 도달 한 경우 왼쪽 리스트 값들을 미리 딕셔너리에 저장해뒀기 때문에 왼쪽 리스트 딕셔너리 값을 cnt 값에 증가시켜주면 S를 만드는 개수가 될 수 있다.
    • 예외 사항으로 왼쪽 리스트, 오른쪽 리스트 값에 각각 구하고자 하는 S 가 이미 들어가 있는 경우도 사전에 체크해서 cnt 값을 확인해줘야 한다.

     

    예시 => 5 0
    -7 -3 -2 5 8
    left_sum = [-10, -7, -3] ( 왼쪽 부분수열의 합 )

    right_sum = [-2, 3, 5, 6, 8, 11, 13] ( 오른쪽 부분수열의 합 )

     

    이미 앞서 부분을 나눠서 모든 부분 수열의 합을 구해뒀기 때문에 '부분 수열의 합 + 부분 수열의 합' 을 통해서 경우의수를 체크할 수 있다.

     

    -10 과 13 을 더하면 3이 나오므로 right_index 값을 -1 해줘야 한다.

    -10 과 11을 더하면 1이 나오므로 right_index 값을 -1 해줘야 한다.

    -10 과 8 을 더하면 -2 가 나오므로 left_index 값을 +1 해줘야 한다.

     

    이렇게 과정을 계속해 나가면 -3과 3에서 같은 값이 나오므로 이미 3은 answer_dict 에 {-7: 1, -10: 1, -3: 1} 가 있기 때문에 cnt 값이 1이 들어가며 정답으로 출력된다.

     

    5. 문제를 푸는데 도움이 되는 지식

     

    • 이분 탐색
    • 중간에서 만나기

    댓글

Designed by Tistory.