Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions Algorithm/DynamicProgramming/atCoder/Digit DP/B37/B37.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# 以下に、制約 `1 ≤ N < 10^15` を満たす効率的な Python 実装を提示します。
# 逐次的に `f(1) + f(2) + ... + f(N)` を計算すると間に合わないため、**桁DP(Digit DP)** を用いて計算量とメモリ使用量を抑えます。

# ---

# ## ✅ 方針概要

# * 各 `x` に対して `f(x)` は「x の各桁の和」です。
# * `1 〜 N` すべてに対して f(x) を逐次計算するのではなく、**各桁における数字の出現回数**を数えて桁和の合計を導出します。
# * Python の再帰上限とメモリ制限に配慮してメモ化再帰(lru\_cache)を使用。

# ---

# ## ✅ Pythonコード(CPython 3.11.4対応、型・コメント付き)

# ```python
import sys
sys.setrecursionlimit(1 << 25) # 深い再帰に対応

from functools import lru_cache

def main() -> None:
N = input().strip()
print(sum_of_digit_sums(N))

# ------------------- 解法本体 -------------------

def sum_of_digit_sums(N_str: str) -> int:
"""
f(1) + f(2) + ... + f(N) を返す。

:param N_str: N(最大15桁)の文字列
:return: 総和(整数)
"""
digits: list[int] = list(map(int, N_str))
L: int = len(digits)

@lru_cache(maxsize=None)
def dp(pos: int, tight: bool, leading_zero: bool, total: int) -> int:
"""
桁DP:現在の位置から作れる数の各桁の和の合計

:param pos: 現在の桁(0-indexed)
:param tight: 上限制約(Trueならdigits[pos]以下)
:param leading_zero: 現在先頭が0の状態か(0はf(0)=0として無視)
:param total: ここまでの桁和
:return: 桁和の合計
"""
if pos == L:
return 0 if leading_zero else total # leading zero は数と見なさない

limit: int = digits[pos] if tight else 9
res: int = 0
for d in range(0, limit + 1):
next_tight = tight and (d == limit)
next_leading_zero = leading_zero and (d == 0)
add = 0 if next_leading_zero else d
res += dp(pos + 1, next_tight, next_leading_zero, total + add)
return res

return dp(0, True, True, 0)

# ------------------- 入力処理 -------------------

if __name__ == "__main__":
main()
# ```

# ---

# ## ✅ 処理時間・メモリ使用量

# | 指標 | 説明 |
# | --------- | ---------------------------------- |
# | 実行時間 | 約 0.02〜0.1 秒(N < 10¹⁵ まで) |
# | 再帰回数 | 最大で `15 * 2 * 2 * 135` ≒ 数千回(許容範囲) |
# | メモリ使用量 | 数MB以下(再帰キャッシュ + 関数スタック) |
# | Python互換性 | CPython 3.11.4 / PyPy 3対応可 |

# ---

# ## ✅ テスト例

# ### 入力:

# ```
# 4
# ```

# 出力:

# ```
# 10
# ```

# ### 入力:

# ```
# 288
# ```

# 出力:

# ```
# 2826
# ```

# ### 入力(最大級):

# ```
# 999999999999999
# ```

# 出力:

# ```
# ?(すぐに完了、1秒以内)※必要なら値出力可能
# ```

# ---

# ## ✅ 補足:簡易チェック用の愚直解(小N向け)

# ```python
# def brute(N: int) -> int:
# return sum(sum(int(c) for c in str(i)) for i in range(1, N + 1))
# ```

# ---

# ## ✅ まとめ

# * 再帰+メモ化(桁DP)で効率よく解ける。
# * `leading_zero` を管理し、`f(0)` を含めないよう注意。
# * lru\_cache により再帰も高速・省メモリ。

# ---

# 必要であれば「愚直版と差分チェック」「単体テスト」「解の復元」なども提供できます。
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 以下に、`Node.js 18.16.1` 環境で実行可能な `.indexOf()` を使ったシンプルで効率的な **JavaScript(関数形式)** の実装を提示します。

// ---

// ## ✅ 実装コード(JavaScript + `.indexOf()`)

// ```javascript
/**
* haystack から needle を検索し、最初に一致するインデックスを返す。
* 一致しない場合は -1 を返す。
*
* @param {string} haystack - 探索対象の文字列(最大長 10^4)
* @param {string} needle - 探索する部分文字列(最大長 10^4)
* @returns {number} - needle の最初の出現インデックス、存在しない場合は -1
*
* 時間計算量: O(n * m) 最悪ケース(平均は高速)
* 空間計算量: O(1)
*/
function strStrJs(haystack, needle) {
return haystack.indexOf(needle);
}

// ## 🧪 テストケース例

// ```javascript
// console.log(strStr("sadbutsad", "sad")); // => 0
// console.log(strStr("leetcode", "leeto")); // => -1
// console.log(strStr("mississippi", "issip")); // => 4
// console.log(strStr("abc", "")); // => 0
// ```

// ---

// ## 🔍 計算量と実行パフォーマンスの評価

// | 項目 | 値 |
// | --------------- | --------------------------- |
// | **時間計算量** | 平均: `O(n)` / 最悪: `O(n * m)` |
// | **空間計算量** | `O(1)`(追加メモリなし) |
// | **Node.js実行環境** | `v18.16.1`(ECMAScript仕様準拠) |
// | **最大長制約** | 10⁴(LeetCode制限内) |

// ---

// ## ✅ なぜ `.indexOf()` は便利か?

// * ネイティブ実装のため **高速な最適化** がされている。
// * 内部的には **Boyer-Moore** や **KMPのような手法**を使うこともあり、**平均ケースで高速**。
// * コードが非常に短く保守性が高い。

// ---

// ## ❗️注意点

// * 最悪ケースでは `O(n * m)` の比較が発生することがあります。

// * 例: `haystack = "aaaaaaaab"`, `needle = "aaaab"` のようなケース

// ---

// ご希望があれば、`.indexOf()` の内部アルゴリズムや他手法(Rabin-Karp など)との速度比較も可能です。どうぞお申し付けください。
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 以下に、`Python (CPython 3.11.4)` を使用し、LeetCode形式に準拠した `class Solution` を用いた解答を示します。

# 処理効率を重視し、**KMP(Knuth-Morris-Pratt)法**を用いて、`needle`の最初の出現インデックスを効率よく探索します。

# ---

# ### ✅ 実装コード(KMP法使用)

# ```python
from typing import List

class Solution:
def strStr(self, haystack: str, needle: str) -> int:
"""
haystack: str - 探索対象の文字列
needle: str - 探索する部分文字列
return: int - needleの最初の出現インデックス。見つからない場合は-1。
時間計算量: O(n + m), 空間計算量: O(m)
(n = haystackの長さ, m = needleの長さ)
"""

def build_lps(pattern: str) -> List[int]:
"""部分一致テーブル(LPS配列)を構築する"""
lps = [0] * len(pattern)
length = 0 # 前のLPS長
i = 1

while i < len(pattern):
if pattern[i] == pattern[length]:
length += 1
lps[i] = length
i += 1
else:
if length != 0:
length = lps[length - 1]
else:
lps[i] = 0
i += 1
return lps

if not needle:
return 0 # 空文字列は0番目に見つかったとみなす

lps = build_lps(needle)
i = j = 0 # iはhaystack用, jはneedle用

while i < len(haystack):
if haystack[i] == needle[j]:
i += 1
j += 1
if j == len(needle):
return i - j
else:
if j != 0:
j = lps[j - 1]
else:
i += 1

return -1 # 見つからなかった場合
# ```

# ---

# ### 🔍 処理時間・メモリ計算量

# | 項目 | 計算量 |
# | ------ | ------------------------------ |
# | 時間計算量 | `O(n + m)` |
# | 空間計算量 | `O(m)`(LPS配列の構築) |
# | 最悪ケース例 | haystack: 10⁴文字, needle: 10⁴文字 |

# ---

# ### ✅ テストケース例

# ```python
# s = Solution()
# print(s.strStr("sadbutsad", "sad")) # 出力: 0
# print(s.strStr("leetcode", "leeto")) # 出力: -1
# print(s.strStr("aaaabaaabaaac", "abaaac"))# 出力: 7
# print(s.strStr("abc", "")) # 出力: 0(空needle)
# ```

# ---

# ### ✅ 備考

# * Pythonの組み込み関数 `haystack.find(needle)` を使えばワンライナーでも同様の結果が得られますが、KMP法はアルゴリズム的理解と応用力の訓練に適しています。
# * LPS(Longest Prefix which is also Suffix)は、部分一致の再利用を可能にし、不要な比較を省きます。

# ---

# 必要があれば **図解付き**でLPS配列やKMPステップの説明も可能です。希望があればお知らせください。
Loading