Chapter 3: Arrays
Introduction
Learning Outcomes
Explain how arrays are laid out in memory and how indexing works.
Declare and use one- and multidimensional arrays correctly.
Search, insert, delete, and sort on arrays.
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.
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.
int[] arr = {7, 2, 9, 5, 1}; int first = arr[0]; // 7 int last = arr[arr.length - 1]; // 1
Basic Concepts
Kinds of Arrays
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.Arraysprovides them.
Basic Concepts
Array Covariance and ArrayStoreException
- Because
Stringis a subtype ofObject,String[]counts as a subtype ofObject[]. - 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!
Arrays in Java
Declaring Arrays
DataType[] arrayName = new DataType[arraySize];
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.
| Type | Default value |
|---|---|
int, long… | 0 |
double | 0.0 |
boolean | false |
char | '\u0000' |
object (String, …) | null |
Arrays in Java
Access, Initialization, and length
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
The initializer list has 5 values → a 5-element int[] is created on the heap; nums holds only its reference.
Read: nums[0] is the first cell. Address = start + 0 × 4 → one step, O(1).
Index 1 is the second element (indices start at 0).
Write: same syntax, on the left of the assignment. The last cell (index 4 = length − 1) goes from 50 to 55.
length is a field (no parentheses) → 5. The size was fixed when the array was created.
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
new int[5]: 5 cells are allocated, all starting at 0, the default for int.
i = 0 < 5 → nums[0] = (0 + 1) × 10 = 10.
i = 1 < 5 → nums[1] = (1 + 1) × 10 = 20.
i = 2 < 5 → nums[2] = (2 + 1) × 10 = 30.
i = 3 < 5 → nums[3] = (3 + 1) × 10 = 40.
i = 4 < 5 → nums[4] = (4 + 1) × 10 = 50.
At i = 5, 5 < 5 is false → the loop ends. With nums.length as the bound, the code stays correct if the size changes.
Index 0 is read → 10; println adds a newline.
The third element is at index 2 → 30.
print adds no newline, so the elements will go on the same line.
for-each: x = 10 (index 0). When you don't need an index variable, this reads better.
x = 20 is printed.
x = 30 is printed.
x = 40 is printed.
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?
An array is an object, created on the heap (#1). The variable arr on the stack holds only its address.
= copies only the address: both variables point to the same array (#1). No new array is created.
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.)
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
| Method | What it does | Cost |
|---|---|---|
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 cell | O(n) |
Arrays.copyOf(a, n) | New array; if n > a.length, the extra cells get the default value | O(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 absent | O(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
Arrays in Java
Varargs (Variable-Length Arguments)
- The
type... namesyntax 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; }
| Call | arr.length | Result |
|---|---|---|
average(4, 8, 15) | 3 | 9.0 |
average(7.5) | 1 | 7.5 |
average() | 0 | NaN |
average(new double[] {1, 2}) | 2 | 1.5 |
Arrays in Java · Step through the code
Code Trace: average(4, 8, 15)
For the call average(4, 8, 15) the compiler packs the arguments into a double[] (each int is widened to double).
arr.length = 3 ≠ 0 → not the empty case, so carry on.
var: the type is inferred from the value on the right → double.
for-each: value = 4.0 (element 1 of the array).
sum = 0.0 + 4.0 = 4.0.
for-each: value = 8.0 (element 2 of the array).
sum = 4.0 + 8.0 = 12.0.
for-each: value = 15.0 (element 3 of the array).
sum = 12.0 + 15.0 = 27.0.
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 | Heap | |
|---|---|---|
| Holds | local variables, references, call frames | objects, arrays |
| Lifetime | until the method returns | as long as it is reachable |
| Managed by | automatic (call / return) | automatic (GC) |
Arrays in Java
"Static" and "Dynamic" Arrays: C vs. Java
| C / C++ | Java | |
|---|---|---|
| Array on the stack | int a[10]; — size fixed at compile time; gone when the function returns | None: every array is on the heap |
| Array on the heap | malloc / new int[n] | new int[n] (initializer lists too) |
| Size | on the heap, set at run time | set at run time, then fixed |
| Freeing | the 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 ofIndexOutOfBoundsException). - If it isn't caught, the program stops. Java checks every access; C doesn't, and silently writes to some other memory.
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; }
Array Algorithms · Step through the code
Code Trace: readArray(sc)
First we ask for the size. print adds no newline, so the cursor waits on the same line.
The user types 4 and nextInt() reads it. (What the user types also appears on the console.)
The size became known at run time → new double[4], every cell 0.0.
i = 0, but the prompt says element 1 (people count from 1).
The user types 4 → arr[0] = 4.0.
i = 1, but the prompt says element 2 (people count from 1).
The user types 8 → arr[1] = 8.0.
i = 2, but the prompt says element 3 (people count from 1).
The user types 15 → arr[2] = 15.0.
i = 3, but the prompt says element 4 (people count from 1).
The user types 16 → arr[3] = 16.0. At i = 4 the loop ends.
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(); }
sum = 66
average = 66 / 5 = 13.2
NaN; better to warn the user.Array Algorithms · Step through the code
Code Trace: Computing the Mean
Suppose this time the user enters 5 values: readArray returns {4, 8, 15, 16, 23}.
The accumulator starts at 0 (int 0 → double 0.0).
sum += arr[0] → 0.0 + 4.0 = 4.0.
sum += arr[1] → 4.0 + 8.0 = 12.0.
sum += arr[2] → 12.0 + 15.0 = 27.0.
sum += arr[3] → 27.0 + 16.0 = 43.0.
sum += arr[4] → 43.0 + 23.0 = 66.0.
i = 5, and 5 < 5 is false → the loop ends. Each element was added once → O(n).
average = 66.0 / 5 = 13.2. With size 0 this would be 0.0 / 0 = NaN.
Arrays.toString turns the array into text of the form [a, b, …]. (println(arr) would print only type@address.)
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).
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; }
Call 1 (even size): median is called with a 4-element array.
Make a copy first, so the caller's array keeps its order.
The copy is sorted; the original arr is unchanged.
size = 4.
4 % 2 == 0 → even: there is no single middle element, but two.
The two middle indices: m1 = 1, m2 = 2.
median = (4.0 + 6.0) / 2 = 5.0.
return 5.0.
Call 2 (odd size): median is called with a 5-element array.
Make a copy first, so the caller's array keeps its order.
The copy is sorted; the original arr is unchanged.
size = 5.
5 % 2 = 1 → odd: take the else branch.
The exact middle: index 5 / 2 = 2 → 7.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).
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; }
Input: {4, 1, 4, 2, 1, 4}. Equal values are scattered.
Once the copy is sorted, equal values sit side by side, and one pass is enough.
Start: the first value (1) is the most frequent so far, seen once.
s[1] = 1 == s[0] → the run continues, count = 2.
2 > the old record → new record: best = 1, 2 times.
s[2] = 2 ≠ 1 → new value; the count resets to 1.
1 > 2 is false → the record stands.
s[3] = 4 ≠ 2 → new value; the count resets to 1.
1 > 2 is false → the record stands.
s[4] = 4 == s[3] → the run continues, count = 2.
2 > 2 is false → the record stands. (On a tie the first value wins.)
s[5] = 4 == s[4] → the run continues, count = 3.
3 > the old record → new record: best = 4, 3 times.
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--; } }
The left pointer starts at the first element.
The right pointer starts at the last element (length − 1 = 5).
0 < 5 → there is another pair to swap.
temp = arr[0] = 3: save the left value so it isn't lost.
arr[0] = arr[5] → 6. (The old 3 now survives only in temp.)
arr[5] = temp → 3. The pair is swapped.
The pointers move inward: start = 1, end = 4.
1 < 4 → there is another pair to swap.
temp = arr[1] = 8: save the left value so it isn't lost.
arr[1] = arr[4] → 4. (The old 8 now survives only in temp.)
arr[4] = temp → 8. The pair is swapped.
The pointers move inward: start = 2, end = 3.
2 < 3 → there is another pair to swap.
temp = arr[2] = 1: save the left value so it isn't lost.
arr[2] = arr[3] → 9. (The old 1 now survives only in temp.)
arr[3] = temp → 1. The pair is swapped.
The pointers move inward: start = 3, end = 2.
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)
toCharArray() copies the string's letters into a new char[]. A String is immutable; the array can be changed in place.
i = 0: 'Z' − 'A' gives the letter's position in the alphabet: 25 (char values are integers).
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).
i = 1: 'E' − 'A' gives the letter's position in the alphabet: 4 (char values are integers).
chars[1] = 'H': shift the position by 3 and turn it back into a letter.
i = 2: 'K' − 'A' gives the letter's position in the alphabet: 10 (char values are integers).
chars[2] = 'N': shift the position by 3 and turn it back into a letter.
i = 3: 'A' − 'A' gives the letter's position in the alphabet: 0 (char values are integers).
chars[3] = 'D': shift the position by 3 and turn it back into a letter.
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
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)
- 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
new int[n][] creates only the outer array of 4 references; the row arrays don't exist yet (null).
p[0] = new int[1]: row 0 is a separate array of length 1; its cells start at 0.
p[0][0] = 1: in a one-cell row, the first and last cells are the same.
p[1] = new int[2]: row 1 is a separate array of length 2; rows of different lengths make a jagged array.
p[1][0] = p[1][1] = 1: both ends of the row are 1 (a chained assignment runs right to left).
p[2] = new int[3]: a new row of 3 cells.
p[2][0] = p[2][2] = 1: both ends of the row are 1 (a chained assignment runs right to left).
p[2][1] = p[1][0] + p[1][1] = 1 + 1 = 2: the sum of the two neighbors above.
p[3] = new int[4]: a new row of 4 cells.
p[3][0] = p[3][3] = 1: both ends of the row are 1 (a chained assignment runs right to left).
p[3][1] = p[2][0] + p[2][1] = 1 + 2 = 3: the sum of the two neighbors above.
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
A 2×2 matrix: the outer array m points to two row arrays (an array of arrays).
m.clone() copies only the outer array: s is a new outer array, but its references point to the same rows → a shallow copy.
s[0][0] = 9 changes the shared row, so m[0][0] becomes 9 too. A common mistake!
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}.
A deep copy starts with a new outer array d; it has no rows yet.
d[0] = m[0].clone(): the elements of row 0 are copied into a new array.
d[1] = m[1].clone(): the elements of row 1 are copied into a new array.
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
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 }
An outer array of 3 rows plus 3 separate row arrays are created; every cell is 0.
i = 0, j = 0 → matrix[0][0] = 0·3 + 0 + 1 = 1. The outer loop picks the row, the inner loop the column.
i = 0, j = 1 → matrix[0][1] = 0·3 + 1 + 1 = 2.
i = 0, j = 2 → matrix[0][2] = 0·3 + 2 + 1 = 3.
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.
i = 1, j = 1 → matrix[1][1] = 1·3 + 1 + 1 = 5.
i = 1, j = 2 → matrix[1][2] = 1·3 + 2 + 1 = 6.
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.
i = 2, j = 1 → matrix[2][1] = 2·3 + 1 + 1 = 8.
i = 2, j = 2 → matrix[2][2] = 2·3 + 2 + 1 = 9.
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 }
The heading is printed; the matrix is now full.
Row 0: j starts at 0 → 1.
matrix[0][1] = 2 goes on the same line (print).
matrix[0][2] = 3 goes on the same line (print).
Row 0 done → println() moves to the next line.
Row 1: j starts at 0 → 4.
matrix[1][1] = 5 goes on the same line (print).
matrix[1][2] = 6 goes on the same line (print).
Row 1 done → println() moves to the next line.
Row 2: j starts at 0 → 7.
matrix[2][1] = 8 goes on the same line (print).
matrix[2][2] = 9 goes on the same line (print).
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)
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(); } }
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 rows, 3 columns.
m.length = the number of rows (the length of the outer array).
m[0].length = the length of the first row = the number of columns.
The dimensions swap: a new 3 × 2 matrix (all 0).
t[0][0] = m[0][0] = 1: row ↔ column.
t[1][0] = m[0][1] = 2: row ↔ column.
t[2][0] = m[0][2] = 3: row ↔ column. Row 0 of m becomes column 0 of t.
t[0][1] = m[1][0] = 4: row ↔ column.
t[1][1] = m[1][1] = 5: row ↔ column.
t[2][1] = m[1][2] = 6: row ↔ column.
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 }
X made the last move. Has X won? Eight lines to check.
i = 0, row 0: t[0][1] = 'O' ≠ 'X' → false. && short-circuits, so the last cell is never checked.
i = 0, column 0: t[1][0] = empty ≠ 'X' → false. && short-circuits, so the last cell is never checked.
i = 1, row 1: t[1][0] = empty ≠ 'X' → false. && short-circuits, so the other 2 cells are never checked.
i = 1, column 1: t[0][1] = 'O' ≠ 'X' → false. && short-circuits, so the other 2 cells are never checked.
i = 2, row 2: t[2][0] = empty ≠ 'X' → false. && short-circuits, so the other 2 cells are never checked.
i = 2, column 2: t[0][2] = empty ≠ 'X' → false. && short-circuits, so the other 2 cells are never checked.
i = 3 → the loop ends: no row or column is all X.
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.
| Operation | Time | Why? |
|---|---|---|
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 middle | O(n) | the elements after it shift |
| Sorting (a good algorithm) | O(n log n) | e.g. merge sort |
| Copying / growing | O(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.
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)
Call 1: search for 5. The array is unsorted, so the only way is to scan it from start to end.
7 ≠ 5 → next element.
2 ≠ 5 → next element.
9 ≠ 5 → next element.
5 == 5 → equal.
return true: found after 4 comparisons; the rest are never examined. If the target is first, one comparison suffices → best case O(1).
Call 2: search for 3. The array is unsorted, so the only way is to scan it from start to end.
7 ≠ 3 → next element.
2 ≠ 3 → next element.
9 ≠ 3 → next element.
5 ≠ 3 → next element.
1 ≠ 3 → next element.
8 ≠ 3 → next element.
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
arr1is compared with every element ofarr2: 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.
Complexity · Step through the code
Code Trace: printMatches()
Outer loop: x = 3; the inner loop scans arr2 from the start. 3 ≠ 5.
3 ≠ 1.
3 == 3 → match.
The match is printed, but the inner loop still runs to the end.
3 ≠ 8.
Outer loop: x = 7; the inner loop scans arr2 from the start. 7 ≠ 5.
7 ≠ 1.
7 ≠ 3.
7 ≠ 8.
5 == 5 → match.
The match is printed, but the inner loop still runs to the end.
5 ≠ 1.
5 ≠ 3.
5 ≠ 8.
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)
Capacity 6, with 5 cells in use. To insert 4 at index 1, we first have to make room.
k = 5: a[5] = a[4] = 1 (copied one place right). We start from the right: starting from the left, 2 would overwrite 9.
k = 4: a[4] = a[3] = 5 (copied one place right).
k = 3: a[3] = a[2] = 9 (copied one place right).
k = 2: a[2] = a[1] = 2 (copied one place right).
k = 1, and 1 > 1 is false → the loop ends. That took 4 shifts; a[1] and a[2] are now equal (2).
Write into the gap: a[1] = 4.
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)
After the previous insertion we delete a[1] = 4. To leave no gap, the elements to its right shift left.
k = 1: a[1] = a[2] = 2. This time we start from the left, overwriting the deleted value.
k = 2: a[2] = a[3] = 9.
k = 3: a[3] = a[4] = 5.
k = 4: a[4] = a[5] = 1.
k = 5 < 5 is false → the loop ends. 4 shifts.
The last cell still held a copy of the old value; write 0 there (null in an object array, which matters for the GC).
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:
| Algorithm | Best | Average | Worst | Extra space |
|---|---|---|---|---|
| Bubble sort | O(n)* | O(n²) | O(n²) | O(1) |
| Selection sort | O(n²) | O(n²) | O(n²) | O(1) |
| Insertion sort | O(n) | O(n²) | O(n²) | O(1) |
| Quick sort | O(n log n) | O(n log n) | O(n²) | O(log n)** |
| Merge sort | O(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; }
An unsorted 5-element array. Each pass will move the smallest element of the unsorted part to the front of that part.
Pass 1: the unsorted part starts at index 0; the smallest so far is arr[0] = 64.
25 < 64 → minIndex = 1.
12 < 25 → minIndex = 2.
22 < 12 is false → no change.
11 < 12 → minIndex = 4.
Swap: arr[0] ↔ arr[4] (64 ↔ 11). Index 0 is now final.
Pass 2: the unsorted part starts at index 1; the smallest so far is arr[1] = 25.
12 < 25 → minIndex = 2.
22 < 12 is false → no change.
64 < 12 is false → no change.
Swap: arr[1] ↔ arr[2] (25 ↔ 12). Index 1 is now final.
Pass 3: the unsorted part starts at index 2; the smallest so far is arr[2] = 25.
22 < 25 → minIndex = 3.
64 < 22 is false → no change.
Swap: arr[2] ↔ arr[3] (25 ↔ 22). Index 2 is now final.
Pass 4: the unsorted part starts at index 3; the smallest so far is arr[3] = 25.
64 < 25 is false → no change.
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
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.
Pass k = 1: set aside key = a[1] = 2; its cell now counts as a gap.
a[0] = 5 > 2 → 5 shifts one place right; the gap moves to index 0.
j == 0: we have reached the front of the sorted part; a[0] = 2. The first 2 elements are now sorted.
Pass k = 2: set aside key = a[2] = 4; its cell now counts as a gap.
a[1] = 5 > 4 → 5 shifts one place right; the gap moves to index 1.
a[0] = 2 ≤ 4: stop; a[1] = 4. The first 3 elements are now sorted.
Pass k = 3: set aside key = a[3] = 1; its cell now counts as a gap.
a[2] = 5 > 1 → 5 shifts one place right; the gap moves to index 2.
a[1] = 4 > 1 → 4 shifts one place right; the gap moves to index 1.
a[0] = 2 > 1 → 2 shifts one place right; the gap moves to index 0.
j == 0: we have reached the front of the sorted part; a[0] = 1. The first 4 elements are now sorted.
Pass k = 4: set aside key = a[4] = 3; its cell now counts as a gap.
a[3] = 5 > 3 → 5 shifts one place right; the gap moves to index 3.
a[2] = 4 > 3 → 4 shifts one place right; the gap moves to index 2.
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 type | Per element | 100 elements (data only) |
|---|---|---|
int[] | 4 bytes | 400 bytes |
char[] (UTF-16) | 2 bytes | 200 bytes |
double[], long[] | 8 bytes | 800 bytes |
Object[] (references) | 4 / 8 bytes | 400 / 800 bytes + the objects |
length).Summary
Common Mistakes with Arrays
<=, 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();==== compares references.Arrays.equals(a, b)println(a) prints type@address ([I@…).Arrays.toString(a)new int[3][], m[0][0] → NullPointerException.m[i] = new int[k];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
= copies the reference, Arrays.copyOf the elements.m[i][j].| Operation | Cost | Operation | Cost |
|---|---|---|---|
Access a[i] | O(1) | Insert / delete in the middle | O(n) |
| Linear search | O(n) | Reversing | O(n) |
| Selection sort | O(n²) | Transpose (m×n) | O(m·n) |
Summary
Sample Problems
- After
int[] a = new int[4];, what happens ona[4]? What isa.length? - After
int[] b = a; b[0] = 9;, what isa[0]? Why? - Write a method that finds the largest element of an n-element array. What is its complexity?
- What does {5, 1, 4, 2, 8} look like after the first two passes of selection sort?
- How many bytes does the data of a 1000-element
double[]take? - How many rows and columns does the transpose of a 3×4 matrix have? Which element is
t[2][1]?
Show answers
ArrayIndexOutOfBoundsException(the valid indices are 0–3);a.length= 4.- 9:
bandapoint to the same array object. int max = a[0]; for (int x : a) if (x > max) max = x;→ O(n) (check for an empty array separately).- Pass 1: {1, 5, 4, 2, 8}; pass 2: {1, 2, 4, 5, 8}.
- 1000 × 8 = 8,000 bytes (+ the array header).
- 4 rows, 3 columns;
t[2][1] = m[1][2].
Summary
Exercises
- After
int[] a = {3, 1, 2}; int[] b = a; b = new int[] {9};, what area[0]andb.length? - Run insertion sort on {7, 3, 5, 1}. What is the array after each outer pass? How many shifts are made?
- After
int[][] m = new int[3][]; m[1] = new int[4];, what arem.length,m[1].length, andm[0]? - What is the worst-case cost of inserting into a sorted array that has room, keeping it sorted?
- What does
encrypt("XYZ", 2)return? Whatkdecrypts it? - Write a method that rotates an array one step left in place (
a[0]moves to the end). What does it cost?
Answers / hints
- 3 and 1: assigning a new array to
bdoesn't affecta. - {3, 7, 5, 1} → {3, 5, 7, 1} → {1, 3, 5, 7}; 5 shifts (the number of inversions).
- 3, 4,
null(m[0][0]→NullPointerException). - O(n): even if the position is found in O(log n), the elements to its right must shift.
"ZAB"(Y: 26 % 26 = 0 → A);k = 24.t = a[0];shift the elements one place left, thena[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
ArraysandSystem.