LC 53 | JZ 42 & LC 918


Solution to LeetCode 53 Maximum Subarray & LeetCode 918 Maximum Sum Circular Subarray.

题目思想: “走完这一生, 如果我和你在一起会变得更好,那我们就在一起,否则我就丢下你。 回顾我最光辉的时刻就是和不同人在一起,变得更好的最长连续时刻。” – LeetCode Comments

LeetCode 53 | JZ 42

Maximum Subarray (Easy). [LeetCode link]

1] Greedy. Any time when the sum is negative (after adding nums[i]), restart from the next nums[i+1] and recalculate the sum from 0. Because negative number does not contribute to increasing the sum. Time complexity O(n), space complexity O(1).

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        res = -float('inf')
        cnt = 0
        for i in range(len(nums)):
            cnt += nums[i]
            if cnt > res: # find the max
                res = cnt
            if cnt <= 0: # restart if negative
                cnt = 0
        return res

2] Dynamic Programming. Define dp[i] as the max sum of the subsequence in the range of the [0,i] of the array. Suppose all numbers in nums are positive, when it comes to num[i], we add it to get larger sum, so we must have dp[i] = dp[i-1] + num[i]. However, any value in the num can be negative.

Case 1: dp[i-1] <= 0, including dp[i-1] won’t increase the sum, we have dp[i] = num[i].

Case 2: dp[i-1] > 0, including dp[i-1] will increase the sum, so we have dp[i] = dp[i-1] + num[i].

The function for DP is: max(nums[i], dp[i-1]+ nums[i]). If dp[i-1]+ nums[i] is smaller than nums[i], dp[i-1] has no contribution to the sum.

Time complexity O(n), space complexity O(n).

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        dp= [0]*(len(nums))
        dp[0] = nums[0]
        for i in range(1, len(nums)):
            dp[i] = max(dp[i-1] + nums[i], nums[i])
        return max(dp)

Use a rolling array to reduce space complexity to O(1). Time complexity is still O(n).

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        for i in range(1, len(nums)):
            nums[i] += max(nums[i-1], 0)
        return max(nums)

Reduced space using two variables:

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
          res = nums[0]
        cur = nums[0]
        for i in nums[1:]:
              cur = max(cur + n, n)
            res = max(res, cur)
        return res

LeetCode 918

Maximum Sum Circular Subarray (Medium). [link]

Case 1: max subarray is in the linear array. This case can be solved by applying the same method in LC53 max(dp).

Case 2: max subarray is on the circle, including the suffix and the prefix of the linear array. Consider re-arranging the suffix and prefix as “prefix + mid + suffix”. There is at least one element in ‘mid’ so that the subarray can be shorter than the whole array. This case can be solved by applying max(-mid), because we want max(prefix + suffix) = max(sum-mid) = sum + maxSubarray(-arr).

(Note one of the base case: when all elements are negative, the answer is the largest element.)

1] Recursive DP implementation

class Solution:
    def maxSubarraySumCircular(self, nums: List[int]) -> int:
        n = len(nums)
        
        @cache
        def dp(i,s):
            if i == 0: return nums[i] * s
            return nums[i]*s + max(0,dp(i-1,s))
          
        max_p = max(dp(i,1) for i in range(n))
        max_n = max(dp(i,-1) for i in range(n))
        return max_p if max_p < 0 else max(max_p, sum(nums) + max_n)

2] Iterative implementation with space saved

class Solution(object):
    def maxSubarraySumCircular(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
          max_cur = nums[0]
          max_res = nums[0]
          min_cur = nums[0]
          min_res = nums[0]
        for i in nums[1:]:
            max_cur = max(i, i + max_cur)
            max_res = max(max_res, max_cur)
            min_cur = min(i, i + min_cur)
            min_res = min(min_res, min_cur)

        if sum(nums) == min_res:
              return max_res
        return max(max_res, sum(nums) - min_res)

  TOC