출처 : 종만북 - 4.2 선형 시간 알고리즘 다이어트 현황 파악 : 이동 평균 계산하기

코드설명

슬라이딩 윈도우(Sliding Window) + 투포인터(Two Pointer) + 누적합(Prefix Sum)을 활용할 수 있습니다.

 

문제에서 두가지 방식으로 M달 간의 이동평균을 계산할 수 있습니다.

상세 설명은 코드 부분에 작성했습니다.

테스트케이스

입력 N(N달간의 몸무게), M(M달간의 이동평균) 이 입력으로 주어집니다.

 

입려 예시 1

30 3
70.8 71.0 71.3 72.0 72.9 73.1 73.2 73.2 73.7 73.7 74.6 74.8 75.8 76.8 77.6 78.4 78.7 79.6 80.1 80.8 80.8 81.8 82.2 82.2 83.0 83.8 84.6 84.7 85.2 86.1

결과 예시 1

 0.00 0.00 71.03 71.43 72.07 72.67 73.07 73.17 73.37 73.53 74.00 74.37 75.07 75.80 76.73 77.60 78.23 78.90 79.47 80.17 80.57 81.13 81.60 82.07 82.47 83.00 83.80 84.37 84.83 85.33

 

입력 예시 2

10 1
70.8 71.6 72.0 72.5 72.5 72.8 73.2 73.9 74.3 75.1

 

결과 예시 2

 70.80 71.60 72.00 72.50 72.50 72.80 73.20 73.90 74.30 75.10

 

입력 예시 3

10 4
69.6 68.8 68.0 67.8 67.1 66.8 65.8 65.1 64.1 64.0

결과 예시 3

 0.00 0.00 0.00 68.55 67.93 67.43 66.88 66.20 65.45 64.75

코드

1. 비효율적인 코드입니다.

O( (N-M+1) * M ) = O ( N*M - M^2 + M)

package Main;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
	public static int N, M;
	public static double answer = 0;
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		double[] weightArr = new double[N];
		
		st = new StringTokenizer(br.readLine());
		for(int i=0;i<N;i++) {
			weightArr[i] = Double.parseDouble(st.nextToken());
		}	
		double[] average1 = movingAverage1(weightArr, M);
		for(double a : average1) {
			System.out.print(" "+a);
		}
		System.out.println();
	}
	
	public static double[] movingAverage1(double[] A, int M) {
		double[] res = new double[N];
		for(int i=M-1;i<N;i++) {
			for(int j=0; j<M;j++) {
				res[i] += A[i - j];
			}
			res[i] /= M;
		}
		return res;
	}
	
}

 

2. 투포인터를 활용하여 개선한 코드입니다. ( 가장 빠릅니다. )

시간복잡도는 O(N) 입니다. 선형시간입니다.

아래와 같이 함수를 작성하면, 만약 M = 3일경우에, 앞의 2가지 계산하고, 나머지 1개를 추가로 더한뒤, 다시 맨 앞 1가지를 빼줍니다. 

이렇게 하면, 투포인터를 손쉽게 구현하는 구조입니다. 

package Main;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;

public class Main {
	public static int N, M;
	public static double answer = 0;
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		double[] weightArr = new double[N];
		
		st = new StringTokenizer(br.readLine());
		for(int i=0;i<N;i++) {
			weightArr[i] = Double.parseDouble(st.nextToken());
		}	
		ArrayList<Double> average2 = movingAverage2(weightArr, M);
		for(double avg : average2) {
			System.out.print(" "+String.format("%.2f", avg));
		}
		System.out.println();
	}
	
	public static ArrayList<Double> movingAverage2(double[] A, int M) {
		ArrayList<Double> res = new ArrayList<>();
		//부분합을 사용합니다.
		double partialSum = 0; 
		//[0,M-2] 구간의 합을 계산합니다.
		for(int i=0;i<M - 1;i++) {
			partialSum += A[i];
			res.add((double) 0);
		}
		
		for(int i=M - 1;i<N;i++) {
			partialSum += A[i];
			res.add(partialSum / M);
			partialSum -= A[i - M + 1];
		}
		return res;
	}
	
}

 

더 깔끔한 투포인터 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
 
public class Main {
	public static int C, N, M;
	public static int answer = Integer.MAX_VALUE;
	public static double[] arr;
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		
		arr = new double[N];
		st = new StringTokenizer(br.readLine());
		for(int i=0;i<N;i++) {
			arr[i] = Double.parseDouble(st.nextToken());
		}
		
		
		double sum = 0;
		for(int i=0;i<M - 1;i++) {
			System.out.print( String.format("%.2f", (double) 0) +" ");
			sum += arr[i];
		}
		
		for(int i=M - 1; i< N; i++) {
			sum += arr[i];
			System.out.print( String.format("%.2f", sum / M) +" ");
			sum -= arr[i - (M - 1)];
		}
		
	}
	
}

 

 

3. 투포인터 보다 느린 누적합 코드입니다.

처음에 문제를 떠올렸을때는, 누적합을 사용해야겠다는 생각이 먼저 떠올랐었습니다. 

하지만, O(N + (N - M) ) = O( 2*N - M ) 의 시간복잡도로써, 2번 투포인터 방식보다 느립니다.

package Main;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;

public class Main {
	public static int N, M;
	public static double answer = 0;
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());

		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		double[] weightArr = new double[N];
		
		st = new StringTokenizer(br.readLine());
		for(int i=0;i<N;i++) {
			weightArr[i] = Double.parseDouble(st.nextToken());
		}	
		ArrayList<Double> average2 = movingAverage2(weightArr, M);
//		for(double avg : average2) {
//			System.out.print(" "+String.format("%.2f", avg));
//		}
//		System.out.println();
	}
	
	public static ArrayList<Double> movingAverage2(double[] A, int M) {
		ArrayList<Double> res = new ArrayList<>();
		//부분합을 사용합니다.
		double[] prefixSum = new double[N + 1];
		for(int i=0;i<N;i++) {
			prefixSum[i + 1] += prefixSum[i] + A[i]; 
		}
		
		for(int i=0;i<M - 1;i++) {
			res.add((double)0);
		}
		
		double tempSum = 0;
		for(int i=M; i<N;i++) {
			tempSum = prefixSum[i] - prefixSum[i - M];
			res.add(tempSum / 3);
		}
		
		return res;
	}
	
}

 

깔끔한 누적합 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
 
public class Main {
	public static int C, N, M;
	public static int answer = Integer.MAX_VALUE;
	public static double[] arr, prefixSum;
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		
		arr = new double[N];
		prefixSum = new double[N+1];
		st = new StringTokenizer(br.readLine());
		
		for(int i=0;i<N;i++) {
			arr[i] = Double.parseDouble(st.nextToken());
			prefixSum[i + 1] = prefixSum[i] + arr[i];
		}
		for(int i=0;i<M - 1;i++) {
			System.out.print(String.format("%.2f ", (double) 0));
		}
		for(int i=M;i<N;i++) {
			System.out.print( String.format("%.2f", (prefixSum[i] - prefixSum[i - M]) / M ) +" ");
		}
		
	}
	
	
}

+ Recent posts