Chapter 3: Arrays

Data Structures
The array conceptArrays in JavaStack and heap Array algorithmsMultidimensional arraysComplexitySortingCaesar cipher
Sercan KÜLCÜ · Giresun University · Computer Engineering

Introduction

Learning Outcomes

The array model

Explain how arrays are laid out in memory and how indexing works.

Arrays in Java

Declare and use one- and multidimensional arrays correctly.

Algorithms

Search, insert, delete, and sort on arrays.

Cost analysis

Compare the time and space costs of array operations.

Basic Concepts

Arrays

  • Stores many values of the same type, in order, under a single name.
  • Its size is fixed when the array is created and cannot change afterwards.
  • The elements sit side by side in contiguous memory slots.
  • Element values can always be updated; "inserting" or "deleting" means shifting elements.
  • Used to represent matrices, images (pixels), sound (samples), and similar data.
index element 13 0 -1 1 4 2 2 3 7 4 arr.length = 5 · first index 0 · last index 4

Basic Concepts

Using Arrays: Access by Index

  • An element is reached by its index: arrayName[index].
  • The first element has index 0, the last length − 1.
  • Because the elements are contiguous and equal in size, each address takes one multiplication and one addition to compute.
Click a cell: the top row is the index, the bottom row the value.
int[] arr = {7, 2, 9, 5, 1};
int first = arr[0];      // 7
int last = arr[arr.length - 1];      // 1

Basic Concepts

Kinds of Arrays

One-dimensional
Elements sit in a single row.int[] a = new int[5];
Two-dimensional
Elements sit in rows and columns (a matrix).int[][] m = new int[3][3];
Multidimensional
More complex layouts, e.g. time × row × column.int[][][] k = new int[3][3][3];

Basic Concepts

Arrays: Pros and Cons

Advantages

  • Fast, direct access by index: O(1).
  • Groups data and keeps it in insertion order.
  • Holds primitives (int, char, …) as well as objects.
  • Contiguous memory: no extra references, and cache-friendly.

Disadvantages

  • Fixed size: a full array cannot grow; you must create a bigger one and copy.
  • Inserting or deleting in the middle means shifting elements: O(n).
  • Unused capacity is wasted.
  • Arrays have no methods for sorting, searching, and the like; java.util.Arrays provides them.
Java arrays are covariant, which buys flexibility at the cost of a hole in type safety.

Basic Concepts

Array Covariance and ArrayStoreException

  • Because String is a subtype of Object, String[] counts as a subtype of Object[].
  • So one method that takes an Object[] works with any array of references.
  • The price: a store of the wrong type is caught at run time, not at compile time.
String[] names = {"Ann", "Alice"};
Object[] objects = names; // compiles
objects[0] = 42;   // compiles, but
                    // throws at run time!
stacknamesobjectsheap: String[] object"Ann""Alice"0142ArrayStore-Exception
FlexibilityYou can write general-purpose methods.static void print(Object[] a)
ContrastGeneric lists are not covariant, so the error is caught at compile time.List<Object> l = new ArrayList<String>(); // error

Arrays in Java

Declaring Arrays

DataType[] arrayName = new DataType[arraySize];
DataTypeThe type of the values the array stores.int, double, String…
arrayNameThe name of the reference variable that points to the array.nums
arraySizeThe number of elements; it may be computed at run time.5, n, 2 * n
int[] nums = new int[5];
// → [0, 0, 0, 0, 0]

Every element of an array created with new starts at its type's default value.

TypeDefault value
int, long…0
double0.0
booleanfalse
char'\u0000'
object (String, …)null

Arrays in Java

Access, Initialization, and length

InitializationAn initializer list supplies the starting values. You don't write the size: the number of elements sets it.
AccessIndices start at 0. Reading and writing use the same syntax.
lengthThe number of elements. It is a field, not a method: no parentheses.
int[] nums = {10, 20, 30, 40, 50};   // 5 initializers → size 5
int first    = nums[0];            // 10
int second = nums[1];            // 20
nums[4] = 55;                         // last element updated
int size   = nums.length;     // 5

nums = new int[] {1, 2, 3};          // later assignment needs new int[]
int[] bad = new int[3] {1, 2, 3};   // compile error: size + list

Arrays in Java · Step through the code

Code Trace: Reading and Writing an Array

nums100201302403504

The initializer list has 5 values → a 5-element int[] is created on the heap; nums holds only its reference.

nums100201302403504

Read: nums[0] is the first cell. Address = start + 0 × 4 → one step, O(1).

nums100201302403504

Index 1 is the second element (indices start at 0).

nums100201302403554

Write: same syntax, on the left of the assignment. The last cell (index 4 = length − 1) goes from 50 to 55.

nums100201302403554

length is a field (no parentheses) → 5. The size was fixed when the array was created.

nums100201302403554garbage (GC)123

A later assignment needs new int[]: a new 3-element array is created and nums points to it. The old array is unchanged; with no reference left, the GC may collect it. size is still 5: it was a copy.

int[] nums = {10, 20, 30, 40, 50};
int first    = nums[0];
int second = nums[1];
nums[4] = 55;
int size   = nums.length;
nums = new int[] {1, 2, 3};

The last line of the previous slide (new int[3] {1, 2, 3}) does not compile.

Arrays in Java

Example: Declare, Fill, Print

public class ArrayExample {
    public static void main(String[] args) {
        int[] nums = new int[5];            // 5-element array
        for (int i = 0; i < nums.length; i++)
            nums[i] = (i + 1) * 10;              // 10, 20, 30, 40, 50

        System.out.println("First element: " + nums[0]);
        System.out.println("Third element: " + nums[2]);

        System.out.print("Elements: ");
        for (int x : nums)                       // for-each: when no index is needed
            System.out.print(x + " ");
    }
}
First element: 10
Third element: 30
Elements: 10 20 30 40 50

A loop, rather than five separate nums[k] = … assignments, keeps working if the size changes. The loop bound is always i < nums.length.

Arrays in Java · Step through the code

Code Trace: Fill and Print

0001020304

new int[5]: 5 cells are allocated, all starting at 0, the default for int.

10001020304▲ i

i = 0 < 5 → nums[0] = (0 + 1) × 10 = 10.

100201020304▲ i

i = 1 < 5 → nums[1] = (1 + 1) × 10 = 20.

1002013020304▲ i

i = 2 < 5 → nums[2] = (2 + 1) × 10 = 30.

10020130240304▲ i

i = 3 < 5 → nums[3] = (3 + 1) × 10 = 40.

100201302403504▲ i

i = 4 < 5 → nums[4] = (4 + 1) × 10 = 50.

100201302403504

At i = 5, 5 < 5 is false → the loop ends. With nums.length as the bound, the code stays correct if the size changes.

100201302403504

Index 0 is read → 10; println adds a newline.

100201302403504

The third element is at index 2 → 30.

100201302403504

print adds no newline, so the elements will go on the same line.

100201302403504

for-each: x = 10 (index 0). When you don't need an index variable, this reads better.

100201302403504

x = 20 is printed.

100201302403504

x = 30 is printed.

100201302403504

x = 40 is printed.

100201302403504

x = 50 is printed. Every element has been visited → the loop ends.

int[] nums = new int[5];
for (int i = 0; i < nums.length; i++)
    nums[i] = (i + 1) * 10;
System.out.println("First element: "
        + nums[0]);
System.out.println("Third element: "
        + nums[2]);
System.out.print("Elements: ");
for (int x : nums)
    System.out.print(x + " ");

Arrays in Java

Copying Arrays: Reference or Copy?

arrstackheap12345#1

An array is an object, created on the heap (#1). The variable arr on the stack holds only its address.

arrrefstackheap12345#1

= copies only the address: both variables point to the same array (#1). No new array is created.

arrrefstackheap12345#1copy12345#2

Arrays.copyOf creates a new array (#2) on the heap and copies the elements one by one → O(n). (arr.clone() and System.arraycopy copy elements too.)

arrrefstackheap992345#1copy12345#2

ref[0] = 99 changes #1 → arr[0] becomes 99 too. copy[0] lives in a separate object and is still 1.

int[] arr = {1, 2, 3, 4, 5};
int[] ref = arr;   // same object
int[] copy =
    Arrays.copyOf(arr, arr.length);
ref[0] = 99;

Arrays in Java

Ready-Made Array Operations: java.util.Arrays

MethodWhat it doesCost
Arrays.toString(a)Returns a string like "[1, 2, 3]"O(n)
Arrays.equals(a, b)Same length and equal elements, one by one?O(n)
Arrays.fill(a, v)Writes v into every cellO(n)
Arrays.copyOf(a, n)New array; if n > a.length, the extra cells get the default valueO(n)
Arrays.sort(a)Sorts in ascending order (in place)O(n log n)
Arrays.binarySearch(a, x)Binary search in a sorted array; negative result if absentO(log n)
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
a == b              // false: different objects
Arrays.equals(a, b) // true: same contents
println(a)          // [I@1b6d3586
ab123123#1#2a == b → false

Arrays in Java

Varargs (Variable-Length Arguments)

  • The type... name syntax lets a method take any number of arguments.
  • Inside the method, the parameter is an ordinary array.
  • Only the last parameter can be varargs, so a method has at most one.
  • You can also pass an existing array directly.
static double average(double... arr) {
    if (arr.length == 0)
        return Double.NaN;  // undefined
    var sum = 0.0;
    for (var value : arr)
        sum += value;
    return sum / arr.length;
}
Callarr.lengthResult
average(4, 8, 15)39.0
average(7.5)17.5
average()0NaN
average(new double[] {1, 2})21.5

Arrays in Java · Step through the code

Code Trace: average(4, 8, 15)

arr4.008.0115.02

For the call average(4, 8, 15) the compiler packs the arguments into a double[] (each int is widened to double).

arr4.008.0115.02

arr.length = 3 ≠ 0 → not the empty case, so carry on.

arr4.008.0115.02

var: the type is inferred from the value on the right → double.

arr4.008.0115.02

for-each: value = 4.0 (element 1 of the array).

arr4.008.0115.02

sum = 0.0 + 4.0 = 4.0.

arr4.008.0115.02

for-each: value = 8.0 (element 2 of the array).

arr4.008.0115.02

sum = 4.0 + 8.0 = 12.0.

arr4.008.0115.02

for-each: value = 15.0 (element 3 of the array).

arr4.008.0115.02

sum = 12.0 + 15.0 = 27.0.

arr4.008.0115.02

The loop is done. return 27.0 / 3 = 9.0. A call to average() would have length 0 and return NaN on line 3.

static double average(double... arr) {
    if (arr.length == 0)
        return Double.NaN;  // undefined
    var sum = 0.0;
    for (var value : arr)
        sum += value;
    return sum / arr.length;
}

Arrays in Java

Arrays in Memory: Stack and Heap

  • Stack: local variables and method calls. When a method returns, its frame is removed automatically.
  • Heap: every object created with new. In Java every array lives on the heap; only its reference sits on the stack.
  • The heap is managed by the garbage collector (GC), which frees objects that no reference points to.
Stack main() frame nums Heap int[] object · length = 5 10 20 30 40 50 managed automatically by the GC
StackHeap
Holdslocal variables, references, call framesobjects, arrays
Lifetimeuntil the method returnsas long as it is reachable
Managed byautomatic (call / return)automatic (GC)

Arrays in Java

"Static" and "Dynamic" Arrays: C vs. Java

C / C++Java
Array on the stackint a[10]; — size fixed at compile time; gone when the function returnsNone: every array is on the heap
Array on the heapmalloc / new int[n]new int[n] (initializer lists too)
Sizeon the heap, set at run timeset at run time, then fixed
Freeingthe programmer: free / delete[]automatic GC; arr = null only drops the reference
int[] dynArr = new int[n];   // room for n elements on the heap
dynArr = null;               // GC may collect it if no other reference exists
System.gc() is only a request and guarantees nothing; Java has no delete. "Dynamic array" also names an array that grows by itself when full (ArrayList).

Arrays in Java

Index Out of Bounds

  • Accessing an index outside the valid range makes Java throw an ArrayIndexOutOfBoundsException (a subclass of IndexOutOfBoundsException).
  • If it isn't caught, the program stops. Java checks every access; C doesn't, and silently writes to some other memory.
0 0 0 1 0 2 0 3 0 4 ? 5 valid indices: 0 … length − 1 arr[5] ✗
int[] arr = new int[5];
arr[5] = 1;  // Index 5 out of
              // bounds for length 5
arr[-1] = 1; // same error
if (index >= 0
        && index < arr.length) {
    int value = arr[index];
} else {
    System.out.println("Invalid");
}

Array Algorithms

Reading an Array from the User

The mean, median, and mode programs all start by reading input the same way, so the shared code goes into a helper (the int[] version for the mode is identical):

import java.util.Scanner;

static double[] readArray(Scanner scanner) {
    System.out.print("Enter the array size: ");
    int size = scanner.nextInt();
    double[] arr = new double[size];  // size known only at run time
    for (int i = 0; i < size; i++) {
        System.out.print("Enter element " + (i + 1) + ": "); // count from 1
        arr[i] = scanner.nextDouble();
    }
    return arr;
}
MeanSum / number of elementsO(n)
MedianThe middle of the sorted arrayO(n log n)
ModeThe most frequent valueO(n log n)

Array Algorithms · Step through the code

Code Trace: readArray(sc)

no array yet (size unknown)

First we ask for the size. print adds no newline, so the cursor waits on the same line.

no array yet (size unknown)

The user types 4 and nextInt() reads it. (What the user types also appears on the console.)

0.000.010.020.03

The size became known at run time → new double[4], every cell 0.0.

0.000.010.020.03▲ i

i = 0, but the prompt says element 1 (people count from 1).

4.000.010.020.03▲ i

The user types 4 → arr[0] = 4.0.

4.000.010.020.03▲ i

i = 1, but the prompt says element 2 (people count from 1).

4.008.010.020.03▲ i

The user types 8 → arr[1] = 8.0.

4.008.010.020.03▲ i

i = 2, but the prompt says element 3 (people count from 1).

4.008.0115.020.03▲ i

The user types 15 → arr[2] = 15.0.

4.008.0115.020.03▲ i

i = 3, but the prompt says element 4 (people count from 1).

4.008.0115.0216.03▲ i

The user types 16 → arr[3] = 16.0. At i = 4 the loop ends.

4.008.0115.0216.03

The method returns the array's reference; the array is on the heap, so it outlives the method.

static double[] readArray(Scanner scanner) {
  System.out.print("Enter the array size: ");
  int size = scanner.nextInt();
  double[] arr = new double[size];
  for (int i = 0; i < size; i++) {
    System.out.print("Enter element " + (i + 1) + ": ");
    arr[i] = scanner.nextDouble();
  }
  return arr;
}

Array Algorithms

Computing the Mean

public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    double[] arr = readArray(sc);
    double sum = 0;
    for (int i = 0; i < arr.length; i++)
        sum += arr[i];
    double average = sum / arr.length;
    System.out.println("Array: "
        + Arrays.toString(arr));
    System.out.println("Average: "
        + average);
    sc.close();
}
Examplearr = {4, 8, 15, 16, 23}
sum = 66
average = 66 / 5 = 13.2
CostEach element is added once → O(n) time, O(1) extra space.
Edge caseWith size 0 we get 0.0 / 0 = NaN; better to warn the user.

Array Algorithms · Step through the code

Code Trace: Computing the Mean

4.008.0115.0216.0323.04

Suppose this time the user enters 5 values: readArray returns {4, 8, 15, 16, 23}.

4.008.0115.0216.0323.04

The accumulator starts at 0 (int 0 → double 0.0).

4.008.0115.0216.0323.04▲ i

sum += arr[0] → 0.0 + 4.0 = 4.0.

4.008.0115.0216.0323.04▲ i

sum += arr[1] → 4.0 + 8.0 = 12.0.

4.008.0115.0216.0323.04▲ i

sum += arr[2] → 12.0 + 15.0 = 27.0.

4.008.0115.0216.0323.04▲ i

sum += arr[3] → 27.0 + 16.0 = 43.0.

4.008.0115.0216.0323.04▲ i

sum += arr[4] → 43.0 + 23.0 = 66.0.

4.008.0115.0216.0323.04

i = 5, and 5 < 5 is false → the loop ends. Each element was added once → O(n).

4.008.0115.0216.0323.04

average = 66.0 / 5 = 13.2. With size 0 this would be 0.0 / 0 = NaN.

4.008.0115.0216.0323.04

Arrays.toString turns the array into text of the form [a, b, …]. (println(arr) would print only type@address.)

4.008.0115.0216.0323.04

The result is printed. Overall: O(n) time, O(1) extra space.

double[] arr = readArray(sc);
double sum = 0;
for (int i = 0; i < arr.length; i++)
    sum += arr[i];
double average = sum / arr.length;
System.out.println("Array: "
    + Arrays.toString(arr));
System.out.println("Average: "
    + average);

Array Algorithms

Computing the Median

static double median(double[] arr) {
    double[] s = arr.clone(); // copy
    Arrays.sort(s);             // O(n log n)
    int size = s.length;
    double median;
    if (size % 2 == 0) {       // even
        int m1 = size/2 - 1, m2 = size/2;
        median = (s[m1] + s[m2]) / 2;
    } else {                    // odd
        median = s[size / 2];
    }
    return median;
}
  • The median is the middle value of the sorted array.
  • For an odd size it is the exact middle; for an even size, the mean of the two middle values.
  • Unlike the mean, it isn't thrown off by outliers (e.g. 1000).
odd (n = 5) 3 0 5 1 7 2 9 3 11 4 → s[2] = 7 even (n = 4) 2 0 4 1 6 2 8 3 → (4 + 6) / 2 = 5

Array Algorithms · Step through the code

Code Trace: median()

static double median(double[] arr) {
    double[] s = arr.clone(); // copy
    Arrays.sort(s);             // O(n log n)
    int size = s.length;
    double median;
    if (size % 2 == 0) {       // even
        int m1 = size/2 - 1, m2 = size/2;
        median = (s[m1] + s[m2]) / 2;
    } else {                    // odd
        median = s[size / 2];
    }
    return median;
}
arr6.002.018.024.03

Call 1 (even size): median is called with a 4-element array.

arr6.002.018.024.03s6.02.08.04.0

Make a copy first, so the caller's array keeps its order.

arr6.002.018.024.03s2.04.06.08.0

The copy is sorted; the original arr is unchanged.

arr6.002.018.024.03s2.04.06.08.0

size = 4.

arr6.002.018.024.03s2.04.06.08.0

4 % 2 == 0 → even: there is no single middle element, but two.

arr6.002.018.024.03s2.04.06.08.0

The two middle indices: m1 = 1, m2 = 2.

arr6.002.018.024.03s2.04.06.08.0

median = (4.0 + 6.0) / 2 = 5.0.

arr6.002.018.024.03s2.04.06.08.0

return 5.0.

arr9.003.0111.027.035.04

Call 2 (odd size): median is called with a 5-element array.

arr9.003.0111.027.035.04s9.03.011.07.05.0

Make a copy first, so the caller's array keeps its order.

arr9.003.0111.027.035.04s3.05.07.09.011.0

The copy is sorted; the original arr is unchanged.

arr9.003.0111.027.035.04s3.05.07.09.011.0

size = 5.

arr9.003.0111.027.035.04s3.05.07.09.011.0

5 % 2 = 1 → odd: take the else branch.

arr9.003.0111.027.035.04s3.05.07.09.011.0

The exact middle: index 5 / 2 = 2 → 7.0.

arr9.003.0111.027.035.04s3.05.07.09.011.0

return 7.0. Sorting dominates → O(n log n).

Array Algorithms

Mode (the Most Frequent Value)

static int mode(int[] arr) { // not empty
    int[] s = arr.clone();
    Arrays.sort(s);  // equal values side by side
    int best = s[0], bestCount = 1;
    int count = 1;
    for (int i = 1; i < s.length; i++) {
        if (s[i] == s[i - 1]) count++;
        else count = 1;   // new value
        if (count > bestCount) {
            bestCount = count;
            best = s[i];
        }
    }
    return best;
}
  • After sorting, equal values are adjacent, so one pass finds the longest run.
  • On a tie the first (smallest) value wins, because the comparison is >.
  • Cost: sorting O(n log n) + scanning O(n).
1 0 1 1 2 2 4 3 4 4 4 5 2 times 1 time 3 times → mode = 4

Array Algorithms · Step through the code

Code Trace: mode({4, 1, 4, 2, 1, 4})

static int mode(int[] arr) { // not empty
    int[] s = arr.clone();
    Arrays.sort(s);  // equal values side by side
    int best = s[0], bestCount = 1;
    int count = 1;
    for (int i = 1; i < s.length; i++) {
        if (s[i] == s[i - 1]) count++;
        else count = 1;   // new value
        if (count > bestCount) {
            bestCount = count;
            best = s[i];
        }
    }
    return best;
}
arr401142231445

Input: {4, 1, 4, 2, 1, 4}. Equal values are scattered.

s101122434445

Once the copy is sorted, equal values sit side by side, and one pass is enough.

s101122434445

Start: the first value (1) is the most frequent so far, seen once.

s101122434445▲ i▲ i−1

s[1] = 1 == s[0] → the run continues, count = 2.

s101122434445▲ i▲ i−1

2 > the old record → new record: best = 1, 2 times.

s101122434445▲ i▲ i−1

s[2] = 2 ≠ 1 → new value; the count resets to 1.

s101122434445▲ i▲ i−1

1 > 2 is false → the record stands.

s101122434445▲ i▲ i−1

s[3] = 4 ≠ 2 → new value; the count resets to 1.

s101122434445▲ i▲ i−1

1 > 2 is false → the record stands.

s101122434445▲ i▲ i−1

s[4] = 4 == s[3] → the run continues, count = 2.

s101122434445▲ i▲ i−1

2 > 2 is false → the record stands. (On a tie the first value wins.)

s101122434445▲ i▲ i−1

s[5] = 4 == s[4] → the run continues, count = 3.

s101122434445▲ i▲ i−1

3 > the old record → new record: best = 4, 3 times.

s101122434445

return 4 (3 times). Sorting O(n log n) + scanning O(n).

Array Algorithms · Step through the code

Reversing an Array in Place

static void reverse(int[] arr) {
    int start = 0;
    int end = arr.length - 1;
    while (start < end) {
        // swap the two outermost elements
        int temp = arr[start];
        arr[start] = arr[end];
        arr[end] = temp;
        start++;   // move inward
        end--;
    }
}
308112934465▲ starttemp = —

The left pointer starts at the first element.

308112934465▲ start▲ endtemp = —

The right pointer starts at the last element (length − 1 = 5).

308112934465▲ start▲ endtemp = —

0 < 5 → there is another pair to swap.

308112934465▲ start▲ endtemp = 3

temp = arr[0] = 3: save the left value so it isn't lost.

608112934465▲ start▲ endtemp = 3

arr[0] = arr[5] → 6. (The old 3 now survives only in temp.)

608112934435▲ start▲ endtemp = 3

arr[5] = temp → 3. The pair is swapped.

608112934435▲ start▲ endtemp = 3

The pointers move inward: start = 1, end = 4.

608112934435▲ start▲ endtemp = 3

1 < 4 → there is another pair to swap.

608112934435▲ start▲ endtemp = 8

temp = arr[1] = 8: save the left value so it isn't lost.

604112934435▲ start▲ endtemp = 8

arr[1] = arr[4] → 4. (The old 8 now survives only in temp.)

604112938435▲ start▲ endtemp = 8

arr[4] = temp → 8. The pair is swapped.

604112938435▲ start▲ endtemp = 8

The pointers move inward: start = 2, end = 3.

604112938435▲ start▲ endtemp = 8

2 < 3 → there is another pair to swap.

604112938435▲ start▲ endtemp = 1

temp = arr[2] = 1: save the left value so it isn't lost.

604192938435▲ start▲ endtemp = 1

arr[2] = arr[3] → 9. (The old 1 now survives only in temp.)

604192138435▲ start▲ endtemp = 1

arr[3] = temp → 1. The pair is swapped.

604192138435▲ start▲ endtemp = 1

The pointers move inward: start = 3, end = 2.

604192138435▲ start▲ endtemp = 1

3 < 2 is false → the pointers have crossed in the middle and the loop ends. ⌊n/2⌋ = 3 swaps → O(n) time, O(1) extra space.

Copying back to front into a new array (result[i] = arr[n - 1 - i]) is also O(n) time, but needs O(n) extra space. With an odd number of elements, the middle one stays put.

Array Algorithms · Step through the code

Application: Caesar Cipher encrypt("ZEKA", 3)

charsZ0E1K2A3

toCharArray() copies the string's letters into a new char[]. A String is immutable; the array can be changed in place.

charsZ0E1K2A3▲ ipos = 'Z' − 'A' = 25

i = 0: 'Z' − 'A' gives the letter's position in the alphabet: 25 (char values are integers).

charsC0E1K2A3▲ ipos = 'Z' − 'A' = 25 → (25 + 3) % 26 = 2 → 'C'

chars[0] = 'C': shift the position by 3 and turn it back into a letter. A position past the end wraps around with % 26 (Z → C).

charsC0E1K2A3▲ ipos = 'E' − 'A' = 4

i = 1: 'E' − 'A' gives the letter's position in the alphabet: 4 (char values are integers).

charsC0H1K2A3▲ ipos = 'E' − 'A' = 4 → (4 + 3) % 26 = 7 → 'H'

chars[1] = 'H': shift the position by 3 and turn it back into a letter.

charsC0H1K2A3▲ ipos = 'K' − 'A' = 10

i = 2: 'K' − 'A' gives the letter's position in the alphabet: 10 (char values are integers).

charsC0H1N2A3▲ ipos = 'K' − 'A' = 10 → (10 + 3) % 26 = 13 → 'N'

chars[2] = 'N': shift the position by 3 and turn it back into a letter.

charsC0H1N2A3▲ ipos = 'A' − 'A' = 0

i = 3: 'A' − 'A' gives the letter's position in the alphabet: 0 (char values are integers).

charsC0H1N2D3▲ ipos = 'A' − 'A' = 0 → (0 + 3) % 26 = 3 → 'D'

chars[3] = 'D': shift the position by 3 and turn it back into a letter.

charsC0H1N2D3

new String(chars) → "CHND". To decrypt, call the same method with k = 26 − 3 = 23. Each letter is handled once: O(n).

static String encrypt(String message, int k) {
    char[] chars = message.toCharArray();
    for (int i = 0; i < chars.length; i++) {
        int pos = chars[i] - 'A';        // 0..25 (A–Z only)
        chars[i] = (char) ('A' + (pos + k) % 26);
    }
    return new String(chars);
}

Multidimensional Arrays

Two-Dimensional Arrays (Matrices)

  • A table-like structure of rows and columns.
  • A natural fit for two-dimensional data: a chessboard, an image (a grid of pixels), a table.
  • An element is reached with two indices: matrix[row][column].
int[][] matrix = new int[3][3];
matrix[1][2] = 7;   // 2nd row, 3rd column
column 0 column 1 column 2 row 0 [0][0] [0][1] [0][2] row 1 [1][0] [1][1] [1][2] row 2 [2][0] [2][1] [2][2]

Multidimensional Arrays

In Java, a Matrix Is an Array of Arrays

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
matrix.length      // 3 (number of rows)
matrix[0].length   // 3 (number of columns)
matrix 1 2 3 matrix[0] 4 5 6 matrix[1] 7 8 9 matrix[2] length = 3 each row is a separate int[]
  • The initializer gives no sizes: the number of rows comes from the outer list, the number of columns from the inner lists.
  • Each row is a separate array, so rows can differ in length (a jagged array): int[][] tri = new int[3][]; tri[0] = new int[1]; …

Multidimensional Arrays · Step through the code

Jagged Array: Pascal's Triangle

p0null1null2null3null

new int[n][] creates only the outer array of 4 references; the row arrays don't exist yet (null).

p00length 11null2null3null

p[0] = new int[1]: row 0 is a separate array of length 1; its cells start at 0.

p01length 11null2null3null

p[0][0] = 1: in a one-cell row, the first and last cells are the same.

p01length 1100length 22null3null

p[1] = new int[2]: row 1 is a separate array of length 2; rows of different lengths make a jagged array.

p01length 1111length 22null3null

p[1][0] = p[1][1] = 1: both ends of the row are 1 (a chained assignment runs right to left).

p01length 1111length 22000length 33null

p[2] = new int[3]: a new row of 3 cells.

p01length 1111length 22101length 33null

p[2][0] = p[2][2] = 1: both ends of the row are 1 (a chained assignment runs right to left).

p01length 1111length 22121length 33null

p[2][1] = p[1][0] + p[1][1] = 1 + 1 = 2: the sum of the two neighbors above.

p01length 1111length 22121length 330000length 4

p[3] = new int[4]: a new row of 4 cells.

p01length 1111length 22121length 331001length 4

p[3][0] = p[3][3] = 1: both ends of the row are 1 (a chained assignment runs right to left).

p01length 1111length 22121length 331301length 4

p[3][1] = p[2][0] + p[2][1] = 1 + 2 = 3: the sum of the two neighbors above.

p01length 1111length 22121length 331331length 4

p[3][2] = p[2][1] + p[2][2] = 2 + 1 = 3: the sum of the two neighbors above. Pascal's triangle is complete: 1+2+3+4 = 10 cells in all (not 4×4 = 16).

int n = 4;
int[][] p = new int[n][];          // no rows yet
for (int i = 0; i < n; i++) {
    p[i] = new int[i + 1];         // length i + 1
    p[i][0] = p[i][i] = 1;
    for (int j = 1; j < i; j++)
        p[i][j] = p[i-1][j-1] + p[i-1][j];
}

Multidimensional Arrays · Step through the code

Shallow and Deep Copies of a 2D Array

1234m

A 2×2 matrix: the outer array m points to two row arrays (an array of arrays).

1234ms

m.clone() copies only the outer array: s is a new outer array, but its references point to the same rows → a shallow copy.

9234ms

s[0][0] = 9 changes the shared row, so m[0][0] becomes 9 too. A common mistake!

923478ms

s[1] = new int[] {7, 8}, on the other hand, changes a reference in s's own outer array; m[1] is still {3, 4}.

923478msdnullnull

A deep copy starts with a new outer array d; it has no rows yet.

92347892msdnull

d[0] = m[0].clone(): the elements of row 0 are copied into a new array.

9234789234msd

d[1] = m[1].clone(): the elements of row 1 are copied into a new array.

9234789534msd

d[0][1] = 5 changes only the copy; m[0][1] is still 2. A deep copy takes O(rows × columns).

int[][] m = { {1, 2}, {3, 4} };
int[][] s = m.clone();          // shallow copy
s[0][0] = 9;                    // m[0][0] is 9 too!
s[1] = new int[] {7, 8};        // only s changes
int[][] d = new int[2][];       // deep copy:
for (int i = 0; i < 2; i++)
    d[i] = m[i].clone();        // rows too
d[0][1] = 5;                    // m unaffected

Multidimensional Arrays

Example: Fill and Print a Matrix

public class MultiArrayExample {
  public static void main(String[] a) {
    int[][] matrix = new int[3][3];
    // initial values: 1…9
    for (int i = 0; i < 3; i++)      // row
      for (int j = 0; j < 3; j++)    // column
        matrix[i][j] = i * 3 + j + 1;

    System.out.println("Matrix contents:");
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++)
        System.out.print(matrix[i][j] + " ");
      System.out.println();   // new line
    }
  }
}
Matrix contents:
1 2 3
4 5 6
7 8 9
Nested loopsThe outer loop walks the rows, the inner loop the columns. 3 × 3 = 9 assignments.i * 3 + j + 1

Writing matrix.length and matrix[i].length instead of the constant 3 makes the code work for any size.

Multidimensional Arrays · Step through the code

Code Trace: Filling the Matrix

int[][] matrix = new int[3][3];
for (int i = 0; i < 3; i++)      // row
  for (int j = 0; j < 3; j++)    // column
    matrix[i][j] = i * 3 + j + 1;
System.out.println("Matrix contents:");
for (int i = 0; i < 3; i++) {
  for (int j = 0; j < 3; j++)
    System.out.print(matrix[i][j] + " ");
  System.out.println();   // new line
}
j=0j=1j=2i=0000i=1000i=2000

An outer array of 3 rows plus 3 separate row arrays are created; every cell is 0.

j=0j=1j=2i=0100i=1000i=2000

i = 0, j = 0 → matrix[0][0] = 0·3 + 0 + 1 = 1. The outer loop picks the row, the inner loop the column.

j=0j=1j=2i=0120i=1000i=2000

i = 0, j = 1 → matrix[0][1] = 0·3 + 1 + 1 = 2.

j=0j=1j=2i=0123i=1000i=2000

i = 0, j = 2 → matrix[0][2] = 0·3 + 2 + 1 = 3.

j=0j=1j=2i=0123i=1400i=2000

i = 1, j = 0 → matrix[1][0] = 1·3 + 0 + 1 = 4. The inner loop finished and the outer loop moved to the next row; j is 0 again.

j=0j=1j=2i=0123i=1450i=2000

i = 1, j = 1 → matrix[1][1] = 1·3 + 1 + 1 = 5.

j=0j=1j=2i=0123i=1456i=2000

i = 1, j = 2 → matrix[1][2] = 1·3 + 2 + 1 = 6.

j=0j=1j=2i=0123i=1456i=2700

i = 2, j = 0 → matrix[2][0] = 2·3 + 0 + 1 = 7. The inner loop finished and the outer loop moved to the next row; j is 0 again.

j=0j=1j=2i=0123i=1456i=2780

i = 2, j = 1 → matrix[2][1] = 2·3 + 1 + 1 = 8.

j=0j=1j=2i=0123i=1456i=2789

i = 2, j = 2 → matrix[2][2] = 2·3 + 2 + 1 = 9.

j=0j=1j=2i=0123i=1456i=2789

i = 3 → the outer loop ends. The inner body ran 3 × 3 = 9 times.

Multidimensional Arrays · Step through the code

Code Trace: Printing the Matrix (continued)

int[][] matrix = new int[3][3];
for (int i = 0; i < 3; i++)      // row
  for (int j = 0; j < 3; j++)    // column
    matrix[i][j] = i * 3 + j + 1;
System.out.println("Matrix contents:");
for (int i = 0; i < 3; i++) {
  for (int j = 0; j < 3; j++)
    System.out.print(matrix[i][j] + " ");
  System.out.println();   // new line
}
j=0j=1j=2i=0123i=1456i=2789

The heading is printed; the matrix is now full.

j=0j=1j=2i=0123i=1456i=2789

Row 0: j starts at 0 → 1.

j=0j=1j=2i=0123i=1456i=2789

matrix[0][1] = 2 goes on the same line (print).

j=0j=1j=2i=0123i=1456i=2789

matrix[0][2] = 3 goes on the same line (print).

j=0j=1j=2i=0123i=1456i=2789

Row 0 done → println() moves to the next line.

j=0j=1j=2i=0123i=1456i=2789

Row 1: j starts at 0 → 4.

j=0j=1j=2i=0123i=1456i=2789

matrix[1][1] = 5 goes on the same line (print).

j=0j=1j=2i=0123i=1456i=2789

matrix[1][2] = 6 goes on the same line (print).

j=0j=1j=2i=0123i=1456i=2789

Row 1 done → println() moves to the next line.

j=0j=1j=2i=0123i=1456i=2789

Row 2: j starts at 0 → 7.

j=0j=1j=2i=0123i=1456i=2789

matrix[2][1] = 8 goes on the same line (print).

j=0j=1j=2i=0123i=1456i=2789

matrix[2][2] = 9 goes on the same line (print).

j=0j=1j=2i=0123i=1456i=2789

Row 2 done → println() moves to the next line. The whole matrix is printed.

Multidimensional Arrays

Three-Dimensional Arrays

  • Java allows arrays of three or more dimensions (e.g. time × row × column, or RGB layers).
  • Each dimension means one more nested loop: a 3 × 3 × 3 array has 27 elements.
int[][][] array3D =
        new int[3][3][3];
for (int x = 0; x < 3; x++)
  for (int y = 0; y < 3; y++)
    for (int z = 0; z < 3; z++)
      array3D[x][y][z] =
          x * 9 + y * 3 + z + 1;

// printing: the same three loops;
// println after the z loop (y),
// println after the y loop (x)
x = 0 1 2 3 4 5 6 7 8 9 x = 1 10 11 12 13 14 15 16 17 18 x = 2 19 20 21 22 23 24 25 26 27 each layer is a 3×3 matrix · row y, column z

Non-ASCII letters in a variable name (größe, say) are legal in Java, but ASCII names are preferred for portability.

Multidimensional Arrays

Matrix Transpose

Rows become columns and columns become rows: the transpose of an m×n matrix is n×m.

static int[][] transpose(int[][] m) {
    int rows = m.length;
    int cols = m[0].length;
    int[][] t = new int[cols][rows];
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            t[j][i] = m[i][j];
    return t;
}
static void printMatrix(int[][] m) {
    for (int[] row : m) {
        for (int x : row)
            System.out.print(x + " ");
        System.out.println();
    }
}
1 2 3 4 5 6 1 4 2 5 3 6 m: 2 × 3 t: 3 × 2 t[j][i] = m[i][j] m[0][1] = 2 → t[1][0]

Cost: each element is copied once → O(m·n).

The helpers must be static: main is static, and without creating an object it can call only static methods.

Multidimensional Arrays · Step through the code

Code Trace: transpose(m)

m: 2 × 3123456

m: 2 rows, 3 columns.

m: 2 × 3123456

m.length = the number of rows (the length of the outer array).

m: 2 × 3123456

m[0].length = the length of the first row = the number of columns.

m: 2 × 3123456t: 3 × 2000000

The dimensions swap: a new 3 × 2 matrix (all 0).

m: 2 × 3123456t: 3 × 2100000m[0][0] → t[0][0]

t[0][0] = m[0][0] = 1: row ↔ column.

m: 2 × 3123456t: 3 × 2102000m[0][1] → t[1][0]

t[1][0] = m[0][1] = 2: row ↔ column.

m: 2 × 3123456t: 3 × 2102030m[0][2] → t[2][0]

t[2][0] = m[0][2] = 3: row ↔ column. Row 0 of m becomes column 0 of t.

m: 2 × 3123456t: 3 × 2142030m[1][0] → t[0][1]

t[0][1] = m[1][0] = 4: row ↔ column.

m: 2 × 3123456t: 3 × 2142530m[1][1] → t[1][1]

t[1][1] = m[1][1] = 5: row ↔ column.

m: 2 × 3123456t: 3 × 2142536m[1][2] → t[2][1]

t[2][1] = m[1][2] = 6: row ↔ column.

m: 2 × 3123456t: 3 × 2142536

return t. 2 × 3 = 6 copies → O(m·n).

static int[][] transpose(int[][] m) {
    int rows = m.length;
    int cols = m[0].length;
    int[][] t = new int[cols][rows];
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            t[j][i] = m[i][j];
    return t;
}

Multidimensional Arrays

Application: Tic Tac Toe

  • A 3×3 game board: char[][] board.
  • Two players, X and O, take turns.
  • Three of your marks in a row, column, or diagonal wins.
  • If the board fills up first, the game is a draw.

Every move is a single array assignment: board[row][col] = player

Multidimensional Arrays

Tic Tac Toe: The Main Loop

public class TicTacToe {   // import java.util.Scanner;
    static Scanner scanner = new Scanner(System.in); // single Scanner

    public static void main(String[] args) {
        char[][] board = new char[3][3];   // 3x3 game board
        char player = 'X';                   // X moves first
        boolean gameOn = true;
        initBoard(board);                  // every cell ' '
        while (gameOn) {
            showBoard(board);
            makeMove(board, player);
            gameOn = isGameOn(board, player);
            player = (player == 'X') ? 'O' : 'X';   // switch turns
        }
        showBoard(board);
    }

The cells of new char[3][3] start as '\u0000'; to show empty cells as ' ', the board is filled first. All helper methods are static.

Multidimensional Arrays

Tic Tac Toe: Board and Moves

static void initBoard(char[][] t) {
  for (int i = 0; i < 3; i++)
    for (int j = 0; j < 3; j++)
      t[i][j] = ' ';
}
static void showBoard(char[][] t) {
  System.out.println("-------------");
  for (int i = 0; i < 3; i++) {
    System.out.print("| ");
    for (int j = 0; j < 3; j++)
      System.out.print(t[i][j]+" | ");
    System.out.println("\n-------------");
  }
}
static void makeMove(char[][] t,
                     char player) {
  int row, col;
  do {
    System.out.print("Player " + player
      + ": row and column (1-3): ");
    row = scanner.nextInt() - 1;
    col = scanner.nextInt() - 1;
  } while (row < 0 || row > 2
        || col < 0 || col > 2
        || t[row][col] != ' ');
  t[row][col] = player;
}
// the user enters 1-3, the array uses 0-2;
// invalid/occupied cell → ask again

Multidimensional Arrays

Tic Tac Toe: Win or Draw

The 8 winning lines (3 rows + 3 columns + 2 diagonals) are checked with one loop (rows and columns) and a single return (diagonals).

static boolean hasWon(char[][] t, char o) {
    for (int i = 0; i < 3; i++)
        if ((t[i][0] == o && t[i][1] == o && t[i][2] == o)     // row i
         || (t[0][i] == o && t[1][i] == o && t[2][i] == o))    // column i
            return true;
    return (t[0][0] == o && t[1][1] == o && t[2][2] == o)      // main diagonal
        || (t[0][2] == o && t[1][1] == o && t[2][0] == o);     // anti-diagonal
}
static boolean isGameOn(char[][] t, char o) {
    if (hasWon(t, o)) {
        System.out.println("Congratulations, player " + o + " wins!");
        return false;
    }
    for (char[] row : t)
        for (char c : row)
            if (c == ' ') return true;          // an empty cell → continue
    System.out.println("The game is a draw.");
    return false;
}

Multidimensional Arrays · Step through the code

Code Trace: hasWon(t, 'X')

static boolean hasWon(char[][] t, char o) {
  for (int i = 0; i < 3; i++)
    if ((t[i][0] == o && t[i][1] == o
          && t[i][2] == o)   // row i
     || (t[0][i] == o && t[1][i] == o
          && t[2][i] == o))  // column i
      return true;
  return (t[0][0] == o && t[1][1] == o
          && t[2][2] == o)   // main diagonal
      || (t[0][2] == o && t[1][1] == o
          && t[2][0] == o);  // anti-diagonal
}
0120XO1XO2X

X made the last move. Has X won? Eight lines to check.

0120XO1XO2X

i = 0, row 0: t[0][1] = 'O' ≠ 'X' → false. && short-circuits, so the last cell is never checked.

0120XO1XO2X

i = 0, column 0: t[1][0] = empty ≠ 'X' → false. && short-circuits, so the last cell is never checked.

0120XO1XO2X

i = 1, row 1: t[1][0] = empty ≠ 'X' → false. && short-circuits, so the other 2 cells are never checked.

0120XO1XO2X

i = 1, column 1: t[0][1] = 'O' ≠ 'X' → false. && short-circuits, so the other 2 cells are never checked.

0120XO1XO2X

i = 2, row 2: t[2][0] = empty ≠ 'X' → false. && short-circuits, so the other 2 cells are never checked.

0120XO1XO2X

i = 2, column 2: t[0][2] = empty ≠ 'X' → false. && short-circuits, so the other 2 cells are never checked.

0120XO1XO2X

i = 3 → the loop ends: no row or column is all X.

0120XO1XO2X

Main diagonal: t[0][0], t[1][1], and t[2][2] are all 'X' → || never looks at its right side; return true.

Complexity

Cost of Array Operations

Complexity describes how running time and memory use grow with the input size n.

OperationTimeWhy?
Access / update by index a[i]O(1)the address is computed directly
Search for a value (unsorted array)O(n)may have to look at every element
Search for a value (sorted array, binary search)O(log n)each step halves the range
Insert / delete at the end (if there is room)O(1)no shifting
Insert / delete in the middleO(n)the elements after it shift
Sorting (a good algorithm)O(n log n)e.g. merge sort
Copying / growingO(n)every element moves to a new array

Complexity

Accessing an Element: O(1)

  • The time doesn't depend on the array's size: it is the same for 5 elements and for 5 million.
  • That's because every element's address can be computed: address = start + i × element size.
10 0 20 1 30 2 40 3 50 4 1000 1004 1008 1012 1016 addressindex address(arr[2]) = 1000 + 2 × 4 = 1008
int[] arr = {10, 20, 30, 40, 50};
int value = arr[2];      // 30 — in one step, O(1)

Complexity · Step through the code

Searching an Array: Linear Search, O(n)

target = 5702192531485

Call 1: search for 5. The array is unsorted, so the only way is to scan it from start to end.

target = 5702192531485▲ x

7 ≠ 5 → next element.

target = 5702192531485▲ x

2 ≠ 5 → next element.

target = 5702192531485▲ x

9 ≠ 5 → next element.

target = 5702192531485▲ x

5 == 5 → equal.

target = 5702192531485▲ x

return true: found after 4 comparisons; the rest are never examined. If the target is first, one comparison suffices → best case O(1).

target = 3702192531485

Call 2: search for 3. The array is unsorted, so the only way is to scan it from start to end.

target = 3702192531485▲ x

7 ≠ 3 → next element.

target = 3702192531485▲ x

2 ≠ 3 → next element.

target = 3702192531485▲ x

9 ≠ 3 → next element.

target = 3702192531485▲ x

5 ≠ 3 → next element.

target = 3702192531485▲ x

1 ≠ 3 → next element.

target = 3702192531485▲ x

8 ≠ 3 → next element.

target = 3702192531485

End of array, return false: all n = 6 elements were examined → worst case O(n).

static boolean linearSearch(int[] arr,
                           int target) {
    for (int x : arr)
        if (x == target)
            return true; // found
    return false;
}

Complexity

Finding Common Elements of Two Arrays: O(n·m)

static void printMatches(int[] arr1, int[] arr2) {
    for (int x : arr1)          // n times
        for (int y : arr2)      // m times
            if (x == y)
                System.out.println(
                    "Match: " + x);
}
  • Every element of arr1 is compared with every element of arr2: n·m comparisons.
  • If both arrays have n elements, that is O(n²): too slow for large data.
  • Better: sort one and use binary search, O(n log n), or use a hash set, O(n) on average.
arr1 ↓ / arr2 → 5 1 3 8 3 · · ✓ · 7 · · · · 5 ✓ · · · 3 × 4 = 12 comparisons, 2 matches

Complexity · Step through the code

Code Trace: printMatches()

arr1arr23755138comparisons: 1

Outer loop: x = 3; the inner loop scans arr2 from the start. 3 ≠ 5.

arr1arr23755138comparisons: 2

3 ≠ 1.

arr1arr23755138comparisons: 3

3 == 3 → match.

arr1arr23755138comparisons: 3

The match is printed, but the inner loop still runs to the end.

arr1arr23755138comparisons: 4

3 ≠ 8.

arr1arr23755138comparisons: 5

Outer loop: x = 7; the inner loop scans arr2 from the start. 7 ≠ 5.

arr1arr23755138comparisons: 6

7 ≠ 1.

arr1arr23755138comparisons: 7

7 ≠ 3.

arr1arr23755138comparisons: 8

7 ≠ 8.

arr1arr23755138comparisons: 9

5 == 5 → match.

arr1arr23755138comparisons: 9

The match is printed, but the inner loop still runs to the end.

arr1arr23755138comparisons: 10

5 ≠ 1.

arr1arr23755138comparisons: 11

5 ≠ 3.

arr1arr23755138comparisons: 12

5 ≠ 8.

arr1arr23755138comparisons: 12

Done: 3 × 4 = 12 comparisons = n·m. If both arrays have n elements, O(n²).

static void printMatches(int[] arr1, int[] arr2) {
    for (int x : arr1)          // n times
        for (int y : arr2)      // m times
            if (x == y)
                System.out.println(
                    "Match: " + x);
}

Complexity · Step through the code

Inserting: Shift Right, O(n)

70219253145size = 5

Capacity 6, with 5 cells in use. To insert 4 at index 1, we first have to make room.

702192531415▲ ksize = 5

k = 5: a[5] = a[4] = 1 (copied one place right). We start from the right: starting from the left, 2 would overwrite 9.

702192535415▲ ksize = 5

k = 4: a[4] = a[3] = 5 (copied one place right).

702192935415▲ ksize = 5

k = 3: a[3] = a[2] = 9 (copied one place right).

702122935415▲ ksize = 5

k = 2: a[2] = a[1] = 2 (copied one place right).

702122935415▲ ksize = 5

k = 1, and 1 > 1 is false → the loop ends. That took 4 shifts; a[1] and a[2] are now equal (2).

704122935415size = 6

Write into the gap: a[1] = 4.

704122935415size = 6

return 6. Inserting at the front takes n shifts, in the middle ≈ n/2 → O(n); at the end (if there is room), O(1).

static int insert(int[] a, int size,
             int index, int value) {
  // precondition: size < a.length
  for (int k = size; k > index; k--)
    a[k] = a[k - 1];   // shift right
  a[index] = value;
  return size + 1;   // new size
}

If the array is full (size == a.length), a bigger array must be created and filled first: O(n).

Complexity · Step through the code

Deleting: Shift Left, O(n)

704122935415size = 6

After the previous insertion we delete a[1] = 4. To leave no gap, the elements to its right shift left.

702122935415▲ ksize = 6

k = 1: a[1] = a[2] = 2. This time we start from the left, overwriting the deleted value.

702192935415▲ ksize = 6

k = 2: a[2] = a[3] = 9.

702192535415▲ ksize = 6

k = 3: a[3] = a[4] = 5.

702192531415▲ ksize = 6

k = 4: a[4] = a[5] = 1.

702192531415▲ ksize = 6

k = 5 < 5 is false → the loop ends. 4 shifts.

702192531405size = 5

The last cell still held a copy of the old value; write 0 there (null in an object array, which matters for the GC).

702192531405size = 5

return 5. Deleting at the front takes n − 1 shifts → O(n); at the end, O(1).

static int remove(int[] a, int size, int index) {
  for (int k = index; k < size - 1; k++)
    a[k] = a[k + 1];   // shift left
  a[size - 1] = 0;    // clear the old last cell
  return size - 1;    // new size
}

Complexity

Sorting an Array: Time Complexity

The cost of sorting depends on the algorithm:

AlgorithmBestAverageWorstExtra space
Bubble sortO(n)*O(n²)O(n²)O(1)
Selection sortO(n²)O(n²)O(n²)O(1)
Insertion sortO(n)O(n²)O(n²)O(1)
Quick sortO(n log n)O(n log n)O(n²)O(log n)**
Merge sortO(n log n)O(n log n)O(n log n)O(n)

* For the version that stops after a pass with no swaps. Quick sort's worst case comes from bad pivots (e.g. sorted input with the first element as pivot). ** Quick sort's extra space is O(log n) on average and O(n) in the worst case (the recursion depth).

Arrays.sort uses dual-pivot quick sort for primitive arrays and stable TimSort for object arrays.

Complexity · Step through the code

Selection Sort

int[] arr = {64, 25, 12, 22, 11};
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
    int minIndex = i;
    for (int j = i + 1; j < n; j++)
        if (arr[j] < arr[minIndex])
            minIndex = j;
    // swap by index, in place
    int temp = arr[minIndex];
    arr[minIndex] = arr[i];
    arr[i] = temp;
}
640251122223114

An unsorted 5-element array. Each pass will move the smallest element of the unsorted part to the front of that part.

640251122223114▲ imin

Pass 1: the unsorted part starts at index 0; the smallest so far is arr[0] = 64.

640251122223114▲ i▲ minj

25 < 64 → minIndex = 1.

640251122223114▲ i▲ minj

12 < 25 → minIndex = 2.

640251122223114▲ i▲ min▲ j

22 < 12 is false → no change.

640251122223114▲ i▲ minj

11 < 12 → minIndex = 4.

110251122223644▲ i

Swap: arr[0] ↔ arr[4] (64 ↔ 11). Index 0 is now final.

110251122223644▲ imin

Pass 2: the unsorted part starts at index 1; the smallest so far is arr[1] = 25.

110251122223644▲ i▲ minj

12 < 25 → minIndex = 2.

110251122223644▲ i▲ min▲ j

22 < 12 is false → no change.

110251122223644▲ i▲ min▲ j

64 < 12 is false → no change.

110121252223644▲ i

Swap: arr[1] ↔ arr[2] (25 ↔ 12). Index 1 is now final.

110121252223644▲ imin

Pass 3: the unsorted part starts at index 2; the smallest so far is arr[2] = 25.

110121252223644▲ i▲ minj

22 < 25 → minIndex = 3.

110121252223644▲ i▲ min▲ j

64 < 22 is false → no change.

110121222253644▲ i

Swap: arr[2] ↔ arr[3] (25 ↔ 22). Index 2 is now final.

110121222253644▲ imin

Pass 4: the unsorted part starts at index 3; the smallest so far is arr[3] = 25.

110121222253644▲ imin▲ j

64 < 25 is false → no change.

110121222253644▲ i

The smallest is already in place (minIndex = i = 3), so it swaps with itself and nothing changes. The last element is then in place too → sorted: 4 + 3 + 2 + 1 = 10 = n(n−1)/2 comparisons, O(n²).

Java passes arguments by value: swap(arr[minIndex], arr[i]) swaps only copies. Swap by index instead (or with swap(arr, i, minIndex)).

Complexity · Step through the code

Insertion Sort

5021421334

At first the sorted part is just a[0] (a single element is always sorted). Each pass puts the next element into its place in the sorted part by shifting.

501421334▲ k, j2key

Pass k = 1: set aside key = a[1] = 2; its cell now counts as a gap.

051421334▲ k▲ j2key

a[0] = 5 > 2 → 5 shifts one place right; the gap moves to index 0.

2051421334▲ j

j == 0: we have reached the front of the sorted part; a[0] = 2. The first 2 elements are now sorted.

205121334▲ k, j4key

Pass k = 2: set aside key = a[2] = 4; its cell now counts as a gap.

201521334▲ k▲ j4key

a[1] = 5 > 4 → 5 shifts one place right; the gap moves to index 1.

2041521334▲ j

a[0] = 2 ≤ 4: stop; a[1] = 4. The first 3 elements are now sorted.

204152334▲ k, j1key

Pass k = 3: set aside key = a[3] = 1; its cell now counts as a gap.

204125334▲ k▲ j1key

a[2] = 5 > 1 → 5 shifts one place right; the gap moves to index 2.

201425334▲ k▲ j1key

a[1] = 4 > 1 → 4 shifts one place right; the gap moves to index 1.

021425334▲ k▲ j1key

a[0] = 2 > 1 → 2 shifts one place right; the gap moves to index 0.

1021425334▲ j

j == 0: we have reached the front of the sorted part; a[0] = 1. The first 4 elements are now sorted.

102142534▲ k, j3key

Pass k = 4: set aside key = a[4] = 3; its cell now counts as a gap.

102142354▲ k▲ j3key

a[3] = 5 > 3 → 5 shifts one place right; the gap moves to index 3.

102124354▲ k▲ j3key

a[2] = 4 > 3 → 4 shifts one place right; the gap moves to index 2.

1021324354▲ j

a[1] = 2 ≤ 3: stop; a[2] = 3. The array is sorted. 7 shifts = the number of inversions in the array. Worst case O(n²); O(n) on an already sorted array.

static void insertionSort(int[] a) {
    for (int k = 1; k < a.length; k++) {
        int key = a[k];
        int j = k;
        while (j > 0 && a[j - 1] > key) {
            a[j] = a[j - 1];   // shift right
            j--;
        }
        a[j] = key;        // insert
    }
}

Complexity

Space Complexity and Memory Use

  • Space complexity describes how the memory an algorithm or data structure needs grows with n.
  • An array of n elements takes O(n) space; the number of bytes depends on the element type.
Array typePer element100 elements (data only)
int[]4 bytes400 bytes
char[] (UTF-16)2 bytes200 bytes
double[], long[]8 bytes800 bytes
Object[] (references)4 / 8 bytes400 / 800 bytes + the objects
OverheadEvery array object also carries a header of about 16 bytes (type information + length).
Arrays of referencesOnly the references are contiguous; the objects they point to take separate space on the heap.

Summary

Common Mistakes with Arrays

Off-by-one errorWith <=, the last iteration accesses a[a.length] → exception.for (i = 0; i < a.length; i++)
length / length()A field on arrays, a method on String.a.length · s.length()
= does not copyb = a points to the same array; for a copy use clone / copyOf.int[] b = a.clone();
Comparing contents with ==== compares references.Arrays.equals(a, b)
Printing an array directlyprintln(a) prints type@address ([I@…).Arrays.toString(a)
Null rows in a jagged arrayAfter new int[3][], m[0][0] → NullPointerException.m[i] = new int[k];
Also: binarySearch gives wrong answers on an unsorted array, and in a 2D array use m[i].length for each row's column count.

Lab · Java code, step by step

Lab: Sorting

Summary

Summary

StructureOne type, fixed size, contiguous memory; indices 0 … length − 1.
In JavaAn array is an object on the heap; = copies the reference, Arrays.copyOf the elements.
MultidimensionalAn array of arrays; rows can differ in length. m[i][j].
OperationCostOperationCost
Access a[i]O(1)Insert / delete in the middleO(n)
Linear searchO(n)ReversingO(n)
Selection sortO(n²)Transpose (m×n)O(m·n)
Interactive simulator: to try access, insertion, deletion, and search step by step, open the Array Simulator.

Summary

Sample Problems

  1. After int[] a = new int[4];, what happens on a[4]? What is a.length?
  2. After int[] b = a; b[0] = 9;, what is a[0]? Why?
  3. Write a method that finds the largest element of an n-element array. What is its complexity?
  4. What does {5, 1, 4, 2, 8} look like after the first two passes of selection sort?
  5. How many bytes does the data of a 1000-element double[] take?
  6. How many rows and columns does the transpose of a 3×4 matrix have? Which element is t[2][1]?
Show answers
  1. ArrayIndexOutOfBoundsException (the valid indices are 0–3); a.length = 4.
  2. 9: b and a point to the same array object.
  3. int max = a[0]; for (int x : a) if (x > max) max = x; → O(n) (check for an empty array separately).
  4. Pass 1: {1, 5, 4, 2, 8}; pass 2: {1, 2, 4, 5, 8}.
  5. 1000 × 8 = 8,000 bytes (+ the array header).
  6. 4 rows, 3 columns; t[2][1] = m[1][2].

Summary

Exercises

  1. After int[] a = {3, 1, 2}; int[] b = a; b = new int[] {9};, what are a[0] and b.length?
  2. Run insertion sort on {7, 3, 5, 1}. What is the array after each outer pass? How many shifts are made?
  3. After int[][] m = new int[3][]; m[1] = new int[4];, what are m.length, m[1].length, and m[0]?
  4. What is the worst-case cost of inserting into a sorted array that has room, keeping it sorted?
  5. What does encrypt("XYZ", 2) return? What k decrypts it?
  6. Write a method that rotates an array one step left in place (a[0] moves to the end). What does it cost?
Answers / hints
  1. 3 and 1: assigning a new array to b doesn't affect a.
  2. {3, 7, 5, 1} → {3, 5, 7, 1} → {1, 3, 5, 7}; 5 shifts (the number of inversions).
  3. 3, 4, null (m[0][0] → NullPointerException).
  4. O(n): even if the position is found in O(log n), the elements to its right must shift.
  5. "ZAB" (Y: 26 % 26 = 0 → A); k = 24.
  6. t = a[0]; shift the elements one place left, then a[n-1] = t; → O(n) time, O(1) extra space.

Summary

Further Reading

  • Goodrich, Tamassia, and Goldwasser, Data Structures and Algorithms in Java, 6th ed., 2014.
  • Lafore, Data Structures and Algorithms in Java, 2nd ed., 2002.
  • Oracle, Java SE 17 API documentation for Arrays and System.

The End

Chapter 3: Arrays
Next deck: Chapter 4: Recursion · Study questions · Simulator: Array Simulator · Glossary
1 / 1 Sercan KÜLCÜ. All rights reserved.