A Classical Interview Question

The description of the question:

Locate the consecutive sub-list in a list that has the highest sum of values.

For example:

Given a list: (-1, 4, -2, 3, 1, 2, -2), the sub-list to return should be (4, -2, 3, 1, 2) with sum of values being 8.

 

First thought of the question:

The solution is expected to be with time complexity O(n), in which 'n' is the length of the original list.

As it seems to me, an intuitive way of achieving that is looking through the incremental sum of the list.

Since the sum of values in sub-list is represented in the incremental sum by the difference between the value of start point (one before the beginning of the sublist) and end point, locating the sublist with highest sum is a matter of finding two points with highest forward difference.

So an algorithm to deal with the problem should on one hand store the lowest point so far (the trough) and the highest increase in value from the lowest so far by inspecting and keeping track of the difference between the current value and that lowest point. Hence the implementation may look like the following:

 

public static int GetSubList(IList<int> source, out int begin, out int end) 
{ 
    int count = source.Count; 
    int recSum = int.MinValue; 
    int value = 0; 
    int currIncSum = 0; 
    int iTrough = -1;
    int lowestTrough = 0; 
    begin = 0; end = 0; 
    for (int i = 0; i < count; i++) 
    { 
        value = source[i]; 
        currIncSum += value; 
        if (currIncSum - lowestTrough > recSum && iTrough < i) 
        { 
            begin = iTrough + 1; 
            end = i + 1; 
            recSum = currIncSum - lowestTrough; 
        } 
        
        if (currIncSum < lowestTrough) 
        { 
            lowestTrough = currIncSum; iTrough = i; 
        } 
    } 
    return recSum; 
}
 

In the source code, currIncSum keeps track of the current incremental sum value with begin and end marking the beginning and end of the sub-sequence, lowestTrough is the lowest value so far with iTrough being the position of the point.

The logic in the first conditional branch updates the highest increase, corresponding to the sublist with highest sum so far. The second conditional branch updates the lowest point and its value.

A subtle aspect of the task the algorithm is to achieve is how to deal with the case in which all values of the list are negative. Depending on the demand, the algorithm can return zero-lengthed list or the entry with highest value in the list as the sub-list. The above code implements the latter, with its recSum, the highest jump so far initialized to the minimal integer value.

 

Another way of handling the problem is simpler, and most likely more efficient but not as straightforward at a first glance. The code is as follows,


public static int GetSubList2(IList<int> a, out int begin, out int end)
{ 
    int n = a.Count;
    int sum = a[0];
    int b = 0;
    int ttBegin = 0;
    begin = 0;
    end = 1;
    for (int i = 0; i < n; i++)
    { 
        if (b < 0) 
        { 
            b = a[i];
            ttBegin = i;
        }
        else 
        { 
            b += a[i];
        } 
        if (sum < b) 
        { 
            begin = ttBegin;
            end = i + 1;
            sum = b;
        } 
    } 
    return sum;
}


The idea is borrowed from (and a similar piece of code in C is provided by) July's blog post at:

http://blog.csdn.net/v_JULY_v/archive/2011/05/25/6444021.aspx

The key of this implementation is it exploited the fact that the candidate beginning point of the sublist only needs to be changed after the sum of current sublist is lower than zero (corresponding to in the perspective of the former algorithm, a new minimum value is found). The addition operation for the sum in this algorithm is reset, suspended and turned to an inspection of incoming value after such point is reached until the value turns to positive again. The process of recording the highest jump is performed in a separate logic that follows which keeps track of the sum value provided by the preceding logic. So the logic that generates the jump theoretically needs only to ensure it does not miss such a maximum value.

 

From this simple but typical example, we can see algorithmic problems need special attention from developers for them to make constant improvement in simplicity, efficiency, etc. and some of the changes can't be easily seen and require certain in-depth understanding, experience, changes in ways of thinking and even talents.

 

 

 

posted @ 2011-06-05 19:56  quanben  阅读(137)  评论(0编辑  收藏  举报