Maximum Number of Moves in a Grid solution and explanation
9 more videos on this problem
Maximum Number of Moves in a Grid | Simplest Approach | Leetcode 2684 | codestorywithMIKcodestorywithMIK · 15:53 · 6K views
Maximum Number of Moves in a Grid | Recursion | DP | Leetcode 2684Techdose · 17:33 · 3K views
2684. Maximum Number of Moves in a Grid - LeetCode Python SolutionCode with Carter · 11:18 · 1K views
2684. Maximum Number of Moves in a Grid | Leetcode Daily (POTD) 29 Oct 2024 | JavaAlgorithmHQ · 13:03 · 1K views
Leetcode Weekly contest 345 - Medium - Maximum Number of Moves in a GridPrakhar Agrawal · 7:13 · 988 views
2684. Maximum Number of Moves in a Grid (Leetcode Medium)Programming Live with Larry · 7:09 · 674 views
Maximum Number of Moves in a Grid - LeetCode 2684 - PythonAlpha-Code · 9:15 · 458 views
2684. Maximum Number of Moves in a Grid | LeetCode Daily ChallengeRupdeep Dey · 8:45 · 400 views
Max Number of Moves In a Grid | Leetcode 2684 | Recursion | Memoization | DPTech With Anubhav · 32:42 · 142 views
Maximum Number of Moves in a Grid - Leetcode 2684 - PythonTim Huang · 6:52 · 101 views
The problem
You are given a 0-indexed m x n matrix grid consisting of positive integers.
You can start at any cell in the first column of the matrix, and traverse the grid in the following way:
- From a cell
(row, col), you can move to any of the cells:(row - 1, col + 1),(row, col + 1)and(row + 1, col + 1)such that the value of the cell you move to, should be strictly bigger than the value of the current cell.
Return the maximum number of moves that you can perform.
Example 1:
Input: grid = [[2,4,3,5],[5,4,9,3],[3,4,2,11],[10,9,13,15]] Output: 3 Explanation: We can start at the cell (0, 0) and make the following moves: - (0, 0) -> (0, 1). - (0, 1) -> (1, 2). - (1, 2) -> (2, 3). It can be shown that it is the maximum number of moves that can be made.
Example 2:
Input: grid = [[3,2,4],[2,1,9],[1,1,7]] Output: 0 Explanation: Starting from any cell in the first column we cannot perform any moves.
Constraints:
m == grid.lengthn == grid[i].length2 <= m, n <= 10004 <= m * n <= 1051 <= grid[i][j] <= 106
Approach overview
Problem Overview: You are given an m x n grid of integers. Starting from any cell in the first column, you can move to (row-1, col+1), (row, col+1), or (row+1, col+1) if the destination value is strictly greater than the current cell. The task is to compute the maximum number of moves possible following these rules.
Approach 1: Divide and Conquer with DFS + Memoization (O(m * n) time, O(m * n) space)
Treat each cell as the start of a subproblem: the maximum moves you can make beginning from that position. From cell (r, c), recursively explore the three valid next positions in column c + 1 that contain larger values. Without caching, this would recompute the same paths many times. Memoization stores the result for each cell so every state is solved once. The recursion effectively performs a depth‑first search across the grid while caching results in a DP table. This approach maps naturally to dynamic programming over a matrix, and the state transition simply picks the best of the three possible moves.
Approach 2: Iterative Subproblem Solving (Bottom‑Up DP) (O(m * n) time, O(m * n) space)
The recursive solution can be converted into a bottom‑up DP. Process columns from right to left since moves always go to col + 1. Maintain a DP table where dp[r][c] represents the maximum moves starting from cell (r, c). For each cell, check the three possible neighbors in the next column. If the neighbor’s value is larger, update dp[r][c] = 1 + dp[next]. The answer is the maximum value among all cells in the first column. This avoids recursion overhead and keeps the transition logic explicit. Iterating column by column also makes boundary handling easier when working with arrays and grids.
Recommended for interviews: The DFS + memoization approach clearly shows the state transition and demonstrates strong understanding of dynamic programming on grids. Interviewers often expect you to first describe the recursive exploration and then optimize it with memoization. The bottom‑up DP version shows additional maturity by eliminating recursion and computing the same states iteratively.
Approach 1: Divide and Conquer
This approach involves breaking down the problem into smaller subproblems. Each subproblem is solved independently, and the solutions to these subproblems are combined to solve the original problem. This is typically implemented through recursive functions.
def solve_sub_problem(data, start, end):
if start >= end:
return
mid = (start + end) // 2
solve_sub_problem(data, start, mid)
solve_sub_problem(data, mid + 1, end)
# Combine results
...
def divide_and_conquer(data):
solve_sub_problem(data, 0, len(data) - 1)
data = [ # Your data here ]
divide_and_conquer(data)public class DivideAndConquer {
public static void solveSubProblem(int[] data, int start, int end) {
if (start >= end) return;
int mid = (start + end) / 2;
solveSubProblem(data, start, mid);
solveSubProblem(data, mid + 1, end);
// Combine results
...
}
public static void divideAndConquer(int[] data) {
solveSubProblem(data, 0, data.length - 1);
}
public static void main(String[] args) {
int[] data = { /* Your data here */ };
divideAndConquer(data);
}
}#include <iostream>
#include <vector>
void solveSubProblem(std::vector<int> &data, int start, int end) {
if (start >= end) return;
int mid = (start + end) / 2;
solveSubProblem(data, start, mid);
solveSubProblem(data, mid + 1, end);
// Combine results
...
}
void divideAndConquer(std::vector<int> &data) {
solveSubProblem(data, 0, data.size() - 1);
}
int main() {
std::vector<int> data = { /* Your data here */ };
divideAndConquer(data);
return 0;
}function solveSubProblem(data, start, end) {
if (start >= end) return;
const mid = Math.floor((start + end) / 2);
solveSubProblem(data, start, mid);
solveSubProblem(data, mid + 1, end);
// Combine results
...
}
function divideAndConquer(data) {
solveSubProblem(data, 0, data.length - 1);
}
const data = [ /* Your data here */ ];
divideAndConquer(data);#include <stdio.h>
void solveSubProblem(int *data, int start, int end) {
if (start >= end) return;
int mid = (start + end) / 2;
solveSubProblem(data, start, mid);
solveSubProblem(data, mid + 1, end);
// Combine results
...
}
void divideAndConquer(int *data, int size) {
solveSubProblem(data, 0, size - 1);
}
int main() {
int data[] = { /* Your data here */ };
int size = sizeof(data) / sizeof(data[0]);
divideAndConquer(data, size);
return 0;
}using System;
class DivideAndConquer
{
public static void SolveSubProblem(int[] data, int start, int end)
{
if (start >= end) return;
int mid = (start + end) / 2;
SolveSubProblem(data, start, mid);
SolveSubProblem(data, mid + 1, end);
// Combine results
...
}
public static void DivideAndConquerMethod(int[] data)
{
SolveSubProblem(data, 0, data.Length - 1);
}
static void Main()
{
int[] data = { /* Your data here */ };
DivideAndConquerMethod(data);
}
}This Python script uses recursion through the function solve_sub_problem to apply the divide and conquer technique, splitting the array and processing subproblems until they are solved individually and then merging them as needed.
Time Complexity: O(n log n), where the problem size is reduced logarithmically and combined linearly.
Space Complexity: O(log n) for the recursive call stack depth.
Approach 2: Iterative Subproblem Solving
This approach avoids recursion by iteratively solving subproblems. It may use data structures as stacks or queues to keep track of subproblems, or directly manipulate indices to iterate over sections of the data.
def solve_iteratively(data):
# Loop through and solve iteratively
...
data = [ # Your data here ]
solve_iteratively(data)public class IterativeSolution {
public static void solveIteratively(int[] data) {
// Implement iterative logic
...
}
public static void main(String[] args) {
int[] data = { /* Your data here */ };
solveIteratively(data);
}
}#include <iostream>
#include <vector>
void solveIteratively(std::vector<int> &data) {
// Implement logic here
...
}
int main() {
std::vector<int> data = { /* Your data here */ };
solveIteratively(data);
return 0;
}function solveIteratively(data) {
// Use a loop to iteratively solve the problem
...
}
const data = [ /* Your data here */ ];
solveIteratively(data);#include <stdio.h>
void solveIteratively(int *data, int size) {
// Implement logic to iteratively handle subproblems
...
}
int main() {
int data[] = { /* Your data here */ };
int size = sizeof(data) / sizeof(data[0]);
solveIteratively(data, size);
return 0;
}using System;
class IterativeSolution
{
public static void SolveIteratively(int[] data)
{
// Iterative implementation logic
...
}
static void Main()
{
int[] data = { /* Your data here */ };
SolveIteratively(data);
}
}Python's solve_iteratively function embodies the iteration process to systematically handle each segment of the data, suitable for contexts where recursion might be too memory-intensive.
Time Complexity: O(n) in the context of a streamline iterative approach.
Space Complexity: O(1), with no additional data structure usage.
Approach 3: BFS
We define a queue q, and initially add all the row coordinates of the first column to the queue.
Next, we start from the first column and traverse column by column. For each column, we take out all the row coordinates in the queue one by one. For each row coordinate i, we get all possible row coordinates k of the next column, and satisfy grid[i][j] < grid[k][j + 1], and add these row coordinates to a new set t. If t is empty, it means that we cannot continue to move, so we return the current column number. Otherwise, we assign t to q and continue to traverse the next column.
Finally, if we have traversed all the columns, it means that we can move to the last column, so we return n - 1.
The time complexity is O(m times n), and the space complexity is O(m). Where m and n are the number of rows and columns in the matrix, respectively.
function maxMoves(grid: number[][]): number {
const m = grid.length;
const n = grid[0].length;
let q = new Set<number>(Array.from({ length: m }, (_, i) => i));
for (let j = 0; j < n - 1; ++j) {
const t = new Set<number>();
for (const i of q) {
for (let k = i - 1; k <= i + 1; ++k) {
if (k >= 0 && k < m && grid[i][j] < grid[k][j + 1]) {
t.add(k);
}
}
}
if (t.size === 0) {
return j;
}
q = t;
}
return n - 1;
}func maxMoves(grid [][]int) (ans int) {
m, n := len(grid), len(grid[0])
q := map[int]bool{}
for i := range grid {
q[i] = true
}
for j := 0; j < n-1; j++ {
t := map[int]bool{}
for i := range q {
for k := i - 1; k <= i+1; k++ {
if k >= 0 && k < m && grid[i][j] < grid[k][j+1] {
t[k] = true
}
}
}
if len(t) == 0 {
return j
}
q = t
}
return n - 1
}Complexity comparison
| Approach | Complexity |
|---|---|
| Divide and Conquer | Time Complexity: O(n log n), where the problem size is reduced logarithmically and combined linearly. |
| Iterative Subproblem Solving | Time Complexity: O(n) in the context of a streamline iterative approach. |
| BFS | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Divide and Conquer (DFS + Memoization) | O(m * n) | O(m * n) | When modeling the problem recursively or explaining the DP state transition during interviews |
| Iterative Subproblem Solving (Bottom-Up DP) | O(m * n) | O(m * n) | When you want an iterative solution without recursion overhead and clearer DP table updates |
Frequently asked questions
Is Maximum Number of Moves in a Grid easy or hard?
Maximum Number of Moves in a Grid is classified as a Medium difficulty problem. The challenge comes from recognizing that it is a dynamic programming problem on a grid and correctly handling the three directional transitions while enforcing the strictly increasing condition.
How to solve Maximum Number of Moves in a Grid in O(m * n)?
Use dynamic programming where dp[r][c] stores the maximum moves starting from cell (r, c). Process columns from right to left and check the three allowed neighbors in column c+1 if their values are greater. Each state is computed once, resulting in O(m * n) time.
What is the best approach for Maximum Number of Moves in a Grid?
Dynamic programming with memoized DFS or bottom-up DP is the most effective approach. Each cell represents a state where you compute the maximum moves reachable from that position. Since every state is evaluated once and each transition checks only three neighbors, the total complexity stays O(m * n).
Is Maximum Number of Moves in a Grid asked at Google/Amazon/Meta?
Grid dynamic programming problems like this appear frequently in interviews at companies such as Amazon, Google, and Meta. Variants involving path transitions, increasing values, or restricted movements are common interview patterns.
What data structure is used in Maximum Number of Moves in a Grid?
The main structure is a 2D dynamic programming table built on top of the input matrix. DFS solutions also use recursion with memoization, while iterative solutions store intermediate results directly in a DP array.
What is the time complexity of Maximum Number of Moves in a Grid?
The optimal solutions run in O(m * n) time where m is the number of rows and n is the number of columns. Each grid cell is processed once, and at most three transitions are evaluated per cell. Space complexity is also O(m * n) for storing memoization or DP results.
Maximum Number of Moves in a Grid Python or Java solution approach?
Both Python and Java implementations typically use DFS with memoization or bottom-up dynamic programming. The algorithm checks three neighbors in the next column and updates the maximum moves for each cell. The complexity remains O(m * n) regardless of language.

Input: grid = [[3,2,4],[2,1,9],[1,1,7]]
Output: 0
Explanation: Starting from any cell in the first column we cannot perform any moves.