An Unexpected value changed in JavaScript while solving Maximum Subarray with Divide & Conquer Approach

Question:

I am trying to solve LeetCode problem 53. Maximum Subarray:

Given an integer array nums, find the subarray which has the largest sum and return its sum.

Here is my failing attempt:

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    function mergeSort(all, left, right) {
        if ( left > right ) {
            return Number.NEGATIVE_INFINITY;
        }
        const mid = Math.floor((left + right) / 2);
        let leftSum = rightSum = currentSum = 0;
        // sum the left side
        for (i = mid - 1; i >= left; i -= 1) {
            currentSum = currentSum + all[i];
            leftSum = Math.max(leftSum, currentSum);
        }
        currentSum = 0;
        for (i = mid + 1; i <= right; i += 1) {
            currentSum = currentSum + all[i];
            rightSum = Math.max(rightSum, currentSum);
        }
        // compare the value and find the max among the left, the right and the middle0
        const result = Math.max(mergeSort(all, left, mid - 1), 
                                mergeSort(all, mid + 1, right), 
                                leftSum + all[mid] + rightSum);
        return result
    }
    return mergeSort(nums, 0, nums.length - 1);
};

const nums =[-2,1,-3,4,-1,2,1,-5,4];
console.log(maxSubArray(nums)); // 4 ????

It seems somehow values change on leftSum and rightSum if I assign them directly as leftSum + all[mid] + rightSum to Math.max(...).

With the input nums =[-2,1,-3,4,-1,2,1,-5,4], the expected return is 6. However, I will get 4 as you can see when running the snippet above.

The cause seems that I don’t store leftSum + all[mid] + rightSum in a variable first — middlePart — like in this version:

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    function mergeSort(all, left, right) {
        if ( left > right ) {
            return Number.NEGATIVE_INFINITY;
        }
        const mid = Math.floor((left + right) / 2);
        let leftSum = rightSum = currentSum = 0;
        // sum the left side
        for (i = mid - 1; i >= left; i -= 1) {
            currentSum = currentSum + all[i];
            leftSum = Math.max(leftSum, currentSum);
        }
        currentSum = 0;
        for (i = mid + 1; i <= right; i += 1) {
            currentSum = currentSum + all[i];
            rightSum = Math.max(rightSum, currentSum);
        }
        // compare the value and find the max among the left, the right and the middle0
        const middlePart = leftSum + all[mid] + rightSum;
        const result = Math.max(mergeSort(all, left, mid - 1), 
                                mergeSort(all, mid + 1, right), 
                                middlePart);
        return result
    }
    return mergeSort(nums, 0, nums.length - 1);
};

const nums =[-2,1,-3,4,-1,2,1,-5,4];
console.log(maxSubArray(nums)); // 6 (good!)

That version of the code works, as I extract the part that should cause problem to a single variable.

Does this issue come out from the ECMAScript specification, or what have I messed up?

I wrote this code inspired by a solution in Python

class Solution:
    def maxSubArray(self, nums):
        def maxSubArray(A, L, R):
            if L > R: return -inf
            mid, left_sum, right_sum, cur_sum = (L + R) // 2, 0, 0, 0
            for i in range(mid-1, L-1, -1):
                left_sum = max(left_sum, cur_sum := cur_sum + A[i])
            cur_sum = 0
            for i in range(mid+1, R+1):
                right_sum = max(right_sum, cur_sum := cur_sum + A[i])
            return max(maxSubArray(A, L, mid-1),
                   maxSubArray(A, mid+1, R),
                   left_sum + A[mid] + right_sum)
        return maxSubArray(nums, 0, len(nums)-1)

In my perspective, it’s an implementation of this divide and conquer concept.

I tried printing out the left, right, mid, leftSum, rightSum, and the i (index) from both JavaScript and Python to compare them. The only difference I found was when printing:

  • JavaScript:

    mergeSort(all, left, mid - 1), 
    mergeSort(all, mid + 1, right), 
    leftSum + all[mid] + rightSum
    
  • Python:

    maxSubArray(A, L, mid-1), 
    maxSubArray(A, mid+1, R), 
    left_sum + A[mid] + right_sum
    

The last expression will be different…

Asked By: YPChen

||

Answers:

This happens because this doesn’t declare all three variables:

        let leftSum = rightSum = currentSum = 0;

This implicitly defines the second and third variables as globals.

It should be:

        let leftSum = 0, rightSum = 0, currentSum = 0;

This difference matters when the expression leftSum + all[mid] + rightSum is only evaluated after the recursive calls have been made (as is the case when you don’t first store that result in a variable, but pass it as last argument). In that case the recursive calls influence the result of that expression as they modify the values of the same global variables.

You should also define i explicitly.

I would advise you to use "use strict" so that such errors are reported.

Answered By: trincot
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.