Skip to content

[forest000014] Week 09 #994

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 9, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clone-graph/forest000014.java
Original file line number Diff line number Diff line change
@@ -70,7 +70,7 @@ public void dfs(Node oldNode) {
Node newNeighbor = createNode(oldNeighbor.val);
newNode.neighbors.add(newNeighbor);
newNeighbor.neighbors.add(newNode);
dfs(oldNeighbor, newNeighbor);
dfs(oldNeighbor);
}
}
}
38 changes: 38 additions & 0 deletions find-minimum-in-rotated-sorted-array/forest000014.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
# Time Complexity: O(logn)
# Space Complexity: O(1)

binary search를 사용해 접근했습니다.
sorted array인 경우는 while문 시작 전, 예외 체크를 했습니다.
binary search를 진행하면, 각각의 loop의 mid 원소는 아래 4가지 경우 중 하나입니다.
maximum / minimum / 앞쪽 수열의 원소 중 하나 / 뒤쪽 수열의 원소 중 하나
maximum이거나 minimum인 경우는 더 이상 탐색할 필요 없이 종료합니다.
앞쪽 수열인 경우, 우리가 원하는 minimum은 왼쪽에는 없으므로, left 포인터를 mid의 오른쪽으로 옮기고 다음 loop를 진행하고,
뒤쪽 수열인 경우, 우리가 원하는 minimum은 오른쪽에는 없으므로, rigth 포인터를 mid의 왼쪽으로 옮기고 다음 loop를 진행합니다.

*/
class Solution {
public int findMin(int[] nums) {
int l = 0;
int r = nums.length - 1;

if (nums[l] <= nums[r]) { // sorted array인 경우 (size가 1인 경우도 포함)
return nums[l];
}

while (l <= r) {
int m = (r - l) / 2 + l; // 만약 문제 조건상 l, r의 합이 int 범위를 넘어가서 overflow가 생길 수 있는 경우에, 이런 식으로 overflow를 방지할 수 있다고 알고 있습니다. 이 문제는 overflow 걱정은 없지만, 나중에 실전에서 나오면 잊지 않으려고 이렇게 구현해보았습니다.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (m > 0 && nums[m - 1] > nums[m]) {
return nums[m];
} else if (m < nums.length - 1 && nums[m] > nums[m + 1]) {
return nums[m + 1];
} else if (nums[m] > nums[l]) {
l = m + 1;
} else {
r = m - 1;
}
}
Comment on lines +25 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

조건문을 세 부분으로 줄일 수는 없을까 고민해봤습니다 :)

  1. return하는 조건
  2. r를 옮기는 조건
  3. l를 옮기는 조건
Suggested change
if (m > 0 && nums[m - 1] > nums[m]) {
return nums[m];
} else if (m < nums.length - 1 && nums[m] > nums[m + 1]) {
return nums[m + 1];
} else if (nums[m] > nums[l]) {
l = m + 1;
} else {
r = m - 1;
}
}
// 문제 조건상 입력 배열은 무조건 1 이상의 rotation이 적용되어 있습니다.
// 그리고 line19에서 우리는 가지런히 정렬된 입력 배열에 대해서는 바로 return할 수 있는 조건문을 작성하였습니다
// 따라서 우리가 m == 0인 nums[m]을 return할 일은 없으므로 m > 0이라는 조건은 생략할 수 있습니다
// 기존의 두번째 조건문은 제가 보기엔 불필요한 것 같습니다
if (nums[m - 1] > nums[m]) {
return nums[m];
// nums[m]이 입력 배열 중 'rotation되지 않은 부분'에 속하는 경우입니다
// 이 경우엔 r을 줄여야 합니다
} else if (nums[m] < nums[nums.length - 1]) {
r = m - 1;
} else {
l = m + 1;
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전반적인 사고 과정은 예전 제 풀이에서의 사고 과정과 비슷합니다 :) 혹시 도움이 될까봐 공유합니다..

(디스커션에 제가 쓴 글도 있으니 궁금하시다면 참고하셔도 됩니다!)


return -1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1을 리턴하는 경우는 없을 것 같아요 :)

}
}
29 changes: 29 additions & 0 deletions linked-list-cycle/forest000014.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
# Time Complexity : O(n)
# Space Complexity: O(1)

* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
private final int VISITED = -999999;
public boolean hasCycle(ListNode head) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 접근인 것 같습니다 :)
하지만 만약 문제 조건이 이렇게 주어졌다면 어땠을까요?
"입력으로 주어진 ListNode에는 변경을 가하지 마시오"

리스트노드의 acyclic 여부를 판명하는 잘 알려진 알고리즘이 있으니 소개드립니다, 참고 바랍니다 :)
https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare

while (head != null) {
if (head.val == VISITED) {
return true;
}

head.val = VISITED;
head = head.next;
}

return false;
}
}
89 changes: 89 additions & 0 deletions maximum-product-subarray/forest000014.java
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그동안 forest님 풀이를 봐오면서 느낀건데, 문제를 작은 단위로 쪼개어서 해결하는 능력과 끝까지 문제를 해결해내는 능력이 훌륭하십니다 :)

절대 이 풀이가 나쁘다는 건 아니구요, 리뷰어 입장에서 느꼈던 피드백 사항이 몇가지 있어 조심스레 남기고자 합니다

  1. 로직이 복잡합니다. 복잡한 로직이 나쁜 건 아니지만, 알고리즘 테스트를 실시간으로 (혹은 면접관 앞에서) 진행하는 상황이라면 이렇게 긴 풀이는 버그 발생 가능성이 높습니다. 최악의 경우엔 많이 꼬여서 시간 안배가 불가능하게 됩니다. (제가 그랬어요)
  2. 로직이 복잡하다는 건, 리뷰어가 해당 코드를 읽기 힘들다는 뜻이기도 합니다. 그리고 본인께서도 면접관께 설명드리기 더 어려우실 겁니다.

좀 더 간단한 로직을 사용하는 풀이를 하나 소개드리니, 참고 바랍니다 :)
https://github.com/DaleStudy/leetcode-study/blob/main/maximum-product-subarray/obzva.cpp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@obzva
감사합니다! 사실 저도 매번 풀 때마다 막연하게 느끼던 부분이었는데, 정확하게 짚어주신 것 같습니다.
제가 주말 전후로 몸살을 심하게 앓아서 이제서야 코멘트를 봤네요. 오늘은 육아하고 잠깐 들어와봤는데, 다시 야근하러 가야 할 것 같아서 내일 마저 읽어보겠습니다 🙇

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
# Time Complexity: O(n)

# Space Complexity: O(1)

# Solution
1. array에 0이 존재하는 경우

(observation: 정답이 음수가 될 가능성이 있는가? Yes. 다만, 원소 개수가 1개이고, 그 원소가 음수인 경우에만 그렇다. 음수인 원소가 2개 이상인 경우는 subarray를 잘 선택하면 그 곱을 항상 0 이상으로 만들 수 있다. 즉, 원소 개수가 1개이고 음수인 경우만 예외 처리를 해주면, 그 외의 경우는 정답이 0이거나 양수이다.)

subarray에 0이 포함되는 순간 곱은 0이 되므로, 0보다 큰 곱을 찾기 위해서는 0을 제외하고 판단한다.
즉, 0을 기준으로 slice해서, 각각의 segment만 독립적으로 검토하면 된다. (0으로 slice한 각 segment가 아래 2번 케이스로 환원된다.)

2. (sub)array에 0이 존재하지 않는 경우
음수의 개수에 따라 접근을 다르게 한다.

2-1. 짝수개
고민할 것 없이, 전체 subarray의 원소를 곱하면 그 subarray에서 얻을 수 있는 곱의 최대값이다.

2-2. 홀수개
subarray 양 끝에서 각각 출발하여 최초의 마이너스(즉 가장 바깥쪽의 마이너스)를 만날 때까지, 원소들을 누적해서 곱하며 이동.
두 값 중 절대값이 작은 쪽을 subarray에서 제외. 남은 부분의 곱을 구하면, 최대값이다.
*/

class Solution {
public int maxProduct(int[] nums) {
if (nums.length == 1) {
return nums[0];
}

int ans = 0;
int start = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
continue;
}

if (i > 0) {
int res = calculateMaxProduct(start, i - 1, nums);
ans = Math.max(ans, res);
}
start = i + 1;
}

if (start <= nums.length - 1) {
int res = calculateMaxProduct(start, nums.length - 1, nums);
ans = Math.max(ans, res);
}

return ans;
}

private int calculateMaxProduct(int l, int r, int[] nums) {
if (l == r) {
return nums[l];
}

int minusCount = 0;
int product = 1;
for (int i = l; i <= r; i++) {
if (nums[i] < 0) {
minusCount++;
}
product *= nums[i];
}

if (minusCount % 2 == 0) {
return product;
} else {
int leftProduct = 1;
for (int i = l; i <= r; i++) {
leftProduct *= nums[i];
if (nums[i] < 0) {
break;
}
}

int rightProduct = 1;
for (int i = r; i >= l; i--) {
rightProduct *= nums[i];
if (nums[i] < 0) {
break;
}
}

return product / Math.max(leftProduct, rightProduct);
}
}
}