-
-
Notifications
You must be signed in to change notification settings - Fork 289
[ys-han00] WEEK 01 solutions #1973
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
base: main
Are you sure you want to change the base?
Conversation
|
Add Project Week 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| sort(nums.begin(), nums.end()); | ||
|
|
||
| for(int i = 0; i < nums.size() - 1; i++) | ||
| if(nums[i] == nums[i+1]) | ||
| return true; | ||
|
|
||
| return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아래와 같은 로직으로 이해했는데 제가 잘 이해한것이 맞을까요?
- 배열
nums를 정렬한 다음 - 배열의 크기만큼 반복문을 돌면서
- 배열의 다음 요소와 같은 값이 있는지를 판단한다
로직이 명확하고 직관적이네요 👍
💡 참고: 다른 접근 방법
주석으로 남겨주신 첫 시도를 보니 이미 해싱 아이디어를 생각하셨던 것 같아요.
다만 배열 대신 unordered_set을 활용하면 시간과 메모리 문제를 해결할 수 있을 것 같아요!
unordered_set 을 이용하면 실제 등장한 숫자만 저장하기 때문에 메모리 효율적으로 동작할 수 있을 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아래와 같은 로직으로 이해했는데 제가 잘 이해한것이 맞을까요? 1. 배열
nums를 정렬한 다음 2. 배열의 크기만큼 반복문을 돌면서 3. 배열의 다음 요소와 같은 값이 있는지를 판단한다 ...
네 맞게 이해하셨습니다! 간단하면서도 시간효율이 제일 좋아서 채택한 풀이입니다.
...
unordered_set을 이용하면 실제 등장한 숫자만 저장하기 때문에 메모리 효율적으로 동작할 수 있을 것 같아요.
코드에는 포함되지 않았지만, 말씀하신 unordered_set 을 사용해서도 풀어봤습니다. 예상하신대로 문제 없이 동작합니다! 하지만, 시간, 메모리 효율이 제출한 코드 보다 좋지 않아 코드에 포함하지 않았습니다 ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미 unordered_set 을 이용해서 풀어보셨군요! 👍
sort 방식은 O(n logn) 방식이고
unordered_set 방식이 O(n)이라서 더 빠를 것 같았는데 실제로는 시간, 메모리 효율이 더 안좋았군요 🤔
리트코드 테스트 케이스에 중복이 없거나 중복이 배열 끝부분에 있는 경우가 많으면 Sort가 더 빠르게 동작했을 것 같아요 ㅎㅎ
궁금해서 Claude한테 벤치마킹을 시켜봤는데 아래와 같이 나왔어요 ㅎㅎ 재미있네요 💪
참고하면 도움될듯하여 공유드려요!
#include <bits/stdc++.h>
using namespace std;
using namespace std::chrono;
class Solution {
public:
// 방법 1: Sort
bool containsDuplicate_Sort(vector<int>& nums) {
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size() - 1; i++)
if(nums[i] == nums[i+1])
return true;
return false;
}
// 방법 2: Unordered Set
bool containsDuplicate_Hash(vector<int>& nums) {
unordered_set<int> s;
for(int num : nums) {
if(s.count(num))
return true;
s.insert(num);
}
return false;
}
};
// 테스트 데이터 생성
vector<int> generateTestData(int size, bool hasDuplicate) {
vector<int> nums;
for(int i = 0; i < size; i++) {
nums.push_back(rand() % (size * 2));
}
if(!hasDuplicate) {
// 중복 제거 (최악의 케이스)
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
// 부족한 만큼 추가
while(nums.size() < size) {
nums.push_back(nums.size());
}
}
return nums;
}
void benchmark(int size, bool hasDuplicate, int iterations = 10) {
Solution sol;
long long total_sort = 0;
long long total_hash = 0;
// 여러 번 실행해서 평균 구하기
for(int iter = 0; iter < iterations; iter++) {
vector<int> data = generateTestData(size, hasDuplicate);
// Sort 방식 측정
auto data1 = data;
auto start1 = high_resolution_clock::now();
bool result1 = sol.containsDuplicate_Sort(data1);
auto end1 = high_resolution_clock::now();
total_sort += duration_cast<microseconds>(end1 - start1).count();
// Hash 방식 측정
auto data2 = data;
auto start2 = high_resolution_clock::now();
bool result2 = sol.containsDuplicate_Hash(data2);
auto end2 = high_resolution_clock::now();
total_hash += duration_cast<microseconds>(end2 - start2).count();
}
double avg_sort = total_sort / (double)iterations;
double avg_hash = total_hash / (double)iterations;
// 결과 출력
cout << "Size: " << setw(8) << size
<< " | Dup: " << (hasDuplicate ? "Yes" : "No ") << " | ";
cout << "Sort: " << setw(8) << fixed << setprecision(1) << avg_sort << " μs | ";
cout << "Hash: " << setw(8) << fixed << setprecision(1) << avg_hash << " μs | ";
if(avg_sort < avg_hash) {
cout << "Winner: Sort (x" << setprecision(2) << (avg_hash / avg_sort) << " faster)";
} else {
cout << "Winner: Hash (x" << setprecision(2) << (avg_sort / avg_hash) << " faster)";
}
cout << endl;
}
int main() {
srand(time(0));
cout << "=== Contains Duplicate: Sort vs Hash Performance ===" << endl;
cout << string(80, '=') << endl << endl;
// 다양한 크기로 테스트
vector<int> sizes = {100, 1000, 10000, 50000, 100000, 500000, 1000000};
cout << "Test with DUPLICATES (best case - early exit possible):" << endl;
cout << string(80, '-') << endl;
for(int size : sizes) {
benchmark(size, true, 5);
}
cout << endl << "Test WITHOUT duplicates (worst case - full scan):" << endl;
cout << string(80, '-') << endl;
for(int size : sizes) {
benchmark(size, false, 5);
}
cout << endl << string(80, '=') << endl;
cout << "Summary:" << endl;
cout << "- Small sizes (< 10K): Sort usually wins due to cache locality" << endl;
cout << "- Large sizes (> 100K): Hash wins with O(n) vs O(n log n)" << endl;
cout << "- Memory: Sort uses O(1), Hash uses O(n)" << endl;
return 0;
}테스트 환경
- 컴파일러: g++ -std=c++17 -O2
- 측정 단위: μs (마이크로초)
- 반복 횟수: 각 케이스당 5회 평균
🔹 시나리오 1: 중복이 있는 경우 (Early Exit 가능)
| Size | Sort (μs) | Hash (μs) | Winner | 성능 차이 |
|---|---|---|---|---|
| 100 | 2.2 | 1.6 | Hash | 1.4x |
| 1,000 | 36.8 | 3.4 | Hash | 10.8x |
| 10,000 | 479.2 | 17.0 | Hash | 28.2x |
| 50,000 | 2,806.0 | 26.6 | Hash | 105.5x |
| 100,000 | 6,092.8 | 43.8 | Hash | 139.1x |
| 500,000 | 33,415.8 | 101.0 | Hash | 330.9x |
| 1,000,000 | 71,354.8 | 111.8 | Hash | 638.2x 🚀 |
→ 중복을 일찍 발견할 수 있으면 Hash가 압도적으로 빠름
🔹 시나리오 2: 중복이 없는 경우 (Worst Case - 전체 스캔)
| Size | Sort (μs) | Hash (μs) | Winner | 성능 차이 |
|---|---|---|---|---|
| 100 | 1.0 | 3.6 | Sort | 3.6x |
| 1,000 | 16.6 | 36.2 | Sort | 2.2x |
| 10,000 | 217.2 | 516.0 | Sort | 2.4x |
| 50,000 | 1,362.6 | 2,795.0 | Sort | 2.1x |
| 100,000 | 2,780.8 | 6,050.8 | Sort | 2.2x |
| 500,000 | 16,313.2 | 43,765.8 | Sort | 2.7x |
| 1,000,000 | 34,415.6 | 95,954.0 | Sort | 2.8x 🚀 |
→ 끝까지 스캔해야 하는 경우 Sort가 2~3배 빠름
| sort(num_idx.begin(), num_idx.end()); | ||
|
|
||
| int left = 0, right = nums.size() - 1; | ||
| while(1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 참고 사항
문제에서 답이 반드시 존재한다고 명시되어 있어서 while(1)도 괜찮지만
더 안전한 코드를 위해 while(left < right)로 작성하는 것도 방법입니다 👍
while(left < right) { // 명확하게 조건을 명시
// ...
}이렇게 명시적으로 처리하면 혹시 모를 예외 상황(답이 없는 경우)에서 무한 루프를 방지할 수 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
더 안전한 코드를 위해
while(left < right)로 작성하는 것도 방법입니다 👍
...
의견 감사합니다! 알고리즘 문제 풀 때 습관인데, 이번 스터디 때 고치도록 해보겠습니다 ㅎㅎ
안녕하세요~! 부끄럽게도 깃을 사용한 협업 경험이 없어서 제 첫 깃 리뷰어십니다 :) PR도 처음이라, 이것저것 해보다가 실수가 많아 힘든 하루였네요 ㅎㅎ 먼저, 작성해주신 리뷰에 대해 답변 달았는데, 실수가 많았아서 잘한지 모르겠네요 ㅜㅜ 추가적으로 푼 문제들과 혹시나 깃 사용 관련해서도 의견이 있으시면 언제든지 의견주세요! 감사합니다. |
unpo88
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
전체적으로 코드 잘 살펴보고 리뷰 남겨놓았습니다!
크게 3가지 부분에서의 리뷰를 남겼는데요.
- 코드 가독성에 대해
- 코드에서 궁금한 부분에 대해
- 참고하면 좋은 정보에 대해
그 외 풀이에는 문제 없다고 생각해서 Approve를 눌러놓을게요!
제안 주신 부분은 선택사항이니 편하신 대로 반영하시면 될 것 같아요 ㅎㅎ
문제 푸느라 고생 고생 많으셨습니다!
안녕하세요~! 부끄럽게도 깃을 사용한 협업 경험이 없어서 제 첫 깃 리뷰어십니다 :)
PR도 처음이라, 이것저것 해보다가 실수가 많아 힘든 하루였네요 ㅎㅎ
먼저, 작성해주신 리뷰에 대해 답변 달았는데, 실수가 많았아서 잘한지 모르겠네요 ㅜㅜ
추가적으로 푼 문제들과 혹시나 깃 사용 관련해서도 의견이 있으시면 언제든지 의견주세요!
감사합니다.
제가 첫 @ys-han00 님의 첫 리뷰어가 되다니.. 영광입니다! 👍
첫 PR이신데도 코드도 깔끔하고 리뷰 답변도 잘 달아주셨어요.
실수하신 거 전혀 없으니 걱정 안하셔도 됩니다 😄 (실수하면 누군가 알려주실테니 반영해서 잘 고치면 되죠 💪 )
궁금한 점 있으면 편하게 Discord나 PR에서 멘션 주세요!
도움드릴 수 있는 부분이면 도움드리겠습니다~! 화이팅입니다!
| sort(nums.begin(), nums.end()); | ||
|
|
||
| for(int i = 0; i < nums.size() - 1; i++) | ||
| if(nums[i] == nums[i+1]) | ||
| return true; | ||
|
|
||
| return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미 unordered_set 을 이용해서 풀어보셨군요! 👍
sort 방식은 O(n logn) 방식이고
unordered_set 방식이 O(n)이라서 더 빠를 것 같았는데 실제로는 시간, 메모리 효율이 더 안좋았군요 🤔
리트코드 테스트 케이스에 중복이 없거나 중복이 배열 끝부분에 있는 경우가 많으면 Sort가 더 빠르게 동작했을 것 같아요 ㅎㅎ
궁금해서 Claude한테 벤치마킹을 시켜봤는데 아래와 같이 나왔어요 ㅎㅎ 재미있네요 💪
참고하면 도움될듯하여 공유드려요!
#include <bits/stdc++.h>
using namespace std;
using namespace std::chrono;
class Solution {
public:
// 방법 1: Sort
bool containsDuplicate_Sort(vector<int>& nums) {
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size() - 1; i++)
if(nums[i] == nums[i+1])
return true;
return false;
}
// 방법 2: Unordered Set
bool containsDuplicate_Hash(vector<int>& nums) {
unordered_set<int> s;
for(int num : nums) {
if(s.count(num))
return true;
s.insert(num);
}
return false;
}
};
// 테스트 데이터 생성
vector<int> generateTestData(int size, bool hasDuplicate) {
vector<int> nums;
for(int i = 0; i < size; i++) {
nums.push_back(rand() % (size * 2));
}
if(!hasDuplicate) {
// 중복 제거 (최악의 케이스)
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
// 부족한 만큼 추가
while(nums.size() < size) {
nums.push_back(nums.size());
}
}
return nums;
}
void benchmark(int size, bool hasDuplicate, int iterations = 10) {
Solution sol;
long long total_sort = 0;
long long total_hash = 0;
// 여러 번 실행해서 평균 구하기
for(int iter = 0; iter < iterations; iter++) {
vector<int> data = generateTestData(size, hasDuplicate);
// Sort 방식 측정
auto data1 = data;
auto start1 = high_resolution_clock::now();
bool result1 = sol.containsDuplicate_Sort(data1);
auto end1 = high_resolution_clock::now();
total_sort += duration_cast<microseconds>(end1 - start1).count();
// Hash 방식 측정
auto data2 = data;
auto start2 = high_resolution_clock::now();
bool result2 = sol.containsDuplicate_Hash(data2);
auto end2 = high_resolution_clock::now();
total_hash += duration_cast<microseconds>(end2 - start2).count();
}
double avg_sort = total_sort / (double)iterations;
double avg_hash = total_hash / (double)iterations;
// 결과 출력
cout << "Size: " << setw(8) << size
<< " | Dup: " << (hasDuplicate ? "Yes" : "No ") << " | ";
cout << "Sort: " << setw(8) << fixed << setprecision(1) << avg_sort << " μs | ";
cout << "Hash: " << setw(8) << fixed << setprecision(1) << avg_hash << " μs | ";
if(avg_sort < avg_hash) {
cout << "Winner: Sort (x" << setprecision(2) << (avg_hash / avg_sort) << " faster)";
} else {
cout << "Winner: Hash (x" << setprecision(2) << (avg_sort / avg_hash) << " faster)";
}
cout << endl;
}
int main() {
srand(time(0));
cout << "=== Contains Duplicate: Sort vs Hash Performance ===" << endl;
cout << string(80, '=') << endl << endl;
// 다양한 크기로 테스트
vector<int> sizes = {100, 1000, 10000, 50000, 100000, 500000, 1000000};
cout << "Test with DUPLICATES (best case - early exit possible):" << endl;
cout << string(80, '-') << endl;
for(int size : sizes) {
benchmark(size, true, 5);
}
cout << endl << "Test WITHOUT duplicates (worst case - full scan):" << endl;
cout << string(80, '-') << endl;
for(int size : sizes) {
benchmark(size, false, 5);
}
cout << endl << string(80, '=') << endl;
cout << "Summary:" << endl;
cout << "- Small sizes (< 10K): Sort usually wins due to cache locality" << endl;
cout << "- Large sizes (> 100K): Hash wins with O(n) vs O(n log n)" << endl;
cout << "- Memory: Sort uses O(1), Hash uses O(n)" << endl;
return 0;
}테스트 환경
- 컴파일러: g++ -std=c++17 -O2
- 측정 단위: μs (마이크로초)
- 반복 횟수: 각 케이스당 5회 평균
🔹 시나리오 1: 중복이 있는 경우 (Early Exit 가능)
| Size | Sort (μs) | Hash (μs) | Winner | 성능 차이 |
|---|---|---|---|---|
| 100 | 2.2 | 1.6 | Hash | 1.4x |
| 1,000 | 36.8 | 3.4 | Hash | 10.8x |
| 10,000 | 479.2 | 17.0 | Hash | 28.2x |
| 50,000 | 2,806.0 | 26.6 | Hash | 105.5x |
| 100,000 | 6,092.8 | 43.8 | Hash | 139.1x |
| 500,000 | 33,415.8 | 101.0 | Hash | 330.9x |
| 1,000,000 | 71,354.8 | 111.8 | Hash | 638.2x 🚀 |
→ 중복을 일찍 발견할 수 있으면 Hash가 압도적으로 빠름
🔹 시나리오 2: 중복이 없는 경우 (Worst Case - 전체 스캔)
| Size | Sort (μs) | Hash (μs) | Winner | 성능 차이 |
|---|---|---|---|---|
| 100 | 1.0 | 3.6 | Sort | 3.6x |
| 1,000 | 16.6 | 36.2 | Sort | 2.2x |
| 10,000 | 217.2 | 516.0 | Sort | 2.4x |
| 50,000 | 1,362.6 | 2,795.0 | Sort | 2.1x |
| 100,000 | 2,780.8 | 6,050.8 | Sort | 2.2x |
| 500,000 | 16,313.2 | 43,765.8 | Sort | 2.7x |
| 1,000,000 | 34,415.6 | 95,954.0 | Sort | 2.8x 🚀 |
→ 끝까지 스캔해야 하는 경우 Sort가 2~3배 빠름
| if(count.find(nums[i]) == count.end()) | ||
| count[nums[i]] = 1; | ||
| else | ||
| count[nums[i]]++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[단순 궁금]
제가 잘 몰라서 질문드려요!
C++ map에서는 키가 없으면 자동으로 0으로 초기화해주거나 하지는 않나요?
python에서는 Key가 없으면 자동으로 0으로 초기화할 수 있는 방법이 있어서 ㅎㅎ
이게 가능하면 find 없이 아래와 같이 코드를 단순화 시킬수도 있을듯하여 질문드려요~!
for(int i = 0; i < num.size(); i++) {
count[num[i]]++;
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여태까지 python dict나 C++ map을 사용할 때 항상 선언 후 사용했었는데, 이런 좋은 기능이 있는지 몰랐네요;;
이번 기회에 비효율적이게 쓰던 코드들 한번 고쳐봐야겠어요!
정말 좋은 팁 감사합니다!!
|
|
||
| sort(nums.begin(), nums.end()); | ||
|
|
||
| int ans = 1, cnt = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cnt가 현재 연속 수열의 길이를 나타내는 것으로 이해했는데 맞을까요?
만약 그렇다면 변수명을 조금 더 명확하게 하면 의도가 더 잘 드러나는 코드가 될 것 같아요!
current_length 와 같은 이름이 떠오르는 것 같습니다 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...
의견 감사합니다! 알고리즘 문제 풀 때 습관인데, 이번 스터디 때 고치도록 해보겠습니다 ㅎㅎ
이것도 알고리즘 풀 때 있는 습관이네요... 고쳐야 할 습관이 많네요
여태까지는 혼자 알고리즘 풀고 맞추면 끝이었는데, 이번 스터디에서는 다른 분들과 공유한다는 점을 참고해서 싹 고쳐봐야겠습니다!
의견 감사합니다.
| if(nums[i] == nums[i - 1]) | ||
| continue; | ||
| if(nums[i] - 1 == nums[i - 1]) | ||
| cnt++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nums[i] 가 현재 순회하고 있는 숫자, nums[i-1] 은 이전 숫자로 이해했는데요 ㅎㅎ
인덱스 접근이다보니 한 눈에 들어오지 않더라구요 😢
아래와 같이 임시 변수로 빼서 처리하는 방법도 있을 것 같은데, 어떠신가요?
int current = nums[i]
int previous = nums[i-1]
if(current == previous) {
...
}
if (current == previous + 1) {
current_length ++;
}이렇게 하면 current, previous로 각 값의 의미가 명확해지는 것 같아서요 ㅎㅎ
current = previous + 1로 "현재가 이전 + 1" 이라는 의미를 더 직관적으로 전달할 수도 있을 것 같습니다
작은 차이지만 가독성 측면에서 좋을 것 같아서 제안드려봐요 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이것도 알고리즘 풀 때 있는 습관이네요... 고쳐야 할 습관이 많네요 여태까지는 혼자 알고리즘 풀고 맞추면 끝이었는데, 이번 스터디에서는 다른 분들과 공유한다는 점을 참고해서 싹 고쳐봐야겠습니다!
숏코딩하려는 습관이 하하. 의견 감사해요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
와우..! 이 문제가 DP로도 풀 수 있었군요! 💪
저는 DFS를 통해서 풀었는데, 새로운 접근법 배워갑니다 👍
|
@unpo88 엄청난 고수분을 첫 리뷰어로 만나다니, 제가 운이 좋네요! 작성해주신 리뷰 다 읽어보고 답변했습니다. 이젠 혼자가 아니니 고쳐야 할 습관이 많이 생겼네요 ㅎㅎ 추후에 궁금한점 생기면 연락드리도록 하겠습니다! 리뷰 수고하셨고, 감사합니다! |
답안 제출 문제
작성자 체크 리스트
In Review로 설정해주세요.검토자 체크 리스트
Important
본인 답안 제출 뿐만 아니라 다른 분 PR 하나 이상을 반드시 검토를 해주셔야 합니다!