diff --git a/3sum/eunhwa99.java b/3sum/eunhwa99.java new file mode 100644 index 000000000..47a9a3402 --- /dev/null +++ b/3sum/eunhwa99.java @@ -0,0 +1,69 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 문제 풀이 + */ +// -4 -1 -1 0 2 2 +// p1 p2 p3 sum < 0 -> p2 앞으로 +// p1 p2 p3 sum < 0 -> p2 앞으로 +// p1 p2 p3 sum < 0 -> p2 앞으로 +// p1 p2p3 sum = 0 -> p1 앞으로 +// p1 p2 p3 sum = 0 -> p3 값 다른 게 나올 때까지 이동 +// p1 p2 p3 sum < 0 -> p2 앞으로 인데, p2 > p3 되므로 p1 앞으로 +// p1 p2 p3 sum = 0 반복 + +/** + * 시간/공간 복잡도 + */ +// 시간 복잡도 - 순회 횟수: n + (n-1) + (n-2) + .. => O(N^2) +// 공간 복잡도 - 배열을 정렬하는 데 O(n log n)의 공간 + 결과를 저장하는 answer 리스트는 문제의 요구에 따라 O(k)의 공간 = O(n log n) (배열 정렬을 위한 공간) + O(k) (결과 저장 공간) + +class Solution { + public List> threeSum(int[] nums) { + Arrays.sort(nums); // Sort the array first + List> answer = new ArrayList<>(); + + for (int pointer1 = 0; pointer1 < nums.length - 2; pointer1++) { + // pointer1 의 중복 값 skip + if (pointer1 > 0 && nums[pointer1] == nums[pointer1 - 1]) { + continue; + } + + int pointer2 = pointer1 + 1; // pointer2 는 pointer1 의 한 칸 앞 + int pointer3 = nums.length - 1; // pointer3 는 끝에서 부터 + + while (pointer2 < pointer3) { + int sum = nums[pointer1] + nums[pointer2] + nums[pointer3]; + + if (sum < 0) { + pointer2++; + } else if (sum > 0) { + pointer3--; + } else { + // sum == 0 + answer.add(Arrays.asList(nums[pointer1], nums[pointer2], nums[pointer3])); + + // pointer2 중복 값 제거 + while (pointer2 < pointer3 && nums[pointer2] == nums[pointer2 + 1]) { + pointer2++; + } + + // pointer3 중복 값 제거 + while (pointer2 < pointer3 && nums[pointer3] == nums[pointer3 - 1]) { + pointer3--; + } + + // 두 값 모두 move + pointer2++; + pointer3--; + } + } + } + + return answer; + } +} + + diff --git a/climbing-stairs/eunhwa99.java b/climbing-stairs/eunhwa99.java new file mode 100644 index 000000000..1e11a7c65 --- /dev/null +++ b/climbing-stairs/eunhwa99.java @@ -0,0 +1,25 @@ +/** + * 문제 풀이 + */ +// n=2 (1,1), (2) -> 2 가지 +// n=3 (n=2, 1), (n=1, 2) -> 2 + 1 = 3가지 +// n=4 (n=3, 1), (n=2, 2) -> 3 + 2 = 5가지 +// n=5 (n=4, 1) , (n=3, 2) +// n=k (n=k-1, 1), (n=k-2, 2) + +/** + * 시간/공간 복잡도 + */ +// 시간 복잡도: 각 칸을 한 번씩 방문 -> O(n) +// 공간 복잡도: DP 배열 크기 -> O(n) +class Solution { + public int climbStairs(int n) { + int[] cntArray = new int[n + 1]; + cntArray[0] = 1; + cntArray[1] = 1; + for (int i = 2; i <= n; ++i) { + cntArray[i] = cntArray[i - 1] + cntArray[i - 2]; + } + return cntArray[n]; + } +} diff --git a/construct-binary-tree-from-preorder-and-inorder-traversal/eunhwa99.java b/construct-binary-tree-from-preorder-and-inorder-traversal/eunhwa99.java new file mode 100644 index 000000000..d51259652 --- /dev/null +++ b/construct-binary-tree-from-preorder-and-inorder-traversal/eunhwa99.java @@ -0,0 +1,64 @@ +// 시간 복잡도: 트리의 모든 노드를 한 번씩만 방문 -> O(n) +// 공간 복잡도: 재귀적으로 트리를 구성 -> +// 트리가 균형 잡힌 경우(즉, 트리의 높이가 log(n)인 경우), 재귀 호출 스택의 깊이는 O(log n) +// 트리가 편향된 형태(예: 모두 왼쪽 자식만 존재하는 경우)라면, 재귀 깊이는 O(n) + +class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode() {} + + TreeNode(int val) { + this.val = val; + } + + TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } +} + +class Solution { + + int preIdx = 0; + + public TreeNode buildTree(int[] preorder, int[] inorder) { + if (preorder == null || inorder == null || preorder.length == 0 || inorder.length == 0) { + return null; + } + + return build(preorder, inorder, 0, inorder.length - 1); + } + + + private TreeNode build(int[] preorder, int[] inorder, int inStart, int inEnd) { + // 재귀 종료 조건 + // 포인터(인덱스)가 배열 길이를 넘었을 + if (preIdx >= preorder.length || inStart > inEnd) { + return null; + } + + // preorder 첫 번째 값은 해당 부분 트리의 root 이다. + int rootVal = preorder[preIdx++]; + TreeNode root = new TreeNode(rootVal); + + // inOrder 배열에서 root 값의 위치를 찾는다. + int rootIndex = -1; + for (int i = inStart; i <= inEnd; i++) { + if (inorder[i] == rootVal) { + rootIndex = i; + break; + } + } + + // root 값을 기준으로 inorder 배열의 왼쪽 부분 배열(inStart ~ rootIndex-1)은 root의 left tree, + // 오른쪽 부분 배열(rootIndex+1 ~ inEnd)은 root의 right tree 가 된다. + root.left = build(preorder, inorder, inStart, rootIndex - 1); + root.right = build(preorder, inorder, rootIndex + 1, inEnd); + + return root; + } +} diff --git a/decode-ways/eunhwa99.java b/decode-ways/eunhwa99.java new file mode 100644 index 000000000..3354af071 --- /dev/null +++ b/decode-ways/eunhwa99.java @@ -0,0 +1,49 @@ +import java.util.Arrays; + +/** + * 문제 풀이 + * 예제) 11106 + * 가장 큰 수는 2자리 이므로 한 번에 갈 수 있는 칸은 1~2칸 + * 현재 칸이 0일 경우는 칸 이동 불가 + * 코드 범위는 1~26 + */ + +//시간 복잡도: 문자열의 각 인덱스를 한 번씩만 처리하므로 전체 O(n) +//공간 복잡도: dp 배열은 문자열의 길이에 비례하여 O(n) 공간을 차지 + +class Solution { + + int stringSize = 0; + int[] dp; + + // DP 이용 + public int numDecodings(String s) { + stringSize = s.length(); + dp = new int[stringSize + 1]; + Arrays.fill(dp, -1); + return numDecodingHelper(s.toCharArray(), 0); + } + + // dp -> O(N) + private int numDecodingHelper(char[] s, int curIndex) { + if (stringSize == curIndex) return 1; + if (s[curIndex] == '0') return 0; // 현재 칸이 0 -> 전진 불가 + if (dp[curIndex] != -1) return dp[curIndex]; + + dp[curIndex] = 0; // 현재 노드 방문 체크 + dp[curIndex] += numDecodingHelper(s, curIndex + 1); // 한 칸 전진 + + if ((curIndex + 1 < stringSize) && checkRange(s[curIndex], s[curIndex + 1])) // 2자리 코드가 10~26 안에 들어간다면 + dp[curIndex] += numDecodingHelper(s, curIndex + 2); // 2칸 전진 + + return dp[curIndex]; + } + + private boolean checkRange(char left, char right) { + int leftNum = left - '0'; + int rightNum = right - '0'; // 숫자로 변환 + + int num = leftNum * 10 + rightNum; + return (num >= 10 && num <= 26); + } +} diff --git a/valid-anagram/eunhwa99.java b/valid-anagram/eunhwa99.java new file mode 100644 index 000000000..431d13f38 --- /dev/null +++ b/valid-anagram/eunhwa99.java @@ -0,0 +1,32 @@ +import java.util.HashMap; +import java.util.Map; + +/** + * 시간/공간 복잡도 + */ +// 시간 복잡도: 문자열을 한 번씩만 방문 -> O(n) +// 공간 복잡도: 문자열 길이만큼 공간 필요(hashmap 크기) -> O(n) +class Solution { + public boolean isAnagram(String s, String t) { + + // s 안의 문자들이 t 에도 동일한 횟수로 등장하는 지 확인 + if (s.length() != t.length()) return false; // 두 문자열의 길이가 다르다면 아나그램이 아니다. + + // 문자별 횟수 저장 map + Map sAlphabetCountMap = new HashMap<>(); + for (char c : s.toCharArray()) { // 시간복잡도: O(n) + sAlphabetCountMap.put(c, sAlphabetCountMap.getOrDefault(c, 0) + 1); + } + + for (char c : t.toCharArray()) { // 시간복잡도: O(n) + if (!sAlphabetCountMap.containsKey(c)) return false; // s에 t가 가진 문자열이 없다면 아나그램이 아니다. + + int count = sAlphabetCountMap.get(c) - 1; + if (count == 0) sAlphabetCountMap.remove(c); + else sAlphabetCountMap.put(c, count); + } + + // 모든 문자가 일치하면 해시맵이 비어 있어야 함 + return sAlphabetCountMap.isEmpty(); + } +}