diff --git a/3sum/invidam.go.md b/3sum/invidam.go.md new file mode 100644 index 000000000..4e329a69d --- /dev/null +++ b/3sum/invidam.go.md @@ -0,0 +1,59 @@ +# Intuition +예전에 풀어봤던 문제였어서 접근법을 알고있었다. + +# Approach +1. 정렬을 하여 배열의 대소관계를 일정하게 한다. +2. i,j,k를 설정해야 하는데, i < j < k이도록 한다. +3. i는 맨 앞에서부터 순회한다. +4. j는 i의 뒤부터 순회한다. +5. k는 맨 뒤에서부터 순회한다. +6. 세 인덱스가 가리키는 값들의 합을 비교하여 j와 k를 수정한다. + +# Complexity +- Time complexity: $$O(n^2)$$ + - 입력 배열의 길이 n에 대하여, `i`, `j와 k`를 순회한다. + +- Space complexity: $$O(n)$$ + - 입력으로 들어온 배열의 길이 n에 대하여, 생성하는 결과 배열의 길이 역시 이와 동일하다. +# Code + +```go +func update(i int, j int, k int, sum int, nums []int) (int, int) { + if sum <= 0 { + j++ + for j < len(nums) && j >= 1 && nums[j] == nums[j-1] { + j++ + } + } else { + k-- + for k >= 0 && k+1 < len(nums) && nums[k] == nums[k+1] { + k-- + } + } + + return j, k +} + +func threeSum(nums []int) [][]int { + var triplets [][]int + + sort.Ints(nums) + for i := 0; i < len(nums); i++ { + j, k := i+1, len(nums)-1 + + if i != 0 && nums[i-1] == nums[i] { + continue + } + + for j < k { + sum := nums[i] + nums[j] + nums[k] + if sum == 0 { + triplets = append(triplets, []int{nums[i], nums[j], nums[k]}) + } + j, k = update(i, j, k, sum, nums) + } + } + return triplets +} + +``` \ No newline at end of file diff --git a/encode-and-decode-strings/invidam.go.md b/encode-and-decode-strings/invidam.go.md new file mode 100644 index 000000000..7553c46e4 --- /dev/null +++ b/encode-and-decode-strings/invidam.go.md @@ -0,0 +1,70 @@ +# Intuition +구분자를 넣어 오류를 방지한다. +# Approach + +1. UTF-8에 벗어나는 구분자 `ㄱ`을 넣어 구분했다. +# Complexity +- Time complexity: $$O(n)$$ + - 문자열의 길이 n에 대하여, 이를 순회하는 비용이 발생한다. + +- Space complexity: $$O(n)$$ + - 문자열의 길이 n에 대하여, `encoded`를 만드는 공간이 발생한다. + +# Code +```go +const DIVIDE_CHAR_V1 = 'ㄱ' +func encodeV1(strs []string) string { + ret := strings.Join(strs, string(DIVIDE_CHAR_V1)) + return ret +} + +func decodeV1(encoded string) []string { + sep := string(DIVIDE_CHAR_V1) + return strings.Split(encoded, sep) +} +``` +- - - +# Intuition +솔루션에서 네트워크 통신을 위한다는 목적을 듣고 수정해보았다. (유니코드를 넣는 게 의도는 아닌 듯했다.) +# Approach +1. 구분자 (`-`)와 이전 문자의 길이를 함께 저장한다. +2. 구분자가 나온다면, 문자열의 길이를 추출하여 문자열을 디코딩한다. +3. 위 과정을 배열을 순회하며 반복한다. +# Complexity +- Time complexity: $$O(n)$$ + - 문자열의 길이 n에 대하여, 이를 순회하는 비용이 발생한다. + +- Space complexity: $$O(n)$$ + - 문자열의 길이 n에 대하여, `encoded`를 만드는 공간이 발생한다. + +# Code +```go +const DIVIDE_CHAR = '-' +func encode(strs []string) string { + ret := "" + for _, str := range strs { + ret += str + ret += fmt.Sprintf("%c%04d", DIVIDE_CHAR, len(str)) + // a-1bcd-3 + } + return ret +} + +func decode(encoded string) []string { + ret := make([]string, 0) + for i := 0; i < len(encoded); { + if encoded[i] == DIVIDE_CHAR { + lenStr := encoded[i+1 : i+5] + len, _ := strconv.Atoi(lenStr) + + decodeStr := encoded[i-len : i] + ret = append(ret, decodeStr) + i += 5 + } else { + i += 1 + } + } + + return ret +} +``` \ No newline at end of file diff --git a/longest-consecutive-sequence/invidam.go.md b/longest-consecutive-sequence/invidam.go.md new file mode 100644 index 000000000..653d7fed6 --- /dev/null +++ b/longest-consecutive-sequence/invidam.go.md @@ -0,0 +1,75 @@ +# Intuition +자기 자신 이후에 몇 개가 연속되었는지를 `dp`로 저장한다. +# Approach +1. 등장 여부를 `appear`에 기록한다. +2. 자기 자신 이후 연속된 원소의 개수를 반환하는 함수를 만들고 매 배열마다 이를 호출한다. + - `dp`로써, 자기 자신(`from`)에 따른 결과들을 유지한다. + - 자기 자신이 등장했는지 여부에 따라 `0` 혹은 `다음 연속 개수 + 1`을 반환한다. +3. 호출 결과 중 최대를 찾아 반환한다. +# Complexity +- Time complexity: $$O(nlog(n))$$ + +- Space complexity: $$O(n+m)$$ +# Code +## Original +```go +func longestConsecutiveFrom(num int, appear map[int]bool, cache map[int]int) (ret int) { + if val, ok := cache[num]; ok { + return val + } + + if _, ok := appear[num]; ok { + ret = longestConsecutiveFrom(num+1, appear, cache) + 1 + } else { + ret = 0 + } + cache[num] = ret + return ret +} + +func longestConsecutive(nums []int) int { + appear := make(map[int]bool, len(nums)/2) + cache := make(map[int]int, len(nums)/2) + for _, num := range nums { + appear[num] = true + } + + var max int + + for _, num := range nums { + ret := longestConsecutiveFrom(num, appear, cache) + if ret > max { + max = ret + } + } + return max +} +``` + +## Bottom-up +```go +func longestConsecutive(nums []int) int { + appear := make(map[int]bool, len(nums)/2) + for _, num := range nums { + appear[num] = true + } + + var max int + + for _, num := range nums { + if appear[num-1] { + continue + } + len := 1 + for appear[num+len] { + len++ + } + + if len > max { + max = len + } + } + return max +} + +``` \ No newline at end of file diff --git a/product-of-array-except-self/invidam.go.md b/product-of-array-except-self/invidam.go.md new file mode 100644 index 000000000..7ecec31f0 --- /dev/null +++ b/product-of-array-except-self/invidam.go.md @@ -0,0 +1,46 @@ +# Intuition +0의 등장 횟수에 따라 경우의 수가 달라진다. 이를 이용한다. +# Approach +(사이트에서 Wrong Case를 제공하였기에 다양한 경우의 수를 대응할 수 있었다, 좋은 풀이는 아니라고 생각한다.) +- 모든 수의 곱을 계산한 후, 배열을 순회하며 자기 자신을 나눈 값을 저장한다. +- 0이 등장한 경우, 0이 아닌 배열의 원소들은 무조건 0을 저장해야 한다. + - `golang`의 경우 `int`의 기본값이 0이라 아무것도 안해주면 된다. +- 0이 2회 이상 등장한 경우, 모든 원소는 0이 된다. + - `golang`의 경우 길이만 선언된 `int` 배열을 반환하면 된다. +# Complexity +- Time complexity: $$O(n)$$ + - 배열의 길이 n에 대하여, 순회하는 비용 n이 발생한다. + +- Space complexity: $$O(n)$$ + - 배열의 길이 n에 대하여, 결과를 저장할 배열의 크기 n이 소모된다. +# Code +```go +func productExceptSelf(nums []int) []int { + answer := make([]int, len(nums)) + product := 1 + zeroCnt := 0 + for _, num := range nums { + if num == 0 { + zeroCnt++ + continue + } + product *= num + } + + if zeroCnt >= 2 { + product = 0 + } + + for i, num := range nums { + if num == 0 { + answer[i] = product + } else if zeroCnt == 0 { + answer[i] = product / num + } + } + return answer +} +``` +# 여담 +부분곱문제로 해결하기 적합하다는 생각이 들었으나, `0`처리 때문에 시도해보진 않았다. +솔루션들을 보며 좌, 우에 대해 2번을 사용하는 방법이 있다는 걸 알게되었다. \ No newline at end of file diff --git a/top-k-frequent-elements/invidam.go.md b/top-k-frequent-elements/invidam.go.md new file mode 100644 index 000000000..64ee7423c --- /dev/null +++ b/top-k-frequent-elements/invidam.go.md @@ -0,0 +1,49 @@ +# Intuition +빈도수 계산, 정렬을 활용하면 가능하다고 생각했다. (깔끔하진 않아 해결될지는 의문이었다.) +# Approach +1. 빈도 수를 저장한다. +2. 원소들을 빈도수(내림차순)대로 정렬한다. +3. 정렬된 원소 중 앞에서부터 k개를 반환한다. +# Complexity +- Time complexity: $$O(nlog(n))$$ + - 배열의 길이 n에 대하여, 정렬비용으로 소모된다. + +- Space complexity: $$O(n+k)$$ + - 배열의 길이 n과 원소의 범위 k에 대하여, 빈도수 저장을 위해 선언하는 비용 `k`와 정렬된 원소를 저장하는 비용 `n`이 소모된다. + +# Code +```go +type NumAndFreq struct { + Num int + Freq int +} + +func topKFrequent(nums []int, k int) []int { + freq := make(map[int]int, 20000) + + for _, num := range nums { + freq[num]++ + } + + numAndFreqs := make([]NumAndFreq, 0, len(freq)) + for n, f := range freq { + numAndFreqs = append(numAndFreqs, NumAndFreq{Num: n, Freq: f}) + } + + sort.Slice(numAndFreqs, func(i, j int) bool { + return numAndFreqs[i].Freq > numAndFreqs[j].Freq + }) + + numsSortedByFreqDesc := make([]int, 0, len(nums)) + for _, e := range numAndFreqs { + numsSortedByFreqDesc = append(numsSortedByFreqDesc, e.Num) + } + + return numsSortedByFreqDesc[0:k] +} + +``` + +# 여담 +- 타 솔루션들을 보며 정렬을 하지 않고 빈도수별로 원소들을 모아놨다가 배열을 만드는 방법도 확인했다. 직관적이진 않다고 느꼈다. +- 문제 해결 도중 `map`, `filter`와 같은 함수형 프로그래밍을 활용하면 좋겠다는 생각이 들어 golang에서도 이를 제공하나 찾아봤다. [링크1](https://stackoverflow.com/questions/71624828/is-there-a-way-to-map-an-array-of-objects-in-go)과 [링크2](https://github.com/golang/go/issues/45955)를 통해 다양한 논의가 있었으나, 여러 이유로 도입하지 않음을 알게되었다. \ No newline at end of file