22import java .util .Map ;
33
44/**
5- * 1번째 풀이: 최종 풀이로 prefix, suffix product의 공간복잡도를 O(1)로 최적화
5+ *
6+ * 마지막 풀이가 최종풀이고 점차 복잡도가 개선됩니다.
7+ *
8+ * 1번째 풀이: 새그먼트 트리를 응용한 범위 곱 게산 구현
69 * 2번째 풀이: 기본적인 prefix, suffix product 구현
7- * 3번째 풀이: 새그먼트 트리를 응용한 범위 곱 게산 구현
10+ * 3번째 풀이: 최종 풀이로 prefix, suffix product의 공간복잡도를 O(1)로 최적화
811 *
912 */
1013class Solution {
1114
12- /**
13- * 풀이요약: 좌우로 누적곱하는 배역을 만들고, i를 제외한 좌우 범위 누적곱을 곱한다.
14- * prefix-product 배열을 출력 배열로 사용하기
15- * suffix-product 배열 대신 오른쪽 누적곱을 한 변수로만 관리한다.
16- *
17- * 풀이결과:
18- * Runtime: 2 ms (Beats 89.36%)
19- * Memory: 72.28 MB (Beats 5.61%)
20- * Space Complexity: O(1)
21- * - 길이가 N인 배열을 1개를 만들지만, return에 쓰이므로 카운팅 안됨.
22- * - suffix-product 게산용 변수 1개
23- * > O(1) > O(1)
24- * Time Complexity: O(N)
25- * - 길이 N인 배열 2번 순회하기 > O(N)
26- * > O(N) > O(N)
27- *
28- */
29- public int [] productExceptSelf (int [] nums ) {
30- int L = nums .length ;
31- int [] leftAccMul = new int [L ];
32-
33- // 0부터 누적곱하기
34- leftAccMul [0 ] = 1 ;
35- for (int i = 1 ; i < L ; i ++) {
36- leftAccMul [i ] = leftAccMul [i - 1 ] * nums [i - 1 ];
37- // System.out.println("i->"+i+" val->"+leftAccMul[i]);
38- }
39- // System.out.println("--------------");
40- // L-1부터 누적곱하기
41- int rightAccMul = nums [L - 1 ];
42- for (int i = L - 2 ; i >= 0 ; i --) {
43- // L-1번째 숫자는 suffix-product에서 곱할게 없다..
44- // 0번째 숫자는 1~L-1 범위 누적곱만 곱해야한다.
45- leftAccMul [i ] *= rightAccMul ;
46- rightAccMul *= nums [i ];
47- // System.out.println("i->"+i+" val->"+leftAccMul[i]);
48- }
49- return leftAccMul ;
50- }
51-
52- /**
53- * 풀이요약: 좌우로 누적곱하는 배역을 만들고, i를 제외한 좌우 범위 누적곱을 곱한다.
54- * prefix-product와 suffix-product를 구하기
55- *
56- * 풀이결과:
57- * Runtime: 3 ms (Beats 21.34%)
58- * Memory: 64.97 MB (Beats 19.65%)
59- * Space Complexity: O(N)
60- * - 길이가 N인 배열을 3개를 만들기
61- * > O(N) + O(N) + O(N) > O(N)
62- * Time Complexity: O(N)
63- * - 길이 N인 배열 2번 순회하기 > O(N)
64- * - 0~N을 순회하면서 누적곱 곱하기 > O(N)
65- * > O(N) + O(N) > O(N)
66- */
67- public int [] productExceptSelf2 (int [] nums ) {
68- int L = nums .length ;
69- int [] leftAccMul = new int [L ];
70- int [] rightAccMul = new int [L ];
71- int [] ans = new int [L ];
72-
73- // 0부터 누적곱하기
74- leftAccMul [0 ] = nums [0 ];
75- for (int i = 1 ; i < L ; i ++) {
76- leftAccMul [i ] = leftAccMul [i - 1 ] * nums [i ];
77- }
78-
79- // L-1부터 누적곱하기
80- rightAccMul [L - 1 ] = nums [L - 1 ];
81- for (int i = L - 2 ; i >= 0 ; i --) {
82- rightAccMul [i ] = rightAccMul [i + 1 ] * nums [i ];
83- }
84-
85- // i를 제외한 누적곱을 곱하기
86- for (int i = 0 ; i < L ; i ++) {
87- int val = 1 ;
88- if (i == 0 ) { // 오른쪽 누적곱만 곱하기
89- val *= rightAccMul [i + 1 ]; // i+1 ~ L-1까지 곱한 값
90- } else if (i == L - 1 ) { // 왼쪽 누적곱만 곱하기
91- val *= leftAccMul [i - 1 ]; // 0~L-2까지 곱한 값
92- } else {
93- val *= leftAccMul [i - 1 ] * rightAccMul [i + 1 ];
94- }
95- ans [i ] = val ;
96- }
97- return ans ;
98- }
99-
10015 /**
10116 * 풀이요약: 범위를 반씩 나누며 곱을 캐싱하고, 제외할 인덱스만 골라 탐색하는 분할 정복 기반 배타 곱 계산
10217 *
@@ -191,4 +106,92 @@ private int fndRngMul(Map<String, Integer> mp, int[] nums, int str, int end) {
191106 // System.out.println("put2 k->"+k+" v->"+v);
192107 return v ;
193108 }
109+
110+ /**
111+ * 풀이요약: 좌우로 누적곱하는 배역을 만들고, i를 제외한 좌우 범위 누적곱을 곱한다.
112+ * prefix-product와 suffix-product를 구하기
113+ *
114+ * 풀이결과:
115+ * Runtime: 3 ms (Beats 21.34%)
116+ * Memory: 64.97 MB (Beats 19.65%)
117+ * Space Complexity: O(N)
118+ * - 길이가 N인 배열을 3개를 만들기
119+ * > O(N) + O(N) + O(N) > O(N)
120+ * Time Complexity: O(N)
121+ * - 길이 N인 배열 2번 순회하기 > O(N)
122+ * - 0~N을 순회하면서 누적곱 곱하기 > O(N)
123+ * > O(N) + O(N) > O(N)
124+ */
125+ public int [] productExceptSelf2 (int [] nums ) {
126+ int L = nums .length ;
127+ int [] leftAccMul = new int [L ];
128+ int [] rightAccMul = new int [L ];
129+ int [] ans = new int [L ];
130+
131+ // 0부터 누적곱하기
132+ leftAccMul [0 ] = nums [0 ];
133+ for (int i = 1 ; i < L ; i ++) {
134+ leftAccMul [i ] = leftAccMul [i - 1 ] * nums [i ];
135+ }
136+
137+ // L-1부터 누적곱하기
138+ rightAccMul [L - 1 ] = nums [L - 1 ];
139+ for (int i = L - 2 ; i >= 0 ; i --) {
140+ rightAccMul [i ] = rightAccMul [i + 1 ] * nums [i ];
141+ }
142+
143+ // i를 제외한 누적곱을 곱하기
144+ for (int i = 0 ; i < L ; i ++) {
145+ int val = 1 ;
146+ if (i == 0 ) { // 오른쪽 누적곱만 곱하기
147+ val *= rightAccMul [i + 1 ]; // i+1 ~ L-1까지 곱한 값
148+ } else if (i == L - 1 ) { // 왼쪽 누적곱만 곱하기
149+ val *= leftAccMul [i - 1 ]; // 0~L-2까지 곱한 값
150+ } else {
151+ val *= leftAccMul [i - 1 ] * rightAccMul [i + 1 ];
152+ }
153+ ans [i ] = val ;
154+ }
155+ return ans ;
156+ }
157+
158+ /**
159+ * 풀이요약: 좌우로 누적곱하는 배역을 만들고, i를 제외한 좌우 범위 누적곱을 곱한다.
160+ * prefix-product 배열을 출력 배열로 사용하기
161+ * suffix-product 배열 대신 오른쪽 누적곱을 한 변수로만 관리한다.
162+ *
163+ * 풀이결과:
164+ * Runtime: 2 ms (Beats 89.36%)
165+ * Memory: 72.28 MB (Beats 5.61%)
166+ * Space Complexity: O(1)
167+ * - 길이가 N인 배열을 1개를 만들지만, return에 쓰이므로 카운팅 안됨.
168+ * - suffix-product 게산용 변수 1개
169+ * > O(1) > O(1)
170+ * Time Complexity: O(N)
171+ * - 길이 N인 배열 2번 순회하기 > O(N)
172+ * > O(N) > O(N)
173+ *
174+ */
175+ public int [] productExceptSelf3 (int [] nums ) {
176+ int L = nums .length ;
177+ int [] leftAccMul = new int [L ];
178+
179+ // 0부터 누적곱하기
180+ leftAccMul [0 ] = 1 ;
181+ for (int i = 1 ; i < L ; i ++) {
182+ leftAccMul [i ] = leftAccMul [i - 1 ] * nums [i - 1 ];
183+ // System.out.println("i->"+i+" val->"+leftAccMul[i]);
184+ }
185+ // System.out.println("--------------");
186+ // L-1부터 누적곱하기
187+ int rightAccMul = nums [L - 1 ];
188+ for (int i = L - 2 ; i >= 0 ; i --) {
189+ // L-1번째 숫자는 suffix-product에서 곱할게 없다..
190+ // 0번째 숫자는 1~L-1 범위 누적곱만 곱해야한다.
191+ leftAccMul [i ] *= rightAccMul ;
192+ rightAccMul *= nums [i ];
193+ // System.out.println("i->"+i+" val->"+leftAccMul[i]);
194+ }
195+ return leftAccMul ;
196+ }
194197}
0 commit comments