Back to Miscellaneous Coding
Q52Question 52 of 52

Largest of N Numbers: read N numbers one by one and print the largest

Key Concepts
  • Seed the running maximum with the first value, not zero, to handle negative inputs correctly
  • Scan remaining values one by one, replacing the maximum whenever a larger value appears
  • Single pass with O(n) time and O(1) space because only one variable tracks the maximum
  • Use BigDecimal.compareTo for exact money comparisons, never double for financial amounts
  • Validate that N >= 1 because the maximum of zero values is undefined
  • This is an online algorithm that works on streaming data without storing the entire input

Definition

Find the maximum of N values in one pass by seeding with the first value and replacing the running maximum whenever a larger value arrives.
  • Running maximum maintains a single variable that holds the largest value seen so far.
  • Seeding initializes the maximum with the first actual value, not zero, so the algorithm is correct for any range including negative numbers.
  • Single pass reads each value exactly once, comparing it against the running maximum and updating if it is larger.
  • Online algorithm works on streaming input without storing all values, using O(1) extra memory.
  • Financial precision uses BigDecimal.compareTo for exact comparisons, never double for money.

National Bank of India processes a day's transactions one at a time and wants the single largest transaction amount for a suspicious-activity report, without holding the whole day in memory. The system remembers the biggest amount seen so far, starts with the first transaction, and updates the record whenever a bigger one arrives.

Understanding the Concepts

What is the Largest of N Numbers problem?

Given a count N followed by N numbers read one at a time, find and print the largest number. This is a fundamental algorithm that introduces the concept of a running state variable: a single variable that accumulates the answer as data streams in.

For input N = 5 and values 3, 9, 2, 15, 7, the largest is 15.

JAVA
package com.bank.audit;

import java.math.BigDecimal;
import java.util.Scanner;

/**
 * Finds the largest of N transaction amounts read one by one.
 * Uses BigDecimal for exact financial arithmetic.
 */
public class LargestTransactionFinder {

    public static BigDecimal findLargest(Scanner scanner) {
        int n = scanner.nextInt();
        if (n < 1) {
            throw new IllegalArgumentException(
                "At least one value is required to find the maximum");
        }

        BigDecimal max = scanner.nextBigDecimal(); // seed with first value

        for (int i = 2; i <= n; i++) {
            BigDecimal x = scanner.nextBigDecimal();
            if (x.compareTo(max) > 0) {
                max = x;
            }
        }
        return max;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        BigDecimal largest = findLargest(scanner);
        System.out.println("Largest: " + largest);
    }
}
TEXT
Input:
5
3.00 9.50 2.25 15.75 7.00

Output:
Largest: 15.75

Why does seeding matter?

The most important correctness detail is how you initialize the running maximum. If you seed it with zero, the algorithm produces wrong results when all values are negative. If you seed it with Integer.MIN_VALUE, it works for integers but is fragile and non-obvious.

Seeding with the first actual value is the correct approach because it makes no assumptions about the range of the input.

JAVA
// WRONG: seeding with zero
BigDecimal max = BigDecimal.ZERO;
for (int i = 0; i < n; i++) {
    BigDecimal x = scanner.nextBigDecimal();
    if (x.compareTo(max) > 0) {
        max = x;
    }
}
// For values [-3, -7, -1], this returns 0, which is wrong.
// The correct answer is -1.
JAVA
// CORRECT: seeding with the first value
BigDecimal max = scanner.nextBigDecimal();
for (int i = 2; i <= n; i++) {
    BigDecimal x = scanner.nextBigDecimal();
    if (x.compareTo(max) > 0) {
        max = x;
    }
}
// For values [-3, -7, -1], this returns -1, which is correct.

How does the algorithm work step by step?

The algorithm reads N, then reads the first value into max. Starting from the second value, it reads each value x and compares it to max. If x > max, it replaces max with x. After all N values, max holds the largest.

The loop runs exactly N - 1 times, making N - 1 comparisons. Only one variable (max) is used, so the space is constant.

JAVA
// Trace for values: 3, 9, 2, 15, 7
// Step 0: max = 3 (seed with first value)
// Step 1: x = 9,  9 > 3  -> max = 9
// Step 2: x = 2,  2 > 9  -> no change, max = 9
// Step 3: x = 15, 15 > 9 -> max = 15
// Step 4: x = 7,  7 > 15 -> no change, max = 15
// Result: 15

How is this different from using Collections or Streams?

If the values are already in a collection, you can use Java streams or Collections.max to find the largest. However, the explicit scan is the right mental model when data streams in and cannot be stored.

| Aspect | Explicit scan | Collections.max | Stream.max |
|---|---|---|---|
| Input | Streaming | Collection in memory | Collection in memory |
| Memory | O(1) | O(n) for collection | O(n) for collection |
| Time | O(n) | O(n) | O(n) |
| Works on stream | Yes | No | No |
| Teaches the concept | Yes | Hides the logic | Hides the logic |

JAVA
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;

// Using stream
List<BigDecimal> amounts = Arrays.asList(
    new BigDecimal("3.00"),
    new BigDecimal("9.50"),
    new BigDecimal("15.75"),
    new BigDecimal("7.00")
);
BigDecimal max = amounts.stream()
    .max(BigDecimal::compareTo)
    .orElse(BigDecimal.ZERO);
System.out.println("Stream max: " + max);
TEXT
Stream max: 15.75

What are the edge cases?

The edge cases to handle are:

  • N = 0: the maximum of zero values is undefined. The algorithm should validate and throw an exception.
  • N = 1: the maximum is the single value itself. The loop does not execute.
  • All values equal: the maximum is that repeated value. The comparison never triggers an update.
  • All negative values: the seed with the first value ensures correctness.
  • Very large values: BigDecimal handles arbitrary precision, so overflow is not possible.
JAVA
package com.bank.audit;

import java.math.BigDecimal;

public class LargestEdgeCases {

    public static BigDecimal findLargestFromArray(BigDecimal[] values) {
        if (values == null || values.length == 0) {
            throw new IllegalArgumentException("At least one value required");
        }
        BigDecimal max = values[0];
        for (int i = 1; i < values.length; i++) {
            if (values[i].compareTo(max) > 0) {
                max = values[i];
            }
        }
        return max;
    }

    public static void main(String[] args) {
        // Single value
        System.out.println("Single: " + findLargestFromArray(
            new BigDecimal[]{new BigDecimal("42.00")}));

        // All negative
        System.out.println("Negative: " + findLargestFromArray(
            new BigDecimal[]{new BigDecimal("-3"), new BigDecimal("-7"),
                             new BigDecimal("-1")}));

        // All equal
        System.out.println("Equal: " + findLargestFromArray(
            new BigDecimal[]{new BigDecimal("5"), new BigDecimal("5"),
                             new BigDecimal("5")}));
    }
}
TEXT
Single: 42.00
Negative: -1
Equal: 5

What are common mistakes?

Besides the zero-seed mistake, another common error is comparing BigDecimal values with == or equals instead of compareTo. In Java, BigDecimal.equals considers scale, so new BigDecimal("5.0").equals(new BigDecimal("5.00")) returns false. Always use compareTo for magnitude comparison.

JAVA
// WRONG: using equals for comparison
BigDecimal a = new BigDecimal("5.0");
BigDecimal b = new BigDecimal("5.00");
System.out.println(a.equals(b)); // false (different scale!)

// CORRECT: using compareTo
System.out.println(a.compareTo(b) == 0); // true (same value)

Another mistake is using double for financial amounts, which introduces floating-point rounding errors.

JAVA
// WRONG: double arithmetic for money
double amount1 = 0.1 + 0.2;
System.out.println(amount1); // 0.30000000000000004

// CORRECT: BigDecimal for money
BigDecimal amt1 = new BigDecimal("0.1").add(new BigDecimal("0.2"));
System.out.println(amt1); // 0.3

What about the flowchart representation?

The problem statement asks for a flowchart. The flowchart uses standard symbols: ovals for start/end, parallelograms for input/output, rectangles for processing, and diamonds for decisions.

The flow is: Start, read N, read first value into max, set i = 2, check if i <= N, if yes read x, check if x > max, if yes set max = x, increment i, loop back. When i > N, print max, End.

JAVA
// Flowchart in code form:
// START
// READ N
// READ first value -> max
// i = 2
// WHILE i <= N:
//   READ x
//   IF x > max THEN max = x
//   i = i + 1
// PRINT max
// END

What about finding the minimum instead?

Finding the minimum uses the same algorithm with the comparison flipped: replace x > max with x < min. Everything else is identical.

JAVA
public static BigDecimal findSmallest(BigDecimal[] values) {
    if (values == null || values.length == 0) {
        throw new IllegalArgumentException("At least one value required");
    }
    BigDecimal min = values[0]; // seed with first value
    for (int i = 1; i < values.length; i++) {
        if (values[i].compareTo(min) < 0) { // flip comparison
            min = values[i];
        }
    }
    return min;
}

Finding both the min and max in a single pass uses two variables and two comparisons per value.

What about thread safety and concurrency?

In a concurrent environment where multiple threads feed transaction amounts into a shared maximum, the comparison and update must be atomic. Using AtomicReference with compareAndSet in a retry loop ensures thread safety.

JAVA
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;

public class ConcurrentMaxFinder {
    private final AtomicReference<BigDecimal> max;

    public ConcurrentMaxFinder(BigDecimal initial) {
        this.max = new AtomicReference<>(initial);
    }

    public void offer(BigDecimal value) {
        BigDecimal current;
        do {
            current = max.get();
            if (value.compareTo(current) <= 0) return;
        } while (!max.compareAndSet(current, value));
    }

    public BigDecimal getMax() {
        return max.get();
    }
}

What should a senior engineer know?

A senior engineer should know that finding the maximum is the simplest case of a streaming aggregation. The same pattern applies to finding the minimum, the sum, the count, and the average. All of these are online algorithms that maintain a constant-size state and process each value once.

In production systems, streaming aggregations run over message queues (Kafka, SQS) and must handle late-arriving data, duplicates, and windowed computations. The simple max scan is the foundation of all these more complex patterns.

For financial systems, always use BigDecimal with compareTo for magnitude comparison. Never use double for money. Never use equals on BigDecimal for value comparison because it is scale-sensitive.

What are common interview follow-up questions?

Interviewers often ask:

  • How do you find the second largest? Track both max and secondMax, updating secondMax when a new value displaces max.
  • How do you find the kth largest? Use a min-heap of size k.
  • What if the values arrive in a stream with no known N? Read until EOF or a sentinel value.
  • How do you find both min and max with fewer than 2(N-1) comparisons? Process values in pairs, comparing each pair first, then comparing the smaller against min and the larger against max. This uses 3N/2 comparisons.

Visual Overview

DIAGRAM
  Flowchart:

  ┌─────────┐
  │  START  │
  └────┬────┘
       │
       ▼
  ┌─────────┐
  │ READ N  │
  └────┬────┘
       │
       ▼
  ┌──────────────────┐
  │ READ first value │
  │ max = first      │
  │ i = 2            │
  └────────┬─────────┘
           │
           ▼
      ┌────────┐     NO
      │ i <= N?├──────────┐
      └───┬────┘          │
          │ YES           │
          ▼               │
  ┌──────────────┐        │
  │   READ x     │        │
  └──────┬───────┘        │
         │                │
         ▼                │
    ┌─────────┐           │
    │ x > max?│           │
    └──┬──┬───┘           │
   YES │  │ NO            │
       ▼  │               │
  ┌────────┐              │
  │max = x │              │
  └───┬────┘              │
      │   │               │
      ▼   ▼               │
  ┌──────────┐            │
  │ i = i+1  │            │
  └────┬─────┘            │
       │                  │
       └──────(loop)──────┘
                          │
                          ▼
                  ┌──────────┐
                  │ PRINT max│
                  └────┬─────┘
                       │
                       ▼
                  ┌─────────┐
                  │   END   │
                  └─────────┘
DIAGRAM
  Trace for values: 3, 9, 2, 15, 7

  max:  3 ──→ 9 ──→ 9 ──→ 15 ──→ 15
        ▲     ▲           ▲
        │     │           │
      seed  update      update

  Comparisons: 9>3(Y), 2>9(N), 15>9(Y), 7>15(N)
  Result: 15

Key Takeaway

Finding the largest of N values read one by one is a single-pass scan that keeps a running maximum. You seed the running maximum with the first actual value, then read each remaining value and replace the running maximum whenever a larger value appears. After the last value, the running maximum is the answer.

The correctness trap is the seed. You must seed with the first actual value, not with zero, because if every value were negative, a zero seed would wrongly report zero as the maximum. Seeding with a real value makes the algorithm correct for any range. For financial amounts, always use BigDecimal with compareTo for exact comparison, never double.

At the National Bank of India, Rajesh used this for a suspicious-activity report that needed the single largest transaction of the day without loading the entire day into memory. His MaxFinder seeded the maximum with the first amount and scanned the rest in one pass, comparing with compareTo and validating that at least one value existed. For a day of 3, 9, 2, 15, and 7 it reported 15.

The maximum scan is the simplest streaming aggregation, and the same pattern extends to minimum, sum, count, and average. In concurrent systems, the comparison and update must be atomic, which is why AtomicReference with compareAndSet is the thread-safe version. The lesson is that a maximum scan is trivial to write but has a real correctness trap in the seed.

Remember one sentence: seed the running maximum with the first real value, scan the rest, and update whenever a larger value arrives.

---

Share
Use ← → arrow keys to navigate