Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions Python3/276. Paint Fence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
## Step 1. Initial Solution

- 初めは制約を気にせずに全ての出現をカウントする方法で実装したがTLEになったので修正
- O(k^n)なので最大10^1500となり、明らかに不可能
- 数学的に一つ前の状態からの遷移を考えて以下のように実装

```python
class Solution:
def countWays(self, n: int, k: int) -> int:
nonconsecutive = k
consecutive = 0
for i in range(1, n):
temp = nonconsecutive
nonconsecutive = (k - 1) * (nonconsecutive + consecutive)
consecutive = temp
return consecutive + nonconsecutive
```

### Complexity Analysis

- 時間計算量:O(n)
- 空間計算量:O(1)

## Step 2. Alternatives

- nonconsecutive, consecutiveとかではなく、一個前, 更にその前という意味で捉えている人が多そう
- https://github.com/ryoooooory/LeetCode/pull/33/files#diff-5f9ade2f5113d8c983ce290c7d633764f0878ef32ad9d23c1b924860ca38bf43R29
- 他の選択肢の幅としては上の理解を踏まえるとリストや辞書型にそのインデックスまでの方法を保持しておくものがある
- 再帰的に解く方法もある
- その際、@cacheで2回以上使う出力を保持する方法がある
- 注意点として、countWaysが複数回呼ばれる場合やインスタンスによってキャッシュが使えるかという問題がある
- 基本的には使う関数の上で良いが、複数インスタンス間で共有したい場合はメソッドとして定義せずに外部で関数として定義するのが良いかもしれないという話
- https://github.com/olsen-blue/Arai60/pull/30/files#r1957966512
- 行列の累乗で求めることもできるらしい
- 累乗は繰り返し二乗法によりlog nで求められる、とのこと
- 使うかはさておき面白い知識だと思った
- https://github.com/tokuhirat/LeetCode/pull/30/files#r2126104372
- あとはlru_cacheが話題に上っているので実装してみる
- 基本的にはHashMapでキャッシュした値にアクセスし、最近アクセスしていないものを消せるようにすることが目的
- https://leetcode.com/problems/lru-cache/
- を元にしてgetとputが外部から呼び出される前提で実装
- https://github.com/olsen-blue/Arai60/pull/30/files#diff-50f7b6331a7f8355594f718601d9bd00080e614a343f52dd67abdc08da922ebaR76
- 凡その流れは分かったのでそのうち復習する

```python
class Node:
def __init__(self, key: int, val: int):
self.key = key
self.val = val
self.previous = None
self.next = None

class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = {}
self.sentinel = Node(-1, -1)
self.sentinel.previous = self.sentinel
self.sentinel.next = self.sentinel
self.size = 0

def get(self, key: int) -> int:
if key not in self.cache:
return -1
node = self.cache[key]
self.remove(key)
self.add(key, node.val)
Comment on lines +66 to +67

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get のときは、既存のキャッシュされたノードが再利用できそうです。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node.valの代わりにnodeを渡した方が良いということですね。
putでvalを入れたかったのでaddの中で作るように統一したんですが、確かに再利用できることを考えるとputでnode作ってnodeを渡す方が良いですね

return node.val

def put(self, key: int, val: int) -> None:
if key in self.cache:
self.remove(key)
if key not in self.cache and self.size == self.capacity:
self.remove(self.sentinel.previous.key)
self.add(key, val)

def remove(self, key: int) -> None:
node = self.cache[key]
node.previous.next = node.next
node.next.previous = node.previous
del self.cache[key]
self.size -= 1

def add(self, key: int, val:int) -> None:
node = Node(key, val)
self.cache[key] = node
if self.sentinel.previous == self.sentinel:
self.sentinel.previous = node
Comment on lines +87 to +88

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここはLinkedListが空のケース判定だと思いますが、おそらく不要かなと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sentinel.previousにLRUのnodeを入れている実装なんですが、基本は追加するノード周りの更新作業しかしないので末尾がつながるようにするには、一つ目のnodeを入れる時につないでおく必要があると思っています。
putでキャパオーバーの時にsentinel.previousを消すときに、ここがないとsentinel.previousは初期状態のまま変更されないので上手くいかないと思います。(ちゃんと実行してなくてすみません)

node.previous = self.sentinel
node.next = self.sentinel.next
node.next.previous = node
self.sentinel.next = node
self.size += 1
```

## Step 3. Final Solution

- 上も悪くはないが、下のStep1のやり方の方が良さそう

```python
class Solution:
def countWays(self, n: int, k: int) -> int:
ways_to_index: dict[int, int] = {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

バケット探索・衝突解決等考えると、配列の方が若干パフォーマンスが高いかもしれないと思いました。メモリ効率も良さそうです。(そもそも直前とその前の要素のみ保持すればという話もありますが)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうなんですね。割と癖でインデックスまでの〇〇を辞書型にしてしまっている節があるので気を付けようと思います。

ways_to_index[1] = k
ways_to_index[2] = k*k
for i in range(3, n + 1):
ways_to_index[i] = \
(k - 1) * (ways_to_index[i-1] + ways_to_index[i-2])
return ways_to_index[n]
```

```python
class Solution:
def countWays(self, n: int, k: int) -> int:
end_same = k
end_dif = 0
for i in range(1, n):
temp = end_same
end_same = (k - 1) * (end_same + end_dif)
end_dif = temp
return end_same + end_dif
```