diff --git a/combination-sum/haxr369.java b/combination-sum/haxr369.java new file mode 100644 index 0000000000..41ff464042 --- /dev/null +++ b/combination-sum/haxr369.java @@ -0,0 +1,139 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 1번째 풀이는 2번째 풀이를 개선한 것. + * 2번째 풀이는 dfs를 이욯해서 중복순열의 경우의 수를 찾기 때문에 시간복잡도가 매우 높다. + * 또한 중복순열에서 중복을 제거하면서도 낭비되는 연산이 매우 많다. + * + */ +class Solution { + + /** + * 중복순열을 구하는 부분은 2번 풀이와 동일하지만, + * 후보배열을 정렬하고, 후보 선택 시 이전 값과 같거나 큰 후보만을 선택하기에 + * 동일 요소가 존재하는 배열을 중복해서 만들지 않음. + * 또한 Set 같은 저장위치를 제거해서 메모리 최적화. + * + * Runtime: 5 ms (Beats 8.49%) + * Memory: 45.77 MB (Beats 16.78%) + * Space Complexity: O(N) + * - 사용된 후보를 저장하는 배열 O(N) + * > O(N) + * Time Complexity: O(2^N/N!) + * - 후보 하나를 선택해서 target과 비교 => O(2^N) + * - target이 0이 되거나 누적 값이 더 커질 때까지 스택을 쌓기 + * - 최대 누적할 수 있는 횟수는 40 / 2 = 20회 => O(20) = O(M) + * - 다만, 중복배열 생성을 방지하는 트릭을 추가함 O(1/N!) + * > O(2^N/N!) + * + * @param candidates + * @param target + * @return + */ + public List> combinationSum(int[] candidates, int target) { + List> ans = new ArrayList<>(); + List acc = new ArrayList<>(); + Arrays.sort(candidates); + + makecombination(ans, acc, target, candidates, 0); + return ans; + } + + /** + * 이전에 더했던 후보와 같거나 큰 후보만 더한다. + * set: return용 조합 저장 + * acc: 사용 후보 누적 + * usedCount: 후보별 사용된 횟수 저장 + * target: 만들 수 + */ + private void makecombination(List> set, List acc, int target, int[] candidates, + int idx) { + // 완성된 경우 리턴하기 + if (target == 0) { + List tmp = new ArrayList<>(acc); // 깊은 복사 + set.add(tmp); + return; + } else if (target < 0 || idx >= candidates.length) { // 타겟이 0 보다 작은 경우 스킵 + return; + } + + // 현재 인덱스 후보 사용 + acc.add(candidates[idx]); + makecombination(set, acc, target - candidates[idx], candidates, idx); + acc.remove(acc.size() - 1); // 마지막 요소 제거 + + // 다음 인덱스 후보 사용 + makecombination(set, acc, target, candidates, idx + 1); + return; + } + + /** + * candidates의 후보들은 중복 사용이 가능하다. + * 모든 후보는 구별된다.=> 중복이 없다. + * + * Runtime: 48 ms (Beats 5.56%) + * Memory: 46.96 MB (Beats 6.1%) + * Space Complexity: O(N) + O(K) + * - 사용되는 후보를 Set으로 관리하기 => O(N) + * - 후보가 사용된 횟수를 관리하기 => O(K) + * > O(N) + O(K) + * Time Complexity: O(2^N) + * - 후보 하나를 선택해서 target과 비교 + * - target이 0이 되거나 누적 값이 더 커질 때까지 스택을 쌓기 => O(2^N) + * > O(2^N) + */ + public List> combinationSum2(int[] candidates, int target) { + Set> set = new HashSet<>(); + Set acc = new HashSet<>(); + int[] usedCount = new int[44]; + dfs(set, acc, usedCount, target, candidates); + + List> ans = new ArrayList<>(set); + return ans; + } + + /** + * target을 만드는 순열을 만든다.. => 중복을 포함하기 때문에 중복 연산이 많다.. + * set: return용 조합 저장 + * acc: 사용 후보 누적 + * usedCount: 후보별 사용된 횟수 저장 + * target: 만들 수 + */ + private void dfs(Set> set, Set acc, int[] usedCount, int target, int[] candidates) { + // 완성된 경우 리턴하기 + if (target == 0) { + // System.out.println("==========완성!! -> "+acc); + List tmp = new ArrayList<>(); + for (int n : candidates) { + if (acc.contains(n)) { + for (int i = 0; i < usedCount[n]; i++) { + tmp.add(n); + } + } + } + set.add(tmp); + return; + } + + for (int n : candidates) { + // target 보다 작은 후보 사용하기 + + if (target >= n) { + // System.out.println("candi->"+n); + usedCount[n]++; + acc.add(n); + // System.out.println(". next target->"+(target-n)); + dfs(set, acc, usedCount, target - n, candidates); + // 원복하기 + usedCount[n]--; + if (usedCount[n] == 0) { + acc.remove(n); + } + } + } + } +} diff --git a/number-of-1-bits/haxr369.java b/number-of-1-bits/haxr369.java new file mode 100644 index 0000000000..4af4a3dd61 --- /dev/null +++ b/number-of-1-bits/haxr369.java @@ -0,0 +1,44 @@ +class Solution { + /** + * 주어진 숫자 n을 2진수로 만들고, 거기에 포함되는 1의 개수를 카운트하자. + * + * 원리: 2진수에서 1의 의미. + * 특정 수 M이 2^k <= M < 2^(k+1)의 관계를 가질 때, + * M이 2^k로 나눠떨어진다면 k번째 bit는 1로 표현할 수 있다. + * Runtime: 1 ms (Beats 18.72%) + * Memory: 42.17 MB (Beats 24.03%) + * Space Complexity: O(1) + * - 2의 제곱수 v와 k를 저장하는 공간 => O(1) + * > O(1) + * Time Complexity: O(N) + * - 30회의 n와 v의 비교 및 나눔셈 => O(31) + * > O(31) => O(1) + */ + public int hammingWeight(int n) { + int ans = 0; + + int maxExponatial = 1 << 30; + // n >= 2^30일 때 수를 줄이기 + if (maxExponatial <= n) { + n = n - maxExponatial; + ans++; // 2^31 사용했기에 1bit 추가 + } + + int k = 29; + int v = 1 << 29; + + // 총 30번 돌기 + while (k >= 0) { + // v <= n 일 때, k번째 bit는 1이다. + if (v <= n) { + n -= v; + ans++; + } + // 항상 k와 v를 줄이기. + k--; + v = v >> 1; + } + return ans; + + } +} diff --git a/valid-palindrome/haxr369.java b/valid-palindrome/haxr369.java new file mode 100644 index 0000000000..b8bfc4cf05 --- /dev/null +++ b/valid-palindrome/haxr369.java @@ -0,0 +1,107 @@ +/** + * 1번 풀이가 2번 풀이를 개선한 풀이 + * 2번 풀이는 전체 조회를 2번 하지만, 1번에서는 s를 한번만 조회하고 바로 비교할 수 있게 개선. + */ +class Solution { + + /** + * 문자열 s를 한번만 조회하면서 알파벳-숫자가 아닌 문자는 스킵하고 + * 대문자는 소문자로 변환 후 왼쪽 문자와 오른쪽 문자를 비교 + * + * Runtime: 1 ms (Beats 100.00%) + * Memory: 44.24 MB (Beats 53.06%) + * Space Complexity: O(1) + * - 문자열 s를 순회하면서 왼쪽 오른쪽 문자, 아스키 숫자를 저장 + * > O(1) + * Time Complexity: O(N) + * - 문자열 s를 전체 조회 => O(N) + * > O(N) => O(N) + */ + public boolean isPalindrome(String s) { + + // System.out.println(buffer); + int leftIdx = 0; + int rightIdx = s.length() - 1; + while (leftIdx < rightIdx) { + char leftC = s.charAt(leftIdx); + char rightC = s.charAt(rightIdx); + int leftNum = (int) leftC; + int rightNum = (int) rightC; + + // 대문자면 소문자로 변환 + if (65 <= leftNum && leftNum <= 90) { + leftC = (char) (leftNum + 32); + } else if (!(97 <= leftNum && leftNum <= 122) && !(48 <= leftNum && leftNum <= 57)) { + // 알파벳, 숫자가 아닌 문자인 경우 스킵! + leftIdx++; + continue; + } + + if (65 <= rightNum && rightNum <= 90) { + rightC = (char) (rightNum + 32); + } else if (!(97 <= rightNum && rightNum <= 122) && !(48 <= rightNum && rightNum <= 57)) { + rightIdx--; + continue; + } + + if (leftC != rightC) { + return false; + } + // 두 문자가 동일하면 다음 문자를 체크하기 + leftIdx++; + rightIdx--; + } + + return true; + } + + /** + * 모든 대문자를 소문자로 만들고 + * 알파벳이 아닌 문자를 다 제거한 문장이, 대칭이면 true이다. 아님 false. + * + * 1. 문장을 스캔하면서 대문자는 소문자로 buffer에 넣고, 영어가 아닌 문자는 넣지 않기 + * 2. 판단할 때 아스키 코드를 이용하기 + * + * Runtime: 121 ms (Beats 7.53%) + * Memory: 48.00 MB (Beats 5.23%) + * Space Complexity: O(1) + * - 문자열 s와 비스한 배렬(buffer)를 생성 => O(N) + * > O(N) + * Time Complexity: O(N) + * - 문자열 s를 조회하면서 buffer에 붙이기 => O(N) + * - buffer를 조회하면서 유효성 검사 => O(N) + * > O(2N) => O(N) + */ + public boolean isPalindrome2(String s) { + char a = 'a'; // 97 + char z = 'z'; // 122 + char A = 'A'; // 65 + char Z = 'Z'; // 90 + char zero = '0'; // 0 + char nine = '9'; // 9 + + String buffer = ""; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if ((a <= c && c <= z) || (zero <= c && c <= nine)) { + buffer += c; + } else if (A <= c && c <= Z) { + char lowerC = (char) ((int) c + 32); + buffer += lowerC; + } + } + + // System.out.println(buffer); + int leftIdx = 0; + int rightIdx = buffer.length() - 1; + while (leftIdx < rightIdx) { + if (buffer.charAt(leftIdx) != buffer.charAt(rightIdx)) { + return false; + } + leftIdx++; + rightIdx--; + } + + return true; + } +}