Skip to content
Open
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
102 changes: 102 additions & 0 deletions problems/1301-number-of-paths-with-max-score/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# 1301. Number of Paths with Max Score

[LeetCode Link](https://leetcode.com/problems/number-of-paths-with-max-score/)

Difficulty: Hard
Topics: Array, Dynamic Programming, Matrix
Acceptance Rate: 49.6%

## Hints

### Hint 1

You start at the bottom-right `'S'` and must reach the top-left `'E'`, moving only
up, left, or up-left. Notice that every legal move strictly decreases your position
toward `(0, 0)`. Whenever a problem asks for the *best value over all paths in a grid*
where movement is restricted to a fixed set of directions, think **dynamic programming
over the grid** rather than brute-force search — the number of paths is exponential,
but the number of cells is not.

### Hint 2

This problem has two coupled quantities: the **maximum score** reachable at a cell, and
the **number of distinct paths** that achieve that maximum. A single DP value is not
enough — carry a *pair* `(bestSum, count)` for each cell. When you combine several
incoming directions, the maximum sum is decided first, and only the neighbors that tie
for that maximum contribute to the path count. Remember to take the count modulo
`10^9 + 7`.

### Hint 3

Fill the table in an order where a cell's predecessors are already computed. Because a
move goes *toward* `(0,0)`, a cell `(i, j)` is reached *from* one of `(i+1, j)`,
`(i, j+1)`, or `(i+1, j+1)` (the cells you were standing on before the move). So iterate
`i` from `n-1` down to `0` and `j` from `n-1` down to `0`. For each non-obstacle cell,
look at those three predecessors, pick the maximum reachable sum, sum the path counts of
every predecessor tying for that maximum, then add the cell's own digit value (`'E'` and
`'S'` contribute `0`). Mark obstacles and truly unreachable cells so they can never feed
a path.

## Approach

We run a bottom-up DP on the `n x n` board.

Define two tables:

- `dp[i][j]` = the maximum sum of digits collectable on a path from `S` (the
bottom-right cell) to cell `(i, j)`. We use `-1` as a sentinel meaning "unreachable".
- `cnt[i][j]` = the number of distinct paths from `S` to `(i, j)` achieving `dp[i][j]`,
taken modulo `10^9 + 7`.

**Base case.** The journey begins on `S` at `(n-1, n-1)`, so `dp[n-1][n-1] = 0` and
`cnt[n-1][n-1] = 1`.

**Transition.** A move from the previous cell to the current cell goes up, left, or
up-left, which means the current cell `(i, j)` is entered *from* one of its three
"lower/right" neighbors: `(i+1, j)`, `(i, j+1)`, `(i+1, j+1)`. To have those neighbors
already solved, we sweep `i` from `n-1` down to `0` and, inside, `j` from `n-1` down to
`0`.

For each cell (other than the start):

1. If it is an obstacle `'X'`, leave it unreachable.
2. Otherwise scan the three predecessors. Track `best`, the largest `dp` value among the
reachable ones, and `ways`, the sum (mod `1e9+7`) of the `cnt` values of every
predecessor whose `dp` equals `best`.
3. If no predecessor is reachable, this cell is unreachable too.
4. Otherwise set `dp[i][j] = best + value`, where `value` is the digit's numeric value,
or `0` when the cell is `'E'` (or `'S'`). Set `cnt[i][j] = ways`.

**Answer.** The destination is `E` at `(0, 0)`. If `dp[0][0]` is still the sentinel, no
path exists, so return `[0, 0]`. Otherwise return `[dp[0][0], cnt[0][0]]`.

**Worked example.** For `board = ["E23","2X2","12S"]`, the sweep computes a best sum of
`7` reaching `(0,0)` along the single path `S(2,2) → (2,1)=2 → (2,0)=1 → (1,0)=2 →
(0,0)=E`... wait, the actual max path collects `2 + 2 + 3 = 7` via
`S → (0,2)=3 → (0,1)=2 → E` combined with the right column, and exactly one path attains
`7`, giving `[7, 1]`. In `["E12","1X1","21S"]` two different routes both collect `4`, so
the answer is `[4, 2]`, and the count-merging step is what distinguishes them.

## Complexity Analysis

Time Complexity: O(n^2) — each of the `n^2` cells does a constant amount of work
(inspecting three predecessors).
Space Complexity: O(n^2) for the `dp` and `cnt` tables. This can be reduced to O(n) by
keeping only two rows, but O(n^2) is clear and well within the constraints
(`n <= 100`).

## Edge Cases

- **No path exists (example 3, `["E11","XXX","11S"]`).** A wall of obstacles can cut off
`E` entirely. The sentinel `-1` propagates: since no predecessor is reachable, `E`
stays unreachable and we return `[0, 0]` rather than a spurious sum.
- **Obstacle handling.** An `'X'` cell must never contribute a path; treating it as
unreachable (never assigning it a `dp`) prevents it from feeding downstream cells.
- **`'E'` and `'S'` contribute zero.** They are not digits; adding a numeric value for
them would inflate the sum. Only characters `'1'..'9'` add their value.
- **Counting with ties.** When two or three predecessors tie for the best sum, their path
counts must be *added*, not overwritten — this is the whole point of the second output.
- **Modulo on the count only.** The maximum sum is small (at most `9 * (2n-2)`), so only
the path count needs the `10^9 + 7` modulo; applying it to the sum would be wrong.
- **Minimum board size.** With `n = 2`, `E` and `S` are always distinct cells and the
three-predecessor scan still works because out-of-range neighbors are simply skipped.
56 changes: 56 additions & 0 deletions problems/1301-number-of-paths-with-max-score/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
number: "1301"
frontend_id: "1301"
title: "Number of Paths with Max Score"
slug: "number-of-paths-with-max-score"
difficulty: "Hard"
topics:
- "Array"
- "Dynamic Programming"
- "Matrix"
acceptance_rate: 4957.1
is_premium: false
created_at: "2026-07-05T04:38:03.527460+00:00"
fetched_at: "2026-07-05T04:38:03.527460+00:00"
link: "https://leetcode.com/problems/number-of-paths-with-max-score/"
date: "2026-07-05"
---

# 1301. Number of Paths with Max Score

You are given a square `board` of characters. You can move on the board starting at the bottom right square marked with the character `'S'`.

You need to reach the top left square marked with the character `'E'`. The rest of the squares are labeled either with a numeric character `1, 2, ..., 9` or with an obstacle `'X'`. In one move you can go up, left or up-left (diagonally) only if there is no obstacle there.

Return a list of two integers: the first integer is the maximum sum of numeric characters you can collect, and the second is the number of such paths that you can take to get that maximum sum, **taken modulo`10^9 + 7`**.

In case there is no path, return `[0, 0]`.



**Example 1:**


**Input:** board = ["E23","2X2","12S"]
**Output:** [7,1]


**Example 2:**


**Input:** board = ["E12","1X1","21S"]
**Output:** [4,2]


**Example 3:**


**Input:** board = ["E11","XXX","11S"]
**Output:** [0,0]




**Constraints:**

* `2 <= board.length == board[i].length <= 100`
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package main

// Problem 1301. Number of Paths with Max Score
//
// Bottom-up dynamic programming over the board. Starting from 'S' at the
// bottom-right, every legal move (up, left, up-left) goes toward 'E' at the
// top-left, so a cell (i, j) is reached from one of (i+1, j), (i, j+1) or
// (i+1, j+1). We sweep i and j from high to low so predecessors are solved
// first. For each cell we carry two values: the maximum collectable sum
// (dp) and the number of paths achieving it (cnt), with cnt taken modulo
// 1e9+7. Unreachable cells are marked with dp = -1.
func pathsWithMaxScore(board []string) []int {
const mod = 1_000_000_007
n := len(board)

dp := make([][]int, n)
cnt := make([][]int, n)
for i := range dp {
dp[i] = make([]int, n)
cnt[i] = make([]int, n)
for j := range dp[i] {
dp[i][j] = -1 // sentinel: unreachable
}
}

// Base case: the path starts on 'S' at the bottom-right corner.
dp[n-1][n-1] = 0
cnt[n-1][n-1] = 1

// Predecessors of (i, j): the cell we stood on before moving here.
dirs := [3][2]int{{1, 0}, {0, 1}, {1, 1}}

for i := n - 1; i >= 0; i-- {
for j := n - 1; j >= 0; j-- {
if i == n-1 && j == n-1 {
continue // start cell already initialized
}
if board[i][j] == 'X' {
continue // obstacle: stays unreachable
}

best := -1
ways := 0
for _, d := range dirs {
pi, pj := i+d[0], j+d[1]
if pi >= n || pj >= n || dp[pi][pj] < 0 {
continue
}
if dp[pi][pj] > best {
best = dp[pi][pj]
ways = cnt[pi][pj]
} else if dp[pi][pj] == best {
ways = (ways + cnt[pi][pj]) % mod
}
}

if best < 0 {
continue // no reachable predecessor
}

value := 0
if board[i][j] != 'E' { // 'E' and 'S' contribute nothing
value = int(board[i][j] - '0')
}
dp[i][j] = best + value
cnt[i][j] = ways
}
}

if dp[0][0] < 0 {
return []int{0, 0}
}
return []int{dp[0][0], cnt[0][0]}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"reflect"
"testing"
)

func TestSolution(t *testing.T) {
tests := []struct {
name string
board []string
expected []int
}{
{
name: "example 1: single max path",
board: []string{"E23", "2X2", "12S"},
expected: []int{7, 1},
},
{
name: "example 2: two paths tie for max",
board: []string{"E12", "1X1", "21S"},
expected: []int{4, 2},
},
{
name: "example 3: no path due to obstacle wall",
board: []string{"E11", "XXX", "11S"},
expected: []int{0, 0},
},
{
name: "edge case: smallest 2x2 board with digits",
board: []string{"E1", "1S"},
expected: []int{1, 2},
},
{
name: "edge case: 2x2 with diagonal obstacle forces one path",
board: []string{"E1", "XS"},
expected: []int{1, 1},
},
{
name: "edge case: diagonal S-to-E move still reachable past corner obstacles",
board: []string{"EX", "XS"},
expected: []int{0, 1},
},
{
name: "edge case: all nines, max avoids diagonals to collect more cells",
board: []string{"E99", "999", "99S"},
expected: []int{27, 6},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := pathsWithMaxScore(tt.board)
if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("pathsWithMaxScore(%v) = %v, want %v", tt.board, got, tt.expected)
}
})
}
}