1+ // # JavaScript コーディング問題解析
2+
3+ // ## 1. 多角的問題分析
4+
5+ // ### 競技プログラミング視点
6+ // - **問題の本質**: 障害物のあるグリッドでの経路数計算(Dynamic Programming)
7+ // - **制約**: m,n ≤ 100 → O(mn) 解法で十分
8+ // - **結果上限**: 2×10^9 → 32bit整数で対応可能
9+
10+ // ### 業務開発視点
11+ // - **入力検証**: グリッドの妥当性チェック必須
12+ // - **エラーハンドリング**: 不正な障害物配置への対応
13+ // - **可読性**: DPテーブルの意味を明確化
14+
15+ // ### JavaScript特有考慮
16+ // - **配列アクセス**: 2次元配列の効率的な操作
17+ // - **数値計算**: 大きな値でもNumber型で安全
18+ // - **メモリ使用**: 100×100程度なら問題なし
19+
20+ // ## 2. アルゴリズム比較表
21+
22+ // |アプローチ|時間計算量|空間計算量|JS実装コスト|可読性|V8最適化|備考|
23+ // |---------|---------|---------|-----------|------|--------|-----|
24+ // |2D DP|O(mn)|O(mn)|低|★★★|適|標準的解法|
25+ // |1D DP|O(mn)|O(n)|中|★★☆|適|空間最適化版|
26+ // |再帰+メモ化|O(mn)|O(mn)|高|★☆☆|不適|スタック深度問題|
27+
28+ // ## 3. JavaScript特有最適化ポイント
29+
30+ // ### V8エンジン最適化
31+ // - **配列事前確保**: `new Array(rows).fill().map()` でHidden Class安定化
32+ // - **インデックスアクセス**: `grid[i][j]` の直接アクセス
33+ // - **条件分岐最小化**: 三項演算子活用
34+
35+ // ### GC負荷軽減
36+ // - **プリミティブ値中心**: 数値のみの処理
37+ // - **配列再利用**: DPテーブルの行単位更新
38+
39+ // ## 4. 実装## 5. 採用アルゴリズムと根拠
40+
41+ // ### 選択理由
42+ // **1D Dynamic Programming(空間最適化版)**を主解法として採用
43+
44+ // - **計算量効率**: O(mn) 時間、O(n) 空間
45+ // - **JavaScript適性**: 配列の行単位処理でV8最適化に適合
46+ // - **実用性**: 制約範囲(100×100)で十分高速、メモリ効率も良好
47+
48+ // ### JavaScript最適化戦略
49+ // 1. **配列事前確保**: `new Array(n).fill(0)` で型安定化
50+ // 2. **インライン計算**: `dp[j] + dp[j-1]` の直接計算
51+ // 3. **条件分岐最小化**: 障害物判定を最小限に抑制
52+
53+ // ### トレードオフ
54+ // - **2D DP版**: 可読性は高いが空間計算量O(mn)
55+ // - **再帰+メモ化**: 直感的だがJavaScriptのスタック制限で不適
56+
57+ // ## 6. パフォーマンス考察
58+
59+ // ### 理論計算量
60+ // - **時間**: O(m×n) - 各セルを1回ずつ処理
61+ // - **空間**: O(n) - 1行分のDPテーブルのみ保持
62+
63+ // ### JavaScript実測予想
64+ // - **V8環境**: 100×100グリッドで1-2ms程度
65+ // - **メモリ使用**: 約400byte(数値配列100要素)
66+ // - **GC負荷**: 最小限(プリミティブ値中心)
67+
68+ // ### 改善余地
69+ // - **更なる最適化**: インプレース更新による定数倍改善
70+ // - **並列化**: Web Workersによる行並列処理(ただし制約サイズでは不要)
71+ // - **型付き配列**: Uint32Arrayによる微細な性能向上
72+
73+ // この実装は競技プログラミングでの高速性と業務開発での保守性を両立し、JavaScript特有の最適化も考慮した実用的な解法です。
74+
75+ /**
76+ * 障害物のあるグリッドでのユニークパス数を計算
77+ * @param {number[][] } obstacleGrid - 障害物グリッド (0: 通路, 1: 障害物)
78+ * @returns {number } 到達可能なユニークパス数
79+ * @throws {TypeError } 入力型エラー
80+ * @throws {RangeError } 制約違反エラー
81+ * @complexity Time: O(m*n), Space: O(n) - 1D DP最適化版
82+ */
83+ function uniquePathsWithObstacles ( obstacleGrid ) {
84+ // 1. 入力検証
85+ validateInput ( obstacleGrid ) ;
86+
87+ const m = obstacleGrid . length ;
88+ const n = obstacleGrid [ 0 ] . length ;
89+
90+ // 2. エッジケース処理
91+ if ( obstacleGrid [ 0 ] [ 0 ] === 1 || obstacleGrid [ m - 1 ] [ n - 1 ] === 1 ) {
92+ return 0 ; // スタート・ゴールが障害物
93+ }
94+
95+ // 3. 1D DP実装(空間最適化)
96+ // dp[j] = 現在の行での列jまでの経路数
97+ const dp = new Array ( n ) . fill ( 0 ) ;
98+ dp [ 0 ] = 1 ; // スタート地点
99+
100+ for ( let i = 0 ; i < m ; i ++ ) {
101+ // 各行の最初の列を処理
102+ if ( obstacleGrid [ i ] [ 0 ] === 1 ) {
103+ dp [ 0 ] = 0 ; // 障害物があれば到達不可
104+ }
105+
106+ // 残りの列を処理
107+ for ( let j = 1 ; j < n ; j ++ ) {
108+ if ( obstacleGrid [ i ] [ j ] === 1 ) {
109+ dp [ j ] = 0 ; // 障害物があれば0
110+ } else {
111+ dp [ j ] = dp [ j ] + dp [ j - 1 ] ; // 上から + 左から
112+ }
113+ }
114+ }
115+
116+ return dp [ n - 1 ] ;
117+ }
118+
119+ /**
120+ * 2D DP実装(可読性重視版)
121+ * @param {number[][] } obstacleGrid - 障害物グリッド
122+ * @returns {number } ユニークパス数
123+ * @complexity Time: O(m*n), Space: O(m*n)
124+ */
125+ function uniquePathsWithObstacles2D ( obstacleGrid ) {
126+ validateInput ( obstacleGrid ) ;
127+
128+ const m = obstacleGrid . length ;
129+ const n = obstacleGrid [ 0 ] . length ;
130+
131+ // エッジケース
132+ if ( obstacleGrid [ 0 ] [ 0 ] === 1 || obstacleGrid [ m - 1 ] [ n - 1 ] === 1 ) {
133+ return 0 ;
134+ }
135+
136+ // DPテーブル初期化
137+ const dp = Array . from ( { length : m } , ( ) => new Array ( n ) . fill ( 0 ) ) ;
138+
139+ // 初期化:最初の行と列
140+ dp [ 0 ] [ 0 ] = 1 ;
141+
142+ // 最初の行
143+ for ( let j = 1 ; j < n ; j ++ ) {
144+ dp [ 0 ] [ j ] = ( obstacleGrid [ 0 ] [ j ] === 1 ) ? 0 : dp [ 0 ] [ j - 1 ] ;
145+ }
146+
147+ // 最初の列
148+ for ( let i = 1 ; i < m ; i ++ ) {
149+ dp [ i ] [ 0 ] = ( obstacleGrid [ i ] [ 0 ] === 1 ) ? 0 : dp [ i - 1 ] [ 0 ] ;
150+ }
151+
152+ // メインのDP計算
153+ for ( let i = 1 ; i < m ; i ++ ) {
154+ for ( let j = 1 ; j < n ; j ++ ) {
155+ if ( obstacleGrid [ i ] [ j ] === 1 ) {
156+ dp [ i ] [ j ] = 0 ;
157+ } else {
158+ dp [ i ] [ j ] = dp [ i - 1 ] [ j ] + dp [ i ] [ j - 1 ] ;
159+ }
160+ }
161+ }
162+
163+ return dp [ m - 1 ] [ n - 1 ] ;
164+ }
165+
166+ /**
167+ * 入力検証ヘルパー
168+ * @param {any } obstacleGrid - 検証対象
169+ * @throws {TypeError } 型エラー
170+ * @throws {RangeError } 制約エラー
171+ */
172+ function validateInput ( obstacleGrid ) {
173+ // 基本型チェック
174+ if ( ! Array . isArray ( obstacleGrid ) ) {
175+ throw new TypeError ( 'obstacleGrid must be an array' ) ;
176+ }
177+
178+ if ( obstacleGrid . length === 0 ) {
179+ throw new RangeError ( 'Grid must not be empty' ) ;
180+ }
181+
182+ const m = obstacleGrid . length ;
183+ const n = obstacleGrid [ 0 ] . length ;
184+
185+ // 制約チェック
186+ if ( m < 1 || m > 100 || n < 1 || n > 100 ) {
187+ throw new RangeError ( 'Grid dimensions must be between 1 and 100' ) ;
188+ }
189+
190+ // グリッド内容チェック
191+ for ( let i = 0 ; i < m ; i ++ ) {
192+ if ( ! Array . isArray ( obstacleGrid [ i ] ) || obstacleGrid [ i ] . length !== n ) {
193+ throw new TypeError ( 'All rows must be arrays of same length' ) ;
194+ }
195+
196+ for ( let j = 0 ; j < n ; j ++ ) {
197+ const val = obstacleGrid [ i ] [ j ] ;
198+ if ( val !== 0 && val !== 1 ) {
199+ throw new RangeError ( 'Grid values must be 0 or 1' ) ;
200+ }
201+ }
202+ }
203+ }
204+
205+ // テストケース実行
206+ function runTests ( ) {
207+ const testCases = [
208+ {
209+ input : [ [ 0 , 0 , 0 ] , [ 0 , 1 , 0 ] , [ 0 , 0 , 0 ] ] ,
210+ expected : 2 ,
211+ description : "Example 1: 3x3 grid with middle obstacle"
212+ } ,
213+ {
214+ input : [ [ 0 , 1 ] , [ 0 , 0 ] ] ,
215+ expected : 1 ,
216+ description : "Example 2: 2x2 grid with top-right obstacle"
217+ } ,
218+ {
219+ input : [ [ 1 ] ] ,
220+ expected : 0 ,
221+ description : "Edge case: Start point blocked"
222+ } ,
223+ {
224+ input : [ [ 0 , 0 ] , [ 1 , 0 ] ] ,
225+ expected : 0 ,
226+ description : "Edge case: End point blocked"
227+ } ,
228+ {
229+ input : [ [ 0 ] ] ,
230+ expected : 1 ,
231+ description : "Edge case: Single cell, no obstacle"
232+ }
233+ ] ;
234+
235+ console . log ( "=== テスト実行結果 ===" ) ;
236+ testCases . forEach ( ( test , index ) => {
237+ const result1D = uniquePathsWithObstacles ( test . input ) ;
238+ const result2D = uniquePathsWithObstacles2D ( test . input ) ;
239+ const passed = result1D === test . expected && result2D === test . expected ;
240+
241+ console . log ( `Test ${ index + 1 } : ${ passed ? "✓ PASS" : "✗ FAIL" } ` ) ;
242+ console . log ( ` ${ test . description } ` ) ;
243+ console . log ( ` Input: ${ JSON . stringify ( test . input ) } ` ) ;
244+ console . log ( ` Expected: ${ test . expected } , Got: 1D=${ result1D } , 2D=${ result2D } ` ) ;
245+ console . log ( ) ;
246+ } ) ;
247+ }
248+
249+ // パフォーマンステスト
250+ function performanceTest ( ) {
251+ // 大きなグリッドでのテスト
252+ const createGrid = ( m , n , obstacleRate = 0.1 ) => {
253+ const grid = Array . from ( { length : m } , ( ) => new Array ( n ) ) ;
254+ for ( let i = 0 ; i < m ; i ++ ) {
255+ for ( let j = 0 ; j < n ; j ++ ) {
256+ grid [ i ] [ j ] = ( Math . random ( ) < obstacleRate ) ? 1 : 0 ;
257+ }
258+ }
259+ grid [ 0 ] [ 0 ] = 0 ; // スタート確保
260+ grid [ m - 1 ] [ n - 1 ] = 0 ; // ゴール確保
261+ return grid ;
262+ } ;
263+
264+ const testGrid = createGrid ( 100 , 100 ) ;
265+
266+ console . log ( "=== パフォーマンステスト ===" ) ;
267+
268+ // 1D DP版
269+ const start1D = Date . now ( ) ;
270+ const result1D = uniquePathsWithObstacles ( testGrid ) ;
271+ const time1D = Date . now ( ) - start1D ;
272+
273+ // 2D DP版
274+ const start2D = Date . now ( ) ;
275+ const result2D = uniquePathsWithObstacles2D ( testGrid ) ;
276+ const time2D = Date . now ( ) - start2D ;
277+
278+ console . log ( `1D DP版: ${ time1D } ms, Result: ${ result1D } ` ) ;
279+ console . log ( `2D DP版: ${ time2D } ms, Result: ${ result2D } ` ) ;
280+ console . log ( `空間効率改善: ${ ( ( testGrid . length * testGrid [ 0 ] . length * 4 - testGrid [ 0 ] . length * 4 ) / 1024 ) . toFixed ( 2 ) } KB削減` ) ;
281+ }
282+
283+ // 実行
284+ runTests ( ) ;
285+ performanceTest ( ) ;
0 commit comments