Skip to content

Commit 000b4f0

Browse files
committed
feat: 3sum solve 시간복잡도 개선 필요
1 parent ee56aac commit 000b4f0

File tree

2 files changed

+157
-90
lines changed

2 files changed

+157
-90
lines changed

3sum/haxr369.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import java.util.ArrayList;
2+
import java.util.Arrays;
3+
import java.util.HashSet;
4+
import java.util.List;
5+
import java.util.Set;
6+
7+
/**
8+
* 세 수의 인덱스가 모두 다르고 세 수 합이 0인 경우의 수를 구하기
9+
* 세 숫자가 들어간 배열은 중복되게 하지 않은다.
10+
*
11+
* 1. 모든 경우의 수를 구한다. => O(N^3)
12+
* 3000*3000*3000 == 9*10^9 => 9억건...
13+
* 2. left, mid, right라고 할 때, mid를 고정하고 left, right만 움직이는 two pointer를 응용하기
14+
*/
15+
class Solution {
16+
/**
17+
* Runtime: 545 ms (Beats 14.44%)
18+
* Memory: 60.78 MB (Beats 5.38%)
19+
* Space Complexity: O(N)
20+
* - 정렬을 위한 공간 => O(N)
21+
* - 세 수를 저장하기 위한 리스트 => O(3)
22+
* > O(N) + O(3) => O(N)
23+
* Time Complexity: O(NlogN)
24+
* - 정렬을 위한 시간 => O(NlogN)
25+
* - mid의 순회 => O(N)
26+
* - two pointer 조회 => O(N)
27+
* > O(NlogN) + O(N)*O(N) ~= O(N^2)
28+
*/
29+
public List<List<Integer>> threeSum(int[] nums) {
30+
int L = nums.length;
31+
Arrays.sort(nums);
32+
33+
Set<List<Integer>> st = new HashSet<>();
34+
35+
// mid를 늘려가면서 투 포인터 진행
36+
for (int mid = 1; mid < L - 1; mid++) {
37+
int left = 0;
38+
int right = L - 1;
39+
while (left < mid && mid < right) {
40+
int sm = nums[left] + nums[mid] + nums[right];
41+
if (sm == 0) {
42+
/**
43+
* left를 더하는 이유:
44+
* 현재 찾은 경우의 수 외에 다른 경우가 존재할 수 있음
45+
* ex) -7,1,6 / -6,1,5
46+
* => mid는 고정이지만, left는 늘리고 right를 줄여서 0을 만들 수 있는 다른 경우가 있음
47+
*/
48+
List<Integer> li = new ArrayList<>();
49+
li.add(nums[left]);
50+
li.add(nums[mid]);
51+
li.add(nums[right]);
52+
st.add(li); // 중복값이 있어도 무시할 수 있음.
53+
left++;
54+
} else if (sm < 0) { // 부족하면 left 늘리기
55+
left++;
56+
} else { // 과하면 right 줄이기
57+
right--;
58+
}
59+
}
60+
}
61+
62+
return new ArrayList<>(st);
63+
}
64+
}

product-of-array-except-self/haxr369.java

Lines changed: 93 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,101 +2,16 @@
22
import 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
*/
1013
class 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

Comments
 (0)