Talentd
FleetCode
Make and follow sheets.Sign in

Minimum Time to Break Locks II solution and explanation

HardArrayDepth-First SearchGraph7 min read45.7% acceptanceUpdated 24 Mar 2026Premium elsewhere, free here

The problem

Bob is stuck in a dungeon and must break n locks, each requiring some amount of energy to break. The required energy for each lock is stored in an array called strength where strength[i] indicates the energy needed to break the ith lock.

To break a lock, Bob uses a sword with the following characteristics:

  • The initial energy of the sword is 0.
  • The initial factor X by which the energy of the sword increases is 1.
  • Every minute, the energy of the sword increases by the current factor X.
  • To break the ith lock, the energy of the sword must reach at least strength[i].
  • After breaking a lock, the energy of the sword resets to 0, and the factor X increases by 1.

Your task is to determine the minimum time in minutes required for Bob to break all n locks and escape the dungeon.

Return the minimum time required for Bob to break all n locks.

Example 1:

Input: strength = [3,4,1]

Output: 4

Explanation:

Time Energy X Action Updated X
0 0 1 Nothing 1
1 1 1 Break 3rd Lock 2
2 2 2 Nothing 2
3 4 2 Break 2nd Lock 3
4 3 3 Break 1st Lock 3

The locks cannot be broken in less than 4 minutes; thus, the answer is 4.

Example 2:

Input: strength = [2,5,4]

Output: 6

Explanation:

Time Energy X Action Updated X
0 0 1 Nothing 1
1 1 1 Nothing 1
2 2 1 Break 1st Lock 2
3 2 2 Nothing 2
4 4 2 Break 3rd Lock 3
5 3 3 Nothing 3
6 6 3 Break 2nd Lock 4

The locks cannot be broken in less than 6 minutes; thus, the answer is 6.

Constraints:

  • n == strength.length
  • 1 <= n <= 80
  • 1 <= strength[i] <= 106
  • n == strength.length

Approach overview

Problem Overview: You are given an array strength where each value represents the effort required to break a lock. Your sword starts with power 1. Breaking a lock takes ceil(strength[i] / power) time. After each lock is broken, the sword power increases by k. The challenge is choosing the order of locks so the total time is minimized.

Approach 1: Brute Force Permutation with DFS (O(n!))

The direct idea is to try every possible order of breaking locks. Use depth-first search to generate permutations of the indices. At each step compute the time required using ceil(strength[i] / currentPower), then increase power by k after breaking the lock. Track the minimum total time across all permutations. This approach is straightforward but becomes expensive because the number of orders grows factorially. It works only for very small n.

Approach 2: Bitmask DFS with Memoization (O(n · 2^n))

Instead of recomputing the same states, treat the problem as a state graph. A state is defined by a bitmask representing which locks are already broken. The number of broken locks determines the current sword power: power = 1 + brokenCount * k. From each state, iterate through all remaining locks and try breaking one next. Recursively compute the cost and cache results for each mask. This transforms the search into a dynamic programming problem over subsets, similar to traveling through nodes in a small graph. Memoization avoids recomputation and reduces the complexity to O(n · 2^n) time with O(2^n) space.

Approach 3: Graph Shortest Path Interpretation (O(n · 2^n))

You can also model each bitmask as a node in a state graph. Edges represent breaking an unbroken lock and moving to a new mask. The edge weight equals the time required with the current power. Running DFS with caching or Dijkstra-style traversal finds the minimum total time to reach the mask where all locks are broken. This framing helps when reasoning about transitions and pruning repeated states. The underlying mechanics still rely on subset DP and efficient traversal of the state space.

Recommended for interviews: The bitmask DFS with memoization approach is the expected solution. Starting with brute force shows you understand the ordering dependency, but recognizing overlapping states and converting the search into subset DP demonstrates strong problem-solving skills. Interviewers usually want to see the transition from permutation exploration to memoized state search using techniques common in array ordering and subset graph traversal.

Solution

class MCFGraph:
    class Edge(NamedTuple):
        src: int
        dst: int
        cap: int
        flow: int
        cost: int

    class _Edge:
        def __init__(self, dst: int, cap: int, cost: int) -> None:
            self.dst = dst
            self.cap = cap
            self.cost = cost
            self.rev: Optional[MCFGraph._Edge] = None

    def __init__(self, n: int) -> None:
        self._n = n
        self._g: List[List[MCFGraph._Edge]] = [[] for _ in range(n)]
        self._edges: List[MCFGraph._Edge] = []

    def add_edge(self, src: int, dst: int, cap: int, cost: int) -> int:
        assert 0 <= src < self._n
        assert 0 <= dst < self._n
        assert 0 <= cap
        m = len(self._edges)
        e = MCFGraph._Edge(dst, cap, cost)
        re = MCFGraph._Edge(src, 0, -cost)
        e.rev = re
        re.rev = e
        self._g[src].append(e)
        self._g[dst].append(re)
        self._edges.append(e)
        return m

    def get_edge(self, i: int) -> Edge:
        assert 0 <= i < len(self._edges)
        e = self._edges[i]
        re = cast(MCFGraph._Edge, e.rev)
        return MCFGraph.Edge(re.dst, e.dst, e.cap + re.cap, re.cap, e.cost)

    def edges(self) -> List[Edge]:
        return [self.get_edge(i) for i in range(len(self._edges))]

    def flow(self, s: int, t: int, flow_limit: Optional[int] = None) -> Tuple[int, int]:
        return self.slope(s, t, flow_limit)[-1]

    def slope(
        self, s: int, t: int, flow_limit: Optional[int] = None
    ) -> List[Tuple[int, int]]:
        assert 0 <= s < self._n
        assert 0 <= t < self._n
        assert s != t
        if flow_limit is None:
            flow_limit = cast(int, sum(e.cap for e in self._g[s]))

        dual = [0] * self._n
        prev: List[Optional[Tuple[int, MCFGraph._Edge]]] = [None] * self._n

        def refine_dual() -> bool:
            pq = [(0, s)]
            visited = [False] * self._n
            dist: List[Optional[int]] = [None] * self._n
            dist[s] = 0
            while pq:
                dist_v, v = heappop(pq)
                if visited[v]:
                    continue
                visited[v] = True
                if v == t:
                    break
                dual_v = dual[v]
                for e in self._g[v]:
                    w = e.dst
                    if visited[w] or e.cap == 0:
                        continue
                    reduced_cost = e.cost - dual[w] + dual_v
                    new_dist = dist_v + reduced_cost
                    dist_w = dist[w]
                    if dist_w is None or new_dist < dist_w:
                        dist[w] = new_dist
                        prev[w] = v, e
                        heappush(pq, (new_dist, w))
            else:
                return False
            dist_t = dist[t]
            for v in range(self._n):
                if visited[v]:
                    dual[v] -= cast(int, dist_t) - cast(int, dist[v])
            return True

        flow = 0
        cost = 0
        prev_cost_per_flow: Optional[int] = None
        result = [(flow, cost)]
        while flow < flow_limit:
            if not refine_dual():
                break
            f = flow_limit - flow
            v = t
            while prev[v] is not None:
                u, e = cast(Tuple[int, MCFGraph._Edge], prev[v])
                f = min(f, e.cap)
                v = u
            v = t
            while prev[v] is not None:
                u, e = cast(Tuple[int, MCFGraph._Edge], prev[v])
                e.cap -= f
                assert e.rev is not None
                e.rev.cap += f
                v = u
            c = -dual[s]
            flow += f
            cost += f * c
            if c == prev_cost_per_flow:
                result.pop()
            result.append((flow, cost))
            prev_cost_per_flow = c
        return result


class Solution:
    def findMinimumTime(self, a: List[int]) -> int:
        n = len(a)
        s = n * 2
        t = s + 1
        g = MCFGraph(t + 1)

        for i in range(n):
            g.add_edge(s, i, 1, 0)
            g.add_edge(i + n, t, 1, 0)
            for j in range(n):
                g.add_edge(i, j + n, 1, (a[i] - 1) // (j + 1) + 1)

        return g.flow(s, t, n)[1]
class MCFGraph {
    static class Edge {
        int src, dst, cap, flow, cost;

        Edge(int src, int dst, int cap, int flow, int cost) {
            this.src = src;
            this.dst = dst;
            this.cap = cap;
            this.flow = flow;
            this.cost = cost;
        }
    }

    static class _Edge {
        int dst, cap, cost;
        _Edge rev;

        _Edge(int dst, int cap, int cost) {
            this.dst = dst;
            this.cap = cap;
            this.cost = cost;
            this.rev = null;
        }
    }

    private int n;
    private List<List<_Edge>> graph;
    private List<_Edge> edges;

    public MCFGraph(int n) {
        this.n = n;
        this.graph = new ArrayList<>();
        this.edges = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            graph.add(new ArrayList<>());
        }
    }

    public int addEdge(int src, int dst, int cap, int cost) {
        assert (0 <= src && src < n);
        assert (0 <= dst && dst < n);
        assert (0 <= cap);

        int m = edges.size();
        _Edge e = new _Edge(dst, cap, cost);
        _Edge re = new _Edge(src, 0, -cost);
        e.rev = re;
        re.rev = e;

        graph.get(src).add(e);
        graph.get(dst).add(re);
        edges.add(e);
        return m;
    }

    public Edge getEdge(int i) {
        assert (0 <= i && i < edges.size());
        _Edge e = edges.get(i);
        _Edge re = e.rev;
        return new Edge(re.dst, e.dst, e.cap + re.cap, re.cap, e.cost);
    }

    public List<Edge> edges() {
        List<Edge> result = new ArrayList<>();
        for (int i = 0; i < edges.size(); i++) {
            result.add(getEdge(i));
        }
        return result;
    }

    public int[] flow(int s, int t, Integer flowLimit) {
        List<int[]> result = slope(s, t, flowLimit);
        return result.get(result.size() - 1);
    }

    public List<int[]> slope(int s, int t, Integer flowLimit) {
        assert (0 <= s && s < n);
        assert (0 <= t && t < n);
        assert (s != t);

        if (flowLimit == null) {
            flowLimit = graph.get(s).stream().mapToInt(e -> e.cap).sum();
        }

        int[] dual = new int[n];
        Tuple[] prev = new Tuple[n];

        List<int[]> result = new ArrayList<>();
        result.add(new int[] {0, 0});

        while (true) {
            if (!refineDual(s, t, dual, prev)) {
                break;
            }

            int f = flowLimit;
            int v = t;
            while (prev[v] != null) {
                Tuple tuple = prev[v];
                int u = tuple.first;
                _Edge e = tuple.second;
                f = Math.min(f, e.cap);
                v = u;
            }

            v = t;
            while (prev[v] != null) {
                Tuple tuple = prev[v];
                int u = tuple.first;
                _Edge e = tuple.second;
                e.cap -= f;
                e.rev.cap += f;
                v = u;
            }

            int c = -dual[s];
            result.add(new int[] {
                result.get(result.size() - 1)[0] + f, result.get(result.size() - 1)[1] + f * c});

            if (c == result.get(result.size() - 2)[1]) {
                result.remove(result.size() - 2);
            }
        }

        return result;
    }

    private boolean refineDual(int s, int t, int[] dual, Tuple[] prev) {
        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
        pq.add(new int[] {0, s});
        boolean[] visited = new boolean[n];
        Integer[] dist = new Integer[n];
        Arrays.fill(dist, null);
        dist[s] = 0;

        while (!pq.isEmpty()) {
            int[] current = pq.poll();
            int distV = current[0];
            int v = current[1];

            if (visited[v]) continue;
            visited[v] = true;

            if (v == t) break;

            int dualV = dual[v];
            for (_Edge e : graph.get(v)) {
                int w = e.dst;
                if (visited[w] || e.cap == 0) continue;

                int reducedCost = e.cost - dual[w] + dualV;
                int newDist = distV + reducedCost;
                Integer distW = dist[w];

                if (distW == null || newDist < distW) {
                    dist[w] = newDist;
                    prev[w] = new Tuple(v, e);
                    pq.add(new int[] {newDist, w});
                }
            }
        }

        if (!visited[t]) return false;

        int distT = dist[t];
        for (int v = 0; v < n; v++) {
            if (visited[v]) {
                dual[v] -= distT - dist[v];
            }
        }

        return true;
    }

    static class Tuple {
        int first;
        _Edge second;

        Tuple(int first, _Edge second) {
            this.first = first;
            this.second = second;
        }
    }
}

class Solution {
    public int findMinimumTime(int[] strength) {
        int n = strength.length;
        int s = n * 2;
        int t = s + 1;
        MCFGraph g = new MCFGraph(t + 1);

        for (int i = 0; i < n; i++) {
            g.addEdge(s, i, 1, 0);
            g.addEdge(i + n, t, 1, 0);
            for (int j = 0; j < n; j++) {
                g.addEdge(i, j + n, 1, (strength[i] - 1) / (j + 1) + 1);
            }
        }

        return g.flow(s, t, n)[1];
    }
}
Try this approach in the editor

Complexity analysis

ApproachTimeSpaceWhen to Use
Brute Force DFS (Permutations)O(n!)O(n)Useful for understanding the ordering dependency or when n is very small
DFS + Memoization (Bitmask DP)O(n · 2^n)O(2^n)Best general solution when n ≤ 10–15 and states repeat frequently
State Graph Shortest PathO(n · 2^n)O(2^n)When modeling the problem explicitly as transitions between bitmask states

Frequently asked questions

Is Minimum Time to Break Locks II easy or hard?

Minimum Time to Break Locks II is classified as Hard. The difficulty comes from recognizing that the lock-breaking order affects future costs and that brute force permutations are too slow. Converting the problem into subset DP with memoized DFS is the key insight.

Minimum Time to Break Locks II Python/Java solution

Implement a recursive DFS function that accepts the current bitmask of broken locks. Use a memo array indexed by mask to cache results. For each unbroken lock, compute the time using ceil(strength[i] / power), update the mask, and recurse. The same logic works in Python, Java, C++, and Go with bit operations and memoization.

How to solve Minimum Time to Break Locks II in O(n · 2^n)?

Represent the set of broken locks as a bitmask. For each mask, compute the current power using the number of bits set. Try breaking each remaining lock, calculate the time cost using ceil(strength[i] / power), and recursively solve the next state. Cache results for each mask so every subset is evaluated only once.

What is the best approach for Minimum Time to Break Locks II?

The most efficient approach is DFS with memoization using a bitmask to represent which locks are already broken. Each state computes the minimum time to finish the remaining locks while deriving the current sword power from the number of broken locks. This reduces repeated work and runs in O(n · 2^n) time with O(2^n) space.

Is Minimum Time to Break Locks II asked at Google/Amazon/Meta?

Problems involving subset DP, permutation optimization, and state graph traversal frequently appear in interviews at companies like Google, Amazon, and Meta. This problem tests similar skills: modeling states, pruning repeated work, and optimizing exponential search with memoization.

What data structure is used in Minimum Time to Break Locks II?

The core structure is a bitmask representing which locks are already broken. A memoization map or array stores results for each mask. DFS or graph traversal is used to explore transitions between states while computing the minimum cost.

What is the time complexity of Minimum Time to Break Locks II?

A naive permutation search takes O(n!) time because every order of locks must be evaluated. With bitmask dynamic programming and memoization, the complexity improves to O(n · 2^n) since each subset of locks is processed once and transitions try up to n remaining locks.

Comments

Now write it yourselfClose this page and solve Minimum Time to Break Locks II in the editor, without looking.
Practise Minimum Time to Break Locks II