2 minute read

Problem Statement

problem-1043

Scratch Notes

note

Brute Force - TLE

Attempted to use backtrack to solve

class Solution:
    def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
        subarrays_max = []
        N = len(arr)
        def dfs(idx):
            if idx >= N:
                return sum(subarrays_max)

            res = 0
            for i in range(1, k + 1):
                val = max(arr[idx:idx + i])
                for _ in range(len(arr[idx:idx + i])):
                    subarrays_max.append(val)

                res = max(res, dfs(idx + i))

                for _ in range(len(arr[idx:idx + i])):
                    subarrays_max.pop()
            
            return res

        return dfs(0)

Other implementation

class Solution:
    def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
        N = len(arr)
        def dfs(idx, curr):

            if idx >= N:
                re = 0
                for subarray in curr:
                    max_val = max(subarray)
                    for _ in range(len(subarray)):
                        re += max_val
                return re

            res = 0
            for i in range(1, k + 1):
                res = max(res, dfs(idx + i, curr + [arr[idx:idx + i]]))

            return res

        return dfs(0, [])

Memoization Approach - Accepted

Intuition

The key idea is likely to consider all possible partitioning strategies and find the one that maximizes the sum.

Approach

The provided code is using a recursive approach with memoization. It defines a function dfs that takes an index i as a parameter, indicating the current position in the array. The function explores all possible partitions by considering subarrays of at most size k, calculating the sum for each partition, and recursively moving to the next position.

The memo dictionary is used to store already computed results for a given index, avoiding redundant calculations.

Complexity

  • Time complexity: O(n*k) the function dfs is called for each index from 0 to N-1, and for each call, it explores up to the next k elements.

  • Space complexity: The space complexity is determined by the size of the memo dictionary, which is proportional to the number of unique subproblems. In the worst case, it could be O(n), where n is the length of the input array.

Code

class Solution:
    def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
        N = len(arr)
        memo = defaultdict()

        def dfs(i):
            if i >= N:
                return 0

            if i in memo:
                return memo[i]

            curr_max = 0
            res = 0

            for j in range(i, min(N, i + k)):
                curr_max = max(curr_max, arr[j])
                window_size = j - i + 1
                res = max(res, curr_max * window_size + dfs(j + 1))

            memo[i] = res
            return res
        return dfs(0)

Editor Solution

Dynamic Programming - 1D array

class Solution:
    def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
        N = len(arr)
        dp = [0] * N
        for i in range(N - 1, -1, -1):
            curr_max = 0
            for j in range(i, min(N, i + k)):
                curr_max = max(curr_max, arr[j])
                window_size = j - i + 1
                prev = dp[j + 1] if j + 1 < N else 0
                dp[i] = max(dp[i], curr_max * window_size + prev)
        
        return dp[0]

Dynamic Programming - 1D array fixed K size

The idea is to realize that the 1D dynamic programming approach only use k-size of previous subarray for the current computation. Therefore, we can reduce space by utilizing just k-size array instead of N-size array to cache the previous sub-problem.

class Solution:
    def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
        N = len(arr)
        K = k + 1
        dp = [0] * K
        for i in range(N - 1, -1, -1):
            curr_max = 0
            for j in range(i, min(N, i + k)):
                curr_max = max(curr_max, arr[j])
                window_size = j - i + 1
                prev = dp[(j + 1) % K]
                dp[i % K] = max(dp[i % K], curr_max * window_size + prev)
                print(dp)
        return dp[0]