Home/Data structures & algorithms/Arrays and linear scans

Arrays and linear scans

Accumulators, indices, and loop invariants

A monitoring job receives request counts in minute order. The report needs the total traffic and the first minute with the highest count. Sorting the readings would do extra work and make the original positions harder to track; storing the whole stream isn't necessary either.

If the busiest value appears again later, the report should still point to its first occurrence. That small requirement decides which comparison belongs in the loop.

Keeping the first busiest minute

Keep the running total and a candidate peak position. Replace the position only for a strictly larger reading; an equal later peak keeps the earlier position. The first reading establishes a candidate, while an empty input never does. This distinguishes an empty report from a real zero-traffic minute.

Summarising the stream once

Suppose you need the total and the first maximum position for [6, 2, 6]. Track the total of the values already visited and the index of their first maximum:

Separate built-in scans expose the tie rule directly:

Python
values = [6, 2, 6]
total = sum(values)
peak = max(values)
first = values.index(peak)
print(total, first)

This prints 14 0. index selects the first occurrence; max would need a separate empty-input policy.

After readingRunning totalFirst maximum index
Nothing0None
Index 0: 660
Index 1: 280
Index 2: 6140

The final equal value does not replace index 0. Replacing on >= would select the last maximum instead.

To isolate a single summary, consider finding only the maximum value in a nonempty iterator of negative readings:

Python
readings = iter([-4, -2, -7])
peak = next(readings)
for value in readings:
    peak = max(peak, value)
print(peak)

This prints -2. Initializing peak to zero would invent a maximum absent from this input. A loop invariant is a statement that remains true after each iteration: peak is the maximum of the consumed prefix. The first reading establishes it, each update extends it, and exhaustion makes that prefix the whole sequence. This example keeps neither a total nor a position; those require their own state and tie policy.

When another pass costs more

A baseline can make separate passes for the total, maximum, and first maximum position. That is still O(n): a constant number of scans is linear. Combining compatible summaries into one pass reduces traversals and can support a one-pass iterator. It does not change the asymptotic bound. Any exact answer involving an arbitrary unseen value requires inspecting that value, so a general scan cannot become sublinear.

O(n) means work grows in proportion to the number of elements; O(1) auxiliary space means the summary does not grow with the input. These conventional bounds count integer arithmetic as a basic operation. Python integers can grow beyond machine-word size.

Use this approach for a full, one-time summary. For many range queries, preprocessing such as prefix sums can avoid rescanning. For frequent ordered queries, a different index may be worthwhile. Before coding, choose the empty-input result and tie rule explicitly.

The monitoring job can emit its total and peak position as soon as the final reading arrives.

Practice Arrays and linear scans

Implement the exercise in a full-screen workspace, then run its tests.

Open practice
Next lessonStrings and frequency maps