Facebook Pixel

3176. Find the Maximum Length of a Good Subsequence I


Problem Description

You are given an integer array nums and a non-negative integer k. A sequence of integers seq is called good if there are at most k indices i in the range [0, seq.length - 2] such that seq[i] != seq[i + 1].

The task is to return the maximum possible length of a good subsequence of nums.

Intuition

To solve this problem, we use a dynamic programming approach. We want to find the longest good subsequence from the given array, subject to the constraint that there are at most k indices where consecutive elements differ.

The key is to use a 2D array f where f[i][h] represents the length of the longest good subsequence ending at index i with no more than h allowed differences between consecutive elements. Initially, we set f[i][h] = 1, as the shortest subsequence can be a single element.

For each element nums[i], we check all previous elements nums[j] where j < i. If nums[i] equals nums[j], we can extend any subsequence ending at j without costing any additional allowed differences. Hence, we update f[i][h] = max(f[i][h], f[j][h] + 1).

If nums[i] differs from nums[j], we can only extend the subsequence if we have some allowed differences left, i.e., if h > 0. In this case, we update f[i][h] = max(f[i][h], f[j][h - 1] + 1).

Finally, the answer is the maximum length found across all possible subsequences that use up to k allowed differences, which is max(f[i][k]).

Learn more about Dynamic Programming patterns.

Solution Approach

The solution employs a dynamic programming approach to determine the maximum length of a good subsequence given the constraints. Here's how the solution is implemented:

  1. Initialization:

    • We start by determining the length of the input array nums and defining a 2D list f where f[i][h] tracks the longest good subsequence ending at index i, with h indicating the number of changes allowed. Each entry in f is initialized to 1 since the minimum subsequence length can be just one element.
  2. Iterating Through nums:

    • We iterate over each element x in the array nums using its index i.
    • For every element x, we consider subsequences ending at each prior element y indexed by j.
  3. Updating the DP Table:

    • Same Element Extension: If x (current element) is equal to y (previous element we are considering), then we can extend the subsequence ending at j without using any of the allowed k changes. Hence, we update f[i][h] = max(f[i][h], f[j][h] + 1) for the same number of changes.

    • Different Element Extension: If x is different from y, we can still extend the subsequence if we have remaining allowed changes (h > 0). In this case, update f[i][h] = max(f[i][h], f[j][h - 1] + 1). This uses one of the allowed changes to accommodate the difference between x and y.

  4. Finding the Result:

    • After processing all elements and possible previous elements for each, the answer will be the maximum value in f[i][k] for any 0 ≤ i < n, which represents the longest sequence possible with up to k allowed changes.

This approach efficiently calculates the maximum length of a good subsequence by leveraging dynamic programming to balance potential subsequences and changes, providing the correct solution by exploring all possibilities within constraints.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's illustrate the solution approach with a small example. Consider the input array nums = [1, 2, 2, 3, 1] and k = 1.

Step 1: Initialization

  • Create a 2D array f. Suppose nums has length n = 5. The array f has dimensions 5 x 2 (since h ranges from 0 to k, inclusive), initialized to 1:

    f = [
      [1, 1],
      [1, 1],
      [1, 1],
      [1, 1],
      [1, 1]
    ]

Step 2: Iterating Through nums

  • For i = 1 (nums[1] = 2):

    • Consider j = 0 (nums[0] = 1):
      • Since 2 ≠ 1, we can extend the subsequence if h > 0:
        • When h = 1: Update f[1][1] = max(f[1][1], f[0][0] + 1) = max(1, 2) = 2.
  • For i = 2 (nums[2] = 2):

    • Consider j = 0 and j = 1:
      • nums[2] == nums[1] (2 == 2), so extend without additional change:
        • f[2][0] = max(f[2][0], f[1][0] + 1) = max(1, 2) = 2.
        • f[2][1] = max(f[2][1], f[1][1] + 1) = max(1, 3) = 3.
      • Again, 2 ≠ 1, we can extend using a change:
        • f[2][1] = max(f[2][1], f[0][0] + 1) = max(3, 2) = 3.
  • For i = 3 (nums[3] = 3):

    • Consider j = 0, j = 1, and j = 2:
      • Different value: 3 ≠ 2, extend with change h = 1:
        • Update f[3][1] = max(f[3][1], f[2][0] + 1) = max(1, 3) = 3.
        • Update f[3][1] = max(f[3][1], f[1][0] + 1) = max(3, 2) = 3.
  • For i = 4 (nums[4] = 1):

    • Consider j = 0, j = 1, j = 2, j = 3:
      • For j = 0 (1 == 1): f[4][0] = max(f[4][0], f[0][0] + 1) = max(1, 2) = 2.
      • Possible with change h = 1 for j = 3: f[4][1] = max(f[4][1], f[3][0] + 1) = max(1, 2) = 2.

Step 3: Finding the Result

  • The answer is the maximum value in any f[i][k]. Calculating, max(f[i][1]) for each i gives 3.

Thus, the maximum possible length of a good subsequence is 3.

Solution Implementation

1from typing import List
2
3class Solution:
4    def maximumLength(self, nums: List[int], k: int) -> int:
5        n = len(nums)  # Length of the input list
6        # Create a 2D list initialized to 1, dimensions are n x (k + 1)
7        dp = [[1] * (k + 1) for _ in range(n)]
8        result = 0  # Variable to store the result
9      
10        # Iterate over each number in the list
11        for i, x in enumerate(nums):
12            # Iterate over possible allowed changes from 0 to k
13            for h in range(k + 1):
14                # Compare the current number with all previous numbers
15                for j, y in enumerate(nums[:i]):
16                    if x == y:
17                        # If the numbers are the same, extend the sequence without changes
18                        dp[i][h] = max(dp[i][h], dp[j][h] + 1)
19                    elif h > 0:
20                        # If we can make a change, consider the sequence where this changed
21                        dp[i][h] = max(dp[i][h], dp[j][h - 1] + 1)
22            # Update the result with the max sequence length found with at most k changes
23            result = max(result, dp[i][k])
24      
25        return result
26
1class Solution {
2    public int maximumLength(int[] nums, int k) {
3        int n = nums.length; // Length of the input array
4        // Dynamic programming array to store max length of subsequence
5        int[][] dp = new int[n][k + 1]; 
6        int maxLength = 0; // Variable to hold the answer
7
8        // Iterate over each element in the array
9        for (int i = 0; i < n; ++i) {
10            // Iterate over allowed number of changes from 0 to k
11            for (int changes = 0; changes <= k; ++changes) {
12                // Check all previous elements for forming a non-decreasing subsequence
13                for (int j = 0; j < i; ++j) {
14                    if (nums[i] == nums[j]) {
15                        // If the current element is equal to the previous element
16                        dp[i][changes] = Math.max(dp[i][changes], dp[j][changes]);
17                    } else if (changes > 0) {
18                        // If different and changes are allowed
19                        dp[i][changes] = Math.max(dp[i][changes], dp[j][changes - 1]);
20                    }
21                }
22                // Count current element in the subsequence length
23                ++dp[i][changes];
24            }
25            // Update the maximum length encountered
26            maxLength = Math.max(maxLength, dp[i][k]);
27        }
28        return maxLength;
29    }
30}
31
1#include <vector>
2#include <cstring>
3#include <algorithm>
4
5class Solution {
6public:
7    int maximumLength(std::vector<int>& nums, int k) {
8        int n = nums.size();
9        // Create a 2D array to store the function values
10        int dp[n][k + 1];
11        // Initialize the array to zero
12        std::memset(dp, 0, sizeof(dp));
13      
14        int ans = 0; // Variable to store the maximum length
15      
16        // Iterate over each element of nums
17        for (int i = 0; i < n; ++i) {
18            // Check for each count of elements removed (h) from 0 to k
19            for (int h = 0; h <= k; ++h) {
20                // Compare the current element with previous elements
21                for (int j = 0; j < i; ++j) {
22                    if (nums[i] == nums[j]) {
23                        // If they are the same, update dp[i][h]
24                        dp[i][h] = std::max(dp[i][h], dp[j][h]);
25                    } else if (h > 0) {
26                        // If they are different and we can remove an element
27                        dp[i][h] = std::max(dp[i][h], dp[j][h - 1]);
28                    }
29                }
30                // Increment the path length for dp[i][h]
31                ++dp[i][h];
32            }
33            // Keep track of the maximum length found so far with k removals
34            ans = std::max(ans, dp[i][k]);
35        }
36      
37        return ans; // Return the maximum subsequence length
38    }
39};
40
1// Function to find the maximum length of a subsequence with at most k swaps
2function maximumLength(nums: number[], k: number): number {
3    const n = nums.length; // Number of elements in the input array
4    // Create a 2D array 'f' to store intermediate maximum lengths
5    const f: number[][] = Array.from({ length: n }, () => Array(k + 1).fill(0));
6    let ans = 0; // Variable to store the result
7
8    // Iterate over each element in the input array
9    for (let i = 0; i < n; ++i) {
10        // Iterate through all possible swap counts from 0 to k
11        for (let h = 0; h <= k; ++h) {
12            // Check previous elements to find a longer subsequence
13            for (let j = 0; j < i; ++j) {
14                if (nums[i] === nums[j]) {
15                    // If the current element is equal to any previous element, update the max without extra swap
16                    f[i][h] = Math.max(f[i][h], f[j][h]);
17                } else if (h > 0) {
18                    // If swaps are available and elements are different, consider adding one swap
19                    f[i][h] = Math.max(f[i][h], f[j][h - 1]);
20                }
21            }
22            // Increment the maximum length for the current element and swap count
23            ++f[i][h];
24        }
25        // Update the answer with the maximum length for the current element with k swaps
26        ans = Math.max(ans, f[i][k]);
27    }
28
29    // Return the maximum length of the subsequence with at most k swaps
30    return ans;
31}
32

Time and Space Complexity

  • Time Complexity: The given code has a time complexity of O(n^2 * k). This is because there are three nested loops:

    • The outer loop iterates n times through the nums list.
    • The second loop iterates k + 1 times, where k is the parameter given to the function.
    • The innermost loop iterates over a sublist of nums that can have up to n elements, leading to a potential n iterations.

    Combining these, the overall time complexity becomes O(n^2 * k).

  • Space Complexity: The space complexity of the algorithm is O(n * k). This results from the f matrix, which contains n rows and k + 1 columns, resulting in a storage need of O(n * (k + 1)), which simplifies to O(n * k).

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


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

How does merge sort divide the problem into subproblems?


Recommended Readings

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


Load More