본문 바로가기

Java/Java 알고리즘

[알고리즘] 02-3. 투 포인터

반응형

1. 투 포인터

: 2개의 포인터로 구하는 알고리즘

-> end_index가 끝에 도달할 때까지 반복

 

문제 06. 연속된 자연수의 합 구하기

어떠한 자연수 N은 몇 개의 연속된 자연수의 합으로 나타낼 수 있다. 당신은 어떤 자연수 N(l <= N <= 10,000,000)을 몇 개의 연속된 자연수의 합으로 나타내는 가짓수를 알고 싶다. 이때 사용하는 자연수는 N이어야 한다. 예를 들어 15를 나타내는 방법은 15,7 + 8, 4 + 5 + 6,1 + 2+ 3 + 4 + 5이다. 반면,10을 나타내는 방법은 10,1 + 2 + 3 + 4이다. N을 입력받아 연속된 자연수의 합으로 나타내는 가짓수를 출력하는 프로그램을 작성하시오.


입력
1 번째 줄에 정수 N이 주어진다.

 

출력
입력된 자연수 N을 연속된 자연수의 합으로 나타내는 가짓수를 출력한다.

 

예제 입력 1

15 // N


예제 출력 1

4

1단계. 문제 분석하기

: 알고리즘의 범위를 줄여야 한다. -> 시간제한이 존재하는데, N의 최댓값이 10,000,000으로 매우 크기 때문이다

: O(nlogn)을 사용하면, 제한 시간을 초과하므로, O(n)의 시간 복잡도를 사용해야 한다.

-> 투 포인트 알고리즘을 사용해 시작 인덱스와 종료 인덱스를 지정해 연속된 수를 표현한다.

 

2단계. 손으로 풀어보기

  1. 입력받은 값을 N에 저장한 후 코드에서 사용할 변수를 모두 초기화
    -> count를 1로 초기화할 경우 N이 결괏값과 같은 경우를 미리 넣은 걸로 가정
  2. 포인터를 이동시키면서, 배열을 끝까지 탐색하고 합이 N이 될경우를 구한다.

    #투 포인터 이동 원칙
    sum > N : sum = sum - start_index; start_index++;
    sum < N : sum = end_index++; sum = sum + end_index++;
    sum == N : end_index++; sum = sum + end_index; count++;

  3. end_index가 N이 될 때까지 반복하면서, 포인터 이동할때마다 총합과 N을 비교해 같으면 count++;

 

3단계. 슈도코드 작성하기

N 변수 저장
사용 변수 초기화(count = 1, start_index = 1, end_index = 1, sum = 1) 
while(en in ex != N) {
    if(sum == N) count 증가, en in ex 증가, sum값 변경
    else if(sum N) sum값 변경, start index 증가
    else if(sum < N) end_index 증가, sum값 변경
}
count 출력하기

 

package datastructure.ch03;

import java.util.Scanner;

public class ds_q06_sol {
	public int solution(int n) {
		int answer=1;
		//n이 본인 n으로 되는 경우 미리 세기
		int sum=1;
		
		int p1=1;
		int p2=1;
		//종료조건
		
		
		//투 포인터 공식
		while(p2 !=n) {
			if(sum==n) {
//				(1) sum에서 p1을 빼는 방법
				answer++;
				sum-=p1;
				p1++;
				
//				(2) sum에서 p2를 추가하는 방법
//				answer++;
//				p2++;
//				sum+=p2;
			}
			else if(sum>n) {
				sum-=p1;
				p1++;
			}
			else {
				p2++;
				sum+=p2;
			}
		}
		

		return answer;
	}

	public static void main(String[] args) {
		ds_q06_sol T = new ds_q06_sol();
		Scanner kb = new Scanner(System.in);
		int n = kb.nextInt();
		System.out.println(T.solution(n));
	}
}

문제 07. 주몽의 명령

주몽은 철기군을 양성하기 위한 프로젝트에 나섰다. 그래서 야철대장에게 철기군이 입을 갑옷을 만들라고 명령했다. 야철대장은 주몽의 명령에 따르기 위해 연구에 착수하던 중 갑옷을 만드는 재료들은 각각 고유한 번호가 있고, 갑옷은 2개의 재료로 만드는 데 2가지 재료의 고유한 번호를 합쳐 M(l < M < 10,000,000)이 되면 갑옷이 만들어진다는 사실을 발견했다.


야철대장은 자신이 만들고 있는 재료로 갑옷을 몇 개나 만들 수 있는지 궁금해졌다. 야철대장의 궁금증을풀어 주기 위해 N(1 <= N <= 15,000)개의 재료와 M이 주어졌을 때 몇 개의 갑옷을 만들 수 있는지를 구하는 프로그램을 작성하시오.

 

입력
1 번째 줄에 재료의 개수 N(1 <= N <= 15,000), 2번째 줄에 갑옷을 만드는 데 필요한 수 M(1 <= M <=10,000,000)01 주어진다. 3번째 줄에는 N개의 재료들이 가진 고유한 번호들이 공백을 사이에 두고 주어진다. 고유한 번호는 100,000보다 작거나 같은 자연수다.

 

출력
1 번째 줄에 갑옷을 만들 수 있는 개수를 출력한다.


예제 입력 1

6 // 재료의 개수
9 // 갑옷이 완성되는 번호의 합
2 7 4 1 5 3 // 재료들

예제 출력 1

2

1단계. 문제 분석하기

: 두 재료의 번호의 합을 이용하는데, 재료의 순서가 정해지지 않았으므로, 정렬을 이용한다.

-> N의 범위가 15,000이므로 O(nlogn) 시간복잡도를 사용해도 됨

-> N개의 재룟값을 정렬 후, 양쪽 끝을 투 포인터로 지정해 접근

 

2단계. 손으로 풀어 보기

  1. 재료 데이터를 배열 arr[n]에 저장 후 오름차순 정렬
  2. 투 포인터 i,j를 양쪽 끝으로 지정 후, 문제의 조건에 맞게 포인터를 이동시켜 탐색
    # 투 포인터 이동 원칙
    arr[i] + arr[j] > M : j--;
    arr[i] + arr[j] < M : i++;
    arr[i] + arr[j] == M : i++; j--; count++;
  3. 종료 조건 : i와 j가 만날 때까지

3단계. 슈도코드 작성하기

N(재료의 개수), M(갑옷이 되는 번호) 저장하기
for(N만큼 반복)
{
	재료 배열 저장하기
}
재료 배열 정렬하기
while(i < j)
{
    if(재료 합〈 M) 작은 번호 재료를 한 칸 위로 변경하기
    else if(재료 합〉M) 큰 번호 재료를 한 칸 아래로 변경하기
    else 경우의 수 증가, 양쪽 index 각각 변경하기
}
count 출력하기
package datastructure.ch03;

import java.util.Arrays;
import java.util.Scanner;

public class ds_q07 {
	public int solution(int m, int[] arr) {
		int answer = 0;
		int p1 = 0;
		int p2 = arr.length-1;
		
		Arrays.sort(arr);
		int sum=arr[p1]+arr[p2];
		// 두 재료의 합이 m이 되어야 하는데, 순서가 정해져 있지 않으므로,
		// 정렬이 필요함

		// 종료조건
		// -> start_index와 end_index가 만나야 종료
		// 오름차순이기 때문에, sum=arr[p1]+arr[p2]의 크기에 따라 증감

		// sum>m -> sum-=arr[p2]; p2--;
		// sum<m -> sum+=arr[p2]; p2--;

		while (p1<p2) {
			if (sum== m) {
				answer++;
				p2=arr.length-1;
				p1++;
				sum=arr[p1]+arr[p2];
			} else if (sum > m) {
				sum-=arr[p2];
				p2--;
				sum+=arr[p2];
			} else {
				sum-=arr[p1];
				p1++;
				sum+=arr[p1];
			}
		}

		return answer;
	}

	public static void main(String[] args) {
		ds_q07 T = new ds_q07();
		Scanner kb = new Scanner(System.in);
		int n = kb.nextInt();
		int m = kb.nextInt();
		int[] arr = new int[n];
		for (int i = 0; i < n; i++)
			arr[i] = kb.nextInt();

		System.out.println(T.solution(m, arr));
	}

}

 

+) 세련된 풀이

import java.util.Arrays;
import java.util.Scanner;

public class Main {
	public int solution(int m, int[] arr) {
		int answer = 0;
		int p1 = 0;
		int p2 = arr.length-1;
		
		Arrays.sort(arr);
		// 두 재료의 합이 m이 되어야 하는데, 순서가 정해져 있지 않으므로,
		// 정렬이 필요함

		// 종료조건
		// -> start_index와 end_index가 만나야 종료
		// 오름차순이기 때문에, sum=arr[p1]+arr[p2]의 크기에 따라 증감

		// sum>m -> sum-=arr[p2]; p2--;
		// sum<m -> sum+=arr[p2]; p2--;

		while (p1<p2) {
			int sum=arr[p1]+arr[p2];
			if (sum== m) {
				answer++;
				p1++;
				p2--;
			} else if (sum > m) {
				p2--;
			} else {
				p1++;
			}
		}

		return answer;
	}

	public static void main(String[] args) {
		Main T = new Main();
		Scanner kb = new Scanner(System.in);
		int n = kb.nextInt();
		int m = kb.nextInt();
		int[] arr = new int[n];
		for (int i = 0; i < n; i++)
			arr[i] = kb.nextInt();

		System.out.println(T.solution(m, arr));
	}

}

문제 08. '좋은 수' 구하기

주어진 N개의 수에서 다른 두 수의 합으로 표현되는 수가 있다면 그 수를 ‘좋은 수’ 라고 한다. N개의 수 중 좋은 수가 총 몇 개인지 출력하시오.

입력
1 번째 줄에 수의 개수 N(1 <= N <= 2,000), 2번째 줄에 N개의 수의 값(Ai)이 주어진다(|Ai|<=1,000,000,000,Ai는 정수)

 

출력
좋은 수의 개수를 출력한다.


예제 입력 1

10 //수의 개수
1 2 3 4 5 6 7 8 9 10


예제 출력 1

8

1단계. 문제 분석하기

: N의 개수가 2,000개지만 -> 하나의 결과를 위한 시간 복잡도가 N^2보다 작아야 한다.

-> 최소의 시간 복잡도 O(nlogn)

->예외처리 : 정렬된 데이터에서 자기 자신은 제외해야 한다. -> 두 수의 합으로 표현되어야 하기 때문에

 

2단계. 손으로 풀어보기

  1. 수를 입력받아 배열에 저장 후 정렬
  2. 투 포인터를 배열의 양쪽 끝에 위치시키고, 포인터를 이동시키면서 탐색을 수행
    # 투 포인터 이동 원칙
    arr[i] + arr[j] > K : j--;
    arr[i] + arr[j] < K : i++;
    arr[i] + arr[j] ==K : i++; j--; count++;
  3. 포인터의 이동을 배열의 모든 수에 반복 [K가 N이 될 때까지]

3단계. 슈도코드 작성하기

N(배열의 데이터 개수)A[N]
배열 선언 for(N만큼 반복하기)
{
     A 배열에 데이터 저장하기
}
A 배열 정렬하기

for(N만큼 반복하기)
{
    변수 초기화하기(찾고자 하는 값 k, 포인터 i, 포인터 j)
    while(丄 < j)
    {
    if(A[i] + A[j] = 찾고자 하는 값)
        두 포인터 i, j가 k가 아닐 때 결팟값에 반영 및 while 문 종료하기
        두 포인터 i, j가 k가 맞을 때 포인터 변경 및 계속 수행하기
    else if(k M) 포인터 i 증가
    else 포인터 j 증가
	}
}
좋은 수의 개수 출력하기

 

package datastructure.ch03;

import java.util.Arrays;
import java.util.Scanner;

public class ds_q08 {
	public int solution(int[] arr) {
		int answer = 0;
		Arrays.sort(arr);
		
		for (int i = 0; i < arr.length; i++) {
			int p1 = 0;
			int p2 = arr.length - 1;
			
			while (p1 < p2) {
				int sum = arr[p1] + arr[p2];

				if (sum == arr[i]) {
					if (i != p1 && i != p2) {
						answer++;
						break;
					} 
					
					//같은 경우를 제외하므로, 증감 조건 분리해야한다.
					else if (p1 == i) {
						p1++;
					} else if (p2 == i) {
						p2--;
					}
				} else if (sum > arr[i]) {
					p2--;
				} else {
					p1++;
				}
			}
		}

		return answer;
	}

	public static void main(String[] args) {
		ds_q08 T = new ds_q08();
		Scanner kb = new Scanner(System.in);
		int n = kb.nextInt();
		int[] arr = new int[n];
		for (int i = 0; i < n; i++)
			arr[i] = kb.nextInt();
		System.out.println(T.solution(arr));
	}

}
반응형