Skip to content
64 changes: 64 additions & 0 deletions 3sum/haxr369.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* 세 수의 인덱스가 모두 다르고 세 수 합이 0인 경우의 수를 구하기
* 세 숫자가 들어간 배열은 중복되게 하지 않은다.
*
* 1. 모든 경우의 수를 구한다. => O(N^3)
* 3000*3000*3000 == 9*10^9 => 9억건...
* 2. left, mid, right라고 할 때, mid를 고정하고 left, right만 움직이는 two pointer를 응용하기
*/
class Solution {
/**
* Runtime: 545 ms (Beats 14.44%)
* Memory: 60.78 MB (Beats 5.38%)
* Space Complexity: O(N)
* - 정렬을 위한 공간 => O(N)
* - 세 수를 저장하기 위한 리스트 => O(3)
* > O(N) + O(3) => O(N)
* Time Complexity: O(NlogN)
* - 정렬을 위한 시간 => O(NlogN)
* - mid의 순회 => O(N)
* - two pointer 조회 => O(N)
* > O(NlogN) + O(N)*O(N) ~= O(N^2)
*/
public List<List<Integer>> threeSum(int[] nums) {
int L = nums.length;
Arrays.sort(nums);

Set<List<Integer>> st = new HashSet<>();

// mid를 늘려가면서 투 포인터 진행
for (int mid = 1; mid < L - 1; mid++) {
int left = 0;
int right = L - 1;
while (left < mid && mid < right) {
int sm = nums[left] + nums[mid] + nums[right];
if (sm == 0) {
/**
* left를 더하는 이유:
* 현재 찾은 경우의 수 외에 다른 경우가 존재할 수 있음
* ex) -7,1,6 / -6,1,5
* => mid는 고정이지만, left는 늘리고 right를 줄여서 0을 만들 수 있는 다른 경우가 있음
*/
List<Integer> li = new ArrayList<>();
li.add(nums[left]);
li.add(nums[mid]);
li.add(nums[right]);
st.add(li); // 중복값이 있어도 무시할 수 있음.
left++;
} else if (sm < 0) { // 부족하면 left 늘리기
left++;
} else { // 과하면 right 줄이기
right--;
}
}
}

return new ArrayList<>(st);
}
}
38 changes: 38 additions & 0 deletions climbing-stairs/haxr369.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class Solution {
/**
* 계산 n개를 오르기 위해 1 또는 2개씩 오르는 모든 경우의 수를 구하기
*
* DP문제 풀이 과정
* 1. 문제 단순화
* - 숫자 N을 만들기 위해 1 또는 2를 더하는 순서를 만드는 경우의 수
* 2. 문제에서 원하는 것
* - N을 만들기 위한 모든 경우의 수
* => DP[N] = N을 만들기 위한 모든 경우의 수
* 2. 규칙 찾기
* - N은 N-1에서 1 더하거나, N-2에서 2를 더하면 도달할 수 있다.
* - 따라서 N에 도달하기 위한 경우의 수는 N-1을 도달하기 위한 경우의 수 + N-2에 도달하기 위한 경우의 수
* - DP[0] = 1 => 움직이지 않는다는 경우의 수
* - DP[1] = 1 => 1칸 움직이는 경우의 수
* - DP[N] = DP[N-2] + DP[N-1]
*/
public int climbStairs(int n) {
/**
* Runtime: 0 ms (Beats 100.00%)
* Memory: 42.00 MB (Beats 15.00%)
* Space Complexity: O(N)
* - N 크기의 배열 1개 사용으로 O(N)
* > O(N)
* Time Complexity: O(N)
* - N회 덧셈으로 => O(N)
* > O(N)
*/
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;

for (int i = 2; i < n + 1; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
197 changes: 197 additions & 0 deletions product-of-array-except-self/haxr369.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import java.util.HashMap;
import java.util.Map;

/**
*
* 마지막 풀이가 최종풀이고 점차 복잡도가 개선됩니다.
*
* 1번째 풀이: 새그먼트 트리를 응용한 범위 곱 게산 구현
* 2번째 풀이: 기본적인 prefix, suffix product 구현
* 3번째 풀이: 최종 풀이로 prefix, suffix product의 공간복잡도를 O(1)로 최적화
*
*/
class Solution {

/**
* 풀이요약: 범위를 반씩 나누며 곱을 캐싱하고, 제외할 인덱스만 골라 탐색하는 분할 정복 기반 배타 곱 계산
*
* 풀이결과:
* Runtime: 872 ms (Beats 3.62%)
* Memory: 137.29 MB (Beats 5.61%)
* Space Complexity: O(NlogN)
* - 길이 N인 배열을 절반씩 자른 범위 안에 수들을 곱한 값을 저장 O(NlogN)
* - 길이 N인 출력 배열 생성 O(N)
* > O(N) + O(NlogN) > O(NlogN)
*
* Time Complexity: O(NlogN)
* - 길이 N인 배열을 절반씩 잘라가면서 안에 수들을 곱하기 O(NlogN)
* - 0~N 범위를 절반씩 잘라가면서 i가 포함되지 않은 범위 수를 곱하기 O(logN)
* - N개의 숫자에 대해서 찾기 => O(NlogN)
* > O(NlogN) + O(NlogN) > O(NlogN)
*/
public int[] productExceptSelf1(int[] nums) {
Map<String, Integer> mp = new HashMap<>();
fndRngMul(mp, nums, 0, nums.length);
int[] ans = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
int val = binaryFnd(mp, i, 0, nums.length);
ans[i] = val;
}

return ans;
}

/***
* 특정 idx를 제외한 구간 곱을 재귀적으로 찾는다.
*
* - 범위가 단일 원소면 곱에 포함될 값이 없으므로 1 반환
* - 범위를 반으로 자르고, idx가 속하지 않은 부분의 곱을 즉시 사용
* - idx가 속한 쪽은 계속 재귀 탐색
*/
private int binaryFnd(Map<String, Integer> mp, int idx, int str, int end) {
int val = 1;

if (end - str == 1) {
return val;
}

int mid = (str + end) / 2;
String leftKey = str + "_" + mid;
String rightKey = mid + "_" + end;
// System.out.println("----idx->"+idx+" mid->"+mid);
// idx는 좌측에 위치하기 때문에 우측 값을 곱함.
if (idx < mid) {
// 하지만 다은 idx가 포함된 범위를 탐색
int leftVal = binaryFnd(mp, idx, str, mid);
// System.out.println(". ----leftVal->"+leftVal+" base->"+mp.get(rightKey)+"
// val-> "+val);
val = mp.get(rightKey) * leftVal;
} else { // idx는 우측에 위치하기 때문에 좌측 값을 곱함.
int rightVal = binaryFnd(mp, idx, mid, end);
// System.out.println(". ----rightVal->"+rightVal+" base->"+mp.get(leftKey)+"
// val-> "+val);
val = mp.get(leftKey) * rightVal;
}
return val;
}

/**
* 배열의 범위 [str, end) 내 값들의 곱을 구해 캐싱한다.
*
* - 원소가 하나만 있는 구간이면 nums[str] 저장
* - 구간을 반으로 나누어 왼쪽/오른쪽 곱을 재귀적으로 계산
* - 전체 구간이 아니라면 left * right 를 캐싱
* - 전체 구간(0~N)은 제외 곱 계산 시 무시해야 하므로 '1' 저장
*/
private int fndRngMul(Map<String, Integer> mp, int[] nums, int str, int end) {
String k = str + "_" + end;

if (end - str == 1) {
// System.out.println("put k->"+k+" v->"+v);
mp.put(k, nums[str]);
return nums[str];
}
int mid = (str + end) / 2;
int leftRngMul = fndRngMul(mp, nums, str, mid);
int rightRngMul = fndRngMul(mp, nums, mid, end);

int v = 1;
// 전체 곱은 구하지 않음.
if (str == 0 && end == nums.length) {
v = 1;
} else {
v = leftRngMul * rightRngMul;
}
mp.put(k, v);
// System.out.println("put2 k->"+k+" v->"+v);
return v;
}

/**
* 풀이요약: 좌우로 누적곱하는 배역을 만들고, i를 제외한 좌우 범위 누적곱을 곱한다.
* prefix-product와 suffix-product를 구하기
*
* 풀이결과:
* Runtime: 3 ms (Beats 21.34%)
* Memory: 64.97 MB (Beats 19.65%)
* Space Complexity: O(N)
* - 길이가 N인 배열을 3개를 만들기
* > O(N) + O(N) + O(N) > O(N)
* Time Complexity: O(N)
* - 길이 N인 배열 2번 순회하기 > O(N)
* - 0~N을 순회하면서 누적곱 곱하기 > O(N)
* > O(N) + O(N) > O(N)
*/
public int[] productExceptSelf2(int[] nums) {
int L = nums.length;
int[] leftAccMul = new int[L];
int[] rightAccMul = new int[L];
int[] ans = new int[L];

// 0부터 누적곱하기
leftAccMul[0] = nums[0];
for (int i = 1; i < L; i++) {
leftAccMul[i] = leftAccMul[i - 1] * nums[i];
}

// L-1부터 누적곱하기
rightAccMul[L - 1] = nums[L - 1];
for (int i = L - 2; i >= 0; i--) {
rightAccMul[i] = rightAccMul[i + 1] * nums[i];
}

// i를 제외한 누적곱을 곱하기
for (int i = 0; i < L; i++) {
int val = 1;
if (i == 0) { // 오른쪽 누적곱만 곱하기
val *= rightAccMul[i + 1]; // i+1 ~ L-1까지 곱한 값
} else if (i == L - 1) { // 왼쪽 누적곱만 곱하기
val *= leftAccMul[i - 1]; // 0~L-2까지 곱한 값
} else {
val *= leftAccMul[i - 1] * rightAccMul[i + 1];
}
ans[i] = val;
}
return ans;
}

/**
* 풀이요약: 좌우로 누적곱하는 배역을 만들고, i를 제외한 좌우 범위 누적곱을 곱한다.
* prefix-product 배열을 출력 배열로 사용하기
* suffix-product 배열 대신 오른쪽 누적곱을 한 변수로만 관리한다.
*
* 풀이결과:
* Runtime: 2 ms (Beats 89.36%)
* Memory: 72.28 MB (Beats 5.61%)
* Space Complexity: O(1)
* - 길이가 N인 배열을 1개를 만들지만, return에 쓰이므로 카운팅 안됨.
* - suffix-product 게산용 변수 1개
* > O(1) > O(1)
* Time Complexity: O(N)
* - 길이 N인 배열 2번 순회하기 > O(N)
* > O(N) > O(N)
*
*/
public int[] productExceptSelf3(int[] nums) {
int L = nums.length;
int[] leftAccMul = new int[L];

// 0부터 누적곱하기
leftAccMul[0] = 1;
for (int i = 1; i < L; i++) {
leftAccMul[i] = leftAccMul[i - 1] * nums[i - 1];
// System.out.println("i->"+i+" val->"+leftAccMul[i]);
}
// System.out.println("--------------");
// L-1부터 누적곱하기
int rightAccMul = nums[L - 1];
for (int i = L - 2; i >= 0; i--) {
// L-1번째 숫자는 suffix-product에서 곱할게 없다..
// 0번째 숫자는 1~L-1 범위 누적곱만 곱해야한다.
leftAccMul[i] *= rightAccMul;
rightAccMul *= nums[i];
// System.out.println("i->"+i+" val->"+leftAccMul[i]);
}
return leftAccMul;
}
}
40 changes: 40 additions & 0 deletions valid-anagram/haxr369.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
class Solution {
/**
* s의 문자 개수와 r의 문자 개수가 동일한지 체크
* 배열1,2에 각각 문자 개수를 저장하기
* a-z까지 26개
*/
public boolean isAnagram(String s, String t) {
/**
* Runtime: 2 ms (Beats 98.15%)
* Memory: 44.74 MB (Beats 38.46%)
* Space Complexity: O(1)
* - 26 크기의 배열 2개 사용으로 2*O(26)
* > O(1)
* Time Complexity: O(N)
* - 문자열 s와 t의 문자를 하나하나 탐색 => 2*O(N)
* - 두 문자 사용건수 저장 배열 탐색 => O(26)
* > O(N)
*/
int[] scount = new int[26];
int[] tcount = new int[26];

char A = 'a';
for (char c : s.toCharArray()) {
scount[c - A] += 1;
}

for (char c : t.toCharArray()) {
tcount[c - A] += 1;
}

for (int i = 0; i < 26; i++) {
if (scount[i] != tcount[i]) {
return false;
}
}

return true;

}
}
Loading