알고리즘 (with JAVA)/코드트리 조별과제

코드트리 조별과제 4회차 (8.05 ~ 8.11)

백_곰 2024. 8. 6. 11:15

Two Pointer

 

[ 문제 ]

[4, 2, 1, 5, 7, 3] 와 같이 숫자들이 주어졌을 때, 특정 구간을 선택하여 구간 내 숫자의 합이

10이
넘지 않으면서 가장 큰 구간의 크기를 구하는 프로그램을 작성하시오.

 

 

[ 해결책(1) - O(N^2) ]

무작정 코드를 작성한다면, 아래 코드처럼 모든 구간 O(N^2)개를 잡아보면서 합이 10을

넘지 않는 경우 중 구간 크기의 최댓값을 구한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{0421573};
        int k = 10;
        int n = 6;
 
        // 가능한 구간 중 최대 크기를 구합니다.
        int ans = 0;
 
        // 모든 구간을 탐색합니다.
        for(int i = 1; i <= n; i++) {
            // 구간 내 합이 10을 넘지 않을때까지 계속 진행합니다.
            int sumVal = 0;
            for(int j = i; j <= n; j++) {
                sumVal += arr[j];
 
                // 구간 내 합이 10을 넘게되면 그만 진행합니다.
                if(sumVal > k)
                    break;
 
                // 현재 구간 [i, j]는 
                // 유효한 구간이므로
                // 구간 크기 중 최댓값을 갱신합니다.
                ans = Math.max(ans, j - i + 1);
            }
        }
 
        // 조건을 만족하는 가장 큰 구간의 크기는
        // [4, 2, 1]로 3이 됩니다.
        System.out.print(ans);
    }
}
 
cs
 

 

[ 해결책(2) - O(N) ]

O(N^2)의 풀이법에서 알 수 있듯이 i가 1씩 증가할 때마다, 최대로 뻗어나갈 수 있는 j의 위치는

항상 같거나 증가한다는 것을 확인할 수 있다.

그 이유는 i가 1이 증가하면 Arr[i]만큼 감소하기 때문에 그만큼 뒤로 이동할 수 있는 여유 공간이

존재하기 때문이다.
이렇듯 문제에서의 특정 조건에 의해 원하는 구간의 양 끝을 나타내는 2개의 포인터가 한 방향으로만

계속 전진하는 형태의 테크닉을 Two Pointer라고 부른다.
Two Pointer를 이용해 i가 1 증가했을 때 j는 이전 값에서 시작하여 감소시키지 않고 최대로 뻗어

나가도록 하면 i, j 모두 한 방향으로만 진행하기 때문에 O(N)이 된다.

아래의 코드를 통해 이해하자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{0421573};
        int k = 10;
        int n = 6;
 
        // 가능한 구간 중 최대 크기를 구합니다.
        int ans = 0;
 
        // 구간을 잡아봅니다.
        int sumVal = 0;
        int j = 0;
        for(int i = 1; i <= n; i++) {
            // 구간 내 합이 10을 넘지 않을때까지 계속 진행합니다.
            while(j + 1 <= n && sumVal + arr[j + 1<= k) {
                sumVal += arr[j + 1];
                j++;
            }
 
            // 현재 구간 [i, j]는
            // i를 시작점으로 하는
            // 가장 긴 구간이므로
            // 구간 크기 중 최댓값을 갱신합니다.
            ans = Math.max(ans, j - i + 1);
 
            // 다음 구간으로 넘어가기 전에
            // arr[i]에 해당하는 값은 구간에서 제외시킵니다.
            sumVal -= arr[i];
        }
 
        // 조건을 만족하는 가장 큰 구간의 크기는
        // [4, 2, 1]로 3이 됩니다.
        System.out.print(ans);
    }
}
 
cs

 

실제로 2중 포문으로 작성되어 있지만, ( i, j )는 모두 한 방향으로만 진행하기 때문에 시간복잡도가

이중 포문이 아닌 합으로 나타내짐에 유의하자.

 

연습문제 - (코드트리) 가장 짧은 부분

 

https://www.codetree.ai/missions/8/problems/shortest-subtotal?&utm_source=clipboard&utm_medium=text

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.util.*;
import java.io.*;
 
public class Main {
    static int N, S, Arr[], ans;
 
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
 
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        S = Integer.parseInt(st.nextToken());
 
        Arr = new int[N+1];
 
        st = new StringTokenizer(br.readLine());
        for(int i=1; i<=N; i++){
            Arr[i] = Integer.parseInt(st.nextToken());
        }
 
        ans = Integer.MAX_VALUE;
 
        solve();
 
        if(ans == Integer.MAX_VALUE) ans = -1;
        System.out.println(ans);
    }
 
    static void solve(){
        long sum = 0;
        int j = 0;
 
        for(int i=1; i<=N; i++){
 
            while(j+1<=&& sum<S){
                sum += Arr[j+1];
                j++;
            }
 
            if(sum<S) break;
 
            ans = Math.min(ans, j-i+1);
 
            sum -= Arr[i];
        }
    }
}
 
cs