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 a given value k.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Input:ength = [3,4,1], k = 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Input:ength = [2,5,4], k = 2
Output: 5
Explanation:
Time | Energy | x | Action | Updated x  
---|---|---|---|---  
0 | 0 | 1 | Nothing | 1  
1 | 1 | 1 | Nothing | 1  
2 | 2 | 1 | Break 1st Lock | 3  
3 | 3 | 3 | Nothing | 3  
4 | 6 | 3 | Break 2nd Lock | 5  
5 | 5 | 5 | Break 3rd Lock | 7  
The locks cannot be broken in less than 5 minutes; thus, the answer is 5.

Constraints

  • n == strength.length
  • 1 <= n <= 8
  • 1 <= K <= 10
  • 1 <= strength[i] <= 10^6

Examples

Solution

Method 1 – Backtracking with Bitmask DP

Intuition

Since the number of locks is small (n ≤ 8), we can try all possible orders to break the locks. For each order, we simulate the sword’s energy growth and update the factor after each lock. Bitmask DP helps avoid recomputation for the same set of broken locks and current factor.

Approach

  1. Use a DP table: dp[mask][x], where mask is the set of broken locks and x is the current factor.
  2. For each unbroken lock, calculate the time needed to reach its strength with current x.
  3. After breaking a lock, update mask and x, and recursively solve for the remaining locks.
  4. Return the minimum time over all possible orders.

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
    int minTimeToBreakLocks(vector<int>& strength, int k) {
        int n = strength.size();
        vector<vector<int>> dp(1<<n, vector<int>(n*k+2, -1));
        function<int(int,int)> dfs = [&](int mask, int x) {
            if (mask == (1<<n)-1) return 0;
            if (dp[mask][x] != -1) return dp[mask][x];
            int ans = INT_MAX;
            for (int i = 0; i < n; ++i) {
                if (!(mask & (1<<i))) {
                    int t = (strength[i]+x-1)/x;
                    ans = min(ans, t + dfs(mask|(1<<i), x+k));
                }
            }
            return dp[mask][x] = ans;
        };
        return dfs(0, 1);
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func minTimeToBreakLocks(strength []int, k int) int {
    n := len(strength)
    dp := make(map[[2]int]int)
    var dfs func(mask, x int) int
    dfs = func(mask, x int) int {
        if mask == (1<<n)-1 { return 0 }
        key := [2]int{mask, x}
        if v, ok := dp[key]; ok { return v }
        ans := 1<<30
        for i := 0; i < n; i++ {
            if mask&(1<<i) == 0 {
                t := (strength[i]+x-1)/x
                v := t + dfs(mask|(1<<i), x+k)
                if v < ans { ans = v }
            }
        }
        dp[key] = ans
        return ans
    }
    return dfs(0, 1)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
    public int minTimeToBreakLocks(int[] strength, int k) {
        int n = strength.length;
        int[][] dp = new int[1<<n][n*k+2];
        for (int[] d : dp) Arrays.fill(d, -1);
        return dfs(0, 1, strength, k, dp);
    }
    private int dfs(int mask, int x, int[] strength, int k, int[][] dp) {
        int n = strength.length;
        if (mask == (1<<n)-1) return 0;
        if (dp[mask][x] != -1) return dp[mask][x];
        int ans = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            if ((mask & (1<<i)) == 0) {
                int t = (strength[i]+x-1)/x;
                ans = Math.min(ans, t + dfs(mask|(1<<i), x+k, strength, k, dp));
            }
        }
        return dp[mask][x] = ans;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
    fun minTimeToBreakLocks(strength: IntArray, k: Int): Int {
        val n = strength.size
        val dp = Array(1 shl n) { IntArray(n*k+2) { -1 } }
        fun dfs(mask: Int, x: Int): Int {
            if (mask == (1 shl n) - 1) return 0
            if (dp[mask][x] != -1) return dp[mask][x]
            var ans = Int.MAX_VALUE
            for (i in 0 until n) {
                if (mask and (1 shl i) == 0) {
                    val t = (strength[i]+x-1)/x
                    ans = minOf(ans, t + dfs(mask or (1 shl i), x+k))
                }
            }
            dp[mask][x] = ans
            return ans
        }
        return dfs(0, 1)
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Solution:
    def minTimeToBreakLocks(self, strength: list[int], k: int) -> int:
        n = len(strength)
        from functools import lru_cache
        @lru_cache(maxsize=None)
        def dfs(mask: int, x: int) -> int:
            if mask == (1<<n)-1: return 0
            ans = float('inf')
            for i in range(n):
                if not (mask & (1<<i)):
                    t = (strength[i]+x-1)//x
                    ans = min(ans, t + dfs(mask|(1<<i), x+k))
            return ans
        return dfs(0, 1)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
impl Solution {
    pub fn min_time_to_break_locks(strength: Vec<i32>, k: i32) -> i32 {
        let n = strength.len();
        use std::collections::HashMap;
        fn dfs(mask: usize, x: i32, n: usize, k: i32, strength: &Vec<i32>, dp: &mut HashMap<(usize,i32),i32>) -> i32 {
            if mask == (1<<n)-1 { return 0; }
            if let Some(&v) = dp.get(&(mask,x)) { return v; }
            let mut ans = i32::MAX;
            for i in 0..n {
                if mask & (1<<i) == 0 {
                    let t = (strength[i]+x-1)/x;
                    let v = t + dfs(mask|(1<<i), x+k, n, k, strength, dp);
                    if v < ans { ans = v; }
                }
            }
            dp.insert((mask,x), ans);
            ans
        }
        let mut dp = HashMap::new();
        dfs(0, 1, n, k, &strength, &mut dp)
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
    minTimeToBreakLocks(strength: number[], k: number): number {
        const n = strength.length;
        const dp = new Map<string, number>();
        function dfs(mask: number, x: number): number {
            if (mask === (1<<n)-1) return 0;
            const key = mask+','+x;
            if (dp.has(key)) return dp.get(key)!;
            let ans = Infinity;
            for (let i = 0; i < n; i++) {
                if (!(mask & (1<<i))) {
                    const t = Math.floor((strength[i]+x-1)/x);
                    ans = Math.min(ans, t + dfs(mask|(1<<i), x+k));
                }
            }
            dp.set(key, ans);
            return ans;
        }
        return dfs(0, 1);
    }
}

Complexity

  • ⏰ Time complexity: O(n! * n * S) — n! for all permutations, n for each lock, S for possible x values. Efficient due to memoization and small n.
  • 🧺 Space complexity: O(n * S) — For memoization table.