diff --git a/3sum/imsosleepy.java b/3sum/imsosleepy.java new file mode 100644 index 000000000..57f905098 --- /dev/null +++ b/3sum/imsosleepy.java @@ -0,0 +1,55 @@ +// 투포인터 방식으로 구현 +// 투포인터의 기본 시간복잡도가 O(2^n)이므로 오름차순으로 정렬하는 O(NlogN)은 마음껏 사용해도 된다. +public List> threeSum(int[] nums) { + List> result = new ArrayList<>(); + Arrays.sort(nums); + for(int i =0; i< nums.length-2;i++) { + if (i > 0 && nums[i] == nums[i - 1]) continue; + int left = i + 1, right = nums.length - 1; + while (left < right) { + int sum = nums[i] + nums[left] + nums[right]; + + if (sum == 0) { + result.add(Arrays.asList(nums[i], nums[left], nums[right])); + + while (left < right && nums[left] == nums[left + 1]) left++; + while (left < right && nums[right] == nums[right - 1]) right--; + + left++; + right--; + } else if (sum < 0) { + left++; + } else { + right--; + } + } + } + return result; +} + + +// dfs 풀이를 생각했으나 dfs 방식은 무조건 O(2^n)의 결과가 나온다. +// 타임 아웃 오답 +public class Solution { + public List> threeSum(int[] nums) { + Set> answerSet = new HashSet<>(); + nums = Arrays.stream(nums).sorted().toArray(); + dfs(nums, 0, new ArrayList<>(), answerSet); + return new ArrayList<>(answerSet); + } + + public void dfs(int[] nums, int index, List temp, Set> answerSet) { + if (temp.size() == 3) { + int result = 0; + for (int element : temp) result += element; + if (result == 0) answerSet.add(new ArrayList<>(temp)); + return; + } + + for (int i = index; i < nums.length; i++) { + temp.add(nums[i]); + dfs(nums, i + 1, temp, answerSet); + temp.remove(temp.size() - 1); + } + } +} diff --git a/climbing-stairs/imsosleepy.java b/climbing-stairs/imsosleepy.java new file mode 100644 index 000000000..23d5c7654 --- /dev/null +++ b/climbing-stairs/imsosleepy.java @@ -0,0 +1,45 @@ +// dfs를 이용한 풀이 +// dp 배열을 이용한 것보다 공간복잡도가 상대적으로 낮게 잡힘 40.4mb -> 40.1mb +// 이유가 뭔지는 조금 더 고민해봐야할 듯 +class Solution { + public int climbStairs(int n) { + return dfs(n, new HashMap<>()); + } + + private int dfs(int n, Map memo) { + if (n == 0) { + return 1; + } + if (n < 0) { + return 0; + } + if (memo.containsKey(n)) { + return memo.get(n); + } + + int result = dfs(n - 1, memo) + dfs(n - 2, memo); + memo.put(n, result); + + return result; + } +} + +// 가장 먼저 생각한 풀이 +// 피보나치 수열의 형태의 결과물을 갖는다. +// O(N)의 점화식을 세울 수 있으면 어느 방식으로도 풀림 +class Solution { + public int climbStairs(int n) { + if(n == 1) return 1; + if(n == 2) return 2; + + int[] dp = new int[n + 1]; + dp[1] = 1; // 1 + dp[2] = 2; // 1+1, 2 + + for (int i = 3; i <= n; i++) { + dp[i] = dp[i-1] + dp[i-2]; + } + + return dp[n]; + } +} diff --git a/construct-binary-tree-from-preorder-and-inorder-traversal/imsosleepy.java b/construct-binary-tree-from-preorder-and-inorder-traversal/imsosleepy.java new file mode 100644 index 000000000..37fbc9692 --- /dev/null +++ b/construct-binary-tree-from-preorder-and-inorder-traversal/imsosleepy.java @@ -0,0 +1,36 @@ +// 혼자서 못풀어서 GPT의 도움을 받음 +// 왜 두 배열이 필요한가? +// 전위 순회(preorder): 트리의 루트를 가장 먼저 방문합니다. 그 후 왼쪽 서브트리, 오른쪽 서브트리를 방문합니다. 하지만 이 정보만으로는 각 서브트리가 어느 부분인지, 그리고 그 서브트리의 구체적인 구성 요소가 무엇인지를 알 수 없습니다. +// +// 중위 순회(inorder): 트리의 왼쪽 서브트리를 먼저 방문하고, 루트를 방문한 후, 오른쪽 서브트리를 방문합니다. 이 배열을 통해 루트가 트리의 어느 위치에 있는지 확인할 수 있습니다. 루트의 위치를 알면, 그 위치를 기준으로 왼쪽과 오른쪽 서브트리를 나눌 수 있습니다. + +// 한 배열로는 트리의 구조를 복원할 수 없는 이유 +// 전위 순회만으로는 각 노드의 왼쪽 자식인지 오른쪽 자식인지 알 수 없습니다. 예를 들어, [3, 9, 20, 15, 7]라는 전위 순회 배열만 있다면, '9'가 3의 왼쪽 자식인지 오른쪽 자식인지 알 수 없습니다. +// 중위 순회만으로도 트리의 루트를 알 수 없으므로, 각 서브트리의 구체적인 구조를 알 수 없습니다. +class Solution { + public TreeNode buildTree(int[] preorder, int[] inorder) { + return buildTreeHelper(preorder, inorder, 0, 0, inorder.length - 1); + } + + private TreeNode buildTreeHelper(int[] preorder, int[] inorder, int preStart, int inStart, int inEnd) { + if (inStart > inEnd) return null; + + int rootVal = preorder[preStart]; + TreeNode root = new TreeNode(rootVal); + + int rootIndex = -1; + for (int i = inStart; i <= inEnd; i++) { + if (inorder[i] == rootVal) { + rootIndex = i; + break; + } + } + + int leftSize = rootIndex - inStart; + root.left = buildTreeHelper(preorder, inorder, preStart + 1, inStart, rootIndex - 1); + root.right = buildTreeHelper(preorder, inorder, preStart + leftSize + 1, rootIndex + 1, inEnd); + + return root; + } + +} diff --git a/decode-ways/imsosleepy.java b/decode-ways/imsosleepy.java new file mode 100644 index 000000000..6e211b60b --- /dev/null +++ b/decode-ways/imsosleepy.java @@ -0,0 +1,25 @@ +// 메인 로직은 맞았는데, 0번째 1번째 때문에 빙빙 돌았던 문제 +// dp[0] = 1이 있어야 두자리 수 디코딩의 초기값이 정상적으로 계산된다. +class Solution { + public int numDecodings(String s) { + if (s == null || s.length() == 0) return 0; + + int n = s.length(); + int[] dp = new int[n + 1]; + + dp[0] = 1; + dp[1] = s.charAt(0) != '0' ? 1 : 0; + + for (int i = 2; i <= n; i++) { + if (s.charAt(i - 1) != '0') { + dp[i] += dp[i - 1]; + } + int twoDigit = Integer.parseInt(s.substring(i - 2, i)); + if (twoDigit >= 10 && twoDigit <= 26) { + dp[i] += dp[i - 2]; + } + } + + return dp[n]; + } +} diff --git a/valid-anagram/imsosleepy.java b/valid-anagram/imsosleepy.java new file mode 100644 index 000000000..ca4b65883 --- /dev/null +++ b/valid-anagram/imsosleepy.java @@ -0,0 +1,33 @@ +// 자바에서는 시간복잡도를 O(N)으로 잡아도 최상위권으로 갈 수 없는 문제 +// 유니코드 고려 +public boolean isAnagram(String s, String t) { + if (s.length() != t.length()) return false; + + Map map = new HashMap<>(); + for (int i = 0; i < s.length(); i++) { + map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1); + map.put(t.charAt(i), map.getOrDefault(t.charAt(i), 0) - 1); + } + for (int value : map.values()) { + if (value != 0) return false; + } + return true; +} +// 알파벳만 고려 +public boolean isAnagram(String s, String t) { + int ALPHABET_COUNT = 26; + if(s.length() != t.length()) { + return false; + } + int[] arr = new int[ALPHABET_COUNT]; // 알파벳 갯수 + for(int i = 0; i < s.length() ; i ++) { + arr[s.charAt(i) - 97]++; + arr[t.charAt(i) - 97]--; + } + for(int i = 0; i < ALPHABET_COUNT; i ++) { + if(arr[i] != 0) { + return false; + } + } + return true; +}