Remove Boxes

Read editorial.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from typing import List

class Solution:
    def removeBoxes(self, boxes: List[int]) -> int:
        n = len(boxes)
        dp = [[[0] * n for _ in range(n)] for _ in range(n)]

        for j in range(n):
            for k in range(j + 1):
                dp[j][j][k] = (k + 1) * (k + 1)

        for l in range(1, n):
            for j in range(l, n):
                i = j - l

                for k in range(i + 1):
                    res = (k + 1) * (k + 1) + dp[i + 1][j][0]

                    for m in range(i + 1, j + 1):
                        if boxes[m] == boxes[i]:
                            res = max(res, dp[i + 1][m - 1][0] + dp[m][j][k + 1])

                    dp[i][j][k] = res

        return 0 if n == 0 else dp[0][n - 1][0]

10 Prerequisite LeetCode Problems

“546. Remove Boxes” is a dynamic programming problem that requires a good understanding of DP with state definitions and recursion. To build up your understanding to tackle this problem, start with these problems:

  1. “303. Range Sum Query - Immutable”: This problem introduces the concept of range queries which is useful in dynamic programming.

  2. “304. Range Sum Query 2D - Immutable”: A variation of the previous problem, this introduces 2D range queries.

  3. “322. Coin Change”: A classical DP problem where you need to find the fewest number of coins that you need to make up a certain amount.

  4. “1143. Longest Common Subsequence”: This problem helps you understand how to define states in dynamic programming related to sequences.

  5. “72. Edit Distance”: This problem is a good introduction to dynamic programming problems that involve making certain decisions at each state (insert, delete, replace in this case).

  6. “198. House Robber”: This problem involves deciding between two options at each state, which is a good practice for decision-making in dynamic programming.

  7. “516. Longest Palindromic Subsequence”: This problem helps understand how to deal with sequences and DP. It also relates to the manipulation of sequences like in “Remove Boxes”.

  8. “647. Palindromic Substrings”: This problem also involves sequences and DP, and introduces the concept of palindromic sequences.

  9. “139. Word Break”: This problem has you making decisions at each step in a sequence, which can be good practice for similar decision-making in the “Remove Boxes” problem.

  10. “91. Decode Ways”: This problem involves making decisions based on current and previous state, which is a theme in many dynamic programming problems.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
    def removeBoxes(self, boxes: List[int]) -> int:
        @lru_cache(None)
        def dp(i: int, j: int, k: int) -> int:
            if i > j:
                return 0

            while i < j and boxes[j] == boxes[j-1]:
                j -= 1
                k += 1

            if i == j:
                return (k+1)**2

            ans = (k+1)**2 + dp(i, j-1, 0)

            for m in range(j-1, i-1, -1):
                if boxes[m] == boxes[j]:
                    ans = max(ans, dp(i, m, k+1) + dp(m+1, j-1, 0))

            return ans

        return dp(0, len(boxes)-1, 0)

Problem Classification

You are given several boxes with different colors represented by different positive numbers.

You may experience several rounds to remove boxes until there is no box left. Each time you can choose some continuous boxes with the same color (i.e., composed of k boxes, k >= 1), remove them and get k * k points.

Return the maximum points you can get.

Example 1:

Input: boxes = [1,3,2,2,2,3,4,3,1] Output: 23 Explanation: [1, 3, 2, 2, 2, 3, 4, 3, 1] —-> [1, 3, 3, 4, 3, 1] (33=9 points) —-> [1, 3, 3, 3, 1] (11=1 points) —-> [1, 1] (33=9 points) —-> [] (22=4 points) Example 2:

Input: boxes = [1,1,1] Output: 9 Example 3:

Input: boxes = [1] Output: 1

Constraints:

1 <= boxes.length <= 100 1 <= boxes[i] <= 100

Language Agnostic Coding Drills

Drill 1: Understanding Data Types and Structures

  • Create a list of integers and perform basic operations like insertion, deletion and modification.
  • Create a function that accepts an integer as an argument and returns another integer.

Drill 2: Understanding Arithmetic Operations and Indexing

  • Given a list of integers, write a function to calculate the sum of all elements in the list.
  • Write a function that accepts a list and an index and returns the element at that index in the list.

Drill 3: Understanding Conditionals and Loops

  • Write a function that loops over a list of integers and prints each one.
  • Write a function that checks if a number exists in a given list of numbers.

Drill 4: Understanding Recursion

  • Implement a recursive function that computes the factorial of a number.
  • Implement a recursive function that finds the nth Fibonacci number.

Drill 5: Understanding the use of Decorators

  • Write a simple decorator function that modifies the behavior of a function (like logging the function call or measuring execution time).
  • Implement memoization in a recursive function using a decorator.

Drill 6: Understanding Dynamic Programming

  • Implement a function that calculates the nth Fibonacci number using dynamic programming.
  • Implement a function that solves the problem of minimum coin change using dynamic programming.

In each of the drills, it would be helpful to ensure the fundamentals of each concept are understood before proceeding to the next one. Once the concepts are well understood in isolation, they can be combined to solve more complex problems such as the box removal problem in the provided Python code.

Targeted Drills in Python

  1. Understanding the use of data types and data structures: The code uses Lists, Integers, and Functions.

  2. Understanding the use of decorators: The decorator @lru_cache(None) is used to add memoization to the function dp, which significantly speeds up recursive functions by storing the results of previous function calls.

  3. Understanding recursion: The function dp calls itself recursively.

  4. Understanding conditionals and loops: The function dp contains if-else statements and a while loop and a for loop.

  5. Understanding indexing: The code uses indexing to manipulate and retrieve values from the List boxes.

  6. Understanding arithmetic operations: The code uses basic operations like addition, subtraction, and exponentiation.

  7. Understanding the concept of dynamic programming: The problem is solved using a dynamic programming approach where the solution of the problem is calculated using the solutions of its sub-problems.

These units can be arranged in increasing order of difficulty as follows:

  1. Understanding the use of data types and data structures
  2. Understanding arithmetic operations and indexing
  3. Understanding conditionals and loops
  4. Understanding recursion
  5. Understanding the use of decorators
  6. Understanding the concept of dynamic programming

The problem-solving approach is as follows:

  1. Define a recursive function dp that takes in three parameters i, j, and k.

  2. If i is greater than j, then no boxes are left, return 0.

  3. Calculate the points for the boxes between i and j, inclusive, which have the same color as box j, plus the points for the rest of the boxes.

  4. Try all possible m so that the boxes in the range [m, j] have the same color, and maximize the points.

  5. Use the decorator lru_cache(None) for memoization to avoid recalculating the points for the same range of boxes.

  6. Return the maximum points by calling the function dp with initial parameters.

Here are the Python coding drills.

Drill 1: Understanding Data Types and Structures

  • Create a list of integers and perform basic operations like insertion, deletion and modification:
1
2
3
4
5
6
7
8
# Creating a list
numbers = [1, 2, 3, 4, 5]
# Inserting an element
numbers.append(6)
# Deleting an element
numbers.remove(2)
# Modifying an element
numbers[0] = 10
  • Create a function that accepts an integer as an argument and returns another integer:
1
2
def add_one(n):
    return n + 1

Drill 2: Understanding Arithmetic Operations and Indexing

  • Given a list of integers, write a function to calculate the sum of all elements in the list:
1
2
def calculate_sum(numbers):
    return sum(numbers)
  • Write a function that accepts a list and an index and returns the element at that index in the list:
1
2
def get_element(numbers, index):
    return numbers[index]

Drill 3: Understanding Conditionals and Loops

  • Write a function that loops over a list of integers and prints each one:
1
2
3
def print_numbers(numbers):
    for num in numbers:
        print(num)
  • Write a function that checks if a number exists in a given list of numbers:
1
2
def number_exists(numbers, n):
    return n in numbers

Drill 4: Understanding Recursion

  • Implement a recursive function that computes the factorial of a number:
1
2
3
4
5
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)
  • Implement a recursive function that finds the nth Fibonacci number:
1
2
3
4
5
6
7
def fibonacci(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

Drill 5: Understanding the use of Decorators

  • Write a simple decorator function that modifies the behavior of a function (like logging the function call or measuring execution time):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import time

def timer_decorator(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"Time taken: {end-start} seconds")
        return result
    return wrapper
  • Implement memoization in a recursive function using a decorator:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from functools import lru_cache

@lru_cache(None)
def memoized_fibonacci(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return memoized_fibonacci(n-1) + memoized_fibonacci(n-2)

Drill 6: Understanding Dynamic Programming

  • Implement a function that calculates the nth Fibonacci number using dynamic programming:
1
2
3
4
5
def dp_fibonacci(n):
    fib_values = [0, 1] + [0] * (n - 1)
    for i in range(2, n + 1):
        fib_values[i] = fib_values[i - 1] + fib_values[i - 2]
    return fib_values[n]
  • Implement a function that solves the problem of minimum coin change using dynamic programming:
1
2
3
4
5
6
7
8
9
def min_coins(coins, target):
    dp = [float('inf')] * (target + 1)
    dp[0] = 0


    for coin in coins:
        for x in range(coin, target + 1):
            dp[x] = min(dp[x], dp[x - coin] + 1)
    return dp[target] if dp[target] != float('inf') else -1

These drills can be combined to create more complex solutions, such as the box removal problem in the Python code you provided earlier.