Talentd
FleetCode
Make and follow sheets.Sign in

Shortest Path With At Most K Consecutive Identical Characters solution and explanation

MediumGraphHeap (Priority Queue)Shortest PathString7 min read44.7% acceptanceUpdated 30 Oct 2026
Leetcode Weekly Contest 507 | Q3 - Shortest Path With At Most K Consecutive | 3970DSA with Kumar K · 44:52 · 774 viewsWatch on YouTube
6 more videos on this problem
  1. Leetcode Weekly Contest 507 | Q3 - Shortest Path With At Most K Consecutive | 3970DSA with Kumar K · 44:52 · 774 views
  2. Shortest Path With At Most K Consecutive Identical Characters | LeetCode 3970 | Weekly Contest 507 Study Placement · 34:53 · 462 views
  3. Shortest Path With At Most K Consecutive Identical Characters | Leetcode 3970 | Dijkstra AlgorithmTechdose · 36:34 · 458 views
  4. Leetcode 3970 | Shortest Path With At Most K Consecutive Identical Characters | weekly contest 507CodeWithMeGuys · 23:31 · 220 views
  5. LeetCode 3970 | Weekly Contest 507 Q3 | Shortest Path With At Most K Consecutive Identical Charac...CodeSprint · 25:35 · 151 views
  6. 3970. Shortest Path With At Most K Consecutive Identical Characters (Leetcode Medium)Programming Live with Larry · 10:46 · 141 views
  7. 3970. Shortest Path With At Most K Consecutive Identical CharactersLeonardo Vo · 1:04:13 · 4 views

The problem

You are given an integer n representing the number of nodes in a directed weighted graph, numbered from 0 to n - 1. This is represented by a 2D integer array edges, where edges[i] = [ui, vi, wi] represents a directed edge from node ui to node vi with weight wi.

You are also given a string labels of length n, where labels[i] is the character assigned to node i, and an integer k.

Return the minimum total edge weight of a path from node 0 to node n - 1 such that the concatenation of the labels of the nodes along the path contains at most k consecutive identical characters. If no valid path exists, return -1.

Example 1:

Input: n = 3, edges = [[0,1,1],[1,2,1],[0,2,3]], labels = "aab", k = 1

Output: 3

Explanation:

The optimal valid path from node 0 to node 2 is as follows:

  • Use edges[2] = [0, 2, 3] to reach node 2 with a weight wi = 3.
The corresponding concatenation of labels is "ab", which satisfies at most k = 1 consecutive identical characters. Thus, the answer is 3.

Example 2:

Input: n = 3, edges = [[0,1,1],[1,2,1],[0,2,3]], labels = "aab", k = 2

Output: 2

Explanation:

The optimal valid path from node 0 to node 2 is as follows:

  • Use edges[0] = [0, 1, 1] to reach node 1 with weight wi = 1.
  • Use edges[1] = [1, 2, 1] to reach node 2 with weight wi = 1.
The corresponding concatenation of labels is "aab", which satisfies at most k = 2 consecutive identical characters. Thus, the answer is 2.

Example 3:

Input: n = 3, edges = [[0,1,1],[1,2,1]], labels = "aaa", k = 2

Output: -1

Explanation:

There is no valid path from node 0 to node 2 that satisfies at most k = 2 consecutive identical characters. Thus, the answer is -1.

Constraints:

  • 1 <= n == labels.length <= 5 * 104
  • 0 <= edges.length <= 5 * 104
  • edges[i] == [ui, vi, wi]
  • 0 <= ui, vi <= n - 1
  • ui != vi
  • 1 <= wi <= 104
  • labels consists of lowercase English letters
  • 1 <= k <= 50

Approach overview

Problem Overview: You need the shortest path between two nodes (or grid cells) while enforcing a constraint: the path cannot contain more than K consecutive identical characters. Each step extends the path, but if the same character repeats more than K times in a row, that path becomes invalid.

Approach 1: Brute Force DFS with Backtracking (Exponential Time, O(V) Space)

The most direct idea is to explore every possible path using depth-first search. Track the current character and how many times it has appeared consecutively. Each recursive call checks whether the next node continues the same character streak or resets the counter. If the streak exceeds K, the branch stops. This works for small graphs but quickly becomes impractical because the number of paths grows exponentially. Time complexity is roughly O(2^V) in dense graphs, with O(V) recursion stack space. This approach mainly helps you reason about the constraint before optimizing.

Approach 2: BFS with State Expansion (O(V * K) Time, O(V * K) Space)

The shortest path requirement strongly suggests Breadth-First Search. Instead of storing just the node in the queue, store an expanded state: (node, lastChar, streakLength). When exploring neighbors, update the streak length. If the next character matches lastChar, increment the streak; otherwise reset it to 1. Skip transitions where the streak exceeds K. A visited structure must include both the node and streak information, otherwise valid states may be incorrectly pruned. Because each node can appear with at most K streak lengths, the complexity becomes O(V * K + E * K), commonly simplified to O(V * K). Space complexity is also O(V * K) for the queue and visited set.

Approach 3: BFS with Distance + State Compression (O(V * K) Time, O(V * K) Space)

A more practical variant stores distances in a 3D structure like dist[node][char][streak] or compresses it to track the best streak seen for each node and character. The queue still performs standard BFS, but transitions are skipped if a better or equal state was already processed. This reduces redundant exploration in graphs with many repeated characters. Conceptually, it combines shortest path traversal with a lightweight form of state-based dynamic programming.

Recommended for interviews: BFS with state expansion. Interviewers expect you to recognize that shortest path problems with extra constraints require expanding the state space. Starting with brute force demonstrates understanding of the rule, but the BFS formulation shows stronger algorithmic intuition and familiarity with constrained shortest-path patterns.

Solution

Thinking

Edge weights are nonnegative, so a shortest path from node 0 to n - 1 is a Dijkstra problem, except the concatenated labels may contain at most k identical characters in a row. The unconstrained shortest path can be exactly the one that overruns a run, while a costlier path stays valid by changing labels along the way. With n and the number of edges both up to 5 times 10^4, enumerating paths is not viable.

Reaching the same node with different trailing run lengths leaves different room for another identical label, so one distance per node drops legal transitions. The run depends only on the current label and how long it has already lasted: the next label increments it when equal and resets it to 1 otherwise, states past k are dropped, and the start label itself counts as a run of 1.

The state is (u, cur), and dist[u][cur] stores the minimum edge-weight sum to reach it. Nonnegative weights let a binary heap expand states by distance, and the first time the destination leaves the heap is the answer.

Let dist[u][cur] be the minimum total edge weight of a path from node 0 to node u whose trailing run of identical labels has length cur.

Build an adjacency list from edges. The start label is a run of length 1, so dist[0][1] = 0. Push (0, 0, 1) into a min-heap. The triple is the distance, the node, and the run length.

Pop the triple (d, u, cur) with the smallest distance. If u = n - 1, nonnegative weights make this the best way to reach the destination, so return d. If d > dist[u][cur], the state is stale and is skipped.

Otherwise scan each outgoing edge (v, w). The next run length is nxt = 1 when the labels differ and nxt = cur + 1 when they match. The new distance is nd = d + w. Update dist[v][nxt] and push the state only when nxt \le k and nd < dist[v][nxt].

If the heap empties before the destination is reached, return -1.

The time complexity is O(k(n + m)log (nk)), and the space complexity is O(nk + m), where n and m are the number of nodes and edges.

class Solution:
    def shortestPath(self, n: int, edges: List[List[int]], labels: str, k: int) -> int:
        g = [[] for _ in range(n)]
        for u, v, w in edges:
            g[u].append((v, w))

        dist = [[inf] * (k + 1) for _ in range(n)]
        dist[0][1] = 0
        pq = [(0, 0, 1)]
        while pq:
            d, u, cur = heappop(pq)
            if u == n - 1:
                return d
            if d > dist[u][cur]:
                continue
            for v, w in g[u]:
                nxt = 1 if labels[v] != labels[u] else cur + 1
                nd = d + w
                if nxt <= k and nd < dist[v][nxt]:
                    dist[v][nxt] = nd
                    heappush(pq, (nd, v, nxt))
        return -1
class Solution {
    public int shortestPath(int n, int[][] edges, String labels, int k) {
        List<int[]>[] g = new List[n];
        Arrays.setAll(g, i -> new ArrayList<>());
        for (int[] e : edges) {
            g[e[0]].add(new int[] {e[1], e[2]});
        }

        int[][] dist = new int[n][k + 1];
        for (int[] d : dist) {
            Arrays.fill(d, Integer.MAX_VALUE);
        }
        dist[0][1] = 0;

        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0]));
        pq.offer(new int[] {0, 0, 1});

        while (!pq.isEmpty()) {
            int[] p = pq.poll();
            int d = p[0], u = p[1], cur = p[2];
            if (u == n - 1) {
                return d;
            }
            if (d > dist[u][cur]) {
                continue;
            }
            for (int[] e : g[u]) {
                int v = e[0], w = e[1];
                int nxt = labels.charAt(v) != labels.charAt(u) ? 1 : cur + 1;
                int nd = d + w;
                if (nxt <= k && nd < dist[v][nxt]) {
                    dist[v][nxt] = nd;
                    pq.offer(new int[] {nd, v, nxt});
                }
            }
        }
        return -1;
    }
}
class Solution {
public:
    int shortestPath(int n, vector<vector<int>>& edges, string labels, int k) {
        vector<vector<pair<int, int>>> g(n);
        for (auto& e : edges) {
            g[e[0]].emplace_back(e[1], e[2]);
        }

        vector<vector<int>> dist(n, vector<int>(k + 1, INT_MAX));
        dist[0][1] = 0;

        priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<>> pq;
        pq.emplace(0, 0, 1);

        while (!pq.empty()) {
            auto [d, u, cur] = pq.top();
            pq.pop();
            if (u == n - 1) {
                return d;
            }
            if (d > dist[u][cur]) {
                continue;
            }
            for (auto [v, w] : g[u]) {
                int nxt = labels[v] != labels[u] ? 1 : cur + 1;
                int nd = d + w;
                if (nxt <= k && nd < dist[v][nxt]) {
                    dist[v][nxt] = nd;
                    pq.emplace(nd, v, nxt);
                }
            }
        }
        return -1;
    }
};
function shortestPath(n: number, edges: number[][], labels: string, k: number): number {
    const g: [number, number][][] = Array.from({ length: n }, () => []);
    for (const [u, v, w] of edges) {
        g[u].push([v, w]);
    }

    const dist = Array.from({ length: n }, () => new Array(k + 1).fill(Infinity));
    dist[0][1] = 0;

    const pq = new PriorityQueue<number[]>((a, b) => a[0] - b[0]);
    pq.enqueue([0, 0, 1]);

    while (!pq.isEmpty()) {
        const [d, u, cur] = pq.dequeue();

        if (u === n - 1) {
            return d;
        }
        if (d > dist[u][cur]) {
            continue;
        }

        for (const [v, w] of g[u]) {
            const nxt = labels[v] !== labels[u] ? 1 : cur + 1;
            const nd = d + w;
            if (nxt <= k && nd < dist[v][nxt]) {
                dist[v][nxt] = nd;
                pq.enqueue([nd, v, nxt]);
            }
        }
    }
    return -1;
}
func shortestPath(n int, edges [][]int, labels string, k int) int {
	g := make([][][2]int, n)
	for _, e := range edges {
		g[e[0]] = append(g[e[0]], [2]int{e[1], e[2]})
	}

	const inf = int(^uint(0) >> 1)
	dist := make([][]int, n)
	for i := range dist {
		dist[i] = make([]int, k+1)
		for j := range dist[i] {
			dist[i][j] = inf
		}
	}

	dist[0][1] = 0
	pq := &hp{{0, 0, 1}}
	heap.Init(pq)

	for pq.Len() > 0 {
		p := heap.Pop(pq).(tuple)
		d, u, cur := p.d, p.u, p.cur

		if u == n-1 {
			return d
		}
		if d > dist[u][cur] {
			continue
		}

		for _, e := range g[u] {
			v, w := e[0], e[1]
			nxt := cur + 1
			if labels[v] != labels[u] {
				nxt = 1
			}
			nd := d + w
			if nxt <= k && nd < dist[v][nxt] {
				dist[v][nxt] = nd
				heap.Push(pq, tuple{nd, v, nxt})
			}
		}
	}
	return -1
}

type tuple struct {
	d, u, cur int
}

type hp []tuple

func (h hp) Len() int           { return len(h) }
func (h hp) Less(i, j int) bool { return h[i].d < h[j].d }
func (h hp) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
func (h *hp) Push(v any)        { *h = append(*h, v.(tuple)) }
func (h *hp) Pop() any {
	a := *h
	v := a[len(a)-1]
	*h = a[:len(a)-1]
	return v
}
Try this approach in the editor

Complexity analysis

ApproachTimeSpaceWhen to Use
DFS Brute ForceO(2^V)O(V)Small graphs or conceptual explanation of the constraint
BFS with State (node, char, streak)O(V * K)O(V * K)General solution for shortest path with consecutive-character constraint
Optimized BFS with Distance TrackingO(V * K)O(V * K)Large graphs where pruning repeated states improves performance

Frequently asked questions

Is Shortest Path With At Most K Consecutive Identical Characters easy or hard?

The problem is typically rated Medium because it combines standard BFS with additional state tracking. The graph traversal itself is straightforward, but recognizing that the state must include the consecutive character count makes it slightly more complex than a basic shortest-path problem.

Shortest Path With At Most K Consecutive Identical Characters Python/Java solution

Implement BFS using a queue and store states like (node, lastChar, streak). When moving to a neighbor, update the streak depending on whether the character repeats or changes. Skip states where the streak exceeds K and maintain a visited structure keyed by node and streak information. The same pattern works in Python, Java, and C++.

How to solve Shortest Path With At Most K Consecutive Identical Characters in O(n)?

Pure O(n) time is usually not possible because the algorithm must track different streak states up to K. The practical solution uses BFS with a state defined as (node, lastChar, streakLength). By pruning states that exceed K or were already visited, the search remains efficient and runs in O(V * K).

What is the best approach for Shortest Path With At Most K Consecutive Identical Characters?

The most effective solution uses Breadth-First Search with an expanded state. Each state stores the node, the last character seen, and the current consecutive count. This allows the algorithm to enforce the K‑limit while still guaranteeing the shortest path property of BFS. The time complexity is typically O(V * K) because each node can appear with up to K consecutive states.

Is Shortest Path With At Most K Consecutive Identical Characters asked at Google/Amazon/Meta?

Variants of constrained shortest-path problems appear frequently in big tech interviews. Companies like Google, Amazon, and Meta often test BFS or graph traversal problems where additional state must be tracked, such as color constraints, step limits, or alternating patterns.

What data structure is used in Shortest Path With At Most K Consecutive Identical Characters?

The main data structure is a queue used for Breadth-First Search. A visited set or distance table tracks states that include the node, the last character, and the consecutive count. This prevents revisiting identical states and keeps the algorithm within O(V * K) complexity.

What is the time complexity of Shortest Path With At Most K Consecutive Identical Characters?

The optimal BFS solution runs in O(V * K + E * K) time, usually simplified to O(V * K) for sparse graphs. Each node may be visited with different streak lengths up to K. Space complexity is also O(V * K) due to the queue and visited-state tracking.

Comments

Now write it yourselfClose this page and solve Shortest Path With At Most K Consecutive Identical Characters in the editor, without looking.
Practise Shortest Path With At Most K Consecutive Identical Characters