Facebook Pixel

1039. Minimum Score Triangulation of Polygon

Problem Description

You are given a convex n-sided polygon where each vertex has an integer value. The vertices are represented by an integer array values where values[i] is the value of the i-th vertex in clockwise order.

Your task is to triangulate this polygon. Triangulation means dividing the polygon into triangles such that:

  • Each triangle's vertices must be vertices from the original polygon
  • Only triangles can be formed (no other shapes allowed)
  • The triangulation will create exactly n - 2 triangles

For each triangle formed during triangulation, its weight is calculated as the product of the values at its three vertices. The total score of a triangulation is the sum of all triangle weights.

You need to find the minimum possible score among all valid triangulations of the polygon.

For example, if you have a quadrilateral with vertices having values [1, 2, 3, 4], you can triangulate it in different ways:

  • One way might create triangles with vertices (0, 1, 2) and (0, 2, 3), giving a score of 1*2*3 + 1*3*4 = 6 + 12 = 18
  • Another way might create triangles with vertices (0, 1, 3) and (1, 2, 3), giving a score of 1*2*4 + 2*3*4 = 8 + 24 = 32

The goal is to find the triangulation that minimizes this total score.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we look at triangulating a polygon, we need to think about how to break down this complex problem into smaller, manageable pieces.

Consider any edge of the polygon, say from vertex i to vertex j. When we triangulate the polygon, this edge will eventually be part of some triangle. The key insight is that we can choose any vertex k between i and j to form this triangle. Once we pick vertex k, we create a triangle with vertices i, k, and j.

Now here's the crucial observation: after forming this triangle, we're left with two smaller polygons:

  1. One polygon from vertex i to vertex k
  2. Another polygon from vertex k to vertex j

Each of these smaller polygons needs to be triangulated independently, and we want the minimum score for each. This naturally suggests a recursive approach where the original problem can be solved by combining solutions to smaller subproblems.

The recursive relationship becomes clear: for any polygon from vertex i to j, we try all possible vertices k between them to form a triangle. The score for choosing k would be:

  • The score of triangulating the left polygon (i to k)
  • Plus the score of triangulating the right polygon (k to j)
  • Plus the weight of the current triangle formed by i, k, and j, which is values[i] * values[k] * values[j]

We want to minimize this total score across all possible choices of k.

The base case is when we have just two vertices (i + 1 = j), which forms an edge but not a triangle, so the score is 0.

This recursive structure with overlapping subproblems (the same polygon segments get computed multiple times) makes it perfect for dynamic programming with memoization. We can cache the results of dfs(i, j) to avoid redundant calculations.

Learn more about Dynamic Programming patterns.

Solution Approach

The solution implements a top-down dynamic programming approach using memoization. We define a recursive function dfs(i, j) that computes the minimum score for triangulating the polygon from vertex i to vertex j.

Implementation Details:

  1. Function Definition: dfs(i, j) represents the minimum triangulation score for the polygon segment from vertex i to vertex j.

  2. Base Case: When i + 1 == j, we have only two consecutive vertices forming an edge. Since we need at least 3 vertices to form a triangle, this returns 0.

  3. Recursive Case: For vertices i to j where j - i > 1:

    • We iterate through all possible middle vertices k where i < k < j
    • For each k, we calculate the total score as:
      • dfs(i, k): Minimum score for triangulating the left sub-polygon
      • dfs(k, j): Minimum score for triangulating the right sub-polygon
      • values[i] * values[k] * values[j]: Weight of the current triangle formed by vertices i, k, and j
    • We take the minimum across all possible values of k
  4. Memoization: The @cache decorator automatically memoizes the function results, preventing redundant calculations for the same (i, j) pairs.

  5. Final Answer: We call dfs(0, len(values) - 1) to get the minimum score for triangulating the entire polygon.

Time Complexity: O(n³) where n is the number of vertices. For each of the O(n²) possible (i, j) pairs, we iterate through up to n values of k.

Space Complexity: O(n²) for the memoization cache storing results for all possible (i, j) pairs.

The algorithm elegantly handles the optimal substructure property of the problem - the optimal triangulation of a polygon can be constructed from optimal triangulations of its sub-polygons, making dynamic programming the perfect approach.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with a pentagon having values [1, 3, 1, 4, 1].

We want to find dfs(0, 4) - the minimum triangulation score for the entire polygon from vertex 0 to vertex 4.

Step 1: Initial Call - dfs(0, 4)

We need to try all possible middle vertices k where 0 < k < 4. So k can be 1, 2, or 3.

  • k = 1: Score = dfs(0, 1) + dfs(1, 4) + (1 × 3 × 1) = 0 + dfs(1, 4) + 3
  • k = 2: Score = dfs(0, 2) + dfs(2, 4) + (1 × 1 × 1) = dfs(0, 2) + dfs(2, 4) + 1
  • k = 3: Score = dfs(0, 3) + dfs(3, 4) + (1 × 4 × 1) = dfs(0, 3) + 0 + 4

Step 2: Compute Required Subproblems

For k = 1, we need dfs(1, 4):

  • Try middle vertices 2 and 3
  • k = 2: dfs(1, 2) + dfs(2, 4) + (3 × 1 × 1) = 0 + dfs(2, 4) + 3
  • k = 3: dfs(1, 3) + dfs(3, 4) + (3 × 4 × 1) = dfs(1, 3) + 0 + 12

For dfs(2, 4), we try k = 3:

  • Score = dfs(2, 3) + dfs(3, 4) + (1 × 4 × 1) = 0 + 0 + 4 = 4

For dfs(1, 3), we try k = 2:

  • Score = dfs(1, 2) + dfs(2, 3) + (3 × 1 × 4) = 0 + 0 + 12 = 12

So dfs(1, 4) = min(0 + 4 + 3, 12 + 0 + 12) = min(7, 24) = 7

For k = 2 in original call, we need dfs(0, 2):

  • Try k = 1: dfs(0, 1) + dfs(1, 2) + (1 × 3 × 1) = 0 + 0 + 3 = 3

For k = 3 in original call, we need dfs(0, 3):

  • Try k = 1: dfs(0, 1) + dfs(1, 3) + (1 × 3 × 4) = 0 + 12 + 12 = 24
  • Try k = 2: dfs(0, 2) + dfs(2, 3) + (1 × 1 × 4) = 3 + 0 + 4 = 7
  • So dfs(0, 3) = min(24, 7) = 7

Step 3: Calculate Final Answer

Now we can complete dfs(0, 4):

  • k = 1: 0 + 7 + 3 = 10
  • k = 2: 3 + 4 + 1 = 8
  • k = 3: 7 + 0 + 4 = 11

Therefore, dfs(0, 4) = min(10, 8, 11) = 8

The optimal triangulation creates triangles:

  1. (0, 1, 2) with weight 1 × 3 × 1 = 3
  2. (2, 3, 4) with weight 1 × 4 × 1 = 4
  3. (0, 2, 4) with weight 1 × 1 × 1 = 1

Total score = 3 + 4 + 1 = 8

Solution Implementation

1class Solution:
2    def minScoreTriangulation(self, values: List[int]) -> int:
3        """
4        Find the minimum score triangulation of a polygon.
5      
6        The polygon is represented as a list of vertex values.
7        The score of a triangle is the product of its three vertex values.
8      
9        Args:
10            values: List of integers representing vertex values of the polygon
11          
12        Returns:
13            The minimum score achievable by triangulating the polygon
14        """
15        from functools import cache
16      
17        @cache
18        def dfs(left: int, right: int) -> int:
19            """
20            Calculate minimum triangulation score for polygon segment.
21          
22            Uses dynamic programming to find the minimum score for triangulating
23            the polygon segment from vertex 'left' to vertex 'right'.
24          
25            Args:
26                left: Starting vertex index of the polygon segment
27                right: Ending vertex index of the polygon segment
28              
29            Returns:
30                Minimum triangulation score for the segment [left, right]
31            """
32            # Base case: adjacent vertices form an edge, not a triangle
33            if left + 1 == right:
34                return 0
35          
36            # Try all possible vertices k to form a triangle with vertices left and right
37            # The triangle (left, k, right) divides the polygon into:
38            # 1. Sub-polygon from left to k
39            # 2. The triangle itself with score: values[left] * values[k] * values[right]
40            # 3. Sub-polygon from k to right
41            min_score = float('inf')
42            for k in range(left + 1, right):
43                current_score = (dfs(left, k) + 
44                                dfs(k, right) + 
45                                values[left] * values[k] * values[right])
46                min_score = min(min_score, current_score)
47          
48            return min_score
49      
50        # Start with the entire polygon from vertex 0 to vertex n-1
51        return dfs(0, len(values) - 1)
52
1class Solution {
2    // Number of vertices in the polygon
3    private int n;
4    // Array storing the values of each vertex
5    private int[] values;
6    // Memoization table for dynamic programming
7    // memo[i][j] stores the minimum score for triangulating polygon from vertex i to j
8    private Integer[][] memo;
9
10    /**
11     * Finds the minimum score of triangulation for a convex polygon.
12     * The score of a triangle is the product of its three vertices' values.
13     * 
14     * @param values Array of vertex values representing the polygon
15     * @return Minimum total score of triangulation
16     */
17    public int minScoreTriangulation(int[] values) {
18        this.n = values.length;
19        this.values = values;
20        this.memo = new Integer[n][n];
21      
22        // Start DFS from the entire polygon (vertex 0 to vertex n-1)
23        return dfs(0, n - 1);
24    }
25
26    /**
27     * Recursively calculates the minimum triangulation score for a sub-polygon.
28     * Uses memoization to avoid redundant calculations.
29     * 
30     * @param i Starting vertex of the sub-polygon
31     * @param j Ending vertex of the sub-polygon
32     * @return Minimum score to triangulate the sub-polygon from vertex i to j
33     */
34    private int dfs(int i, int j) {
35        // Base case: adjacent vertices form an edge, not a polygon
36        // No triangulation needed, score is 0
37        if (i + 1 == j) {
38            return 0;
39        }
40      
41        // Check if result is already computed (memoization)
42        if (memo[i][j] != null) {
43            return memo[i][j];
44        }
45      
46        // Initialize minimum score to a large value
47        int minScore = 1 << 30;  // Equivalent to 2^30
48      
49        // Try all possible vertices k to form a triangle with vertices i and j
50        // Triangle formed: (i, k, j)
51        for (int k = i + 1; k < j; k++) {
52            // Calculate score: 
53            // - Score of left sub-polygon (i to k)
54            // - Score of right sub-polygon (k to j)  
55            // - Score of current triangle (i, k, j)
56            int currentScore = dfs(i, k) + dfs(k, j) + values[i] * values[k] * values[j];
57            minScore = Math.min(minScore, currentScore);
58        }
59      
60        // Store and return the minimum score for this sub-polygon
61        memo[i][j] = minScore;
62        return minScore;
63    }
64}
65
1class Solution {
2public:
3    int minScoreTriangulation(vector<int>& values) {
4        int n = values.size();
5      
6        // dp[i][j] stores the minimum score for triangulating the polygon 
7        // formed by vertices from index i to j
8        int dp[n][n];
9        memset(dp, 0, sizeof(dp));
10      
11        // Recursive function with memoization to find minimum triangulation score
12        // Parameters: i - starting vertex index, j - ending vertex index
13        // Returns: minimum score for triangulating polygon from vertex i to j
14        function<int(int, int)> calculateMinScore = [&](int i, int j) -> int {
15            // Base case: if only two vertices, no triangle can be formed
16            if (i + 1 == j) {
17                return 0;
18            }
19          
20            // Return memoized result if already calculated
21            if (dp[i][j] != 0) {
22                return dp[i][j];
23            }
24          
25            // Initialize result with a large value
26            int minScore = INT_MAX;
27          
28            // Try all possible vertices k between i and j to form a triangle (i, k, j)
29            // The polygon is then divided into three parts:
30            // 1. Polygon from i to k
31            // 2. Triangle (i, k, j)
32            // 3. Polygon from k to j
33            for (int k = i + 1; k < j; ++k) {
34                int currentScore = calculateMinScore(i, k) + 
35                                  calculateMinScore(k, j) + 
36                                  values[i] * values[k] * values[j];
37                minScore = min(minScore, currentScore);
38            }
39          
40            // Store and return the minimum score
41            dp[i][j] = minScore;
42            return minScore;
43        };
44      
45        // Calculate minimum score for the entire polygon (from vertex 0 to n-1)
46        return calculateMinScore(0, n - 1);
47    }
48};
49
1/**
2 * Finds the minimum score triangulation of a polygon represented by vertex values.
3 * The score of a triangle formed by vertices i, j, k is values[i] * values[j] * values[k].
4 * 
5 * @param values - Array of vertex values representing the polygon
6 * @returns The minimum score of triangulation
7 */
8function minScoreTriangulation(values: number[]): number {
9    const polygonSize: number = values.length;
10  
11    // Memoization table to store results of subproblems
12    // memo[i][j] represents the minimum score to triangulate vertices from i to j
13    const memo: number[][] = Array.from(
14        { length: polygonSize }, 
15        () => Array.from({ length: polygonSize }, () => 0)
16    );
17  
18    /**
19     * Recursively calculates the minimum triangulation score for vertices from startIndex to endIndex.
20     * Uses dynamic programming with memoization to avoid redundant calculations.
21     * 
22     * @param startIndex - Starting vertex index of the polygon segment
23     * @param endIndex - Ending vertex index of the polygon segment
24     * @returns Minimum triangulation score for the given segment
25     */
26    const calculateMinScore = (startIndex: number, endIndex: number): number => {
27        // Base case: adjacent vertices form an edge, not a triangle
28        if (startIndex + 1 === endIndex) {
29            return 0;
30        }
31      
32        // Return memoized result if already calculated
33        if (memo[startIndex][endIndex] > 0) {
34            return memo[startIndex][endIndex];
35        }
36      
37        // Initialize minimum score with a large value
38        let minScore: number = 1 << 30;
39      
40        // Try all possible vertices k to form a triangle with startIndex and endIndex
41        // Triangle vertices: (startIndex, k, endIndex)
42        for (let k: number = startIndex + 1; k < endIndex; ++k) {
43            // Calculate score: left subproblem + right subproblem + current triangle score
44            const currentScore: number = calculateMinScore(startIndex, k) + 
45                                        calculateMinScore(k, endIndex) + 
46                                        values[startIndex] * values[k] * values[endIndex];
47          
48            minScore = Math.min(minScore, currentScore);
49        }
50      
51        // Store the result in memoization table
52        memo[startIndex][endIndex] = minScore;
53        return minScore;
54    };
55  
56    // Calculate minimum score for the entire polygon (from vertex 0 to n-1)
57    return calculateMinScore(0, polygonSize - 1);
58}
59

Time and Space Complexity

The time complexity is O(n^3), where n is the number of vertices in the polygon.

This is because:

  • The memoized recursive function dfs(i, j) has O(n^2) possible states, as i and j can each range from 0 to n-1
  • For each state (i, j), we iterate through all possible values of k where i + 1 ≤ k < j, which takes O(j - i - 1) = O(n) time in the worst case
  • Each state is computed only once due to the @cache decorator
  • Therefore, the total time complexity is O(n^2) × O(n) = O(n^3)

The space complexity is O(n^2), where n is the number of vertices in the polygon.

This is because:

  • The @cache decorator stores the results of all computed states, which requires O(n^2) space for all possible (i, j) pairs
  • The recursion depth can be at most O(n), contributing O(n) to the call stack space
  • The dominant factor is the cache storage, giving us O(n^2) space complexity

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Base Case Handling

A frequent mistake is misunderstanding when the base case should return 0. Developers often confuse the condition and might write:

Incorrect Implementation:

if right - left <= 1:  # Wrong condition!
    return 0

This is problematic because when right - left == 1, we have exactly two vertices (e.g., vertices at indices 0 and 1), which should return 0. But when right - left == 2, we have three vertices forming a triangle, which should NOT return 0.

Correct Implementation:

if left + 1 == right:  # or equivalently: right - left == 1
    return 0

2. Off-by-One Errors in Loop Boundaries

Another common mistake is using incorrect loop boundaries when iterating through the middle vertex k:

Incorrect Implementation:

for k in range(left, right + 1):  # Wrong! Includes left and right
    # This would try to form degenerate triangles

or

for k in range(left + 1, right + 1):  # Wrong! Includes right
    # This would create invalid sub-problems

Correct Implementation:

for k in range(left + 1, right):  # k must be strictly between left and right
    # Ensures valid triangles and sub-polygons

3. Forgetting to Import or Use Memoization

Without memoization, the solution becomes exponentially slow due to repeated calculations:

Incorrect Implementation:

def dfs(left, right):  # No memoization!
    if left + 1 == right:
        return 0
    # ... rest of the code

This would result in Time Limit Exceeded for larger inputs as the same sub-problems are computed multiple times.

Correct Implementation:

from functools import cache

@cache  # Essential for performance
def dfs(left, right):
    # ... implementation

4. Confusion About Polygon Representation

Some developers mistakenly think they need to handle the circular nature of the polygon explicitly:

Incorrect Implementation:

def dfs(i, j):
    # Wrong! Trying to handle wraparound
    if j < i:
        j += len(values)
    # ... complicated logic to handle circular indices

The correct approach treats the polygon as a linear array from index 0 to n-1. The polygon's circular nature is already encoded in the problem structure - we're finding triangulations that connect vertex 0 to vertex n-1.

5. Integer Overflow in Other Languages

While Python handles large integers automatically, if implementing in languages like Java or C++, the product values[left] * values[k] * values[right] might overflow:

Solution for Other Languages:

// In C++, use long long
long long score = (long long)values[left] * values[k] * values[right];

Complete Corrected Solution Template:

class Solution:
    def minScoreTriangulation(self, values: List[int]) -> int:
        from functools import cache
      
        @cache
        def dfs(left: int, right: int) -> int:
            # Correct base case: adjacent vertices
            if left + 1 == right:
                return 0
          
            min_score = float('inf')
            # Correct loop boundaries: k strictly between left and right
            for k in range(left + 1, right):
                current_score = (dfs(left, k) + 
                                dfs(k, right) + 
                                values[left] * values[k] * values[right])
                min_score = min(min_score, current_score)
          
            return min_score
      
        # Process entire polygon
        return dfs(0, len(values) - 1)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More