Skip to content

Commit bc10bcd

Browse files
authored
Merge pull request #2115 from haxr369/main
[haxr369] WEEK 03 solutions
2 parents 269c967 + 4b6a633 commit bc10bcd

File tree

3 files changed

+290
-0
lines changed

3 files changed

+290
-0
lines changed

combination-sum/haxr369.java

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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+
* 1번째 풀이는 2번째 풀이를 개선한 것.
9+
* 2번째 풀이는 dfs를 이욯해서 중복순열의 경우의 수를 찾기 때문에 시간복잡도가 매우 높다.
10+
* 또한 중복순열에서 중복을 제거하면서도 낭비되는 연산이 매우 많다.
11+
*
12+
*/
13+
class Solution {
14+
15+
/**
16+
* 중복순열을 구하는 부분은 2번 풀이와 동일하지만,
17+
* 후보배열을 정렬하고, 후보 선택 시 이전 값과 같거나 큰 후보만을 선택하기에
18+
* 동일 요소가 존재하는 배열을 중복해서 만들지 않음.
19+
* 또한 Set 같은 저장위치를 제거해서 메모리 최적화.
20+
*
21+
* Runtime: 5 ms (Beats 8.49%)
22+
* Memory: 45.77 MB (Beats 16.78%)
23+
* Space Complexity: O(N)
24+
* - 사용된 후보를 저장하는 배열 O(N)
25+
* > O(N)
26+
* Time Complexity: O(2^N/N!)
27+
* - 후보 하나를 선택해서 target과 비교 => O(2^N)
28+
* - target이 0이 되거나 누적 값이 더 커질 때까지 스택을 쌓기
29+
* - 최대 누적할 수 있는 횟수는 40 / 2 = 20회 => O(20) = O(M)
30+
* - 다만, 중복배열 생성을 방지하는 트릭을 추가함 O(1/N!)
31+
* > O(2^N/N!)
32+
*
33+
* @param candidates
34+
* @param target
35+
* @return
36+
*/
37+
public List<List<Integer>> combinationSum(int[] candidates, int target) {
38+
List<List<Integer>> ans = new ArrayList<>();
39+
List<Integer> acc = new ArrayList<>();
40+
Arrays.sort(candidates);
41+
42+
makecombination(ans, acc, target, candidates, 0);
43+
return ans;
44+
}
45+
46+
/**
47+
* 이전에 더했던 후보와 같거나 큰 후보만 더한다.
48+
* set: return용 조합 저장
49+
* acc: 사용 후보 누적
50+
* usedCount: 후보별 사용된 횟수 저장
51+
* target: 만들 수
52+
*/
53+
private void makecombination(List<List<Integer>> set, List<Integer> acc, int target, int[] candidates,
54+
int idx) {
55+
// 완성된 경우 리턴하기
56+
if (target == 0) {
57+
List<Integer> tmp = new ArrayList<>(acc); // 깊은 복사
58+
set.add(tmp);
59+
return;
60+
} else if (target < 0 || idx >= candidates.length) { // 타겟이 0 보다 작은 경우 스킵
61+
return;
62+
}
63+
64+
// 현재 인덱스 후보 사용
65+
acc.add(candidates[idx]);
66+
makecombination(set, acc, target - candidates[idx], candidates, idx);
67+
acc.remove(acc.size() - 1); // 마지막 요소 제거
68+
69+
// 다음 인덱스 후보 사용
70+
makecombination(set, acc, target, candidates, idx + 1);
71+
return;
72+
}
73+
74+
/**
75+
* candidates의 후보들은 중복 사용이 가능하다.
76+
* 모든 후보는 구별된다.=> 중복이 없다.
77+
*
78+
* Runtime: 48 ms (Beats 5.56%)
79+
* Memory: 46.96 MB (Beats 6.1%)
80+
* Space Complexity: O(N) + O(K)
81+
* - 사용되는 후보를 Set으로 관리하기 => O(N)
82+
* - 후보가 사용된 횟수를 관리하기 => O(K)
83+
* > O(N) + O(K)
84+
* Time Complexity: O(2^N)
85+
* - 후보 하나를 선택해서 target과 비교
86+
* - target이 0이 되거나 누적 값이 더 커질 때까지 스택을 쌓기 => O(2^N)
87+
* > O(2^N)
88+
*/
89+
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
90+
Set<List<Integer>> set = new HashSet<>();
91+
Set<Integer> acc = new HashSet<>();
92+
int[] usedCount = new int[44];
93+
dfs(set, acc, usedCount, target, candidates);
94+
95+
List<List<Integer>> ans = new ArrayList<>(set);
96+
return ans;
97+
}
98+
99+
/**
100+
* target을 만드는 순열을 만든다.. => 중복을 포함하기 때문에 중복 연산이 많다..
101+
* set: return용 조합 저장
102+
* acc: 사용 후보 누적
103+
* usedCount: 후보별 사용된 횟수 저장
104+
* target: 만들 수
105+
*/
106+
private void dfs(Set<List<Integer>> set, Set<Integer> acc, int[] usedCount, int target, int[] candidates) {
107+
// 완성된 경우 리턴하기
108+
if (target == 0) {
109+
// System.out.println("==========완성!! -> "+acc);
110+
List<Integer> tmp = new ArrayList<>();
111+
for (int n : candidates) {
112+
if (acc.contains(n)) {
113+
for (int i = 0; i < usedCount[n]; i++) {
114+
tmp.add(n);
115+
}
116+
}
117+
}
118+
set.add(tmp);
119+
return;
120+
}
121+
122+
for (int n : candidates) {
123+
// target 보다 작은 후보 사용하기
124+
125+
if (target >= n) {
126+
// System.out.println("candi->"+n);
127+
usedCount[n]++;
128+
acc.add(n);
129+
// System.out.println(". next target->"+(target-n));
130+
dfs(set, acc, usedCount, target - n, candidates);
131+
// 원복하기
132+
usedCount[n]--;
133+
if (usedCount[n] == 0) {
134+
acc.remove(n);
135+
}
136+
}
137+
}
138+
}
139+
}

number-of-1-bits/haxr369.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
class Solution {
2+
/**
3+
* 주어진 숫자 n을 2진수로 만들고, 거기에 포함되는 1의 개수를 카운트하자.
4+
*
5+
* 원리: 2진수에서 1의 의미.
6+
* 특정 수 M이 2^k <= M < 2^(k+1)의 관계를 가질 때,
7+
* M이 2^k로 나눠떨어진다면 k번째 bit는 1로 표현할 수 있다.
8+
* Runtime: 1 ms (Beats 18.72%)
9+
* Memory: 42.17 MB (Beats 24.03%)
10+
* Space Complexity: O(1)
11+
* - 2의 제곱수 v와 k를 저장하는 공간 => O(1)
12+
* > O(1)
13+
* Time Complexity: O(N)
14+
* - 30회의 n와 v의 비교 및 나눔셈 => O(31)
15+
* > O(31) => O(1)
16+
*/
17+
public int hammingWeight(int n) {
18+
int ans = 0;
19+
20+
int maxExponatial = 1 << 30;
21+
// n >= 2^30일 때 수를 줄이기
22+
if (maxExponatial <= n) {
23+
n = n - maxExponatial;
24+
ans++; // 2^31 사용했기에 1bit 추가
25+
}
26+
27+
int k = 29;
28+
int v = 1 << 29;
29+
30+
// 총 30번 돌기
31+
while (k >= 0) {
32+
// v <= n 일 때, k번째 bit는 1이다.
33+
if (v <= n) {
34+
n -= v;
35+
ans++;
36+
}
37+
// 항상 k와 v를 줄이기.
38+
k--;
39+
v = v >> 1;
40+
}
41+
return ans;
42+
43+
}
44+
}

valid-palindrome/haxr369.java

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* 1번 풀이가 2번 풀이를 개선한 풀이
3+
* 2번 풀이는 전체 조회를 2번 하지만, 1번에서는 s를 한번만 조회하고 바로 비교할 수 있게 개선.
4+
*/
5+
class Solution {
6+
7+
/**
8+
* 문자열 s를 한번만 조회하면서 알파벳-숫자가 아닌 문자는 스킵하고
9+
* 대문자는 소문자로 변환 후 왼쪽 문자와 오른쪽 문자를 비교
10+
*
11+
* Runtime: 1 ms (Beats 100.00%)
12+
* Memory: 44.24 MB (Beats 53.06%)
13+
* Space Complexity: O(1)
14+
* - 문자열 s를 순회하면서 왼쪽 오른쪽 문자, 아스키 숫자를 저장
15+
* > O(1)
16+
* Time Complexity: O(N)
17+
* - 문자열 s를 전체 조회 => O(N)
18+
* > O(N) => O(N)
19+
*/
20+
public boolean isPalindrome(String s) {
21+
22+
// System.out.println(buffer);
23+
int leftIdx = 0;
24+
int rightIdx = s.length() - 1;
25+
while (leftIdx < rightIdx) {
26+
char leftC = s.charAt(leftIdx);
27+
char rightC = s.charAt(rightIdx);
28+
int leftNum = (int) leftC;
29+
int rightNum = (int) rightC;
30+
31+
// 대문자면 소문자로 변환
32+
if (65 <= leftNum && leftNum <= 90) {
33+
leftC = (char) (leftNum + 32);
34+
} else if (!(97 <= leftNum && leftNum <= 122) && !(48 <= leftNum && leftNum <= 57)) {
35+
// 알파벳, 숫자가 아닌 문자인 경우 스킵!
36+
leftIdx++;
37+
continue;
38+
}
39+
40+
if (65 <= rightNum && rightNum <= 90) {
41+
rightC = (char) (rightNum + 32);
42+
} else if (!(97 <= rightNum && rightNum <= 122) && !(48 <= rightNum && rightNum <= 57)) {
43+
rightIdx--;
44+
continue;
45+
}
46+
47+
if (leftC != rightC) {
48+
return false;
49+
}
50+
// 두 문자가 동일하면 다음 문자를 체크하기
51+
leftIdx++;
52+
rightIdx--;
53+
}
54+
55+
return true;
56+
}
57+
58+
/**
59+
* 모든 대문자를 소문자로 만들고
60+
* 알파벳이 아닌 문자를 다 제거한 문장이, 대칭이면 true이다. 아님 false.
61+
*
62+
* 1. 문장을 스캔하면서 대문자는 소문자로 buffer에 넣고, 영어가 아닌 문자는 넣지 않기
63+
* 2. 판단할 때 아스키 코드를 이용하기
64+
*
65+
* Runtime: 121 ms (Beats 7.53%)
66+
* Memory: 48.00 MB (Beats 5.23%)
67+
* Space Complexity: O(1)
68+
* - 문자열 s와 비스한 배렬(buffer)를 생성 => O(N)
69+
* > O(N)
70+
* Time Complexity: O(N)
71+
* - 문자열 s를 조회하면서 buffer에 붙이기 => O(N)
72+
* - buffer를 조회하면서 유효성 검사 => O(N)
73+
* > O(2N) => O(N)
74+
*/
75+
public boolean isPalindrome2(String s) {
76+
char a = 'a'; // 97
77+
char z = 'z'; // 122
78+
char A = 'A'; // 65
79+
char Z = 'Z'; // 90
80+
char zero = '0'; // 0
81+
char nine = '9'; // 9
82+
83+
String buffer = "";
84+
for (int i = 0; i < s.length(); i++) {
85+
char c = s.charAt(i);
86+
if ((a <= c && c <= z) || (zero <= c && c <= nine)) {
87+
buffer += c;
88+
} else if (A <= c && c <= Z) {
89+
char lowerC = (char) ((int) c + 32);
90+
buffer += lowerC;
91+
}
92+
}
93+
94+
// System.out.println(buffer);
95+
int leftIdx = 0;
96+
int rightIdx = buffer.length() - 1;
97+
while (leftIdx < rightIdx) {
98+
if (buffer.charAt(leftIdx) != buffer.charAt(rightIdx)) {
99+
return false;
100+
}
101+
leftIdx++;
102+
rightIdx--;
103+
}
104+
105+
return true;
106+
}
107+
}

0 commit comments

Comments
 (0)