3177. Find the Maximum Length of a Good Subsequence II

Dare2Solve

Dare2Solve

3177. Find the Maximum Length of a Good Subsequence II
SAMSUNG 49-Inch Odyssey G9
SAMSUNG 49-Inch Odyssey G9
Because earth is not flat

Intuition

To find the longest good subsequence where there are at most k changes allowed (i.e., adjacent elements that are different), we can use dynamic programming. The idea is to maintain a DP table that tracks the length of the longest good subsequence ending at each index with a certain number of changes. By iterating through the array and updating this table based on the conditions specified, we can find the maximum length of such a subsequence.

Approach

  1. Define the DP Array:

    • Use a 2D DP array dp[i][j], where i is the index in nums and j represents the number of changes allowed (from 0 to k).
    • dp[i][j] stores the length of the longest good subsequence that ends at index i with exactly j changes.
  2. Initialize the DP Array:

    • Set dp[i][0] to 1 for all i because a single element subsequence is always a good subsequence with 0 changes.
  3. Fill the DP Array:

    • For each element nums[i], check all previous elements nums[p] (where p < i).
    • If nums[p] == nums[i], extend the subsequence without adding a change.
    • If nums[p] != nums[i] and j > 0, extend the subsequence with one additional change.
  4. Keep Track of the Maximum Length:

    • Continuously update the maximum length of the good subsequence found.

Approach Transition: From O(n^2k) to O(nk)

Problem Understanding

Given an integer array nums and a non-negative integer k, find the maximum possible length of a good subsequence, where a good subsequence allows at most k adjacent indices with unequal elements.

Initial Solution: O(n^2*k)

Dynamic Programming Approach

Step 1: Initialize DP Table

Step 2: Base Case

Step 3: Fill the DP Table

Complexity Analysis:

Optimized Solution: O(n*k)

Rolling Sum Optimization

Idea

Step 1: Initialize DP Table and Variables

Step 2: Base Case

Step 3: Fill the DP Table

Complexity

Code

C++

class Solution {
public:
    int maximumLength(std::vector<int>& nums, int k) {
        int n = nums.size();
        if (n == 0) return 0;

        std::vector<std::vector<int>> dp(n, std::vector<int>(k + 1, 0));
        for (int i = 0; i < n; ++i) dp[i][0] = 1;

        int res = 1;
        for (int j = 0; j <= k; ++j) {
            int max1 = 1;
            std::unordered_map<int, int> numMap;
            numMap[nums[0]] = 0;

            for (int i = 1; i < n; ++i) {
                dp[i][j] = 1;
                if (i > 0 && j > 0) max1 = std::max(max1, dp[i - 1][j - 1] + 1);
                dp[i][j] = std::max(dp[i][j], max1);

                if (numMap.find(nums[i]) != numMap.end()) {
                    dp[i][j] = std::max(dp[i][j], dp[numMap[nums[i]]][j] + 1);
                }

                numMap[nums[i]] = i;
                res = std::max(res, dp[i][j]);
            }
        }

        return res;
    }
};

Python

class Solution:
    def maximumLength(self, nums: List[int], k: int) -> int:
        n = len(nums)
        if n == 0:
            return 0

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

        res = 1
        for j in range(k + 1):
            max1 = 1
            num_map = {nums[0]: 0}

            for i in range(1, n):
                dp[i][j] = 1
                if i > 0 and j > 0:
                    max1 = max(max1, dp[i - 1][j - 1] + 1)
                dp[i][j] = max(dp[i][j], max1)

                if nums[i] in num_map:
                    dp[i][j] = max(dp[i][j], dp[num_map[nums[i]]][j] + 1)

                num_map[nums[i]] = i
                res = max(res, dp[i][j])

        return res

Java

class Solution {
    public int maximumLength(int[] nums, int k) {
        int n = nums.length;
        if (n == 0) return 0;

        int[][] dp = new int[n][k + 1];
        for (int i = 0; i < n; ++i) dp[i][0] = 1;

        int res = 1;
        for (int j = 0; j <= k; ++j) {
            int max1 = 1;
            Map<Integer, Integer> numMap = new HashMap<>();
            numMap.put(nums[0], 0);

            for (int i = 1; i < n; ++i) {
                dp[i][j] = 1;
                if (i > 0 && j > 0) max1 = Math.max(max1, dp[i - 1][j - 1] + 1);
                dp[i][j] = Math.max(dp[i][j], max1);

                if (numMap.containsKey(nums[i])) {
                    dp[i][j] = Math.max(dp[i][j], dp[numMap.get(nums[i])][j] + 1);
                }

                numMap.put(nums[i], i);
                res = Math.max(res, dp[i][j]);
            }
        }

        return res;
    }
}

JavaScript

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var maximumLength = function (nums, k) {
    const n = nums.length;
    if (n === 0) return 0;
    const dp = Array.from({ length: n }, () => Array(k + 1).fill(0));
    for (let i = 0; i < n; ++i) dp[i][0] = 1;

    let res = 1;
    for (let j = 0; j <= k; ++j) {
        var max1 = 1;
        var map = new Map();
        map.set(nums[0], 0);
        for (let i = 1; i < n; ++i) {
            dp[i][j] = 1;
            if (i > 0 && j > 0) max1 = Math.max(max1, dp[i - 1][j - 1] + 1);
            dp[i][j] = Math.max(dp[i][j], max1);
            if (map.has(nums[i])) {
                dp[i][j] = Math.max(dp[i][j], dp[map.get(nums[i])][j] + 1);
            }
            map.set(nums[i], i);
            res = Math.max(res, dp[i][j]);
        }
    }

    return res;
};

Go

func maximumLength(nums []int, k int) int {
    n := len(nums)
    if n == 0 {
        return 0
    }
    dp := make([][]int, n)
    for i := range dp {
        dp[i] = make([]int, k+1)
    }
    for i := 0; i < n; i++ {
        dp[i][0] = 1
    }

    res := 1
    for j := 0; j <= k; j++ {
        max1 := 1
        numMap := make(map[int]int)
        numMap[nums[0]] = 0
        for i := 1; i < n; i++ {
            dp[i][j] = 1
            if i > 0 && j > 0 {
                max1 = max(max1, dp[i-1][j-1]+1)
            }
            dp[i][j] = max(dp[i][j], max1)
            if idx, exists := numMap[nums[i]]; exists {
                dp[i][j] = max(dp[i][j], dp[idx][j]+1)
            }
            numMap[nums[i]] = i
            res = max(res, dp[i][j])
        }
    }

    return res
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

C#

public class Solution {
    public int MaximumLength(int[] nums, int k) {
        int n = nums.Length;
        if (n == 0) return 0;

        int[][] dp = new int[n][];
        for (int i = 0; i < n; ++i) {
            dp[i] = new int[k + 1];
        }
        for (int i = 0; i < n; ++i) dp[i][0] = 1;

        int res = 1;
        for (int j = 0; j <= k; ++j) {
            int max1 = 1;
            Dictionary<int, int> numMap = new Dictionary<int, int>();
            numMap[nums[0]] = 0;

            for (int i = 1; i < n; ++i) {
                dp[i][j] = 1;
                if (i > 0 && j > 0) max1 = Math.Max(max1, dp[i - 1][j - 1] + 1);
                dp[i][j] = Math.Max(dp[i][j], max1);

                if (numMap.ContainsKey(nums[i])) {
                    dp[i][j] = Math.Max(dp[i][j], dp[numMap[nums[i]]][j] + 1);
                }

                numMap[nums[i]] = i;
                res = Math.Max(res, dp[i][j]);
            }
        }

        return res;
    }
}

Conclusion

By utilizing dynamic programming, we effectively track the longest good subsequence ending at each index with a certain number of allowed changes. This method ensures that we consider all possible subsequences and changes efficiently. The algorithm operates within acceptable time and space complexities, making it suitable for large input sizes typical in competitive programming and coding interviews.