Skip to content
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

[Flynn] Week6 #464

Merged
merged 8 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
41 changes: 41 additions & 0 deletions container-with-most-water/flynn.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* 풀이
* - container의 밑변이 넓고 높이가 높을 수록 저장하는 물의 양이 많습니다
* - 따라서 밑변이 가장 넓은 container부터 탐색하면 탐색 효율을 높일 수 있다는 생각을 했습니다
* - 양 끝에서부 two pointer를 이용하여 탐색을 시작합니다
* - lo, hi 중에서 높이가 더 낮은 pointer를 안쪽으로 이동시킵니다
* - 왜냐하면 높이가 더 낮은 pointer를 이동시켰을 때 기존 container보다 더 높이가 높은 container를 만들 수 있는 가능성이 생기기 때문입니다
*
* Big O
* - N: 주어진 배열 height의 크기
*
* - Time complexity: O(N)
* - 배열 height를 조회하므로 전체 실행시간 또한 N에 비례하여 선형적으로 증가합니다
*
* - Space complexity: O(1)
*/

class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size();
int lo = 0;
int hi = n - 1;

int res = 0;
while (lo < hi) {
int w = hi - lo;
int h = min(height[lo], height[hi]);

res = max(res, w * h);

if (height[lo] > height[hi]) {
--hi;
} else {
++lo;
}
}

return res;
}
};
89 changes: 89 additions & 0 deletions design-add-and-search-words-data-structure/flynn.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* 풀이
* - Trie 구조를 활용하여 풀이할 수 있습니다
* - wildcard인 '.'에 대한 처리가 필요합니다
*
* Big O
* - N: 주어지는 문자열 word의 길이
* - M: 현재 WordDictionary에 저장되어 있는 TrieNode의 수
*
* - void addWord(string word)
* - Time complexity: O(N)
* - Space complexity: O(N)
* - 최악의 경우 word의 모든 문자에 대해 새로운 TrieNode를 추가해야 합니다
*
* - bool search(string word)
* - bool _search(string word, int idx, TrieNode* node)
* - Time complexity: best O(N), worst O(M) < O(26^N)
* - wildcard 사용 및 기존에 저장된 word의 상태에 따라 달라집니다
* - Space complexity: O(N)
* - _search가 재귀적으로 호출되므로 재귀 호출 스택의 깊이만큼 추가적인 공간이 사용됩니다
* - 재귀 호출 스택의 깊이는 현재 찾는 word의 길이에 선형적으로 비례합니다
*/

class TrieNode {
public:
array<TrieNode*, 27> links;
bool word;

TrieNode(): word(false) {
links.fill(nullptr);
}
};

class WordDictionary {
public:
WordDictionary(): root(new TrieNode()) {}

void addWord(string word) {
TrieNode* current = root;

for (char c : word) {
if (current->links[c - 'a'] == nullptr) {
current->links[c - 'a'] = new TrieNode();
}
current = current->links[c - 'a'];
}

current->word = true;
}

bool search(string word) {
return _search(word, 0, root);
}

private:
TrieNode* root;

bool _search(string word, int idx, TrieNode* node) {
obzva marked this conversation as resolved.
Show resolved Hide resolved
if (word.size() == idx) return node->word;

char c = word[idx];

if (c != '.') {
if (node->links[c - 'a'] == nullptr) return false;

TrieNode* next_node = node->links[c - 'a'];
int next_idx = idx + 1;

return _search(word, next_idx, next_node);
} else {
for (TrieNode* link : node->links) {
if (link != nullptr) {
TrieNode* next_node = link;
int next_idx = idx + 1;

if (_search(word, next_idx, next_node)) return true;
}
}
return false;
}
}
};

/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary* obj = new WordDictionary();
* obj->addWord(word);
* bool param_2 = obj->search(word);
*/
114 changes: 114 additions & 0 deletions longest-increasing-subsequence/flynn.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* 풀이
* - 아래와 같은 배열 memo를 이용하여 이중 반복문을 실행하는 풀이입니다
* - memo[i]: nums[i]로 끝나는 subsequence 중에서 길이가 가장 긴 subsequence의 길이
*
* Big O
* - N: 배열 nums의 길이
* - Time complexity: O(N^2)
* - Space complexity: O(N)
*/

class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> memo;
memo.push_back(1);

int res = 1;
for (int i = 1; i < nums.size(); ++i) {
int tmp = 1;
for (int j = 0; j < i; ++j) {
if (nums[j] < nums[i]) tmp = max(tmp, memo[j] + 1);
}
memo.push_back(tmp);
res = max(res, tmp);
}

return res;
}
};

/**
* 풀이
* - wikipedia의 pseudo code를 참고하였습니다
* 달레님 블로그에 실린 풀이를 통해서 훨씬 간단하게 문제에서 요구하는 바를 구할 수 있으므로, 문제의 풀이만을 원하신다면 달레님 블로그를 추천드리고
* 좀 더 general하고 확장성 있는 알고리즘에 대해 궁금하신 분들께서는 wiki도 읽어보시는 걸 추천드립니다 (이해하는 데에는 시간이 좀 걸렸습니다)
*
* 제가 읽고 이해한 결과 wiki 풀이와 달레님 풀이의 비교는 다음과 같습니다
*
* 공통점: 문제에서 요구하는 바를 구할 수 있음 (LIS의 길이)
* 차이점: wiki 풀이는 문제에서 요구하는 바를 구하는 것에 비해 overkill입니다 (이 문제에서는 굳이 필요 없는 부분이 꽤 있음)
* 대신, wiki 풀이는 확장성이 좀 더 넓은 풀이입니다 (각 길이에 해당하는 increasing subsequence를 재구축할 수 있음)
*
* 관심 있으신 분들께서는 한 번 읽어보시는 것을 추천합니다 :)
* - 참고: https://en.wikipedia.org/wiki/Longest_increasing_subsequence#Efficient_algorithms
obzva marked this conversation as resolved.
Show resolved Hide resolved
*
* - memo[l]: 현재 nums[i]를 탐색중이라고 할 때, l <= i인 l에 대하여
* 길이가 l인 increasing subsequence들의 마지막 원소 중에서
* 가장 최소값인 nums[k]의 인덱스 k
* nums를 순차적으로 탐색해 나감에 따라 계속 갱신되며 정렬된 상태를 유지하게 됩니다 (if x < y then nums[memo[x]] < nums[memo[y]])
*
* - predec[i]: nums[i]를 마지막 원소로 하는 가장 긴 increasing subsequence에서 nums[i] 바로 앞에 오는 원소의 index
*
* - nums를 순차적으로 탐색하며, 현재 탐색 중인 nums[i]를 마지막 원소로 삼는 가장 긴 Increasing subsequence를 찾습니다
* 가장 긴 Increasing subsequence는 memo 배열에 대해 이분탐색을 실행하여 알아낼 수 있습니다
*
* Big O
* - N: 배열 nums의 길이
*
* - Time complexity: O(NlogN)
* - nums의 각 원소마다 memo에 대해 이분탐색을 실행하므로 N이 증가함에 따라 실행 시간은 N * logN 형태로 증가합니다
* - Space complexity: O(N)
* - memo 배열의 크기는 N이 증가함에 따라 선형적으로 증가합니다
* (- predec 배열의 크기 또한 N이 증가함에 따라 선형적으로 증가합니다)
*/

class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();

vector<int> memo(n + 1, -1);

// vector<int> predec(n, -1);
// 각 길이에 맞는 increasing subsequence를 재구축할 때 쓰입니다

int max_len = 0;
for (int i = 0; i < nums.size(); ++i) {
int lo = 1;
int hi = max_len + 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (nums[memo[mid]] < nums[i]) lo = mid + 1;
else hi = mid;
}
// 위 이분탐색을 마치면 lo == hi인데
// lo (혹은 hi)가 의미하는 바는 `nums[i]가 마지막 원소인 increasing subsequence 중에 길이가 가장 긴 녀석의 길이` 입니다
int curr_len = lo;
// 이해하기 쉽게끔 curr_len이라는 변수를 선언해줍니다

// predec[i] = memo[curr_len - 1];
// nums[i]가 마지막으로 오는 가장 긴 increasing subsequence에서 nums[i]의 바로 전 원소의 index를 기록해줍니다
//
memo[curr_len] = i;

if (curr_len > max_len) {
// 만약 이전까지 찾았던 max_len보다 더 길이가 긴 increasing subsequence를 max_len
max_len = curr_len;
}
}

return max_len;

// 길이 L짜리 increasing subsequence 중 하나를 재구축하려면 아래처럼 하면 됩니다
// [P...P[memo[L]], ..., P[P[memo[L]]], P[memo[L]] ,memo[L]]

// vector<int> s(L, -1);
// int k = memo[L];
// for (int i = L - 1; i >= 0; --i) {
// s[i] = nums[k];
// k = predec[k];
// }
}
};
58 changes: 58 additions & 0 deletions spiral-matrix/flynn.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* 풀이
* - 탐색 방향을 90도씩 회전해나가면서 주어진 2차원 배열 matrix를 탐색합니다
* - 한계: 주어진 matrix를 변형하게 되며, 해당 변형을 피하기 위해서는 추가적인 공간 사용이 필요합니다
*
* Big O
* - M: 주어진 matrix의 행의 개수
* - N: 열의 개수
*
* - Time complexity: O(MN)
* - Space complexity: O(1)
*/

class Solution {
public:
pair<int, int> rotate(pair<int, int> dir) {
// 시계방향 90도 회전
// 행렬곱으로 구해줄 수 있습니다
// | 0 -1 | | dir.first | = | -dir.second |
// | 1 0 | | dir.second | | dir.first |
return {dir.second, -dir.first};
}

pair<int, int> get_next(pair<int, int> curr, pair<int, int> dir) {
return {curr.first + dir.first, curr.second + dir.second};
}

vector<int> spiralOrder(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
int cnt = m * n;

pair<int, int> curr = {0, 0};
pair<int, int> curr_dir = {0, 1};

vector<int> res;

while (cnt) {
res.push_back(matrix[curr.first][curr.second]);

matrix[curr.first][curr.second] = 101; // constraint 밖의 값 101로 방문 여부를 표시합니다
--cnt;

pair<int, int> next = get_next(curr, curr_dir);

if (0 > next.first || next.first >= m
|| 0 > next.second || next.second >= n
|| matrix[next.first][next.second] == 101) {
curr_dir = rotate(curr_dir);
curr = get_next(curr, curr_dir);
} else {
curr = next;
}
}

return res;
}
};
31 changes: 31 additions & 0 deletions valid-parentheses/flynn.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* 풀이
* - stack 자료구조를 이용합니다
*
* Big O
* - N: 주어진 문자열 s의 길이
*
* - Time complexity: O(N)
* - 문자열 s 전체를 순회할 경우 실행시간은 N에 선형적으로 비례하여 증가합니다
* - Space complexity: O(N)
* - "((((((...((((((" 와 같은 입력을 받으면 stack의 크기가 최대 N까지 증가합니다
*/

class Solution {
public:
bool isValid(string s) {
stack<char> st;
for (char ch : s) {
if (ch == '(' || ch == '{' || ch == '[') {
st.push(ch);
} else {
if (st.empty()) return false;
else if (st.top() == '(' && ch == ')') st.pop();
else if (st.top() == '{' && ch == '}') st.pop();
else if (st.top() == '[' && ch == ']') st.pop();
else return false;
}
}
return st.empty();
}
};