Skip to content

Commit cfd795b

Browse files
authored
Merge pull request #216 from myoshi2891/dev-from-macmini
Math: Hacker Rank Number Theory Constructing a Number
2 parents d5c1a1c + 56aee0a commit cfd795b

1 file changed

Lines changed: 354 additions & 0 deletions

File tree

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "97dc61cc",
6+
"metadata": {},
7+
"source": [
8+
"まず結論から言うと、この問題は「すべての数字の **各桁の合計** が 3 で割り切れるかどうか」だけ見れば解けます。\n",
9+
"\n",
10+
"---\n",
11+
"\n",
12+
"## 1. 問題分析結果\n",
13+
"\n",
14+
"### 本質\n",
15+
"\n",
16+
"* 3 の倍数の判定条件:\n",
17+
"\n",
18+
" * ある整数が 3 で割り切れるかどうかは、「その数の **全ての桁の和** が 3 で割り切れるかどうか」で決まる。\n",
19+
"* 桁を並べ替えても、使う桁の集合(マルチセット)が同じなら、**桁の和は不変**。\n",
20+
"* この問題では、\n",
21+
"\n",
22+
" * 「与えられた整数列の **すべての桁を一度ずつ** 使う」\n",
23+
" * 「並べ替え自由(順番は自由)」\n",
24+
" * 「先頭が 0 でもよい」\n",
25+
"* 従って、\n",
26+
"\n",
27+
" * 各整数の「桁の和」を全て足し合わせ、\n",
28+
" * その合計が `sum_digits % 3 == 0` であれば「Yes」、そうでなければ「No」。\n",
29+
"\n",
30+
"### 制約・実装方針\n",
31+
"\n",
32+
"* クエリ数 Q が複数あるが、HackerRank 側の `main` が\n",
33+
"\n",
34+
" * 各クエリごとに `canConstruct(a: List[int]) -> str` を呼び出す形になる想定。\n",
35+
"* 我々は `canConstruct` だけ正しく実装すればよい(入出力は書かない)。\n",
36+
"* 合計桁数を D とすると、必要なのは **O(D)** で各桁を一度ずつ見るだけでよい。\n",
37+
"\n",
38+
"---\n",
39+
"\n",
40+
"## 2. アルゴリズム比較表\n",
41+
"\n",
42+
"| アプローチ | 内容 | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化観点 | 備考 |\n",
43+
"| ----- | -------------------------------------------------------- | ----- | ------------------------- | ----------- | --- | ------------ | ------------------------- | -------------- |\n",
44+
"| 方法A | 数値 → 文字列変換して各文字を桁として扱い、`int(ch)` を加算 | O(D) | O(1) 追加だが、一時的な文字列オブジェクトあり | 低 | ★★★ | `str`, イテレータ | 文字列処理は C 実装で高速 | 一番読みやすい |\n",
45+
"| 方法B | 各整数について `while x > 0: sum += x % 10; x //= 10` で直接桁を取り出す | O(D) | O(1) | 中 | ★★☆ | - | 整数演算も C 実装だが Python ループ多め | 文字列生成を避けたい場合向き |\n",
46+
"\n",
47+
"* どちらも漸近計算量は同じ O(D) ですが、\n",
48+
"\n",
49+
" * **業務開発視点**では「方法A(文字列ベース)」の方が読みやすくバグりにくい。\n",
50+
" * **競技プログラミング視点**では「方法B(算術ベース)」も十分シンプルで、文字列生成を避けられます。\n",
51+
"\n",
52+
"ここではテンプレートに合わせて、\n",
53+
"\n",
54+
"* `solve_production` では **可読性重視の方法A(文字列ベース)**\n",
55+
"* `solve_competitive` では **ループ最小限の方法B(算術ベース)**\n",
56+
"\n",
57+
"という二枚看板にしておきます。\n",
58+
"\n",
59+
"---\n",
60+
"\n",
61+
"## 3. 採用アルゴリズムと根拠\n",
62+
"\n",
63+
"### アルゴリズム\n",
64+
"\n",
65+
"1. 配列 `a: List[int]` を受け取る。\n",
66+
"2. すべての整数について、その **10 進表記の各桁の合計** を求める。\n",
67+
"3. 全部足して `digit_sum` とする。\n",
68+
"4. `digit_sum % 3 == 0` なら `\"Yes\"`、そうでなければ `\"No\"` を返す。\n",
69+
"\n",
70+
"### 選択理由\n",
71+
"\n",
72+
"* 3 の倍数判定の性質により、**状態を保持する必要がなく**、桁和だけ見ればよい。\n",
73+
"* 並べ替え自由・先頭ゼロ許容なので、「実際に数を構成する」必要もない。\n",
74+
"* メモリ使用量は O(1)(桁和だけ保持)で、クエリごとに一回スキャンすればよい。\n",
75+
"* Python では\n",
76+
"\n",
77+
" * `str(x)` とループ、あるいは\n",
78+
" * 割り算・剰余演算\n",
79+
" がどちらも C レベルで最適化されており、問題の想定制約なら十分高速。\n",
80+
"\n",
81+
"### トレードオフ\n",
82+
"\n",
83+
"* 「速度ガチ全振り」なら方法B一択でもよいが、コードは多少冗長。\n",
84+
"* 業務コードとしては、バグリスクを下げるために方法Aの方がよく、テンプレートの `solve_production` に採用。\n",
85+
"* HackerRank のエントリ関数 `canConstruct` では、競技プログラミング寄りということで `solve_competitive` を呼び出します。\n",
86+
"\n",
87+
"---\n",
88+
"\n",
89+
"## 4. 検証方針(テスト観点)\n",
90+
"\n",
91+
"※ 実際にテストコードは書きませんが、想定テストは以下のようなものです。\n",
92+
"\n",
93+
"1. 単一要素\n",
94+
"\n",
95+
" * `[0]` → 桁和 0 → `\"Yes\"`\n",
96+
" * `[3]` → 桁和 3 → `\"Yes\"`\n",
97+
" * `[4]` → 桁和 4 → `\"No\"`\n",
98+
"2. 複数要素で並べ替えが効く例\n",
99+
"\n",
100+
" * `[10, 20]` → 桁: 1,0,2,0 → 合計 3 → `\"Yes\"`\n",
101+
" * `[1, 2]` → 桁: 1,2 → 合計 3 → `\"Yes\"`(`21` 等を構成可能)\n",
102+
" * `[11, 11]` → 桁: 1,1,1,1 → 合計 4 → `\"No\"`\n",
103+
"3. サンプルに相当するケース\n",
104+
"\n",
105+
" * 問題文の 3 クエリ分をそのまま入れて `\"Yes\"`, `\"Yes\"`, `\"No\"` が返ること。\n",
106+
"4. 境界値\n",
107+
"\n",
108+
" * 非常に大きい桁数の整数(例えば 10^100 など)を含む配列。\n",
109+
" * `a` の長さが最大制約付近のケース。\n",
110+
"5. エラー系(業務向け)\n",
111+
"\n",
112+
" * 負数が混ざる、int 以外が混ざる、空リストなど → `solve_production` では例外を投げる。\n",
113+
"\n",
114+
"---\n",
115+
"\n",
116+
"## 5. 実装(HackerRank 向け / 関数形式のみ)\n",
117+
"\n",
118+
"HackerRank 側のスタブから呼ばれることを想定して、**I/O は一切書かず**、`canConstruct` 関数だけをエントリポイントにしています。\n",
119+
"型注釈は Pylance を意識して付与しています。\n",
120+
"\n",
121+
"```python\n",
122+
"from typing import List, Iterable, Any\n",
123+
"\n",
124+
"YES: str = \"Yes\"\n",
125+
"NO: str = \"No\"\n",
126+
"\n",
127+
"\n",
128+
"def canConstruct(a: List[int]) -> str:\n",
129+
" \"\"\"\n",
130+
" HackerRank から呼ばれるエントリポイント。\n",
131+
" クエリごとに 1 回呼ばれ、与えられた整数配列 a の桁を全て用いて\n",
132+
" 3 で割り切れる数を構成できるかどうかを判定する。\n",
133+
"\n",
134+
" Args:\n",
135+
" a: 非負整数のリスト\n",
136+
"\n",
137+
" Returns:\n",
138+
" \"Yes\" または \"No\"\n",
139+
" \"\"\"\n",
140+
" # 競技プログラミング向け・性能優先の実装を利用\n",
141+
" return solve_competitive(a)\n",
142+
"\n",
143+
"\n",
144+
"# =========================\n",
145+
"# 業務開発向け 実装\n",
146+
"# =========================\n",
147+
"\n",
148+
"def solve_production(input_data: List[int]) -> str:\n",
149+
" \"\"\"\n",
150+
" 業務開発向け実装(型安全・エラーハンドリング重視)\n",
151+
"\n",
152+
" Args:\n",
153+
" input_data: 非負整数リスト\n",
154+
"\n",
155+
" Returns:\n",
156+
" \"Yes\" または \"No\"\n",
157+
"\n",
158+
" Raises:\n",
159+
" ValueError: 入力値が制約を満たさない場合\n",
160+
" TypeError: 入力型が不正な場合\n",
161+
" \"\"\"\n",
162+
" _validate_input(input_data)\n",
163+
"\n",
164+
" if _is_edge_case(input_data):\n",
165+
" return _handle_edge_case(input_data)\n",
166+
"\n",
167+
" digit_sum: int = _sum_digits_str_based(input_data)\n",
168+
" return YES if (digit_sum % 3 == 0) else NO\n",
169+
"\n",
170+
"\n",
171+
"# =========================\n",
172+
"# 競技プログラミング向け 実装\n",
173+
"# =========================\n",
174+
"\n",
175+
"def solve_competitive(input_data: List[int]) -> str:\n",
176+
" \"\"\"\n",
177+
" 競技プログラミング向け実装。\n",
178+
" エラーハンドリングを省略し、性能を優先する。\n",
179+
"\n",
180+
" Time Complexity:\n",
181+
" O(D) (D は全ての整数の桁数の合計)\n",
182+
" Space Complexity:\n",
183+
" O(1) 追加メモリは桁和のみ\n",
184+
" \"\"\"\n",
185+
" digit_sum: int = _sum_digits_arithmetic(input_data)\n",
186+
" return YES if (digit_sum % 3 == 0) else NO\n",
187+
"\n",
188+
"\n",
189+
"# =========================\n",
190+
"# 共通ヘルパー\n",
191+
"# =========================\n",
192+
"\n",
193+
"def _validate_input(data: Any) -> None:\n",
194+
" \"\"\"\n",
195+
" 業務開発向けの防御的バリデーション。\n",
196+
" HackerRank 環境では想定通りの入力が来る前提だが、\n",
197+
" ライブラリとして再利用することも考えて型・値をチェックする。\n",
198+
" \"\"\"\n",
199+
" if not isinstance(data, list):\n",
200+
" raise TypeError(\"Input must be a list of integers\")\n",
201+
"\n",
202+
" if not data:\n",
203+
" raise ValueError(\"Input list cannot be empty\")\n",
204+
"\n",
205+
" for x in data:\n",
206+
" if not isinstance(x, int):\n",
207+
" raise TypeError(\"All elements must be integers\")\n",
208+
" if x < 0:\n",
209+
" raise ValueError(\"All elements must be non-negative\")\n",
210+
"\n",
211+
"\n",
212+
"def _is_edge_case(data: List[int]) -> bool:\n",
213+
" \"\"\"\n",
214+
" エッジケース判定。\n",
215+
" 今回はシンプルに「要素数 1」の場合を特別扱い。\n",
216+
" \"\"\"\n",
217+
" return len(data) == 1\n",
218+
"\n",
219+
"\n",
220+
"def _handle_edge_case(data: List[int]) -> str:\n",
221+
" \"\"\"\n",
222+
" 単一要素のみのときの早期判定。\n",
223+
" \"\"\"\n",
224+
" only: int = data[0]\n",
225+
" # 直接 3 で割るのではなく、桁和で判定する実装に揃えておく。\n",
226+
" # (仕様変更で「別の基準の倍数」に変わっても差し替えが容易。)\n",
227+
" digit_sum: int = _sum_digits_arithmetic([only])\n",
228+
" return YES if (digit_sum % 3 == 0) else NO\n",
229+
"\n",
230+
"\n",
231+
"def _sum_digits_str_based(numbers: Iterable[int]) -> int:\n",
232+
" \"\"\"\n",
233+
" 文字列変換ベースの桁和計算(業務コード向け・可読性重視)。\n",
234+
" \"\"\"\n",
235+
" total: int = 0\n",
236+
" for value in numbers:\n",
237+
" # str(value) は C 実装で高速、桁数分の文字列を一度だけ生成\n",
238+
" for ch in str(value):\n",
239+
" total += ord(ch) - ord(\"0\")\n",
240+
" return total\n",
241+
"\n",
242+
"\n",
243+
"def _sum_digits_arithmetic(numbers: Iterable[int]) -> int:\n",
244+
" \"\"\"\n",
245+
" 算術演算ベースの桁和計算(競技向け・追加オブジェクト削減)。\n",
246+
"\n",
247+
" 0 の扱い:\n",
248+
" 0 の桁和は 0 なので、単純にスキップしても問題ない。\n",
249+
" (全て 0 のときは合計 0 → 3 で割り切れる)\n",
250+
" \"\"\"\n",
251+
" total: int = 0\n",
252+
" for value in numbers:\n",
253+
" x: int = value\n",
254+
" if x == 0:\n",
255+
" # 0 の桁和は 0 なので何もしない\n",
256+
" continue\n",
257+
" while x > 0:\n",
258+
" total += x % 10\n",
259+
" x //= 10\n",
260+
" return total\n",
261+
"```\n",
262+
"\n",
263+
"このまま HackerRank の関数実装部分に貼り付ければ動作する想定です(スタブ側で `canConstruct` が呼ばれる前提)。もし実際の stub のシグネチャが異なっていたら、その関数名・引数名にだけ合わせてください。アルゴリズム自体はそのまま流用できます。\n",
264+
"\n",
265+
"```python\n",
266+
"#!/bin/python3\n",
267+
"\n",
268+
"import math\n",
269+
"import os\n",
270+
"import random\n",
271+
"import re\n",
272+
"import sys\n",
273+
"from typing import List, Iterable, Any\n",
274+
"\n",
275+
"YES: str = \"Yes\"\n",
276+
"NO: str = \"No\"\n",
277+
"#\n",
278+
"# Complete the 'canConstruct' function below.\n",
279+
"#\n",
280+
"# The function is expected to return a STRING.\n",
281+
"# The function accepts INTEGER_ARRAY a as parameter.\n",
282+
"#\n",
283+
"\n",
284+
"def canConstruct(a: List[int]) -> str:\n",
285+
" # Return \"Yes\" or \"No\" denoting whether you can construct the required number.\n",
286+
" #\n",
287+
" # ポイント:\n",
288+
" # ・3 の倍数判定は「全桁の和 % 3 == 0」で決まる\n",
289+
" # ・桁の並べ替えや先頭 0 は自由なので、桁の和だけ見ればよい\n",
290+
"\n",
291+
" digit_sum: int = 0\n",
292+
"\n",
293+
" for value in a:\n",
294+
" x: int = value\n",
295+
" if x == 0:\n",
296+
" # 0 の桁和は 0 なのでスキップでよい\n",
297+
" continue\n",
298+
" # 各整数の桁和を算術演算で求める\n",
299+
" while x > 0:\n",
300+
" digit_sum += x % 10\n",
301+
" x //= 10\n",
302+
"\n",
303+
" return YES if (digit_sum % 3 == 0) else NO\n",
304+
"\n",
305+
"\n",
306+
"if __name__ == '__main__':\n",
307+
" fptr = open(os.environ['OUTPUT_PATH'], 'w')\n",
308+
"\n",
309+
" t = int(input().strip())\n",
310+
"\n",
311+
" for t_itr in range(t):\n",
312+
" n = int(input().strip())\n",
313+
"\n",
314+
" a = list(map(int, input().rstrip().split()))\n",
315+
"\n",
316+
" result = canConstruct(a)\n",
317+
"\n",
318+
" fptr.write(result + '\\n')\n",
319+
"\n",
320+
" fptr.close()\n",
321+
"```"
322+
]
323+
},
324+
{
325+
"cell_type": "code",
326+
"execution_count": null,
327+
"id": "36f18572",
328+
"metadata": {},
329+
"outputs": [],
330+
"source": []
331+
}
332+
],
333+
"metadata": {
334+
"kernelspec": {
335+
"display_name": ".venv",
336+
"language": "python",
337+
"name": "python3"
338+
},
339+
"language_info": {
340+
"codemirror_mode": {
341+
"name": "ipython",
342+
"version": 3
343+
},
344+
"file_extension": ".py",
345+
"mimetype": "text/x-python",
346+
"name": "python",
347+
"nbconvert_exporter": "python",
348+
"pygments_lexer": "ipython3",
349+
"version": "3.12.4"
350+
}
351+
},
352+
"nbformat": 4,
353+
"nbformat_minor": 5
354+
}

0 commit comments

Comments
 (0)