diff --git a/swejp75/01 - 242. Valid Anagram.md b/swejp75/01 - 242. Valid Anagram.md new file mode 100644 index 0000000..d8391b1 --- /dev/null +++ b/swejp75/01 - 242. Valid Anagram.md @@ -0,0 +1,7 @@ +### Solution 1 + +```python +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + return Counter(s) == Counter(t) +``` diff --git a/swejp75/02 - 300. Longest Increasing Subsequence.md b/swejp75/02 - 300. Longest Increasing Subsequence.md new file mode 100644 index 0000000..1d72298 --- /dev/null +++ b/swejp75/02 - 300. Longest Increasing Subsequence.md @@ -0,0 +1,14 @@ +### Solution 1 + +```python +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: + min_tails_for_length = [] + for num in nums: + idx = bisect_left(min_tails_for_length, num) + if idx == len(min_tails_for_length): + min_tails_for_length.append(num) + else: + min_tails_for_length[idx] = num + return len(min_tails_for_length) +``` diff --git a/swejp75/03 - 11. Container with Most Water.md b/swejp75/03 - 11. Container with Most Water.md new file mode 100644 index 0000000..e3387cf --- /dev/null +++ b/swejp75/03 - 11. Container with Most Water.md @@ -0,0 +1,53 @@ +### Solution 1 + +```python +class Solution: + def maxArea(self, height: List[int]) -> int: + max_area = 0 + left = 0 + right = len(height) - 1 + while left < right: + area = (right - left) * min(height[left], height[right]) + max_area = max(max_area, area) + if height[left] < height[right]: + left += 1 + else: + right -= 1 + return max_area +``` + +#### 解法についての補足メモ(Geminiとの会話) + +Q. その瞬間よさそうだからって短いほうを内側に進めて全体最適のものを見逃しちゃう可能性はないのかにょ? + +A. + +1. 「見逃す」可能性があるケースを考えてみるにょ + +今、左端の壁 i が右端の壁 j より短い(h[i] List[int]: + result = [1] * len(nums) + cumprod_left = 1 + for i in range(len(nums)): + result[i] *= cumprod_left + cumprod_left *= nums[i] + cumprod_right = 1 + for i in reversed(range(len(nums))): + result[i] *= cumprod_right + cumprod_right *= nums[i] + return result +``` diff --git a/swejp75/05 - 152. Maximum Product Subarray.md b/swejp75/05 - 152. Maximum Product Subarray.md new file mode 100644 index 0000000..2d36e99 --- /dev/null +++ b/swejp75/05 - 152. Maximum Product Subarray.md @@ -0,0 +1,16 @@ +### Solution 1 + +最初正の値と負の値を分けて保存していたのだが、場合分けが結構面倒になってしまったので単純に最小値と最大値として保存する方式にしてみた。 + +```python +class Solution: + def maxProduct(self, nums: List[int]) -> int: + current_max = current_min = result = nums[0] + for i in range(1, len(nums)): + num = nums[i] + values = num, current_max * num, current_min * num + current_max = max(values) + current_min = min(values) + result = max(result, current_max) + return result +``` diff --git a/swejp75/06 - 443. String Compression.md b/swejp75/06 - 443. String Compression.md new file mode 100644 index 0000000..424279d --- /dev/null +++ b/swejp75/06 - 443. String Compression.md @@ -0,0 +1,21 @@ +### Solution 1 + +```python +class Solution: + def compress(self, chars: List[str]) -> int: + writer = 0 + reader = 0 + while reader < len(chars): + char = chars[reader] + count = 0 + while reader < len(chars) and chars[reader] == char: + count += 1 + reader += 1 + chars[writer] = char + writer += 1 + if count >= 2: + for digit in str(count): + chars[writer] = digit + writer += 1 + return writer +``` diff --git a/swejp75/07 - 49. Group Anagrams.md b/swejp75/07 - 49. Group Anagrams.md new file mode 100644 index 0000000..be56eec --- /dev/null +++ b/swejp75/07 - 49. Group Anagrams.md @@ -0,0 +1,11 @@ +### Solution 1 + +```python +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + group_to_strings = defaultdict(list) + for s in strs: + group = "".join(sorted(s)) + group_to_strings[group].append(s) + return list(group_to_strings.values()) +``` diff --git a/swejp75/08 - 215. Kth Largest Element in an Array.md b/swejp75/08 - 215. Kth Largest Element in an Array.md new file mode 100644 index 0000000..c9958e5 --- /dev/null +++ b/swejp75/08 - 215. Kth Largest Element in an Array.md @@ -0,0 +1,54 @@ +### Solution 1 + +min heapを使った解法。Arai60の問題に引っ張られて最初にこの解法で書いたがarrayでこれをやるのは結構不自然かも。 +こんなことをやるんだったら配列をソートしてk-1番目を出力した方がいい気がする。 + +```python +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + min_heap = [] + for num in nums: + if len(min_heap) < k: + heapq.heappush(min_heap, num) + else: + if num < min_heap[0]: + continue + heapq.heappushpop(min_heap, num) + return min_heap[0] +``` + +### Solution 2 + +3-partition quick select +普通のquick selectは以下と未満の判別をしないので都合が悪い(全部同じ要素、みたいな配列が入ってくるとkをスキップしてしまう) + +```python +class Solution: + def findKthLargest(self, nums: List[int], k: int) -> int: + if k > len(nums): + raise ValueError("invalid k for the list") + nums = nums[:] + left = 0 + right = len(nums) - 1 + while True: + pivot = nums[random.randint(left, right)] + l = left + r = right + idx = left + while idx <= r: + if nums[idx] > pivot: + nums[l], nums[idx] = nums[idx], nums[l] + l += 1 + idx += 1 + elif nums[idx] == pivot: + idx += 1 + else: + nums[idx], nums[r] = nums[r], nums[idx] + r -= 1 + if l <= k - 1 <= r: + return pivot + elif k - 1 < l: + right = l - 1 + elif r < k - 1: + left = r + 1 +``` diff --git a/swejp75/09 - 560. Subarray Sum Equals K.md b/swejp75/09 - 560. Subarray Sum Equals K.md new file mode 100644 index 0000000..573e068 --- /dev/null +++ b/swejp75/09 - 560. Subarray Sum Equals K.md @@ -0,0 +1,15 @@ +### Solution 1 + +```python +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + result = 0 + cumsum_to_count = defaultdict(int) + cumsum_to_count[0] += 1 + cumsum = 0 + for num in nums: + cumsum += num + result += cumsum_to_count[cumsum - k] + cumsum_to_count[cumsum] += 1 + return result +``` diff --git a/swejp75/10 - 125. Valid Palindrome.md b/swejp75/10 - 125. Valid Palindrome.md new file mode 100644 index 0000000..26faf5a --- /dev/null +++ b/swejp75/10 - 125. Valid Palindrome.md @@ -0,0 +1,39 @@ +### Solution 1 + +```python +class Solution: + def isPalindrome(self, s: str) -> bool: + s = "".join([char for char in s if char.isalnum()]) + s = s.lower() + left = 0 + right = len(s) - 1 + while left < right: + if s[left] != s[right]: + return False + left += 1 + right -= 1 + return True +``` + +### Solution 2 + +追加メモリを使わないバージョン。 + +```python +class Solution: + def isPalindrome(self, s: str) -> bool: + left = 0 + right = len(s) - 1 + while left < right: + if not s[left].isalnum(): + left += 1 + continue + if not s[right].isalnum(): + right -= 1 + continue + if s[left].lower() != s[right].lower(): + return False + left += 1 + right -= 1 + return True +``` diff --git a/swejp75/11 - 283. Move Zeroes.md b/swejp75/11 - 283. Move Zeroes.md new file mode 100644 index 0000000..fc6f732 --- /dev/null +++ b/swejp75/11 - 283. Move Zeroes.md @@ -0,0 +1,15 @@ +### Solution 1 + +```python +class Solution: + def moveZeroes(self, nums: List[int]) -> None: + """ + Do not return anything, modify nums in-place instead. + """ + non_zero = 0 + for i in range(len(nums)): + if nums[i] != 0: + if non_zero != i: + nums[non_zero], nums[i] = nums[i], nums[non_zero] + non_zero += 1 +``` diff --git a/swejp75/12 - 15. 3Sum.md b/swejp75/12 - 15. 3Sum.md new file mode 100644 index 0000000..3b34e96 --- /dev/null +++ b/swejp75/12 - 15. 3Sum.md @@ -0,0 +1,86 @@ +### Solution 1 + +最初にやろうとした方針のコード。 +(ちなみにこれはLeetcode上ではTLEになる。概念上の計算量自体はO(N^2)だと思うのだが、おそらくオーバーヘッドが結構ある) + +```python +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + three_sum_groups = set() + two_sum_to_pairs = defaultdict(set) + for i in range(len(nums)): + for pair in two_sum_to_pairs[-nums[i]]: + group = tuple(sorted((*pair, nums[i]))) + three_sum_groups.add(group) + del two_sum_to_pairs[-nums[i]] + for j in range(i): + pair = tuple(sorted((nums[i], nums[j]))) + two_sum_to_pairs[nums[i] + nums[j]].add(pair) + return [list(group) for group in three_sum_groups] +``` + +### Solution 2 + +配列をソートした後、一つのiを固定し、合計が目標値になるような組み合わせを2ポインタのone-passで見つける。 +すでに見た一つ目の数字や二つ目の数字はスキップする。 + +```python +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + nums = sorted(nums) + result = [] + i = 0 + while i < len(nums): + j = i + 1 + k = len(nums) - 1 + while j < k: + if nums[j] + nums[k] < -nums[i]: + j += 1 + continue + if nums[j] + nums[k] > -nums[i]: + k -= 1 + continue + result.append([nums[i], nums[j], nums[k]]) + current_j_num = nums[j] + while j < k and nums[j] == temp: + j += 1 + current_i_num = nums[i] + while i < len(nums) and nums[i] == temp: + i += 1 + return result +``` + +### Solution 3 + +こんなやり方もあるみたい(gemini出力コード) + +```python +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + res = [] + # 1. 各数字の出現回数をカウント + count = Counter(nums) + # 2. ユニークな数字だけをソートして取り出す + unique_nums = sorted(count.keys()) + + for i, num1 in enumerate(unique_nums): + # ケース1: 同じ数字を3つ使う (0, 0, 0) + if num1 == 0: + if count[num1] >= 3: + res.append([0, 0, 0]) + # ケース2: 同じ数字を2つ使う (x, x, y) + elif count[num1] >= 2: + target = -2 * num1 + if target in count and target != num1: + res.append([num1, num1, target]) + + # ケース3: 3つとも異なる数字を使う (num1 < num2 < num3) + # 重複を避けるため、num2 は num1 より大きい範囲から選ぶ + for num2 in unique_nums[i+1:]: + num3 = -(num1 + num2) + # num3 が num2 より大きく、かつ存在すればOK + if num3 > num2 and num3 in count: + res.append([num1, num2, num3]) + + return res +``` diff --git a/swejp75/13 - 88. Merge Sorted Array.md b/swejp75/13 - 88. Merge Sorted Array.md new file mode 100644 index 0000000..af59f94 --- /dev/null +++ b/swejp75/13 - 88. Merge Sorted Array.md @@ -0,0 +1,23 @@ +### Solution 1 + +頭から読んでnums1に作っていくとnums1をどこかに保存しておく必要がある。 +後ろから読むと保存しなくてよさそう。 + +```python +class Solution: + def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: + """ + Do not return anything, modify nums1 in-place instead. + """ + w = len(nums1) - 1 + r1 = len(nums1) - len(nums2) - 1 + r2 = len(nums2) - 1 + while w >= 0: + if r2 < 0 or r1 >= 0 and nums1[r1] > nums2[r2]: + nums1[w] = nums1[r1] + r1 -= 1 + else: + nums1[w] = nums2[r2] + r2 -= 1 + w -= 1 +``` diff --git a/swejp75/14 - 42. Trapping Rain Water.md b/swejp75/14 - 42. Trapping Rain Water.md new file mode 100644 index 0000000..bf30d07 --- /dev/null +++ b/swejp75/14 - 42. Trapping Rain Water.md @@ -0,0 +1,51 @@ +### Solution 1 + +左の壁と右の壁の高さがわかればできそうだと思って最初に書いたコード。 + +```python +class Solution: + def trap(self, height: List[int]) -> int: + lefts = [0] * len(height) + left = 0 + for i in range(len(height)): + lefts[i] = left + left = max(left, height[i]) + + rights = [0] * len(height) + right = 0 + for i in reversed(range(len(height))): + rights[i] = right + right = max(right, height[i]) + + result = 0 + for i in range(len(height)): + surface = min(lefts[i], rights[i]) + water_depth = max(0, surface - height[i]) + result += water_depth + return result +``` + +### Solution 2 + +左と右の両方の高さがわかなくても、どちらか低い方の高ささえわかっていれば水がこぼれるかどうかはわかる、という発想の追加メモリを使わないコード。 +左の要素と右の要素を考えなきゃいけないようなものは両側から見ていくことによって2 pointer 1 pass でできないか考えてみるべきかも。 + +```python +class Solution: + def trap(self, height: List[int]) -> int: + result = 0 + left = 0 + right = len(height) - 1 + max_left = 0 + max_right = 0 + while left <= right: + if max_left <= max_right: + result += max(0, max_left - height[left]) + max_left = max(max_left, height[left]) + left += 1 + else: + result += max(0, max_right - height[right]) + max_right = max(max_right, height[right]) + right -= 1 + return result +``` diff --git a/swejp75/15 - 20. Valid Parentheses.md b/swejp75/15 - 20. Valid Parentheses.md new file mode 100644 index 0000000..d90effe --- /dev/null +++ b/swejp75/15 - 20. Valid Parentheses.md @@ -0,0 +1,21 @@ +### Solution 1 + +```python +class Solution: + def isValid(self, s: str) -> bool: + opener_to_closer = { + '(': ')', + '{': '}', + '[': ']', + } + stack = [] + for char in s: + if char in opener_to_closer: + stack.append(char) + else: + if not stack or char != opener_to_closer[stack.pop()]: + return False + if stack: + return False + return True +``` diff --git a/swejp75/16 - 735. Asteroid Collision.md b/swejp75/16 - 735. Asteroid Collision.md new file mode 100644 index 0000000..603e850 --- /dev/null +++ b/swejp75/16 - 735. Asteroid Collision.md @@ -0,0 +1,22 @@ +### Solution 1 + +```python +class Solution: + def asteroidCollision(self, asteroids: List[int]) -> List[int]: + stack = [] + for a in asteroids: + if a > 0: + stack.append(a) + continue + while stack and stack[-1] > 0: + if stack[-1] < abs(a): + stack.pop() + elif stack[-1] == abs(a): + stack.pop() + break + else: + break + else: + stack.append(a) + return stack +``` diff --git a/swejp75/17 - 22. Generate Parentheses.md b/swejp75/17 - 22. Generate Parentheses.md new file mode 100644 index 0000000..f598218 --- /dev/null +++ b/swejp75/17 - 22. Generate Parentheses.md @@ -0,0 +1,31 @@ +### Solution 1 + +backtrack + +```python +class Solution: + def generateParenthesis(self, n: int) -> List[str]: + def helper(p: str, n_open: int, n_close: int): + if n_open == n_close == n: + result.append("".join(p)) + return + if n_open < n: + p.append("(") + helper(p, n_open + 1, n_close) + p.pop() + if n_close < n_open: + p.append(")") + helper(p, n_open, n_close + 1) + p.pop() + result = [] + helper([], 0, 0) + return result +``` + +### Solution 2 (概要だけ) + +再帰的に書く方法として、関数をfとすると、0からn-1までの数字iについて +( + f(i)中のそれぞれのカッコ列 + ) + f(n - 1 - i)中のそれぞれのカッコ列 +という組み合わせをまとめることによっても出力できる。(全てのカッコ列は最初のカッコに対応するカッコの位置でMECEにグループ分けできるため) + +カッコ列の個数とかだけ知りたい場合はこっちのが良さそう。 diff --git a/swejp75/18 - 155. Min Stack.md b/swejp75/18 - 155. Min Stack.md new file mode 100644 index 0000000..fe27dfc --- /dev/null +++ b/swejp75/18 - 155. Min Stack.md @@ -0,0 +1,33 @@ +### Solution 1 + +```python +class MinStack: + + def __init__(self): + self.minimums = [] + self.stack = [] + + def push(self, val: int) -> None: + self.stack.append(val) + if self.minimums: + new_minimum = min(self.minimums[-1], val) + self.minimums.append(new_minimum) + else: + self.minimums.append(val) + + def pop(self) -> None: + self.stack.pop() + self.minimums.pop() + + def top(self) -> int: + if self.stack: + return self.stack[-1] + else: + raise IndexError + + def getMin(self) -> int: + if self.minimums: + return self.minimums[-1] + else: + return None +``` diff --git a/swejp75/19 - 739. Daily Temperatures.md b/swejp75/19 - 739. Daily Temperatures.md new file mode 100644 index 0000000..645f2d5 --- /dev/null +++ b/swejp75/19 - 739. Daily Temperatures.md @@ -0,0 +1,61 @@ +### Solution 1 + +スタックにためているものは常に単調減少になっているはずなので、最後だけチェックすればよい。 +こういうスタックを単調スタック (Monotonic Stack) というらしい。 + +```python +class Solution: + def dailyTemperatures(self, temperatures: List[int]) -> List[int]: + stack = [] + result = [0] * len(temperatures) + for day, temp in enumerate(temperatures): + while stack and stack[-1][1] < temp: + stacked_day, stacked_temp = stack.pop() + result[stacked_day] = day - stacked_day + stack.append((day, temp)) + return result +``` + +### Solution 2 + +temperaturesを右から見て行ってanswerを構築する必要もあったらしい。これでも計算量O(n)みたい。 + +```python +class Solution: + def dailyTemperatures(self, temperatures: List[int]) -> List[int]: + result = [0] * len(temperatures) + for i in reversed(range(len(temperatures))): + days_after = 1 + while i + days_after < len(temperatures): + if temperatures[i + days_after] > temperatures[i]: + result[i] = days_after + break + elif result[i + days_after] > 0: + days_after += result[i + days_after] + else: # there is no warmer day after this + break + return result +``` + +#### 解法についての補足メモ(Geminiとの会話) + +Q. これほんとに計算量O(N)なの? + +A. + +「for の中に while があるから O(n²) に見えちゃう」というのは、アルゴリズムを学び始めた人が必ず通る非常に鋭い疑問です。 + +結論から言うと、このコードの時間計算量が O(n) になる理由は、**「各要素をスキャンする回数が、トータルで見ると定数回(平均して1〜2回程度)に抑えられているから」です。これを専門用語で「ならし計算量(Amortized Analysis)」**と呼びます。 + +**なぜ線形(O(n))と言い切れるのか:** + +1. 通常の O(n²) の 2 重ループは、外側の各 i に対して、内側の while が毎回「まだ見ていない要素すべて」を律儀にチェックします。 +2. しかし、このジャンプ解法では 「すでに分かっている答え(answer の値)」を再利用して、不要なチェックをゴッソリ飛ばしています。 + +**「各要素の視点」で考えてみる:** + +- ある日(nxt)が、左側の日(i)の「比較対象」になるとき、もし nxt の方が温度が低ければ、次は nxt + answer[nxt] までジャンプします。 +- このジャンプによって飛ばされた「nxt と『次の温かい日』の間にある日」は、二度と i の比較対象として選ばれることはありません。 +- つまり、各要素は「自分より温かい日を探している誰か」によってチェックされますが、一度「ジャンプの踏み台」にされたら、その探索ルートからは完全に除外されます。 + +結果として、プログラム全体で while ループの中身(比較や加算)が実行される回数の合計は、おおよそ 2n 回を超えないことが保証されます。 diff --git a/swejp75/20 - 227. Basic Calculator II.md b/swejp75/20 - 227. Basic Calculator II.md new file mode 100644 index 0000000..8ebdb4b --- /dev/null +++ b/swejp75/20 - 227. Basic Calculator II.md @@ -0,0 +1,76 @@ +### Solution 1 + +```python +class Solution: + def calculate(self, s: str) -> int: + def get_next_number(index: int) -> int: + num = 0 + for i in range(index, len(s)): + char = s[i] + if char.isdigit(): + num = num * 10 + int(char) + else: + break + return num, i + s = s.replace(" ", "") + stack = [] + i = 0 + sign = 1 + while i < len(s): + num, i = get_next_number(i) + while i < len(s) and (s[i] == '*' or s[i] == '/'): + if s[i] == '*': + next_num, i = get_next_number(i + 1) + num *= next_num + if s[i] == '/': + next_num, i = get_next_number(i + 1) + num = num // next_num + stack.append(num * sign) + if i < len(s) and s[i] == '+': + sign = 1 + if i < len(s) and s[i] == '-': + sign = -1 + i += 1 + return sum(stack) +``` + +ほかに優先度が高い演算子(**など)が出てきた場合はどうするべき? +**の直前のノードを単体として保存しておく必要がある。list[list]みたいな感じでstackを保存しておけば良いかな? +(外側のリストが足し算、内側のリストが掛け算を処理するイメージ) + +### Solution 2 + +少し違う書き方。こっちの方が簡潔かも + +```python +class Solution: + def calculate(self, s: str) -> int: + stack = [] + prev_operator = '+' + num = 0 + for i in range(len(s) + 1): + if i < len(s) and s[i] == " ": + continue + if i < len(s) and s[i].isdigit(): + num = num * 10 + int(s[i]) + continue + match prev_operator: + case '+': + stack.append(num) + case '-': + stack.append(-num) + case '*': + stack.append(stack.pop() * num) + case '/': + stack.append(int(stack.pop() / num)) + if i < len(s): + prev_operator = s[i] + num = 0 + return sum(stack) +``` + +### Solution 3 (概要だけ) + +最初に問題を見た時良い方法が思いつかず、数字と演算子からなる双方向linked listを作ろうとしてしまった。 +実装の手間がそんなわけなさそうすぎて結局解答を見たが、一応これでもできそう。 +(リストを構築するときに演算子の優先度からそれぞれの演算子を辿れるようにするイメージです) diff --git a/swejp75/21 - 875. Koko Eating Bananas.md b/swejp75/21 - 875. Koko Eating Bananas.md new file mode 100644 index 0000000..54aa6b2 --- /dev/null +++ b/swejp75/21 - 875. Koko Eating Bananas.md @@ -0,0 +1,20 @@ +### Solution 1 + +```python +class Solution: + def minEatingSpeed(self, piles: List[int], h: int) -> int: + low = 1 + high = max(piles) + while low < high: + speed = (low + high) // 2 + hours_required = 0 + for pile in piles: + hours_required += math.ceil(pile / speed) + if hours_required <= h: + high = speed + else: + low = speed + 1 + return low +``` + +math.ceilの代替として、(a + b - 1) // b という式があるみたい。これは覚えておいても良さそう。 diff --git a/swejp75/22 - 1011. Capacity To Ship Packages Within D Days.md b/swejp75/22 - 1011. Capacity To Ship Packages Within D Days.md new file mode 100644 index 0000000..d264eb0 --- /dev/null +++ b/swejp75/22 - 1011. Capacity To Ship Packages Within D Days.md @@ -0,0 +1,24 @@ +### Solution 1 + +```python +class Solution: + def shipWithinDays(self, weights: List[int], days: int) -> int: + lowest = max(weights) + highest = sum(weights) + while lowest < highest: + capacity = (lowest + highest) // 2 + required_days = 1 + loaded = 0 + for weight in weights: + if loaded + weight > capacity: + required_days += 1 + if required_days > days: + break + loaded = 0 + loaded += weight + if required_days > days: + lowest = capacity + 1 + else: + highest = capacity + return lowest +``` diff --git a/swejp75/23 - 153. Find Minimum in Rotated Sorted Array.md b/swejp75/23 - 153. Find Minimum in Rotated Sorted Array.md new file mode 100644 index 0000000..f33cf7b --- /dev/null +++ b/swejp75/23 - 153. Find Minimum in Rotated Sorted Array.md @@ -0,0 +1,15 @@ +### Solution 1 + +```python +class Solution: + def findMin(self, nums: List[int]) -> int: + left = 0 + right = len(nums) - 1 + while left < right: + mid = (left + right) // 2 + if nums[mid] > nums[-1]: + left = mid + 1 + else: + right = mid + return nums[left] +``` diff --git a/swejp75/24 - 162. Find Peak Element.md b/swejp75/24 - 162. Find Peak Element.md new file mode 100644 index 0000000..fb4e321 --- /dev/null +++ b/swejp75/24 - 162. Find Peak Element.md @@ -0,0 +1,18 @@ +### Solution 1 + +```python +class Solution: + def findPeakElement(self, nums: List[int]) -> int: + left = 0 + right = len(nums) - 1 + while left <= right: + mid = (left + right) // 2 + greater_than_left = mid - 1 < 0 or nums[mid] > nums[mid - 1] + greater_than_right = mid + 1 >= len(nums) or nums[mid] > nums[mid + 1] + if greater_than_left and greater_than_right: + return mid + if not greater_than_left: + right = mid - 1 + else: + left = mid + 1 +``` diff --git a/swejp75/25 - 121. Best Time to Buy and Sell Stock.md b/swejp75/25 - 121. Best Time to Buy and Sell Stock.md new file mode 100644 index 0000000..38cf51a --- /dev/null +++ b/swejp75/25 - 121. Best Time to Buy and Sell Stock.md @@ -0,0 +1,12 @@ +### Solution 1 + +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + min_price = float("inf") + max_profit = 0 + for price in prices: + max_profit = max(max_profit, price - min_price) + min_price = min(min_price, price) + return max_profit +``` diff --git a/swejp75/26 - 3. Longest Substring Without Repeating Characters.md b/swejp75/26 - 3. Longest Substring Without Repeating Characters.md new file mode 100644 index 0000000..b501d4b --- /dev/null +++ b/swejp75/26 - 3. Longest Substring Without Repeating Characters.md @@ -0,0 +1,16 @@ +### Solution 1 + +```python +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + chars = set() + start = 0 + result = 0 + for c in s: + while c in chars: + chars.remove(s[start]) + start += 1 + chars.add(c) + result = max(result, len(chars)) + return result +``` diff --git a/swejp75/27 - 424. Longest Repeating Character Replacement.md b/swejp75/27 - 424. Longest Repeating Character Replacement.md new file mode 100644 index 0000000..b7be13b --- /dev/null +++ b/swejp75/27 - 424. Longest Repeating Character Replacement.md @@ -0,0 +1,94 @@ +### Solution 1 + +この解法は文字の種類が限られていないと計算量が増えてしまう。 + +```python +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + counter = [0] * 26 + start = 0 + end = 0 + result = 0 + while end < len(s): + char = ord(s[end]) - ord("A") + counter[char] += 1 + while (end - start + 1 - max(counter)) > k: + removing_char = ord(s[start]) - ord("A") + counter[removing_char] -= 1 + start += 1 + result = max(result, end - start + 1) + end += 1 + return result +``` + +### Solution 2 + +いちいちmaxを求めず、今までのall time最頻値だけを記憶しておけばよかったらしい。途中の最頻値は最長のものを知りたいだけだったら意味がない。 + +""" +窓が不適格なときは、ただスライドさせるだけ。max_freq が本当の意味で更新される(=記録が塗り替えられる)まで、窓を広げる準備をして待つ」という、非常にストイックなアルゴリズム。 +この考え方は、他のスライディングウィンドウの難問(例えば 76. Minimum Window Substring)でも応用できる、超重要なマインドセット! +""" +だそうです。(Gemini情報) + +```python +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + counter = [0] * 26 + max_freq = 0 + start = 0 + for end in range(len(s)): + char = ord(s[end]) - ord("A") + counter[char] += 1 + max_freq = max(max_freq, counter[char]) + if end - start + 1 > max_freq + k: + removing_char = ord(s[start]) - ord("A") + counter[removing_char] -= 1 + start += 1 + return end - start + 1 +``` + +### Solution 3 + +heap q でその時々の最頻値をlog(文字数の種類数)分の計算量でとれることを利用してどうにか各瞬間の最頻値を取得できないかということをGeminiに相談してできたコード。 + +Q. なんとかしてヒープの中に入れてしまった値を更新することはできないの? + +A. + +Python の標準ライブラリ heapq には「特定の値を O(logn) で更新する」というメソッドは存在しない。そのため、実務では **「Lazy Removal(遅延削除)」** というテクニックを使うのが一般的。 + +**ヒープでの「更新」は「追加」で代用する:** + +- 古い値を無視する: ヒープの中に古い (頻度, 文字) が残っていても気にせず、新しい (新しい頻度, 文字) を heappush する。 +- 取り出すときにチェックする: ヒープの先頭(最大値)を取り出すときに、「この頻度は、今の counter に記録されている最新の頻度と同じかな?」と確認する。 +- 不一致なら捨てる: もし古かったら、それは「窓が動いて賞味期限切れになったデータ」なので、heappop で捨てて次の要素を見に行く。 + +```python +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + heap = [] + counter = defaultdict(int) + def get_max_count(): + while -heap[0][0] != counter[heap[0][1]]: + heapq.heappop(heap) + return -heap[0][0] + + start = 0 + result = 0 + for end in range(len(s)): + c = s[end] + counter[c] += 1 + heapq.heappush(heap, (-counter[c], c)) + while end - start + 1 > get_max_count() + k: + removing_c = s[start] + counter[removing_c] -= 1 + heapq.heappush(heap, (-counter[removing_c], removing_c)) + start += 1 + result = max(result, end - start + 1) + return result +``` + +heapqの内部のグラフ構造をいじれる場合は、多分途中で入れてしまった要素もO(logN)でheapqの構造を保ちながら増やせると思う。 + +あとはセグメント木でも同じようなことができるのかも。 diff --git a/swejp75/28 - 5. Longest Palindromic Substring.md b/swejp75/28 - 5. Longest Palindromic Substring.md new file mode 100644 index 0000000..c455943 --- /dev/null +++ b/swejp75/28 - 5. Longest Palindromic Substring.md @@ -0,0 +1,46 @@ +### Solution 1 + +真ん中を決めればすでに回文が出来上がってることを不変の事項として両端を見るだけで広げていけそう。 + +```python +class Solution: + def longestPalindrome(self, s: str) -> str: + pair = (0, 0) + for starting_left in range(len(s)): + for starting_right in range(starting_left, starting_left + 2): + left = starting_left + right = starting_right + while left >= 0 and right < len(s) and s[left] == s[right]: + if right - left + 1 > pair[1] - pair[0] + 1: + pair = (left, right) + left -= 1 + right += 1 + return s[pair[0] : pair[1] + 1] +``` + +### Solution 2 + +O(n)でできるManacher's Algorithmなるものがあるらしい。むずすぎ + +```python +class Solution: + def longestPalindrome(self, s: str) -> str: + string = ' ' + ' '.join(s) + ' ' + center_to_radius = [0] * len(string) + frontier_center = frontier_right = 0 + for center in range(len(string)): + mirrored_center = 2 * frontier_center - center + radius = min(center_to_radius[mirrored_center], frontier_right - center) + while ( + center - radius - 1 >= 0 + and center + radius + 1 < len(string) + and string[center - radius - 1] == string[center + radius + 1] + ): + radius += 1 + center_to_radius[center] = radius + if center + radius > frontier_right: + frontier_center = center + frontier_right = center + radius + max_center, max_radius = max(enumerate(center_to_radius), key=lambda x: x[1]) + return s[(max_center - max_radius) // 2 : (max_center + max_radius) // 2] +``` diff --git a/swejp75/29 - 76. Minimum Window Substring.md b/swejp75/29 - 76. Minimum Window Substring.md new file mode 100644 index 0000000..4aa5e33 --- /dev/null +++ b/swejp75/29 - 76. Minimum Window Substring.md @@ -0,0 +1,34 @@ +### Solution 1 + +```python +class Solution: + def minWindow(self, s: str, t: str) -> str: + if not t: + return "" + target = Counter(t) + counter = defaultdict(int) + num_letters = len(target) + num_sufficient_letters = 0 + optimal_pair = None + left = right = 0 + while right < len(s): + while right < len(s) and num_sufficient_letters < num_letters: + c = s[right] + if c in target: + counter[c] += 1 + if counter[c] == target[c]: + num_sufficient_letters += 1 + right += 1 + while left < right and num_sufficient_letters >= num_letters: + if optimal_pair == None or right - left < optimal_pair[1] - optimal_pair[0]: + optimal_pair = (left, right) + c = s[left] + if c in target: + counter[c] -= 1 + if counter[c] < target[c]: + num_sufficient_letters -= 1 + left += 1 + if not optimal_pair: + return "" + return s[optimal_pair[0] : optimal_pair[1]] +``` diff --git a/swejp75/30 - 21. Merge Two Sorted Lists.md b/swejp75/30 - 21. Merge Two Sorted Lists.md new file mode 100644 index 0000000..0891369 --- /dev/null +++ b/swejp75/30 - 21. Merge Two Sorted Lists.md @@ -0,0 +1,37 @@ +### Solution 1 + +```python +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + node1 = list1 + node2 = list2 + sentinel = ListNode() + prev = sentinel + while node1 is not None and node2 is not None: + if node1.val <= node2.val: + prev.next = node1 + prev = prev.next + node1 = node1.next + else: + prev.next = node2 + prev = prev.next + node2 = node2.next + prev.next = node1 if node1 is not None else node2 + return sentinel.next +``` + +### Solution 2 + +再帰でもかけるみたい。 + +```python +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + if not list1 and not list2: + return None + if list1 and (not list2 or list1.val <= list2.val): + list1.next = self.mergeTwoLists(list1.next, list2) + return list1 + list2.next = self.mergeTwoLists(list1, list2.next) + return list2 +``` diff --git a/swejp75/31 - 141. Linked List Cycle.md b/swejp75/31 - 141. Linked List Cycle.md new file mode 100644 index 0000000..de1a37d --- /dev/null +++ b/swejp75/31 - 141. Linked List Cycle.md @@ -0,0 +1,13 @@ +### Solution 1 + +```python +class Solution: + def hasCycle(self, head: Optional[ListNode]) -> bool: + slow = fast = head + while fast is not None and fast.next is not None: + slow = slow.next + fast = fast.next.next + if slow is fast: + return True + return False +``` diff --git a/swejp75/32 - 206. Reverse Linked List.md b/swejp75/32 - 206. Reverse Linked List.md new file mode 100644 index 0000000..dd15739 --- /dev/null +++ b/swejp75/32 - 206. Reverse Linked List.md @@ -0,0 +1,14 @@ +### Solution 1 + +```python +class Solution: + def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + prev = None + node = head + while node is not None: + next_node = node.next + node.next = prev + prev = node + node = next_node + return prev +``` diff --git a/swejp75/33 - 143. Reorder List.md b/swejp75/33 - 143. Reorder List.md new file mode 100644 index 0000000..a0ad0ef --- /dev/null +++ b/swejp75/33 - 143. Reorder List.md @@ -0,0 +1,101 @@ +### Solution 1 + +追加メモリを使う方法 + +```python +class Solution: + def reorderList(self, head: Optional[ListNode]) -> None: + """ + Do not return anything, modify head in-place instead. + """ + nodes = [] + node = head + while node is not None: + nodes.append(node) + node = node.next + left = 0 + right = len(nodes) - 1 + while left < right: + nodes[left].next = nodes[right] + left += 1 + if left == right: + break + nodes[right].next = nodes[left] + right -= 1 + nodes[left].next = None + return head +``` + +同じような感じだけどこっちの方がわかりやすかったりするかな? + +```python +class Solution: + def reorderList(self, head: Optional[ListNode]) -> None: + """ + Do not return anything, modify head in-place instead. + """ + nodes = [] + node = head + while node is not None: + nodes.append(node) + node = node.next + i = 0 + step = len(nodes) - 1 + while step != 0: + nodes[i].next = nodes[i + step] + i = i + step + step *= -1 + if step > 0: + step -= 1 + else: + step += 1 + nodes[i].next = None + return head +``` + +### Solution 2 + +constant additional memory でやる方法 + +1. 半分に分ける: fast と slow ポインタを使って、リストの中間地点を見つける。 +2. 後半を逆転させる: 後半部分のリストを Reverse Linked List の要領でひっくり返す。 +3. 交互にマージする: 前半と(逆転させた)後半を 1 つずつ交互に繋ぎ合わせる。 + +方針だけgeminiに教えてもらったあと自分で書こうとしたら、ポインタのイメージに苦労してテストに通るコードを作るのに1時間ぐらいかかった。 +こういうのがスムーズに書けたら苦労しない気がするけどなかなかならない・・・ + +```python +class Solution: + def reorderList(self, head: Optional[ListNode]) -> None: + """ + Do not return anything, modify head in-place instead. + """ + slow = fast = head + while fast is not None and fast.next is not None: + slow = slow.next + fast = fast.next.next + + prev = None + node = slow + while node is not None: + successor = node.next + node.next = prev + prev = node + node = successor + + left = head + right = prev + while left is not right: + successor = left.next + left.next = right + left = successor + if left is right: + break + successor = right.next + right.next = left + right = successor + + return head +``` + +left is rightはNoneで同じになる時とあるnodeで同じになる時(入力のリストの長さが3以上の時)で実は違う二つのことを同時に考えてたりするから条件の付け方としては良くないのかも?と思いつつ、良いロバストな書き方が他に思いつかない。 diff --git a/swejp75/34 - 148. Sort List.md b/swejp75/34 - 148. Sort List.md new file mode 100644 index 0000000..21f00e4 --- /dev/null +++ b/swejp75/34 - 148. Sort List.md @@ -0,0 +1,167 @@ +### Solution 1 + +Follow up: Can you sort the linked list in O(n logn) time and O(1) memory (i.e. constant space)? を満たそうと作ったコード + +方針は10分ぐらいで立ったけど実装とデバッグ修正(結局10回ぐらいWAの実行を見た)合わせて結局2時間ぐらいかかってしまった。 +連結リストの想像めっちゃむずい・・・ + +```python +class Solution: + def count_size(self, head: Optional[ListNode]) -> int: + size = 0 + node = head + while node: + node = node.next + size += 1 + return size + + def cut(self, head: Optional[ListNode], size: int): + node = head + for _ in range(size - 1): + if not node: + break + node = node.next + new_head = node.next if node else None + if node: + node.next = None + return new_head + + def merge(self, head1: Optional[ListNode], head2: Optional[ListNode]) -> Optional[ListNode]: + sentinel = ListNode() + prev = sentinel + node1 = head1 + node2 = head2 + while node1 and node2: + if node1.val <= node2.val: + prev.next = node1 + prev = node1 + node1 = node1.next + else: + prev.next = node2 + prev = node2 + node2 = node2.next + prev.next = node1 if node1 else node2 + while prev.next: + prev = prev.next + return sentinel.next, prev + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + sentinel = ListNode() + sentinel.next = head + size = self.count_size(head) + sorted_size = 1 + while sorted_size < size: + prev_merged_tail = sentinel + next_node = sentinel.next + while next_node: + head1 = next_node + head2 = self.cut(head1, size=sorted_size) + next_node = self.cut(head2, size=sorted_size) + prev_merged_tail.next, prev_merged_tail = self.merge(head1, head2) + sorted_size *= 2 + return sentinel.next +``` + +個々の処理(これでいうとcut, mergeとか)のアルゴリズムも含めて最初から全部書き、あとから必要に応じて関数に切り出すより、ある程度処理が思い浮かんでいるなら関数の名前だけで先に全体を書き、そのほうがよさそうなら関数の処理を直接記述するようにしたほうが絶対良いと思った。途中まで全部直接記述しようとしてわけわからなくなった。 + +リストをカットせずにやろうとしたバージョン(やたら気を使わなきゃいけなくなるので普通に切ってしまったほうがよさそう) + +```python +class Solution: + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + def count_size(node): + size = 0 + while node: + node = node.next + size += 1 + return size + + def merge(node1, node2, size): + num_left1 = num_left2 = size + sentinel = ListNode() + prev = sentinel + while node1 and num_left1 > 0 and node2 and num_left2 > 0: + if node1.val <= node2.val: + prev.next = node1 + num_left1 -= 1 + node1 = node1.next + else: + prev.next = node2 + num_left2 -= 1 + node2 = node2.next + prev = prev.next + prev.next = node1 if node1 and num_left1 > 0 else node2 + if not node2: + num_left2 = 0 + for _ in range(num_left1 + num_left2): + if not prev.next: + break + prev = prev.next + return sentinel.next, prev + + sentinel = ListNode() + sentinel.next = head + size = count_size(head) + sorted_size = 1 + while sorted_size < size: + last_merged_tail = sentinel + next_node = sentinel.next + while next_node: + n = next_node + node1 = n + for _ in range(sorted_size): + if not n: + break + n = n.next + node2 = n + for _ in range(sorted_size): + if not n: + break + n = n.next + next_node = n + last_merged_tail.next, last_merged_tail = merge(node1, node2, sorted_size) + last_merged_tail.next = None + sorted_size *= 2 + return sentinel.next +``` + +### Solution 2 + +普通にこうするのがいい + +```python +class Solution: + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + nodes = [] + node = head + while node: + nodes.append(node) + node = node.next + nodes.sort(key=lambda n: n.val) + for i in range(len(nodes)): + if i < len(nodes) - 1: + nodes[i].next = nodes[i + 1] + else: + nodes[i].next = None + return nodes[0] if nodes else None +``` + +こっちのがいいかな? + +```python +class Solution: + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: + nodes = [] + node = head + while node: + nodes.append(node) + node = node.next + nodes.sort(key=lambda n: n.val) + sentinel = ListNode() + prev = sentinel + for node in nodes: + prev.next = node + prev = node + prev.next = None + return sentinel.next +``` diff --git a/swejp75/35 - 19. Remove Nth Node From End of List.md b/swejp75/35 - 19. Remove Nth Node From End of List.md new file mode 100644 index 0000000..c8a5461 --- /dev/null +++ b/swejp75/35 - 19. Remove Nth Node From End of List.md @@ -0,0 +1,42 @@ +### Solution 1 + +```python +class Solution: + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + size = 0 + node = head + while node: + size += 1 + node = node.next + steps = size - n + if steps == 0: + return head.next + node = head + for _ in range(steps - 1): + node = node.next + node.next = node.next.next + return head +``` + +### Solution 2 + +stackを使わずに Follow up: Could you do this in one pass? を満たす方法 + +```python +class Solution: + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + if n <= 0: + raise ValueError("invalid n") + scout = head + for _ in range(n): + if not scout: + raise ValueError("invalid n") + scout = scout.next + sentinel = ListNode(next=head) + node = sentinel + while scout is not None: + node = node.next + scout = scout.next + node.next = node.next.next + return sentinel.next +``` diff --git a/swejp75/36 - 226. Invert Binary Tree.md b/swejp75/36 - 226. Invert Binary Tree.md new file mode 100644 index 0000000..34e5be2 --- /dev/null +++ b/swejp75/36 - 226. Invert Binary Tree.md @@ -0,0 +1,28 @@ +### Solution 1 + +```python +class Solution: + def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: + if not root: + return None + root.left, root.right = self.invertTree(root.right), self.invertTree(root.left) + return root +``` + +### Solution 2 + +stackバージョン + +```python +class Solution: + def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: + stack = [root] + while stack: + node = stack.pop() + if node is None: + continue + node.left, node.right = node.right, node.left + stack.append(node.left) + stack.append(node.right) + return root +``` diff --git a/swejp75/37 - 110. Balanced Binary Tree.md b/swejp75/37 - 110. Balanced Binary Tree.md new file mode 100644 index 0000000..dfa6d0a --- /dev/null +++ b/swejp75/37 - 110. Balanced Binary Tree.md @@ -0,0 +1,67 @@ +### Solution 1 + +初見で書いたコード。 + +```python +class Solution: + def isBalanced(self, root: Optional[TreeNode]) -> bool: + def isBalancedHelper(root: Optional[TreeNode]) -> tuple[bool, int]: + if not root: + return (True, 0) + is_left_balanced, left_depth = isBalancedHelper(root.left) + is_right_balanced, right_depth = isBalancedHelper(root.right) + is_root_balanced = abs(left_depth - right_depth) <= 1 + is_balanced = is_left_balanced and is_right_balanced and is_root_balanced + root_depth = max(left_depth, right_depth) + 1 + return is_balanced, root_depth + result, _ = isBalancedHelper(root) + return result +``` + +### Solution 2 + +helper関数の返り値をintとして二つの意味を持たせてみた。 + +```python +class Solution: + def isBalanced(self, root: Optional[TreeNode]) -> bool: + def isBalancedHelper(root: Optional[TreeNode]) -> int: + if not root: + return 0 + left_result = isBalancedHelper(root.left) + if left_result == -1: + return -1 + right_result = isBalancedHelper(root.right) + if right_result == -1: + return -1 + if abs(left_result - right_result) > 1: + return -1 + return max(left_result, right_result) + 1 + return isBalancedHelper(root) != -1 +``` + +### Solution 3 + +辞書を使って結果とノードの処理状況を表してみる。 + +```python +class Solution: + def isBalanced(self, root: Optional[TreeNode]) -> bool: + if not root: + return True + depths = {None: 0} + stack = [root] + while stack: + node = stack[-1] + if node.left not in depths: + stack.append(node.left) + continue + if node.right not in depths: + stack.append(node.right) + continue + if abs(depths[node.left] - depths[node.right]) > 1: + return False + depths[node] = max(depths[node.left], depths[node.right]) + 1 + stack.pop() + return True +``` diff --git a/swejp75/38 - 230. Kth Smallest Element in a BST.md b/swejp75/38 - 230. Kth Smallest Element in a BST.md new file mode 100644 index 0000000..7337a82 --- /dev/null +++ b/swejp75/38 - 230. Kth Smallest Element in a BST.md @@ -0,0 +1,21 @@ +### Solution 1 + +```python +class Solution: + def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: + count = 0 + def kthSmallestHelper(node: Optional[TreeNode]): + nonlocal count + if not node: + return + value = kthSmallestHelper(node.left) + if value is not None: + return value + count += 1 + if count == k: + return node.val + value = kthSmallestHelper(node.right) + if value is not None: + return value + return kthSmallestHelper(root) +``` diff --git a/swejp75/39 - 199. Binary Tree Right Side View.md b/swejp75/39 - 199. Binary Tree Right Side View.md new file mode 100644 index 0000000..4b8bb4a --- /dev/null +++ b/swejp75/39 - 199. Binary Tree Right Side View.md @@ -0,0 +1,40 @@ +### Solution 1 + +```python +class Solution: + def rightSideView(self, root: Optional[TreeNode]) -> List[int]: + result = [] + nodes = [(root, 0)] + while nodes: + node, depth = nodes.pop() + if not node: + continue + if depth >= len(result): + result.append(node.val) + nodes.append((node.left, depth + 1)) + nodes.append((node.right, depth + 1)) + return result +``` + +### Solution 2 + +BFSでとらえる + +```python +class Solution: + def rightSideView(self, root: Optional[TreeNode]) -> List[int]: + if not root: + return [] + result = [] + nodes = [root] + while nodes: + result.append(nodes[-1].val) + new_nodes = [] + for node in nodes: + if node.left: + new_nodes.append(node.left) + if node.right: + new_nodes.append(node.right) + nodes = new_nodes + return result +``` diff --git a/swejp75/40 - 98. Validate Binary Search Tree.md b/swejp75/40 - 98. Validate Binary Search Tree.md new file mode 100644 index 0000000..ace461d --- /dev/null +++ b/swejp75/40 - 98. Validate Binary Search Tree.md @@ -0,0 +1,15 @@ +### Solution 1 + +```python +class Solution: + def isValidBST(self, root: Optional[TreeNode]) -> bool: + def isValidBSTHelper(node, low, high): + if not node: + return True + return ( + low < node.val < high and + isValidBSTHelper(node.left, low, node.val) and + isValidBSTHelper(node.right, node.val, high) + ) + return isValidBSTHelper(root, float("-inf"), float("inf")) +``` diff --git a/swejp75/41 - 105. Construct Binary Tree from Preorder and Inorder Traversal.md b/swejp75/41 - 105. Construct Binary Tree from Preorder and Inorder Traversal.md new file mode 100644 index 0000000..5b4fb22 --- /dev/null +++ b/swejp75/41 - 105. Construct Binary Tree from Preorder and Inorder Traversal.md @@ -0,0 +1,18 @@ +### Solution 1 + +```python +class Solution: + def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: + val_to_inorder_index = {val: index for index, val in enumerate(inorder)} + def buildTreeHelper(preorder_begin, inorder_begin, tree_size): + if tree_size == 0: + return None + root_value = preorder[preorder_begin] + left_subtree_size = val_to_inorder_index[root_value] - inorder_begin + right_subtree_size = tree_size - left_subtree_size - 1 + root = TreeNode(root_value) + root.left = buildTreeHelper(preorder_begin + 1, inorder_begin, left_subtree_size) + root.right = buildTreeHelper(preorder_begin + 1 + left_subtree_size, inorder_begin + left_subtree_size + 1, right_subtree_size) + return root + return buildTreeHelper(0, 0, len(preorder)) +``` diff --git a/swejp75/42 - 208. Implement Trie (Prefix Tree).md b/swejp75/42 - 208. Implement Trie (Prefix Tree).md new file mode 100644 index 0000000..90fee87 --- /dev/null +++ b/swejp75/42 - 208. Implement Trie (Prefix Tree).md @@ -0,0 +1,138 @@ +### Solution 1 + +なんか絶対こういうことじゃなくない? + +```python +class Trie: + + def __init__(self): + self.words = set() + self.prefixes = set() + + def insert(self, word: str) -> None: + self.words.add(word) + for i in range(len(word)): + self.prefixes.add(word[:i + 1]) + + def search(self, word: str) -> bool: + return word in self.words + + def startsWith(self, prefix: str) -> bool: + return prefix in self.prefixes +``` + +### Solution 2 + +どうやらこれが求められてるっぽい + +LeetCode上の入力制約だと時間内に終わらなさそうだけど +At most 3 \* 10^4 calls in total が 1 <= word.length, prefix.length <= 2000 で呼び出されたら最悪60,000,000回ぐらいハッシュ計算とか重めの計算をしないといけない気がする。軽めのテストケースが多いんだろうか + +https://docs.python.org/ja/3/reference/lexical_analysis.html#octal-character +終端文字っぽいキーを作ってみました(pythonでやるの変ですかね?普通に"end"とかにした方がいいかも) + +```python +class Trie: + + def __init__(self): + self.root = dict() + + def insert(self, word: str) -> None: + current = self.root + for char in word: + if char not in current: + current[char] = dict() + current = current[char] + current["\0"] = True + + def search(self, word: str) -> bool: + current = self.root + for char in word: + if char not in current: + return False + current = current[char] + return "\0" in current + + def startsWith(self, prefix: str) -> bool: + current = self.root + for char in prefix: + if char not in current: + return False + current = current[char] + return True +``` + +### 補足メモ + +こうするとrecursive_dictを再帰的に定義できるらしい。 +ただし渡したデフォルトファクトリーの中身は実行時(存在しないキーが参照され、default_factoryが呼ばれる段階になった時)に名前空間に基づいて評価されるので、途中でrecursive_dictの名前が変わると内容も変わる + +結論から申し上げますと、defaultdict に渡されるものは**文字列やキーではなく、関数(呼び出し可能なオブジェクト)の「メモリアドレス」**です。 + +関数のメモリアドレス(lambda)が渡されていて、そのlambdaが実行時にラベル(ほぼ文字列)を評価しようとした結果として、そのラムダは再び defaultdict(recursive_dict) を実行しようとします。しかし、この時には既に recursive_dict が "abcdef" に書き換わっています。 + +https://docs.python.org/ja/3.7/tutorial/classes.html#python-scopes-and-namespaces + +```python +class Trie: + + def __init__(self): + test = lambda: defaultdict(aaaaa) # これは別にエラーにならない(aaaaaというものがその時点で存在しなくても) + recursive_dict = lambda: defaultdict(recursive_dict) + self.root = recursive_dict() + # recursive_dict = "abcdef" # ここのコメントを外すとエラーになる + + def insert(self, word: str) -> None: + current = self.root + for char in word: + current = current[char] + current["\0"] = True + + def search(self, word: str) -> bool: + current = self.root + for char in word: + if char not in current: + return False + current = current[char] + return "\0" in current + + def startsWith(self, prefix: str) -> bool: + current = self.root + for char in prefix: + if char not in current: + return False + current = current[char] + return True +``` + +下のようにするとあとからfを変えても大丈夫っぽい。(ラベルを経由せず関数ポインタが直接使われている?から?) + +```python +class Trie: + + def __init__(self): + f = lambda f: defaultdict(lambda: f(f)) + self.root = f(f) + + def insert(self, word: str) -> None: + current = self.root + for char in word: + current = current[char] + current["\0"] = True + + def search(self, word: str) -> bool: + current = self.root + for char in word: + if char not in current: + return False + current = current[char] + return "\0" in current + + def startsWith(self, prefix: str) -> bool: + current = self.root + for char in prefix: + if char not in current: + return False + current = current[char] + return True +``` diff --git a/swejp75/43 - 124. Binary Tree Maximum Path Sum.md b/swejp75/43 - 124. Binary Tree Maximum Path Sum.md new file mode 100644 index 0000000..9a210f0 --- /dev/null +++ b/swejp75/43 - 124. Binary Tree Maximum Path Sum.md @@ -0,0 +1,46 @@ +### Solution 1 + +```python +class Solution: + def maxPathSum(self, root: Optional[TreeNode]) -> int: + max_path_sum = float("-inf") + def traverse(root: Optional[TreeNode]) -> int | None: + nonlocal max_path_sum + if not root: + return 0 + left_max_without_branching = traverse(root.left) + right_max_without_branching = traverse(root.right) + max_without_branching = max( + root.val, + root.val + left_max_without_branching, + root.val + right_max_without_branching + ) + max_with_branching = max( + max_without_branching, + root.val + left_max_without_branching + right_max_without_branching + ) + max_path_sum = max(max_path_sum, max_with_branching) + return max_without_branching + traverse(root) + return max_path_sum +``` + +### Solution 2 + +うーん、関数名はtraverseのがいいかな。本当は返り値がどういうものかとpath_sumの更新の副作用の効果の両方の要素を名前に入れ込みたいんですけど。(max_without_branching_while_refreshing_path_sumとか?長すぎてわかりづらい気もします) + +```python +class Solution: + def maxPathSum(self, root: Optional[TreeNode]) -> int: + max_path_sum = float("-inf") + def max_without_branching(root: Optional[TreeNode]) -> int: + nonlocal max_path_sum + if not root: + return 0 + left_max = max_without_branching(root.left) + right_max = max_without_branching(root.right) + max_path_sum = max(max_path_sum, root.val + left_max + right_max) # update path sum + return max(0, root.val + max(left_max, right_max)) + max_without_branching(root) + return max_path_sum +``` diff --git a/swejp75/44 - 297. Serialize and Deserialize Binary Tree.md b/swejp75/44 - 297. Serialize and Deserialize Binary Tree.md new file mode 100644 index 0000000..51f6ff3 --- /dev/null +++ b/swejp75/44 - 297. Serialize and Deserialize Binary Tree.md @@ -0,0 +1,202 @@ +### Solution 1 (DFS 最初に書いたもの) + +```python +class Codec: + + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + list_data = [] + stack = [root] + while stack: + node = stack.pop() + if node is not None: + list_data.append(str(node.val)) + stack.append(node.right) + stack.append(node.left) + list_data.append(',') + return "".join(list_data) + + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + data = self._convert_to_list(data) + sentinel = TreeNode() + stack = [(sentinel, "left")] + for element in data: + node = None if element is None else TreeNode(element) + parent, position = stack[-1] + if position == "left": + parent.left = node + elif position == "right": + parent.right = node + stack.pop() + if node: + stack.append((node, "right")) + stack.append((node, "left")) + return sentinel.left + + + def _convert_to_list(self, data): + result = [] + string = "" + for char in data: + if char == ',': + if string == "": + result.append(None) + else: + result.append(int(string)) + string = "" + continue + string += char + return result +``` + +### Solution 2 (DFSその2 なんか右側から探索してて気持ち悪いかも) + +```python +class Codec: + + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + result = [] + stack = [root] + while stack: + node = stack.pop() + if node is None: + result.append("null") + continue + result.append(str(node.val)) + stack.append(node.left) + stack.append(node.right) + return ",".join(result) + + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + sentinel = TreeNode() + stack = [(sentinel, "left")] + values = data.split(',') + for value in values: + parent, position = stack.pop() + if value == "null": + continue + node = TreeNode(int(value)) + if position == "left": + parent.left = node + else: + parent.right = node + stack.append((node, "left")) + stack.append((node, "right")) + return sentinel.left +``` + +### Solution 3 (BFS これが一番読みやすいかな?) + +```python +class Codec: + + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + values = [] + queue = deque([root]) + while queue: + node = queue.popleft() + if not node: + values.append("null") + else: + values.append(str(node.val)) + queue.append(node.left) + queue.append(node.right) + return ",".join(values) + + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + values = data.split(",") + sentinel = TreeNode() + queue = deque([(sentinel, "right")]) + for value in values: + parent, position = queue.popleft() + if value == "null": + node = None + else: + node = TreeNode(int(value)) + queue.append((node, "left")) + queue.append((node, "right")) + if position == "left": + parent.left = node + else: + parent.right = node + return sentinel.right +``` + +### Solution 4 (BFSその2 parent側で処理をするようにしてみる) + +```python +class Codec: + + def serialize(self, root): + """Encodes a tree to a single string. + + :type root: TreeNode + :rtype: str + """ + queue = deque([root]) + result = [] + while queue: + node = queue.popleft() + if node is None: + result.append("null") + continue + result.append(str(node.val)) + queue.append(node.left) + queue.append(node.right) + return ",".join(result) + + + def deserialize(self, data): + """Decodes your encoded data to tree. + + :type data: str + :rtype: TreeNode + """ + values = deque(data.split(",")) + root_value = values.popleft() + if root_value == "null": + return None + root = TreeNode(int(root_value)) + queue = deque([root]) + while queue: + node = queue.popleft() + if (left_value := values.popleft()) != "null": + node.left = TreeNode(int(left_value)) + queue.append(node.left) + if (right_value := values.popleft()) != "null": + node.right = TreeNode(int(right_value)) + queue.append(node.right) + return root +``` diff --git a/swejp75/45 - 46. Permutations.md b/swejp75/45 - 46. Permutations.md new file mode 100644 index 0000000..0cf4d91 --- /dev/null +++ b/swejp75/45 - 46. Permutations.md @@ -0,0 +1,17 @@ +### Solution 1 + +```python +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + nums = nums[:] + result = [] + def permuteHelper(index): + if index == len(nums): + result.append(nums[:]) + for i in range(index, len(nums)): + nums[i], nums[index] = nums[index], nums[i] + permuteHelper(index + 1) + nums[i], nums[index] = nums[index], nums[i] + permuteHelper(0) + return result +``` diff --git a/swejp75/46 - 39. Combination Sum.md b/swejp75/46 - 39. Combination Sum.md new file mode 100644 index 0000000..d21dc65 --- /dev/null +++ b/swejp75/46 - 39. Combination Sum.md @@ -0,0 +1,21 @@ +### Solution 1 + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + result = [] + def combinationSumHelper(index, combination, combination_sum): + if combination_sum > target: + return + elif combination_sum == target: + result.append(combination[:]) + return + if index >= len(candidates): + return + combination.append(candidates[index]) + combinationSumHelper(index, combination, combination_sum + candidates[index]) + combination.pop() + combinationSumHelper(index + 1, combination, combination_sum) + combinationSumHelper(0, [], 0) + return result +``` diff --git a/swejp75/47 - 79. Word Search.md b/swejp75/47 - 79. Word Search.md new file mode 100644 index 0000000..4beb4ab --- /dev/null +++ b/swejp75/47 - 79. Word Search.md @@ -0,0 +1,121 @@ +### Solution 1 + +とりあえず書いたもの + +```python +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + for starting_y in range(len(board)): + for starting_x in range(len(board[0])): + if board[starting_y][starting_x] != word[0]: + continue + path = [(starting_x, starting_y)] + def check_from(x, y): + if len(path) == len(word): + return True + dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)] + for dx, dy in dirs: + new_x = x + dx + new_y = y + dy + if not 0 <= new_x < len(board[0]) or not 0 <= new_y < len(board): + continue + if board[new_y][new_x] == word[len(path)] and (new_x, new_y) not in path: + path.append((new_x, new_y)) + if check_from(new_x, new_y): + return True + path.pop() + if check_from(starting_x, starting_y): + return True + return False +``` + +### Solution 2 + +なんかいい感じにしてみる + +```python +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + num_rows = len(board) + num_columns = len(board[0]) + path = set() + + def exist_helper(r, c): + if len(path) == len(word): + return True + if ( + not 0 <= r < num_rows or + not 0 <= c < num_columns or + (r, c) in path or + board[r][c] != word[len(path)] + ): + return False + path.add((r, c)) + result = ( + exist_helper(r, c + 1) or + exist_helper(r, c - 1) or + exist_helper(r + 1, c) or + exist_helper(r - 1, c) + ) + path.remove((r, c)) + return result + + for r in range(num_rows): + for c in range(num_columns): + if exist_helper(r, c): + return True + return False +``` + +### Solution 3 + +Solution 2に早期returnを入れてみたもの + +なんかleetcodeの実行時間はめっちゃ早くなった + +```python +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + num_rows = len(board) + num_columns = len(board[0]) + + if len(word) > num_rows * num_columns: + return False + + board_counter = Counter(itertools.chain.from_iterable(board)) + word_counter = Counter(word) + for c in word_counter: + if board_counter[c] < word_counter[c]: + return False + + if board_counter[word[0]] > board_counter[word[-1]]: + word = word[::-1] + + path = set() + + def exist_helper(r, c): + if len(path) == len(word): + return True + if ( + not 0 <= r < num_rows or + not 0 <= c < num_columns or + (r, c) in path or + board[r][c] != word[len(path)] + ): + return False + path.add((r, c)) + result = ( + exist_helper(r, c + 1) or + exist_helper(r, c - 1) or + exist_helper(r + 1, c) or + exist_helper(r - 1, c) + ) + path.remove((r, c)) + return result + + for r in range(num_rows): + for c in range(num_columns): + if exist_helper(r, c): + return True + return False +``` diff --git a/swejp75/48 - 90. Subsets II.md b/swejp75/48 - 90. Subsets II.md new file mode 100644 index 0000000..9920cae --- /dev/null +++ b/swejp75/48 - 90. Subsets II.md @@ -0,0 +1,58 @@ +### Solution 1 + +```python +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + num_to_count = Counter(nums) + unique_nums = list(num_to_count.keys()) + result = [] + subset = [] + def subsetWithDupHelper(idx): + if idx == len(unique_nums): + result.append(subset[:]) + return + num = unique_nums[idx] + subsetWithDupHelper(idx + 1) + for i in range(num_to_count[num]): + subset.append(num) + subsetWithDupHelper(idx + 1) + for i in range(num_to_count[num]): + subset.pop() + subsetWithDupHelper(0) + return result +``` + +### Solution 2 + +```python +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + num_to_count = Counter(nums) + unique_nums = list(num_to_count.keys()) + result = [] + products = itertools.product(*(range(count + 1) for count in num_to_count.values())) + for product in products: + subset = [] + for idx, count in enumerate(product): + num = unique_nums[idx] + for i in range(count): + subset.append(num) + result.append(subset) + return result +``` + +### Solution 3 + +直前までの結果を分岐させていく方法 + +```python +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + num_to_count = Counter(nums) + subsets = [[]] + for num, count in num_to_count.items(): + for i in range(len(subsets)): + for c in range(1, count + 1): + subsets.append(subsets[i] + [num] * c) + return subsets +``` diff --git a/swejp75/49 - 1359. Count All Valid Pickup and Delivery Options.md b/swejp75/49 - 1359. Count All Valid Pickup and Delivery Options.md new file mode 100644 index 0000000..7b5f36a --- /dev/null +++ b/swejp75/49 - 1359. Count All Valid Pickup and Delivery Options.md @@ -0,0 +1,57 @@ +### Solution 1 + +最初に書いたコード +n - 1の結果のどこに新しいdeliveryのpickupとdeliverを配置するかで考えた + +```python +class Solution: + def countOrders(self, n: int) -> int: + if n == 1: + return 1 + modulo = 10 ** 9 + 7 + num_insertion_pattern = (2 * n - 1) * (2 * n) // 2 + result = (self.countOrders(n - 1) * num_insertion_pattern) % modulo + return result +``` + +iterativeにしてみる + +```python +class Solution: + def countOrders(self, n: int) -> int: + if n < 0: + raise ValueError("n must be 0 or greater") + modulo = 10 ** 9 + 7 + num_sequences = 1 + for i in range(2, n + 1): + num_sequences *= (2 * i - 1) * (2 * i) // 2 + num_sequences %= modulo + return num_sequences +``` + +### Solution 2 + +backtrackを使った解法ってこれのことかな? + +```python +class Solution: + def countOrders(self, n: int) -> int: + if n < 0: + raise ValueError("n must be 0 or greater") + modulo = 10 ** 9 + 7 + @functools.cache + def countOrdersHelper(num_picked, num_delivered): + if num_picked == num_delivered == n: + return 1 + count = 0 + if num_delivered < num_picked: + count += (num_picked - num_delivered) * countOrdersHelper(num_picked, num_delivered + 1) + if num_picked < n: + count += (n - num_picked) * countOrdersHelper(num_picked + 1, num_delivered) + return count % modulo + return countOrdersHelper(0, 0) +``` + +究極的には(2n)! / 2^nで個数はもとまるみたい(個々の並べ替えの組み合わせ全てのうち、n個ののdelivery/pickupペアの順番を確定させたもの) + +あとmodular inverseというのを使うと直前の結果のmodがわかっていればそれをある整数で割った結果のmodもわかるらしい(実務で使うのかな?) diff --git a/swejp75/50 - 140. Word Break II.md b/swejp75/50 - 140. Word Break II.md new file mode 100644 index 0000000..94535e3 --- /dev/null +++ b/swejp75/50 - 140. Word Break II.md @@ -0,0 +1,34 @@ +### Solution 1 + +sの長さが長いとwordへの代入でコピーが発生する都合でちょっと遅くなるかも。ただこの問題の制約だったらfor word in wordDictで回すよりこっちのが良さそう。(wordDict.lengthが1000ぐらいになることを考えると) + +c言語で回るから文字列のスライスも結構早いかも、嫌だったらstringをdecode変換してからmemoryviewを使うという方法があるかもしれません + +```python +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> List[str]: + word_set = set(wordDict) + result = [] + words = [] + def wordBreakHelper(start): + if start == len(s): + sentence = ' '.join(words) + result.append(sentence) + return + for end in range(start + 1, len(s) + 1): + if (word := s[start:end]) in word_set: + words.append(word) + wordBreakHelper(end) + words.pop() + wordBreakHelper(0) + return result +``` + +#### 補足メモ + +Input is generated in a way that the length of the answer doesn't exceed 10^5. の制約がない場合、再帰はどれぐらい発生するんだろう? + +wordDict = ["a", "aa", "aaa", "aaaa", ...] +s = "aaaaaaaaaaaaaaaaaaaa" + +のケースを考えると、多分これが最大のケースで、結局有効な結果の数は20個のaの間にあるスペースをどこに入れるか、で2^19になりそう。 diff --git a/swejp75/51 - 347. Top K Frequent Elements.md b/swejp75/51 - 347. Top K Frequent Elements.md new file mode 100644 index 0000000..0a29099 --- /dev/null +++ b/swejp75/51 - 347. Top K Frequent Elements.md @@ -0,0 +1,28 @@ +### Solution 1 + +時間計算量nlogn + +```python +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + num_to_count = Counter(nums) + sorted_by_count = sorted(num_to_count.keys(), key=num_to_count.get, reverse=True) + return sorted_by_count[:k] +``` + +### Solution 2 + +時間計算量nlogk + +```python +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + num_to_count = Counter(nums) + top_k_heap = [] + for num, count in num_to_count.items(): + if len(top_k_heap) < k: + heapq.heappush(top_k_heap, (count, num)) + else: + heapq.heappushpop(top_k_heap, (count, num)) + return list(reversed([num for count, num in top_k_heap])) +``` diff --git a/swejp75/52 - 295. Find Median from Data Stream.md b/swejp75/52 - 295. Find Median from Data Stream.md new file mode 100644 index 0000000..fab6aba --- /dev/null +++ b/swejp75/52 - 295. Find Median from Data Stream.md @@ -0,0 +1,80 @@ +### Solution 1 + +実装に40分ぐらいかかってしまった。 +これがheapの問題であるという事前情報がなかったらこの計算量のコードは自力では思いつかなかったかも + +thread safetyについて考えてみると、この問題設定だと正確な中央値は一個ずつ入れるしか無理な気がするが、self.medianをNoneにしたりしなかったりしていると結構よくないことが起きるかも + +```python +class MedianFinder: + + def __init__(self): + self.size = 0 + self.median_data = None + self.max_heap_below_median = [] + self.min_heap_above_median = [] + + def addNum(self, num: int) -> None: + if self.size == 0: + self.median_data = num + elif self.size & 1: + if num > self.median_data: + heapq.heappush_max(self.max_heap_below_median, self.median_data) + heapq.heappush(self.min_heap_above_median, num) + else: + heapq.heappush_max(self.max_heap_below_median, num) + heapq.heappush(self.min_heap_above_median, self.median_data) + self.median_data = None + else: + if num < self.max_heap_below_median[0]: + self.median_data = heapq.heappop_max(self.max_heap_below_median) + heapq.heappush_max(self.max_heap_below_median, num) + elif num > self.min_heap_above_median[0]: + self.median_data = heapq.heappop(self.min_heap_above_median) + heapq.heappush(self.min_heap_above_median, num) + else: + self.median_data = num + self.size += 1 + + def findMedian(self) -> float: + if self.size == 0: + return None + elif self.size & 1: + return self.median_data + else: + return (self.max_heap_below_median[0] + self.min_heap_above_median[0]) / 2 +``` + +### Solution 2 + +取り合えず入れてしまってからサイズを調整するアプローチ。こっちは思いつかなかった(正確には一瞬考えたが実装が複雑になりそうな気がしてやめた)が、実際には場合分けをするよりも単純に済むようだった。アルゴリズムの全体的な方針を思い浮かべてからもうちょっと具体的なコードを思い浮かべるスピードと正確性が足りていない気がする。頭の中で全部想像しようとするのではなく、とりあえず書きながら考えるようにしてみようかな? + +```python +class MedianFinder: + + def __init__(self): + self.lesser_max_heap = [] + self.greater_min_heap = [] + + def addNum(self, num: int) -> None: + if self.greater_min_heap and num > self.greater_min_heap[0]: + heapq.heappush(self.greater_min_heap, num) + else: + heapq.heappush_max(self.lesser_max_heap, num) + if len(self.lesser_max_heap) > len(self.greater_min_heap) + 1: + num_to_transfer = heapq.heappop_max(self.lesser_max_heap) + heapq.heappush(self.greater_min_heap, num_to_transfer) + elif len(self.greater_min_heap) > len(self.lesser_max_heap) + 1: + num_to_transfer = heapq.heappop(self.greater_min_heap) + heapq.heappush_max(self.lesser_max_heap, num_to_transfer) + + def findMedian(self) -> float: + if len(self.lesser_max_heap) > len(self.greater_min_heap): + return self.lesser_max_heap[0] + elif len(self.lesser_max_heap) < len(self.greater_min_heap): + return self.greater_min_heap[0] + else: + if not self.lesser_max_heap: + return None + return (self.lesser_max_heap[0] + self.greater_min_heap[0]) / 2 +``` diff --git a/swejp75/53 - 23. Merge K Sorted Lists.md b/swejp75/53 - 23. Merge K Sorted Lists.md new file mode 100644 index 0000000..03200f7 --- /dev/null +++ b/swejp75/53 - 23. Merge K Sorted Lists.md @@ -0,0 +1,55 @@ +### Solution 1 + +なかなかうまく書けた気がする。 + +```python +class Solution: + def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: + sentinel = ListNode() + prev = sentinel + nodes = [] + for list_id, node in enumerate(lists): + if node is not None: + heapq.heappush(nodes, (node.val, list_id, node)) + while nodes: + _, list_id, node = heapq.heappop(nodes) + prev.next = node + prev = node + if node.next is not None: + heapq.heappush(nodes, (node.next.val, list_id, node.next)) + return sentinel.next +``` + +### Solution 2 + +divide and conquer(二つずつマージしていく) + +```python +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]): + node1 = list1 + node2 = list2 + sentinel = ListNode() + prev = sentinel + while node1 is not None and node2 is not None: + if node1.val <= node2.val: + prev.next = node1 + prev = node1 + node1 = node1.next + else: + prev.next = node2 + prev = node2 + node2 = node2.next + prev.next = node1 if node1 is not None else node2 + return sentinel.next + + def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: + if not lists: + return None + sorted_size = 1 + while sorted_size < len(lists): + for i in range(0, len(lists) - sorted_size, sorted_size * 2): + lists[i] = self.mergeTwoLists(lists[i], lists[i + sorted_size]) + sorted_size *= 2 + return lists[0] +``` diff --git a/swejp75/54 - 200. Number of Islands.md b/swejp75/54 - 200. Number of Islands.md new file mode 100644 index 0000000..bf146d9 --- /dev/null +++ b/swejp75/54 - 200. Number of Islands.md @@ -0,0 +1,35 @@ +### Solution 1 + +```python +class Solution: + def numIslands(self, grid: List[List[str]]) -> int: + visited_lands = set() + num_rows = len(grid) + num_columns = len(grid[0]) + def is_unvisited_land(r, c): + return ( + 0 <= r < num_rows and + 0 <= c < num_columns and + grid[r][c] == "1" and + (r, c) not in visited_lands + ) + def visit_island(starting_r, starting_c): + visited_lands.add((starting_r, starting_c)) + frontier = [(starting_r, starting_c)] + while frontier: + r, c = frontier.pop() + dirs = [(1, 0), (0, 1), (0, -1), (-1, 0)] + for dr, dc in dirs: + new_r = r + dr + new_c = c + dc + if is_unvisited_land(new_r, new_c): + visited_lands.add((new_r, new_c)) + frontier.append((new_r, new_c)) + num_islands = 0 + for r in range(num_rows): + for c in range(num_columns): + if is_unvisited_land(r, c): + visit_island(r, c) + num_islands += 1 + return num_islands +``` diff --git a/swejp75/55 - 695. Max Area of Island.md b/swejp75/55 - 695. Max Area of Island.md new file mode 100644 index 0000000..d77a300 --- /dev/null +++ b/swejp75/55 - 695. Max Area of Island.md @@ -0,0 +1,38 @@ +### Solution 1 + +```python +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + visited_lands = set() + num_rows = len(grid) + num_columns = len(grid[0]) + def is_unvisited_land(r, c): + return ( + 0 <= r < num_rows and + 0 <= c < num_columns and + grid[r][c] == 1 and + (r, c) not in visited_lands + ) + def measure_island(starting_r, starting_c): + area = 0 + visited_lands.add((starting_r, starting_c)) + frontier = [(starting_r, starting_c)] + while frontier: + r, c = frontier.pop() + directions = [(1, 0), (0, 1), (0, -1), (-1, 0)] + for dr, dc in directions: + new_r = r + dr + new_c = c + dc + if is_unvisited_land(new_r, new_c): + visited_lands.add((new_r, new_c)) + frontier.append((new_r, new_c)) + area += 1 + return area + max_area = 0 + for r in range(num_rows): + for c in range(num_columns): + if is_unvisited_land(r, c): + area = measure_island(r, c) + max_area = max(max_area, area) + return max_area +``` diff --git a/swejp75/56 - 207. Course Schedule.md b/swejp75/56 - 207. Course Schedule.md new file mode 100644 index 0000000..9ac85cb --- /dev/null +++ b/swejp75/56 - 207. Course Schedule.md @@ -0,0 +1,83 @@ +### Solution 1 + +```python +class Solution: + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + course_to_num_untaken_requirements = [0 for _ in range(numCourses)] + course_to_next_courses = [[] for _ in range(numCourses)] + for course, requirement in prerequisites: + course_to_num_untaken_requirements[course] += 1 + course_to_next_courses[requirement].append(course) + takeable_courses = [] + for course, num_untaken_requirements in enumerate(course_to_num_untaken_requirements): + if num_untaken_requirements == 0: + takeable_courses.append(course) + num_taken_courses = 0 + while takeable_courses: + course = takeable_courses.pop() + num_taken_courses += 1 + for next_course in course_to_next_courses[course]: + course_to_num_untaken_requirements[next_course] -= 1 + if course_to_num_untaken_requirements[next_course] == 0: + takeable_courses.append(next_course) + return num_taken_courses == numCourses +``` + +### Solution 2 + +サイクル検知の方法 + +```python +class Solution: + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + course_to_next_courses = [[] for _ in range(numCourses)] + for course, requirement in prerequisites: + course_to_next_courses[requirement].append(course) + path = set() + has_no_cycle_from = set() + def has_cycle_from(course): + if course in has_no_cycle_from: + return False + if course in path: + return True + for next_course in course_to_next_courses[course]: + path.add(course) + if has_cycle_from(next_course): + return True + path.remove(course) + has_no_cycle_from.add(course) + return False + for course in range(numCourses): + if has_cycle_from(course): + return False + return True +``` + +これでもいいかも + +```python +class Solution: + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + course_to_next_courses = [[] for _ in range(numCourses)] + for course, requirement in prerequisites: + course_to_next_courses[requirement].append(course) + path = set() + # has_no_cycle_from = set() + @functools.cache + def has_cycle_from(course): + # if course in has_no_cycle_from: + # return False + if course in path: + return True + for next_course in course_to_next_courses[course]: + path.add(course) + if has_cycle_from(next_course): + return True + path.remove(course) + # has_no_cycle_from.add(course) + return False + for course in range(numCourses): + if has_cycle_from(course): + return False + return True +``` diff --git a/swejp75/57 - 210. Course Schedule II.md b/swejp75/57 - 210. Course Schedule II.md new file mode 100644 index 0000000..d58deb7 --- /dev/null +++ b/swejp75/57 - 210. Course Schedule II.md @@ -0,0 +1,95 @@ +### Solution 1 + +サイクル検知で頑張って書こうとしたもの + +```python +class Solution: + def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: + course_to_required_courses = [[] for _ in range(numCourses)] + for course, requirement in prerequisites: + course_to_required_courses[course].append(requirement) + order = [] + already_ordered_courses = set() + currently_constructing_courses = set() + def consruct_order(course): + if course in already_ordered_courses: + return True + if course in currently_constructing_courses: + return False + currently_constructing_courses.add(course) + for required_course in course_to_required_courses[course]: + succeeded = consruct_order(required_course) + if not succeeded: + return False + currently_constructing_courses.remove(course) + already_ordered_courses.add(course) + order.append(course) + return True + for starting_course in range(numCourses): + succeeded = consruct_order(starting_course) + if not succeeded: + return [] + return order +``` + +状態管理の方法をちょっと変えてみたバージョン + +```python +class Solution: + def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: + UNCHECKED = 0 + CHECKING = 1 + ADDED = 2 + course_to_required_courses = [[] for _ in range(numCourses)] + for course, requirement in prerequisites: + course_to_required_courses[course].append(requirement) + course_status = [0 for _ in range(numCourses)] + order = [] + def consruct_order(course): + if course_status[course] == CHECKING: + return False + if course_status[course] == ADDED: + return True + course_status[course] = CHECKING + for required_course in course_to_required_courses[course]: + success = consruct_order(required_course) + if not success: + return False + order.append(course) + course_status[course] = ADDED + return True + for starting_course in range(numCourses): + success = consruct_order(starting_course) + if not success: + return [] + return order +``` + +### Solution 2 + +入次数で管理するバージョン。こっちのが読みやすいと感じる + +```python +class Solution: + def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: + course_to_num_untaken_requirements = [0 for _ in range(numCourses)] + course_to_next_courses = [[] for _ in range(numCourses)] + for course, requirement in prerequisites: + course_to_num_untaken_requirements[course] += 1 + course_to_next_courses[requirement].append(course) + order = [] + takeable_courses = [] + for course in range(numCourses): + if course_to_num_untaken_requirements[course] == 0: + takeable_courses.append(course) + while takeable_courses: + course = takeable_courses.pop() + order.append(course) + for next_course in course_to_next_courses[course]: + course_to_num_untaken_requirements[next_course] -= 1 + if course_to_num_untaken_requirements[next_course] == 0: + takeable_courses.append(next_course) + if len(order) != numCourses: + return [] + return order +``` diff --git a/swejp75/58 - 127. Word Ladder.md b/swejp75/58 - 127. Word Ladder.md new file mode 100644 index 0000000..db1043a --- /dev/null +++ b/swejp75/58 - 127. Word Ladder.md @@ -0,0 +1,26 @@ +### Solution 1 + +```python +class Solution: + def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: + masked_to_words = defaultdict(set) + for word in wordList: + for i in range(len(word)): + masked = word[:i] + "*" + word[i + 1:] + masked_to_words[masked].add(word) + visited_words = set() + queue = deque() + queue.append((beginWord, 1)) + while queue: + word, depth = queue.popleft() + for i in range(len(word)): + masked = word[:i] + "*" + word[i + 1:] + for next_word in masked_to_words[masked]: + if next_word in visited_words: + continue + if next_word == endWord: + return depth + 1 + queue.append((next_word, depth + 1)) + visited_words.add(next_word) + return 0 +``` diff --git a/swejp75/59 - 139. Word Break.md b/swejp75/59 - 139. Word Break.md new file mode 100644 index 0000000..1e6464b --- /dev/null +++ b/swejp75/59 - 139. Word Break.md @@ -0,0 +1,29 @@ +### Solution 1 + +```python +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + @functools.cache + def is_breakable_from(start: int) -> bool: + if start == len(s): + return True + for end in range(start + 1, len(s) + 1): + if s[start:end] in wordDict and is_breakable_from(end): + return True + return False + return is_breakable_from(0) +``` + +### Solution 2 + +```python +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + indexes_breakable_until = [0] + for end in range(1, len(s) + 1): + for start in indexes_breakable_until: + if s[start:end] in wordDict: + indexes_breakable_until.append(end) + break + return indexes_breakable_until[-1] == end +``` diff --git a/swejp75/60 - 322. Coin Change.md b/swejp75/60 - 322. Coin Change.md new file mode 100644 index 0000000..74fedf5 --- /dev/null +++ b/swejp75/60 - 322. Coin Change.md @@ -0,0 +1,42 @@ +### Solution 1 + +```python +class Solution: + def coinChange(self, coins: List[int], target_amount: int) -> int: + num_required_coins = [float("inf")] * (target_amount + 1) + num_required_coins[0] = 0 + for amount in range(target_amount + 1): + if num_required_coins[amount] == float("inf"): + continue + for coin in coins: + if amount + coin > target_amount: + continue + num_required_coins[amount + coin] = min( + num_required_coins[amount + coin], + num_required_coins[amount] + 1 + ) + if num_required_coins[target_amount] == float("inf"): + return -1 + return num_required_coins[target_amount] +``` + +### Solution 2 + +ビット演算でやる方法もあったらしい。 +(解答後に出てくるleetcodeのグラフの左の方クリックしたら発見しました) + +```python +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + num_steps = 0 + changeable = 1 << amount + while not changeable & 1: + new_changeable = changeable + for coin in coins: + new_changeable |= changeable >> coin + if new_changeable == changeable: + return -1 + num_steps += 1 + changeable = new_changeable + return num_steps +``` diff --git a/swejp75/61 - 91. Decode Ways.md b/swejp75/61 - 91. Decode Ways.md new file mode 100644 index 0000000..2517ce8 --- /dev/null +++ b/swejp75/61 - 91. Decode Ways.md @@ -0,0 +1,34 @@ +### Solution 1 + +```python +class Solution: + def numDecodings(self, s: str) -> int: + num_decodings = [0] * (len(s) + 1) + num_decodings[0] = 1 + for i in range(1, len(s) + 1): + if s[i - 1] != "0": + num_decodings[i] += num_decodings[i - 1] + if i >= 2 and s[i - 2] != "0" and 1 <= int(s[i - 2 : i]) <= 26: + num_decodings[i] += num_decodings[i - 2] + return num_decodings[-1] +``` + +### Solution 2 + +空間計算量を最適化したバージョン + +```python +class Solution: + def numDecodings(self, s: str) -> int: + prev_num_decodings = 0 + num_decodings = 1 + for i in range(len(s)): + new_num_decodings = 0 + if s[i] != "0": + new_num_decodings += num_decodings + if i >= 1 and 10 <= int(s[i - 1:i + 1]) <= 26: + new_num_decodings += prev_num_decodings + prev_num_decodings = num_decodings + num_decodings = new_num_decodings + return num_decodings +``` diff --git a/swejp75/62 - 253. Meeting Rooms II.md b/swejp75/62 - 253. Meeting Rooms II.md new file mode 100644 index 0000000..0d9cbb3 --- /dev/null +++ b/swejp75/62 - 253. Meeting Rooms II.md @@ -0,0 +1,16 @@ +### Solution 1 + +```python +def num_required_meeting_rooms(intervals: List[List[int]]) -> int: + events = [] + for start_time, end_time in intervals: + events.append((start_time, 1)) + events.append((end_time, -1)) + events.sort() + num_rooms = 0 + max_num_rooms = 0 + for time, num_change in events: + num_rooms += num_change + max_num_rooms = max(max_num_rooms, num_rooms) + return max_num_rooms +``` diff --git a/swejp75/63 - 56. Merge Intervals.md b/swejp75/63 - 56. Merge Intervals.md new file mode 100644 index 0000000..c539d2c --- /dev/null +++ b/swejp75/63 - 56. Merge Intervals.md @@ -0,0 +1,18 @@ +### Solution 1 + +```python +class Solution: + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + intervals = sorted(intervals, key=lambda x: x[0]) + result = [] + holding = intervals[0][:] + for i in range(1, len(intervals)): + interval = intervals[i] + if interval[0] <= holding[1]: + holding[1] = max(holding[1], interval[1]) + else: + result.append(holding) + holding = interval[:] + result.append(holding) + return result +``` diff --git a/swejp75/64 - 1143. Longest Common Subsequence.md b/swejp75/64 - 1143. Longest Common Subsequence.md new file mode 100644 index 0000000..4f215e8 --- /dev/null +++ b/swejp75/64 - 1143. Longest Common Subsequence.md @@ -0,0 +1,38 @@ +### Solution 1 + +```python +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + lcs_lengths = [[0 for _ in range(len(text2) + 1)] for _ in range(len(text1) + 1)] + for i1 in range(1, len(text1) + 1): + for i2 in range(1, len(text2) + 1): + if text1[i1 - 1] == text2[i2 - 1]: + lcs_lengths[i1][i2] = lcs_lengths[i1 - 1][i2 - 1] + 1 + else: + lcs_lengths[i1][i2] = max( + lcs_lengths[i1][i2 - 1], + lcs_lengths[i1 - 1][i2] + ) + return lcs_lengths[-1][-1] +``` + +dp問題はテーブルを埋める順番とか、どの情報を結局使っているのかとかに注目すれば空間計算量が削減できがちかも。 +この解答だったら直前の行だけ保存しておけばあとは減らすことができそう(でもわかりにくくなりそう) + +### Solution 2 + +なんかこんなコードも見つけたけど考えても意味がいまいちよくわからなかった。。。 + +```python +class Solution: + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + dp = [0] * (len(text1)) # text1がちょうどindexにある文字で最後のlcs文字を構成する時のlcsの長さ(?) + for char in text2: + curr_length = 0 + for i in range(len(dp)): + if curr_length < dp[i]: + curr_length = dp[i] + elif char == text1[i]: + dp[i] = curr_length + 1 + return max(dp, default=0) +``` diff --git a/swejp75/65 - 190. Reverse Bits.md b/swejp75/65 - 190. Reverse Bits.md new file mode 100644 index 0000000..68941db --- /dev/null +++ b/swejp75/65 - 190. Reverse Bits.md @@ -0,0 +1,24 @@ +### Solution 1 + +```python +class Solution: + def reverseBits(self, n: int) -> int: + result = 0 + for i in range(32): + if n & 1 << i: + result |= 1 << 31 - i + return result +``` + +### Solution 2 (分割統治) + +```python +class Solution: + def reverseBits(self, n: int) -> int: + n = (n & 0xffff0000) >> 16 | (n & 0x0000ffff) << 16 + n = (n & 0xff00ff00) >> 8 | (n & 0x00ff00ff) << 8 + n = (n & 0xf0f0f0f0) >> 4 | (n & 0x0f0f0f0f) << 4 + n = (n & 0xcccccccc) >> 2 | (n & 0x33333333) << 2 + n = (n & 0xaaaaaaaa) >> 1 | (n & 0x55555555) << 1 + return n +``` diff --git a/swejp75/66 - 191. Number of 1 Bits.md b/swejp75/66 - 191. Number of 1 Bits.md new file mode 100644 index 0000000..b8bde82 --- /dev/null +++ b/swejp75/66 - 191. Number of 1 Bits.md @@ -0,0 +1,46 @@ +### Solution 1 + +```python +class Solution: + def hammingWeight(self, n: int) -> int: + return n.bit_count() +``` + +### Solution 2 + +```python +class Solution: + def hammingWeight(self, n: int) -> int: + bit_count = 0 + while n: + bit_count += n & 1 + n >>= 1 + return bit_count +``` + +### Solution 3 + +```python +class Solution: + def hammingWeight(self, n: int) -> int: + n = (n & 0x55555555) + ((n & 0xaaaaaaaa) >> 1) + n = (n & 0x33333333) + ((n & 0xcccccccc) >> 2) + n = (n & 0x0f0f0f0f) + ((n & 0xf0f0f0f0) >> 4) + n = (n & 0x00ff00ff) + ((n & 0xff00ff00) >> 8) + n = (n & 0x0000ffff) + ((n & 0xffff0000) >> 16) + return n +``` + +### Solution 4 + +ループごとに一番右のビット1がなくなったものが出力されることを利用するっぽい + +```python +class Solution: + def hammingWeight(self, n: int) -> int: + result = 0 + while n: + n &= (n - 1) + result += 1 + return result +``` diff --git a/swejp75/67 - 268. Missing Number.md b/swejp75/67 - 268. Missing Number.md new file mode 100644 index 0000000..fee3f24 --- /dev/null +++ b/swejp75/67 - 268. Missing Number.md @@ -0,0 +1,20 @@ +### Solution 1 + +```python +class Solution: + def missingNumber(self, nums: List[int]) -> int: + return sum(range(len(nums) + 1)) - sum(nums) +``` + +### Solution 2 + +```python +class Solution: + def missingNumber(self, nums: List[int]) -> int: + cumulative_xor = 0 + for i in range(len(nums) + 1): + cumulative_xor ^= i + for num in nums: + cumulative_xor ^= num + return cumulative_xor +``` diff --git a/swejp75/68 - 338. Counting Bits.md b/swejp75/68 - 338. Counting Bits.md new file mode 100644 index 0000000..adbe827 --- /dev/null +++ b/swejp75/68 - 338. Counting Bits.md @@ -0,0 +1,56 @@ +### Solution 1 + +```python +class Solution: + def countBits(self, n: int) -> List[int]: + if n < 0: + raise ValueError("n must be 0 or greater") + result = [0] + while True: + if len(result) >= n + 1: + break + for i in range(len(result)): + result.append(result[i] + 1) + if len(result) >= n + 1: + break + return result +``` + +### Solution 2 + +これが一番読みやすいかな + +```python +class Solution: + def countBits(self, n: int) -> List[int]: + if n < 0: + raise ValueError("n must be 0 or greater") + result = [0] + loop_length = 1 + corresponding_index = 0 + for _ in range(1, n + 1): + result.append(result[corresponding_index] + 1) + corresponding_index += 1 + if corresponding_index >= loop_length: + loop_length *= 2 + corresponding_index = 0 + return result +``` + +### Solution 3 + +offsetを引いてindexを出す方法 + +```python +class Solution: + def countBits(self, n: int) -> List[int]: + if n < 0: + raise ValueError("n must be 0 or greater") + result = [0] + offset = 1 + for i in range(1, n + 1): + if i == offset * 2: + offset *= 2 + result.append(result[i - offset] + 1) + return result +``` diff --git a/swejp75/69 - 146. LRU Cache.md b/swejp75/69 - 146. LRU Cache.md new file mode 100644 index 0000000..ed97327 --- /dev/null +++ b/swejp75/69 - 146. LRU Cache.md @@ -0,0 +1,207 @@ +### Solution 1 + +50分ぐらいかかった + +```python +class LRUCache: + + def __init__(self, capacity: int): + self._capacity = capacity + self._values = dict() + self._prevs = dict() + self._nexts = dict() + self._head = None + self._tail = None + + def _evict_tail(self): + if self._tail is None: + raise Exception + next_tail = self._nexts[self._tail] + del self._values[self._tail] + del self._prevs[self._tail] + del self._nexts[self._tail] + if next_tail is not None: + self._prevs[next_tail] = None + self._tail = next_tail + + def _refresh(self, key: int): + if not key in self._values: + raise Exception + if key == self._head: + return + next_key = self._nexts[key] + prev_key = self._prevs[key] + if key == self._tail: + self._tail = next_key + if next_key is not None: + self._prevs[next_key] = prev_key + if prev_key is not None: + self._nexts[prev_key] = next_key + if self._head is not None: + self._nexts[self._head] = key + self._prevs[key] = self._head + self._head = key + + def get(self, key: int) -> int: + if key in self._values: + self._refresh(key) + return self._values[key] + else: + return -1 + + def put(self, key: int, value: int) -> None: + if key not in self._values: + if self._head is not None: + self._nexts[self._head] = key + self._prevs[key] = self._head + self._head = key + if not self._values: + self._tail = key + else: + self._refresh(key) + self._values[key] = value + if len(self._values) > self._capacity: + self._evict_tail() +``` + +### Solution 2 + +Nodeクラスを使うようにしてみた + +```python +class LRUCache: + + class Node: + def __init__(self, key=None, value=None): + self.key = key + self.value = value + self.next = None + self.prev = None + + def __init__(self, capacity: int): + self._capacity = capacity + self._sentinel = LRUCache.Node() + self._sentinel.next = self._sentinel + self._sentinel.prev = self._sentinel + self._key_to_node = dict() + + def _detach(self, node): + prev_node = node.prev + next_node = node.next + prev_node.next = next_node + next_node.prev = prev_node + + def _insert_front(self, node): + node.prev = self._sentinel + node.next = self._sentinel.next + node.prev.next = node + node.next.prev = node + + def get(self, key: int) -> int: + if key not in self._key_to_node: + return -1 + node = self._key_to_node[key] + self._detach(node) + self._insert_front(node) + return node.value + + def put(self, key: int, value: int) -> None: + if key in self._key_to_node: + node = self._key_to_node[key] + self._detach(node) + self._insert_front(node) + node.value = value + return + node = LRUCache.Node(key, value) + self._insert_front(node) + self._key_to_node[key] = node + if len(self._key_to_node) > self._capacity: + tail_node = self._sentinel.prev + self._detach(tail_node) + del self._key_to_node[tail_node.key] +``` + +### Solution 3 (Pythonのdictの順序性を使ってみる) + +```python +class LRUCache: + + def __init__(self, capacity: int): + self._capacity = capacity + self._dict = dict() + + def get(self, key: int) -> int: + if key in self._dict: + value = self._dict[key] + del self._dict[key] + self._dict[key] = value + return value + else: + return -1 + + def put(self, key: int, value: int) -> None: + if key in self._dict: + del self._dict[key] + self._dict[key] = value + if len(self._dict) > self._capacity: + first_key = next(iter(self._dict)) + del self._dict[first_key] +``` + +OrderedDictバージョン + +```python +class LRUCache: + + def __init__(self, capacity: int): + self._capacity = capacity + self._ordered_dict = OrderedDict() + + def get(self, key: int) -> int: + if key in self._ordered_dict: + self._ordered_dict.move_to_end(key) + return self._ordered_dict[key] + else: + return -1 + + def put(self, key: int, value: int) -> None: + self._ordered_dict[key] = value + self._ordered_dict.move_to_end(key) + if len(self._ordered_dict) > self._capacity: + self._ordered_dict.popitem(last=False) +``` + +### Solution 4 (Heap + Lazy Removal) + +```python +class LRUCache: + + def __init__(self, capacity: int): + self._capacity = capacity + self._key_to_value = dict() + self._last_used = dict() + self._last_used_heap = [] + self._time = 0 + + def get(self, key: int) -> int: + if key in self._key_to_value: + self._last_used[key] = self._time + heapq.heappush(self._last_used_heap, (self._time, key)) + output = self._key_to_value[key] + else: + output = -1 + self._time += 1 + return output + + def put(self, key: int, value: int) -> None: + self._key_to_value[key] = value + self._last_used[key] = self._time + heapq.heappush(self._last_used_heap, (self._time, key)) + if len(self._key_to_value) > self._capacity: + while True: + time, key = heapq.heappop(self._last_used_heap) + if self._last_used[key] == time: + break + del self._key_to_value[key] + self._time += 1 +``` diff --git a/swejp75/70 - 1472. Design Browser History.md b/swejp75/70 - 1472. Design Browser History.md new file mode 100644 index 0000000..911e11a --- /dev/null +++ b/swejp75/70 - 1472. Design Browser History.md @@ -0,0 +1,39 @@ +### Solution 1 + +もうちょっと早くもできるけどユースケースを考えるとこれでいいんじゃないだろうか +(グラフ構造みたいにすればvisitが来たときに先にあるpathが消えないようにもできるかもと思った) + +```python +class BrowserHistory: + + def __init__(self, homepage: str): + self._history = [homepage] + self._current_index = 0 + + def visit(self, url: str) -> None: + while self._current_index < len(self._history) - 1: + self._history.pop() + self._history.append(url) + self._current_index += 1 + + def back(self, steps: int) -> str: + self._current_index = max(self._current_index - steps, 0) + return self._history[self._current_index] + + def forward(self, steps: int) -> str: + self._current_index = min(self._current_index + steps, len(self._history) - 1) + return self._history[self._current_index] +``` + +```python +while self._current_index < len(self._history) - 1: + self._history.pop() +``` + +ここは + +```python +del self._history[self._current_index + 1:] +``` + +でも良いかも diff --git a/swejp75/71 - 432. All O`one Data Structure.md b/swejp75/71 - 432. All O`one Data Structure.md new file mode 100644 index 0000000..689a9a5 --- /dev/null +++ b/swejp75/71 - 432. All O`one Data Structure.md @@ -0,0 +1,166 @@ +### Solution 1 + +初めに書いたバージョン(1時間半の格闘の末できた) + +```python +class AllOne: + + class Node: + def __init__(self, count): + self.count = count + self.keys = set() + self.next = None + self.prev = None + + def __init__(self): + self.key_to_count = dict() + self.count_to_node = dict() + self.zero_node = AllOne.Node(0) + self.infinity_node = AllOne.Node(float("inf")) + self.zero_node.next = self.infinity_node + self.infinity_node.prev = self.zero_node + self.count_to_node[0] = self.zero_node + + def _debug(self): + node = self.zero_node + while node is not None: + print(node.count, node.keys) + node = node.next + + def _delete_node(self, node): + prev_node = node.prev + next_node = node.next + prev_node.next = next_node + next_node.prev = prev_node + del self.count_to_node[node.count] + + def _insert_node(self, node, prev_node, next_node): + node.prev = prev_node + node.next = next_node + prev_node.next = node + next_node.prev = node + self.count_to_node[node.count] = node + + def inc(self, key: str) -> None: + count = self.key_to_count.get(key, 0) + old_node = self.count_to_node[count] + if old_node.next.count == count + 1: + new_node = old_node.next + else: + new_node = AllOne.Node(count + 1) + self._insert_node(new_node, old_node, old_node.next) + if count > 0: + old_node.keys.remove(key) + if not old_node.keys: + self._delete_node(old_node) + new_node.keys.add(key) + self.key_to_count[key] = count + 1 + # self._debug() + + def dec(self, key: str) -> None: + count = self.key_to_count[key] + old_node = self.count_to_node[count] + if count >= 2: + if old_node.prev.count == count - 1: + new_node = old_node.prev + else: + new_node = AllOne.Node(count - 1) + self._insert_node(new_node, old_node.prev, old_node) + new_node.keys.add(key) + self.key_to_count[key] -= 1 + else: + del self.key_to_count[key] + old_node.keys.remove(key) + if not old_node.keys: + self._delete_node(old_node) + # self._debug() + + def getMaxKey(self) -> str: + if self.infinity_node.prev == self.zero_node: + return "" + return next(iter(self.infinity_node.prev.keys)) + + def getMinKey(self) -> str: + if self.zero_node.next == self.infinity_node: + return "" + return next(iter(self.zero_node.next.keys)) +``` + +### Solution 2 (改良後) + +読みやすくなったかな? + +```python +class AllOne: + + class Node: + def __init__(self, count: int): + self.count = count + self.keys = set() + self.prev = None + self.next = None + + def __init__(self): + self._zero_node = AllOne.Node(0) + self._inf_node = AllOne.Node(float("inf")) + self._zero_node.next = self._inf_node + self._inf_node.prev = self._zero_node + self._key_to_node = dict() + + def _delete_node(self, node): + prev_node = node.prev + next_node = node.next + prev_node.next = next_node + next_node.prev = prev_node + + def _insert_node_between(self, node, prev_node, next_node): + node.prev = prev_node + node.next = next_node + prev_node.next = node + next_node.prev = node + + def inc(self, key: str) -> None: + if key in self._key_to_node: + old_node = self._key_to_node[key] + else: + old_node = self._zero_node + if old_node.next.count == old_node.count + 1: + new_node = old_node.next + else: + new_node = AllOne.Node(old_node.count + 1) + self._insert_node_between(new_node, old_node, old_node.next) + if old_node is not self._zero_node: + old_node.keys.remove(key) + if not old_node.keys: + self._delete_node(old_node) + new_node.keys.add(key) + self._key_to_node[key] = new_node + + def dec(self, key: str) -> None: + if key not in self._key_to_node: + raise Exception + old_node = self._key_to_node[key] + if old_node.prev.count == old_node.count - 1: + new_node = old_node.prev + else: + new_node = AllOne.Node(old_node.count - 1) + self._insert_node_between(new_node, old_node.prev, old_node) + old_node.keys.remove(key) + if not old_node.keys: + self._delete_node(old_node) + if new_node is self._zero_node: + del self._key_to_node[key] + return + new_node.keys.add(key) + self._key_to_node[key] = new_node + + def getMaxKey(self) -> str: + if self._inf_node.prev is self._zero_node: + return "" + return next(iter(self._inf_node.prev.keys)) + + def getMinKey(self) -> str: + if self._zero_node.next is self._inf_node: + return "" + return next(iter(self._zero_node.next.keys)) +``` diff --git a/swejp75/72 - 50. Pow(x, n).md b/swejp75/72 - 50. Pow(x, n).md new file mode 100644 index 0000000..7d115dc --- /dev/null +++ b/swejp75/72 - 50. Pow(x, n).md @@ -0,0 +1,15 @@ +### Solution 1 + +```python +class Solution: + def myPow(self, x: float, n: int) -> float: + if n < 0: + return 1 / self.myPow(x, -n) + result = 1 + while n: + if n & 1: + result *= x + x *= x + n //= 2 + return result +``` diff --git a/swejp75/73 - 202. Happy Number.md b/swejp75/73 - 202. Happy Number.md new file mode 100644 index 0000000..f1dc4e3 --- /dev/null +++ b/swejp75/73 - 202. Happy Number.md @@ -0,0 +1,42 @@ +### Solution 1 + +```python +class Solution: + def isHappy(self, n: int) -> bool: + seen = set() + number = n + seen.add(number) + while number != 1: + new_number = 0 + processing = number + while processing: + processing, digit = divmod(processing, 10) + new_number += digit ** 2 + if new_number in seen: + return False + number = new_number + seen.add(number) + return True +``` + +### Solution 2 (ウサギとかめアルゴリズム) + +空間計算量O(1)(ウサギとかめアルゴリズムの応用) 全然思いつかなかった。 + +```python +class Solution: + def isHappy(self, n: int) -> bool: + def next_number_of(number): + new_number = 0 + processing = number + while processing: + processing, digit = divmod(processing, 10) + new_number += digit ** 2 + return new_number + slow = n + fast = next_number_of(n) + while fast != 1 and slow != fast: + slow = next_number_of(slow) + fast = next_number_of(next_number_of(fast)) + return fast == 1 +``` diff --git a/swejp75/74 - 48. Rotate Image.md b/swejp75/74 - 48. Rotate Image.md new file mode 100644 index 0000000..4dddc5e --- /dev/null +++ b/swejp75/74 - 48. Rotate Image.md @@ -0,0 +1,79 @@ +### Solution 1 + +```python +class Solution: + def rotate(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + def swap(position1, position2): + i1, j1 = position1 + i2, j2 = position2 + matrix[i1][j1], matrix[i2][j2] = matrix[i2][j2], matrix[i1][j1] + rotated = set() + def rotate_group(i, j): + position1 = (i, j) + position2 = (n - 1 - j, i) + position3 = (n - 1 - i, n - 1 - j) + position4 = (j, n - 1 - i) + swap(position1, position2) + swap(position2, position3) + swap(position3, position4) + rotated.add(position1) + rotated.add(position2) + rotated.add(position3) + rotated.add(position4) + n = len(matrix) + for i in range(n): + for j in range(n): + if (i, j) not in rotated: + rotate_group(i, j) +``` + +### Solution 2 + +Solution 1の改良版 + +```python +class Solution: + def rotate(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + def swap(position1, position2): + i1, j1 = position1 + i2, j2 = position2 + matrix[i1][j1], matrix[i2][j2] = matrix[i2][j2], matrix[i1][j1] + def rotate_group(i, j): + position1 = (i, j) + position2 = (n - 1 - j, i) # position1 rotated 90deg counter-clockwise + position3 = (n - 1 - i, n - 1 - j) # position2 rotated 90deg counter-clockwise + position4 = (j, n - 1 - i) # position3 rotated 90deg counter-clockwise + swap(position1, position2) + swap(position2, position3) + swap(position3, position4) + n = len(matrix) + for i in range(n): + for j in range(i, n - i - 1): + rotate_group(i, j) +``` + +### Solution 3 + +どうやら転置+反転でも右回転が表現できるらしい + +```python +class Solution: + def rotate(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + n = len(matrix) + for i in range(n): + for j in range(i): + matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] + for row in matrix: + row.reverse() +``` + +回転位置をcomplex numberか何かで計算する方法もありそう。一瞬挑戦したけど難しかったので諦めました・・・ diff --git a/swejp75/75 - 73. Set Matrix Zeroes.md b/swejp75/75 - 73. Set Matrix Zeroes.md new file mode 100644 index 0000000..71de19a --- /dev/null +++ b/swejp75/75 - 73. Set Matrix Zeroes.md @@ -0,0 +1,66 @@ +### Solution 1 + +空間計算量O(n+m) + +```python +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + zero_rows = set() + zero_cols = set() + for row in range(len(matrix)): + for col in range(len(matrix[0])): + if matrix[row][col] == 0: + zero_rows.add(row) + zero_cols.add(col) + for row in range(len(matrix)): + for col in range(len(matrix[0])): + if row in zero_rows or col in zero_cols: + matrix[row][col] = 0 +``` + +Follow up: + +A straightforward solution using O(mn) space is probably a bad idea. +A simple improvement uses O(m + n) space, but still not the best solution. +Could you devise a constant space solution? + +無理じゃね? + +### Solution 2 (constant space) + +入力をマークのためのメモリとして利用してしまう方法。全然思いつかなかった +本当はrowだけを保存しておけば良いようにする方法もあるみたい(YouTubeの動画参照)ですがループする順番とかが重要になってわかりにくい気がしたのでrowとcolの二つを保存するようにしてみました(どっちのがいいですかね?) + +```python +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + should_first_row_be_zeroed = False + should_first_col_be_zeroed = False + for r in range(len(matrix)): + if matrix[r][0] == 0: + should_first_col_be_zeroed = True + for c in range(len(matrix[0])): + if matrix[0][c] == 0: + should_first_row_be_zeroed = True + for r in range(1, len(matrix)): + for c in range(1, len(matrix[0])): + if matrix[r][c] == 0: + matrix[r][0] = 0 + matrix[0][c] = 0 + for r in range(1, len(matrix)): + for c in range(1, len(matrix[0])): + if matrix[r][0] == 0 or matrix[0][c] == 0: + matrix[r][c] = 0 + if should_first_col_be_zeroed: + for r in range(len(matrix)): + matrix[r][0] = 0 + if should_first_row_be_zeroed: + for c in range(len(matrix[0])): + matrix[0][c] = 0 +```