Skip to content
Merged
Show file tree
Hide file tree
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
24 changes: 24 additions & 0 deletions best-time-to-buy-and-sell-stock/radiantchoi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# profit: 최대 이익
profit = 0

# current_minimum: 현재까지의 최소 가격
current_minimum = prices[0]

# current_profit: 현재까지의 최대 이익
current_profit = 0

# 전체 배열에서 최솟값과, 최솟값 인덱스 이후 최댓값을 이익으로 계산하는 반복문
for i in range(1, len(prices)):
# 이익이 발생할 경우 현재 이익 갱신
# 현재 이익을 갱신할 때마다 지금까지의 최대 이익도 갱신
if prices[i] > current_minimum:
current_profit = max(current_profit, prices[i] - current_minimum)
profit = max(profit, current_profit)
# 이익이 발생하지 않을 경우 최솟값 갱신
else:
current_minimum = prices[i]

# 최대 이익만 저장되어 반환
return profit
47 changes: 47 additions & 0 deletions group-anagrams/radiantchoi.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
class Solution {
func groupAnagrams(_ strs: [String]) -> [[String]] {
// 애너그램을 저장할 딕셔너리
var groups = [String: [String]]()

for str in strs {
// 각각의 문자열별로 등장하는 글자의 갯수를 저장할 딕셔너리
var occurences = [Character: Int]()

// 등장하는 글자의 갯수를 계산
// Swift에서는 글자를 다루기 위해 배열로 변환하는 것이 좋음
for letter in Array(str) {
if let val = occurences[letter] {
occurences[letter] = val + 1
} else {
occurences[letter] = 1
}
}

let anagramKey = makeAnagramKey(for: occurences)

// 딕셔너리에 이미 같은 애너그램 키가 존재할 경우, 해당 키에 대한 값에 현재 문자열 추가
if let group = groups[anagramKey] {
groups[anagramKey]!.append(str)
} else {
// 딕셔너리에 키가 존재하지 않을 경우, 키를 추가하고 현재 문자열을 값으로 추가
groups[anagramKey] = [str]
}
}

// 딕셔너리의 값들을 배열로 반환 - 반환 순서는 상관없다고 문제에 명시
// 반환 순서가 필요할 경우 애너그램 키의 등장 순서를 저장하는 딕셔너리를 새로 만들 수 있음
return Array(groups.values)
}

// 등장하는 글자의 갯수에 따라 딕셔너리의 키를 만드는 함수
func makeAnagramKey(for occurences: [Character: Int]) -> String {
// 같은 애너그램을 가질 때 같은 키를 반환하게 하기 위해, 정렬 적용
return occurences.keys.sorted().reduce("") { (previous, key) in
var previous = previous
previous += String(key)
previous += String(occurences[key]!)

return previous
}
}
}
79 changes: 79 additions & 0 deletions implement-trie-prefix-tree/radiantchoi.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
class Trie {
private let root: TrieNode

init() {
self.root = TrieNode()
}

func insert(_ word: String) {
var node = root // 루트 노드의 포인터를 가져오고

// 단어의 각 글자를 순회하며, 이미 있다면 해당 노드로 이동하고,
// 그렇지 않다면 새로운 노드를 생성하여 연결
for letter in word {
if let childNode = node.children[letter] {
node = childNode
} else {
let newNode = TrieNode()
node.children[letter] = newNode
node = newNode
}
}

// 단어의 마지막 글자 노드에 isTerminating을 true로 설정
node.isTerminating = true
}

func search(_ word: String) -> Bool {
var node = root

// 노드 포인터만 제공하여 순회 로직을 수행하고,
// 마지막 노드가 isTerminating이 true인지 확인
guard traverse(startsWith: &node, quote: word) else { return false }
guard node.isTerminating else { return false }

return true
}

func startsWith(_ prefix: String) -> Bool {
var node = root
// 노드 포인터만 제공하여 순회 로직을 수행하고 그대로 반환
return traverse(startsWith: &node, quote: prefix)
}

// 트리 탐색 로직
// 노드 포인터를 받아서, 순회 로직을 수행하고 포인터를 교체
private func traverse(startsWith node: inout TrieNode, quote: String) -> Bool {
for letter in quote {
guard let childNode = node.children[letter] else {
return false
}

node = childNode
}

return true
}
}

// 트라이의 각각의 노드 구현
// 자식 노드 딕셔너리와, 이 글자로 끝나는 단어가 있는지 여부를 저장
// 글자 자체는 부모 노드 딕셔너리의 키로 저장되므로 따로 저장하지 않음
// 루트에는 아무 글자도 들어가지 않는다는 Trie의 조건도 자동으로 만족
class TrieNode {
var children: [Character: TrieNode]
var isTerminating: Bool

init(children: [Character: TrieNode] = [:], isTerminating: Bool = false) {
self.children = children
self.isTerminating = isTerminating
}
}

/**
* Your Trie object will be instantiated and called as such:
* let obj = Trie()
* obj.insert(word)
* let ret_2: Bool = obj.search(word)
* let ret_3: Bool = obj.startsWith(prefix)
*/
32 changes: 32 additions & 0 deletions word-break/radiantchoi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Solution:
def traverse(self, s: str, wordDict: List[str], checked: int, memo: Dict[int,bool]) -> bool:
# checked의 의미: 현재까지 체크한 문자열의 길이 - 아직 여기서부터 시작해서 끝까지 갈 수 있다고 장담할 수 없음
# 시작 인덱스가 문자열의 길이와 같은 경우, 이 문자열에 대해 검사를 마쳤다는 뜻 (남은 길이가 0)
if checked == len(s):
return True

# 지금 다루려는 인덱스를 이미 체크했다면, 후속 계산을 하지 않고 바로 반환
if checked in memo:
return memo[checked]

for word in wordDict:
# 슬라이싱 대신 startswith를 쓰는 것이 성능상 이점이 있다
# 지금 보려는 단어가 문자열의 prefix인 경우, 즉 다음 탐색을 수행해 볼 여건이 되는 경우
# 여기서 시작해서 끝까지 갔을 때 문장이 완성이 되니? 라는 것

# 만약 가능할 경우, 거쳐온 모든 path에 대해 memo 딕셔너리에는 True가 저장되게 된다.
# 또한 early return을 통해 후속 케이스를 계산하지 않는다.
if s.startswith(word, checked):
if self.traverse(s, wordDict, checked + len(word), memo):
memo[checked] = True
return True

# 위의 for문에서 만족하는 게 없다면, 여기서 시작해가지고서는 끝까지 갈 수 없다는 것
memo[checked] = False
return False


def wordBreak(self, s: str, wordDict: List[str]) -> bool:
memo = {}

return self.traverse(s, wordDict, 0, memo)