Linear Search
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an array of integers nums and a value target. Return the index of the first occurrence of target in nums, or -1 if it does not occur. The array is not sorted, so there is no structure to exploit: every element is a candidate until it has been looked at.
Constraints: 0 ≤ n ≤ 10^5, values and target fit in a 32-bit signed integer.
Example 1
nums = [6, 7, 8, 4, 1], target = 4
answer = 3 // nums[3] == 4
Example 2
nums = [6, 7, 8, 4, 1], target = 9
answer = -1 // 9 is not in the array
Example 3
nums = [2, 3, 2, 3], target = 3
answer = 1 // the first 3 is at index 1; index 3 also holds a 3 but is not the first
Linear search is the baseline every other search is measured against. It also hides a few genuinely useful ideas: a clean loop-with-early-return, the recursive formulation that later becomes backtracking, and the sentinel trick that removes a comparison from the inner loop.
Approach 1: Iterative scan
Intuition. Look at each element in turn. The moment one equals target, its index is the answer, and because we scan from the left it is automatically the first occurrence. If the loop runs off the end, the value is absent.
Algorithm.
- For
ifrom0ton - 1: ifnums[i] == target, returni. - Return
-1.
static int iterative(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) return i;
}
return -1;
}
Complexity. Time O(n) in the worst case (target absent or last); O(1) in the best case. Space O(1).
Why can this not be improved for unsorted input? Any algorithm that skips an element could be fooled by placing the target exactly there, so in the worst case every element must be examined. n comparisons is a lower bound as well as an upper bound. The only way to search faster is to know something about the data, such as sortedness, which is what Search X in sorted array exploits with binary search.
Approach 2: Recursive scan
Intuition. “Search nums[i..n-1] for target” can be defined in terms of a smaller problem: if i is past the end, the answer is -1; if nums[i] is the target, the answer is i; otherwise the answer is whatever searching nums[i+1..n-1] gives. Each call handles one element and delegates the rest.
Algorithm.
- Entry point: call the helper with
i = 0. - Helper: if
i == n, return-1. Ifnums[i] == target, returni. Otherwise return the helper’s result fori + 1.
static int recursive(int[] nums, int target) {
return recursive(nums, target, 0);
}
static int recursive(int[] nums, int target, int i) {
if (i == nums.length) return -1; // ran off the end: not found
if (nums[i] == target) return i;
return recursive(nums, target, i + 1);
}
Complexity. Time O(n). Space O(n) for the call stack, one frame per element examined. Java does not perform tail-call elimination, so with n = 10^5 this will throw StackOverflowError on a default stack. The recursion is here to show the shape, not to use in production.
The shape matters: “check this position, otherwise recurse on the rest” is the skeleton of every recursive array traversal, and adding a second recursive call (take this element or skip it) turns it into subset enumeration.
Approach 3: Sentinel search
Intuition. The loop in Approach 1 does two comparisons per element: i < nums.length and nums[i] == target. The bounds check exists only because the target might be absent. Make it present: overwrite the last element with target. Now the scan is guaranteed to stop, so the loop can be while (nums[i] != target) i++ with no bounds check at all. Afterwards restore the last element and decide what the stopping index means: if the scan stopped before the last slot it found a real match; if it stopped at the last slot, that is a real match only if the original last element was the target.
Algorithm.
- If
n == 0, return-1. - Save
last = nums[n - 1]and setnums[n - 1] = target. - Scan
ifrom0whilenums[i] != target. - Restore
nums[n - 1] = last. - If
i < n - 1, orlast == target, returni; otherwise return-1.
static int sentinel(int[] nums, int target) {
int n = nums.length;
if (n == 0) return -1;
int last = nums[n - 1];
nums[n - 1] = target; // the scan is now guaranteed to stop
int i = 0;
while (nums[i] != target) i++;
nums[n - 1] = last; // undo the temporary write
if (i < n - 1 || last == target) return i; // a real match, either before the sentinel or at it
return -1;
}
Dry run on nums = [6, 7, 8, 4, 1], target = 9: last = 1, the array temporarily becomes [6, 7, 8, 4, 9]. The scan passes 6, 7, 8, 4 and stops at i = 4. Restore nums[4] = 1. Since i == n - 1 and last != target, return -1. With target = 1 instead: the array is unchanged by the write, the scan stops at i = 4, and last == target, so return 4.
Complexity. Time O(n), with roughly half the comparisons of the plain loop. Space O(1).
Modern JIT compilers often make this optimisation irrelevant, and the method temporarily mutates its input, which is a real downside if another thread is reading the array. Treat it as a classic worth recognising rather than a default to reach for. The tests confirm that the array is bit-for-bit identical after the call.
Variant: all occurrences
When the question is “every index where target appears”, the early return disappears and the matches are collected:
static List<Integer> allOccurrences(int[] nums, int target) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) result.add(i);
}
return result;
}
Time O(n), space O(k) for k matches. The first element of this list is what the three methods above return, and an empty list corresponds to -1.
Edge cases and pitfalls
- Empty array.
iterativeandrecursivefall through to-1.sentinelmust return early: withn = 0there is no last slot to write to. - Target at the last index. This is the sentinel method’s trickiest case. The scan stops at
n - 1, which could mean “found” or “hit the sentinel”; comparing the savedlastwithtargetdisambiguates. The tests check[6, 7, 8, 4, 1]with target1. - Duplicates. Return the first index. Scanning from the left guarantees it; scanning from the right would return the last.
- Returning the value instead of the index. The two are different: for
[6, 7, 8, 4, 1]and target4the answer is3, not4. -1as “not found”. Safe here because indices are never negative. Do not confuse it with the problem of returning a value, where-1can be a legitimate element.- Recursion depth.
recursiveuses one stack frame per element. It is fine for the sizes in the tests and dangerous for10^5; prefer the loop. - Mutation in
sentinel. The temporary write must be undone on every path, including the “not found” path. Restoring before the finalifkeeps the code honest. - Extreme values. Comparisons only, no arithmetic, so
Integer.MIN_VALUEandInteger.MAX_VALUEneed no special care.
Related problems
- Largest Element is the same single pass, tracking the maximum instead of looking for a given value.
- Search X in sorted array shows what sortedness buys: binary search in
O(log n). - Find missing number uses repeated linear search as its brute force and then beats it with arithmetic.
- Two Sum is “linear search for a complement”, made fast with a hash map.