Skip to content
Merged
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
354 changes: 354 additions & 0 deletions Mathematics/Number Theory/Constructing_a_Number.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "97dc61cc",
"metadata": {},
"source": [
"まず結論から言うと、この問題は「すべての数字の **各桁の合計** が 3 で割り切れるかどうか」だけ見れば解けます。\n",
"\n",
"---\n",
"\n",
"## 1. 問題分析結果\n",
"\n",
"### 本質\n",
"\n",
"* 3 の倍数の判定条件:\n",
"\n",
" * ある整数が 3 で割り切れるかどうかは、「その数の **全ての桁の和** が 3 で割り切れるかどうか」で決まる。\n",
"* 桁を並べ替えても、使う桁の集合(マルチセット)が同じなら、**桁の和は不変**。\n",
"* この問題では、\n",
"\n",
" * 「与えられた整数列の **すべての桁を一度ずつ** 使う」\n",
" * 「並べ替え自由(順番は自由)」\n",
" * 「先頭が 0 でもよい」\n",
"* 従って、\n",
"\n",
" * 各整数の「桁の和」を全て足し合わせ、\n",
" * その合計が `sum_digits % 3 == 0` であれば「Yes」、そうでなければ「No」。\n",
"\n",
"### 制約・実装方針\n",
"\n",
"* クエリ数 Q が複数あるが、HackerRank 側の `main` が\n",
"\n",
" * 各クエリごとに `canConstruct(a: List[int]) -> str` を呼び出す形になる想定。\n",
"* 我々は `canConstruct` だけ正しく実装すればよい(入出力は書かない)。\n",
"* 合計桁数を D とすると、必要なのは **O(D)** で各桁を一度ずつ見るだけでよい。\n",
"\n",
"---\n",
"\n",
"## 2. アルゴリズム比較表\n",
"\n",
"| アプローチ | 内容 | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化観点 | 備考 |\n",
"| ----- | -------------------------------------------------------- | ----- | ------------------------- | ----------- | --- | ------------ | ------------------------- | -------------- |\n",
"| 方法A | 数値 → 文字列変換して各文字を桁として扱い、`int(ch)` を加算 | O(D) | O(1) 追加だが、一時的な文字列オブジェクトあり | 低 | ★★★ | `str`, イテレータ | 文字列処理は C 実装で高速 | 一番読みやすい |\n",
"| 方法B | 各整数について `while x > 0: sum += x % 10; x //= 10` で直接桁を取り出す | O(D) | O(1) | 中 | ★★☆ | - | 整数演算も C 実装だが Python ループ多め | 文字列生成を避けたい場合向き |\n",
"\n",
"* どちらも漸近計算量は同じ O(D) ですが、\n",
"\n",
" * **業務開発視点**では「方法A(文字列ベース)」の方が読みやすくバグりにくい。\n",
" * **競技プログラミング視点**では「方法B(算術ベース)」も十分シンプルで、文字列生成を避けられます。\n",
"\n",
"ここではテンプレートに合わせて、\n",
"\n",
"* `solve_production` では **可読性重視の方法A(文字列ベース)**\n",
"* `solve_competitive` では **ループ最小限の方法B(算術ベース)**\n",
"\n",
"という二枚看板にしておきます。\n",
"\n",
"---\n",
"\n",
"## 3. 採用アルゴリズムと根拠\n",
"\n",
"### アルゴリズム\n",
"\n",
"1. 配列 `a: List[int]` を受け取る。\n",
"2. すべての整数について、その **10 進表記の各桁の合計** を求める。\n",
"3. 全部足して `digit_sum` とする。\n",
"4. `digit_sum % 3 == 0` なら `\"Yes\"`、そうでなければ `\"No\"` を返す。\n",
"\n",
"### 選択理由\n",
"\n",
"* 3 の倍数判定の性質により、**状態を保持する必要がなく**、桁和だけ見ればよい。\n",
"* 並べ替え自由・先頭ゼロ許容なので、「実際に数を構成する」必要もない。\n",
"* メモリ使用量は O(1)(桁和だけ保持)で、クエリごとに一回スキャンすればよい。\n",
"* Python では\n",
"\n",
" * `str(x)` とループ、あるいは\n",
" * 割り算・剰余演算\n",
" がどちらも C レベルで最適化されており、問題の想定制約なら十分高速。\n",
"\n",
"### トレードオフ\n",
"\n",
"* 「速度ガチ全振り」なら方法B一択でもよいが、コードは多少冗長。\n",
"* 業務コードとしては、バグリスクを下げるために方法Aの方がよく、テンプレートの `solve_production` に採用。\n",
"* HackerRank のエントリ関数 `canConstruct` では、競技プログラミング寄りということで `solve_competitive` を呼び出します。\n",
"\n",
"---\n",
"\n",
"## 4. 検証方針(テスト観点)\n",
"\n",
"※ 実際にテストコードは書きませんが、想定テストは以下のようなものです。\n",
"\n",
"1. 単一要素\n",
"\n",
" * `[0]` → 桁和 0 → `\"Yes\"`\n",
" * `[3]` → 桁和 3 → `\"Yes\"`\n",
" * `[4]` → 桁和 4 → `\"No\"`\n",
"2. 複数要素で並べ替えが効く例\n",
"\n",
" * `[10, 20]` → 桁: 1,0,2,0 → 合計 3 → `\"Yes\"`\n",
" * `[1, 2]` → 桁: 1,2 → 合計 3 → `\"Yes\"`(`21` 等を構成可能)\n",
" * `[11, 11]` → 桁: 1,1,1,1 → 合計 4 → `\"No\"`\n",
"3. サンプルに相当するケース\n",
"\n",
" * 問題文の 3 クエリ分をそのまま入れて `\"Yes\"`, `\"Yes\"`, `\"No\"` が返ること。\n",
"4. 境界値\n",
"\n",
" * 非常に大きい桁数の整数(例えば 10^100 など)を含む配列。\n",
" * `a` の長さが最大制約付近のケース。\n",
"5. エラー系(業務向け)\n",
"\n",
" * 負数が混ざる、int 以外が混ざる、空リストなど → `solve_production` では例外を投げる。\n",
"\n",
"---\n",
"\n",
"## 5. 実装(HackerRank 向け / 関数形式のみ)\n",
"\n",
"HackerRank 側のスタブから呼ばれることを想定して、**I/O は一切書かず**、`canConstruct` 関数だけをエントリポイントにしています。\n",
"型注釈は Pylance を意識して付与しています。\n",
"\n",
"```python\n",
"from typing import List, Iterable, Any\n",
"\n",
"YES: str = \"Yes\"\n",
"NO: str = \"No\"\n",
"\n",
"\n",
"def canConstruct(a: List[int]) -> str:\n",
" \"\"\"\n",
" HackerRank から呼ばれるエントリポイント。\n",
" クエリごとに 1 回呼ばれ、与えられた整数配列 a の桁を全て用いて\n",
" 3 で割り切れる数を構成できるかどうかを判定する。\n",
"\n",
" Args:\n",
" a: 非負整数のリスト\n",
"\n",
" Returns:\n",
" \"Yes\" または \"No\"\n",
" \"\"\"\n",
" # 競技プログラミング向け・性能優先の実装を利用\n",
" return solve_competitive(a)\n",
"\n",
"\n",
"# =========================\n",
"# 業務開発向け 実装\n",
"# =========================\n",
"\n",
"def solve_production(input_data: List[int]) -> str:\n",
" \"\"\"\n",
" 業務開発向け実装(型安全・エラーハンドリング重視)\n",
"\n",
" Args:\n",
" input_data: 非負整数リスト\n",
"\n",
" Returns:\n",
" \"Yes\" または \"No\"\n",
"\n",
" Raises:\n",
" ValueError: 入力値が制約を満たさない場合\n",
" TypeError: 入力型が不正な場合\n",
" \"\"\"\n",
" _validate_input(input_data)\n",
"\n",
" if _is_edge_case(input_data):\n",
" return _handle_edge_case(input_data)\n",
"\n",
" digit_sum: int = _sum_digits_str_based(input_data)\n",
" return YES if (digit_sum % 3 == 0) else NO\n",
"\n",
"\n",
"# =========================\n",
"# 競技プログラミング向け 実装\n",
"# =========================\n",
"\n",
"def solve_competitive(input_data: List[int]) -> str:\n",
" \"\"\"\n",
" 競技プログラミング向け実装。\n",
" エラーハンドリングを省略し、性能を優先する。\n",
"\n",
" Time Complexity:\n",
" O(D) (D は全ての整数の桁数の合計)\n",
" Space Complexity:\n",
" O(1) 追加メモリは桁和のみ\n",
" \"\"\"\n",
" digit_sum: int = _sum_digits_arithmetic(input_data)\n",
" return YES if (digit_sum % 3 == 0) else NO\n",
"\n",
"\n",
"# =========================\n",
"# 共通ヘルパー\n",
"# =========================\n",
"\n",
"def _validate_input(data: Any) -> None:\n",
" \"\"\"\n",
" 業務開発向けの防御的バリデーション。\n",
" HackerRank 環境では想定通りの入力が来る前提だが、\n",
" ライブラリとして再利用することも考えて型・値をチェックする。\n",
" \"\"\"\n",
" if not isinstance(data, list):\n",
" raise TypeError(\"Input must be a list of integers\")\n",
"\n",
" if not data:\n",
" raise ValueError(\"Input list cannot be empty\")\n",
"\n",
" for x in data:\n",
" if not isinstance(x, int):\n",
" raise TypeError(\"All elements must be integers\")\n",
" if x < 0:\n",
" raise ValueError(\"All elements must be non-negative\")\n",
"\n",
"\n",
"def _is_edge_case(data: List[int]) -> bool:\n",
" \"\"\"\n",
" エッジケース判定。\n",
" 今回はシンプルに「要素数 1」の場合を特別扱い。\n",
" \"\"\"\n",
" return len(data) == 1\n",
"\n",
"\n",
"def _handle_edge_case(data: List[int]) -> str:\n",
" \"\"\"\n",
" 単一要素のみのときの早期判定。\n",
" \"\"\"\n",
" only: int = data[0]\n",
" # 直接 3 で割るのではなく、桁和で判定する実装に揃えておく。\n",
" # (仕様変更で「別の基準の倍数」に変わっても差し替えが容易。)\n",
" digit_sum: int = _sum_digits_arithmetic([only])\n",
" return YES if (digit_sum % 3 == 0) else NO\n",
"\n",
"\n",
"def _sum_digits_str_based(numbers: Iterable[int]) -> int:\n",
" \"\"\"\n",
" 文字列変換ベースの桁和計算(業務コード向け・可読性重視)。\n",
" \"\"\"\n",
" total: int = 0\n",
" for value in numbers:\n",
" # str(value) は C 実装で高速、桁数分の文字列を一度だけ生成\n",
" for ch in str(value):\n",
" total += ord(ch) - ord(\"0\")\n",
" return total\n",
"\n",
"\n",
"def _sum_digits_arithmetic(numbers: Iterable[int]) -> int:\n",
" \"\"\"\n",
" 算術演算ベースの桁和計算(競技向け・追加オブジェクト削減)。\n",
"\n",
" 0 の扱い:\n",
" 0 の桁和は 0 なので、単純にスキップしても問題ない。\n",
" (全て 0 のときは合計 0 → 3 で割り切れる)\n",
" \"\"\"\n",
" total: int = 0\n",
" for value in numbers:\n",
" x: int = value\n",
" if x == 0:\n",
" # 0 の桁和は 0 なので何もしない\n",
" continue\n",
" while x > 0:\n",
" total += x % 10\n",
" x //= 10\n",
" return total\n",
"```\n",
"\n",
"このまま HackerRank の関数実装部分に貼り付ければ動作する想定です(スタブ側で `canConstruct` が呼ばれる前提)。もし実際の stub のシグネチャが異なっていたら、その関数名・引数名にだけ合わせてください。アルゴリズム自体はそのまま流用できます。\n",
"\n",
"```python\n",
"#!/bin/python3\n",
"\n",
"import math\n",
"import os\n",
"import random\n",
"import re\n",
"import sys\n",
"from typing import List, Iterable, Any\n",
"\n",
"YES: str = \"Yes\"\n",
"NO: str = \"No\"\n",
"#\n",
"# Complete the 'canConstruct' function below.\n",
"#\n",
"# The function is expected to return a STRING.\n",
"# The function accepts INTEGER_ARRAY a as parameter.\n",
"#\n",
"\n",
"def canConstruct(a: List[int]) -> str:\n",
" # Return \"Yes\" or \"No\" denoting whether you can construct the required number.\n",
" #\n",
" # ポイント:\n",
" # ・3 の倍数判定は「全桁の和 % 3 == 0」で決まる\n",
" # ・桁の並べ替えや先頭 0 は自由なので、桁の和だけ見ればよい\n",
"\n",
" digit_sum: int = 0\n",
"\n",
" for value in a:\n",
" x: int = value\n",
" if x == 0:\n",
" # 0 の桁和は 0 なのでスキップでよい\n",
" continue\n",
" # 各整数の桁和を算術演算で求める\n",
" while x > 0:\n",
" digit_sum += x % 10\n",
" x //= 10\n",
"\n",
" return YES if (digit_sum % 3 == 0) else NO\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
" fptr = open(os.environ['OUTPUT_PATH'], 'w')\n",
"\n",
" t = int(input().strip())\n",
"\n",
" for t_itr in range(t):\n",
" n = int(input().strip())\n",
"\n",
" a = list(map(int, input().rstrip().split()))\n",
"\n",
" result = canConstruct(a)\n",
"\n",
" fptr.write(result + '\\n')\n",
"\n",
" fptr.close()\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36f18572",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}