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
90 changes: 90 additions & 0 deletions memo.md
Original file line number Diff line number Diff line change
@@ -1 +1,91 @@
# Step1

## Code1-1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分でも解いてみまし。シンプルな解法が思いつかず、 range query を平方分割で実装して解きました。初めは Binary Indexed Tree を二次元配列で書き始めたのですが、更新部分が上手く書けず、平方分割に逃げました。
供養のために貼っておきます。

class Solution {
public:
    bool canReach(string s, int minJump, int maxJump) {
        if (s[s.size() - 1] != '0') {
            return false;
        }

        vector<int8_t> reached(BASE * BASE);
        vector<int> reached_sqrt_count(BASE);
        set(reached, reached_sqrt_count, 0, 1);

        for (int i = 0; i < s.size(); ++i) {
            if (s[i] != '0') {
                continue;
            }

            if (!get(reached, reached_sqrt_count, i)) {
                continue;
            }

            set(reached, reached_sqrt_count, i + minJump, min(i + maxJump, (int)s.size() - 1) + 1);
        }

        return get(reached, reached_sqrt_count, s.size() - 1);
    }

    void set(vector<int8_t>& reached, vector<int>& reached_sqrt_count, int begin, int end) {
        int left = (begin + BASE - 1) / BASE * BASE;
        int right = end / BASE * BASE;
        for (int bucket = left / BASE; bucket < right / BASE; ++bucket) {
            reached_sqrt_count[bucket] = BASE;
        }

        if (begin / BASE == end / BASE) {
            for (int i = begin; i < end; ++i) {
                set(reached, reached_sqrt_count, i);
            }
        } else {
            for (int i = begin; i < left; ++i) {
                set(reached, reached_sqrt_count, i);
            }

            for (int i = right; i < end; ++i) {
                set(reached, reached_sqrt_count, i);
            }
        }
    }

    void set(vector<int8_t>& reached, vector<int>& reached_sqrt_count, int i) {
        if (reached[i]) {
            return;
        }
        reached[i] = true;
        ++reached_sqrt_count[i / BASE];
    }

    bool get(const vector<int8_t>& reached, const vector<int>& reached_sqrt_count, int index) {
        return reached_sqrt_count[index / BASE] == BASE ||
            reached[index];
    }

private:
    static constexpr const int BASE = 1000;
};

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

reached_sqrt_count[i]はバケットiのうち到達可能な個数を表しているのですか?

        for (int bucket = left / BASE; bucket < right / BASE; ++bucket) {
            reached_sqrt_count[bucket] = BASE;
        }

この部分の更新は, バケット内すべてを到達可能とみなしているように思いましたが、バケット内のインデックスjでs[j]=1なるjに対しても, 到達可能としていることになりませんか?

Copy link
Copy Markdown

@nodchip nodchip May 28, 2026

Choose a reason for hiding this comment

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

その理解であっているように思います。

本来やりたいことは reached の [begin, end) の範囲を true にすることです。一方、これには O(N) かかります。これを避けるため、 reached を sqrt(reached.size()) 個のバケットに分け、 reached の [left, right) の範囲を true とする代わりに、対応する reached_sqrt_count を BASE に設定しています。これによりO(sqrt(N)) になります。
また、 get() する際に、 reached_sqrt_count が BASE となっている、つまりバケット内のすべての reached が true となっているか、 reached[index] のどちらがが true となっていれば、 true を返すようにしています。

ただ、 set() が間違っていることに気づきました。正しくは以下の通りです。

    void set(vector<int8_t>& reached, vector<int>& reached_sqrt_count, int i) {
        if (get(reached, reached_sqrt_count, i)) {
            return;
        }
        reached[i] = true;
        ++reached_sqrt_count[i / BASE];
    }

元のコードでも Accepted してしまっていました。 LeetCode のテストケースが弱かったためだと思います。

なお、平方分割はソフトウェアエンジニアの常識には含まれていないと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

理解できました。平方分割は自分もよくわからなくて調べながら理解したのですが、解法として浮かんでいなかったので勉強になりました。


* O(N^2)になるので, TLEになった

```python
import functools


class Solution:
def canReach(self, s: str, minJump: int, maxJump: int) -> bool:
@functools.cache
def can_reach_helper(i: int):
if i == len(s) - 1:
return s[i] == "0"

if s[i] == "1":
return False

for jump in range(minJump, maxJump + 1):
if i + jump >= len(s):
break
if can_reach_helper(i + jump):
return True

return False

return can_reach_helper(0)

```

## Code1-2

* O(N)でやりたい
* 各インデックスごとに, いける範囲を引き継ぐ
* `s = "011010", minJump = 2, maxJump = 3`
* `01[10]10`
* `01101[0`
* `s = "010010", minJump = 2, maxJump = 3`
* `01[00]10`
* `01[00](10)`
* 範囲の右端にきたら, その範囲はそれ以上残しておく必要はない
* 範囲の寿命は, その範囲の右端にくるまでだから, 範囲の長さ(最大でN)
* 各インデックスごとに, 対象の範囲をみるとすると, 結局最大でO(N^2)になる
* いける範囲に重なりがあるから, それをうまいこと対処できたら嬉しい気がする
* これが常に2区間に抑えられるならいいけど、飛び石上になっていたらめんどう
* `01110000000000, min=4, max=5`
* `0111[00]00000000`
* `0111[00]00(00)0000`
* `0111[00]00(0{0)0}000`
* `0111[00]00(0{0)0}0|00|`
* `0111[00]00(0{0)0}0|0"0|`
* 到達可能かどうかをTrue/Falseで保存しておく
* Trueとなっている0の値に到達するたびに, 配列を更新する
* すでにいけると更新をしたおしりは記録しておく
* `010010`, `TFFFFF`
* `0100|10`, `TFTTFF`
* `010010|`, `TFTTFT`
* ある時点iでいけない点j(s[j]=0)があったとき, i以降のs[k]=0となる点をもってしても, jには到達できない
* AC


```python
class Solution:
def canReach(self, s: str, minJump: int, maxJump: int) -> bool:
if not s:
return True
if s[0] == "1":
return False

reachable = [False] * len(s)
reachable[0] = True

updated_tail = 0
for i in range(len(s)):
if updated_tail >= len(s):
break
if s[i] == "1":
continue
if not reachable[i]:
continue
for j in range(max(updated_tail + 1, i + minJump), min(len(s), i + maxJump + 1)):
if s[j] == "0":
reachable[j] = True

updated_tail = i + maxJump

return reachable[-1]

```