Part 4 · Lesson 19 · 22 min read

Array Traversal Problems

Largest and second largest, rotating, moving zeros, missing numbers and more — the problems every array round starts with.

On this page

  1. The problems every array round starts with
  2. Largest, second largest, and is it sorted?
  3. The read/write pointer pattern
  4. Traced: removing duplicates in place
  5. Moving zeros to the end
  6. Rotating by k: the reversal method
  7. Missing number and single number (XOR)
  8. Union of two sorted arrays
  9. Practice questions (7)
  10. Make it stick
  11. What's next

The problems every array round starts with

Part 4 is about arrays. An array is a list of values stored one after another, and each value has a position number called an index. Most interview questions use arrays. This first lesson collects the classic “warm-up” problems. Each one needs only one or two passes (a pass is one trip through the array from start to end). Each one also teaches a small technique you will use again: a write pointer, a reversal, XOR, or a merge-style walk.

A pointer here is just a variable that holds an index. It “points” at one place in the array, like a finger on a line of text.

Interviewers often add one rule: in place. In place means you change the array you were given, and you do not make a second big array. You may use only O(1) extra memory. O(1) means a small, fixed amount of memory (a few variables), however long the array is. This rule makes the problems interesting.

Largest, second largest, and is it sorted?

You have met the first two already: the “best so far” loop (Lesson 8) and the two-variable second largest (Lesson 11). Checking whether an array is sorted is the same kind of single pass. Compare each item with the one before it. One step down is enough to say “not sorted”:

JavaScript
function isSorted(nums) {  for (let i = 1; i < nums.length; i++) {    if (nums[i] < nums[i - 1]) return false;   // one step down is enough to fail  }  return true;}console.log(isSorted([1, 2, 2, 7])); // trueconsole.log(isSorted([1, 3, 2]));    // false

The read/write pointer pattern

Many in-place problems ask you to keep some items and drop the others. For these, use two pointers that move in the same direction:

  • read looks at every item, one by one.
  • write marks the place where the next kept item should go. It never gets ahead of read.

When read finds an item to keep, copy it to write, then move write forward by one. At the end, the first write positions hold the answer. This is the “same-direction” form of the two-pointer technique (Lesson 21).

Traced: removing duplicates in place

step 1 / 11
Set up
nums = [1, 1, 2, 3, 3]

Sorted, so equal values sit next to each other.

▶const nums = [1, 1, 2, 3, 3];2let write = 1;3for (let read = 1; read < nums.length; read++) {4  if (nums[read] !== nums[write - 1]) {5    nums[write] = nums[read];6    write++;7  }8}9console.log(write, nums.slice(0, write));
Variables
nums[1, 1, 2, 3, 3]
Console
(nothing printed yet)
read checks every item; write only moves when a new value appears. Because the array is sorted, a value is new exactly when it differs from the last kept one. · interactive

This takes one pass: O(n) time (the steps grow in line with the array length n) and O(1) space. If the array were not sorted, you would need a Set to know which values you have already seen. A Set is a collection that keeps each value only once and can tell you quickly whether it holds a value.

Moving zeros to the end

This uses the same pattern. Keep the non-zero values, in order, at the front. A swap exchanges two values. If you swap instead of copy, the zeros move to the back by themselves.

JavaScript
function moveZeroes(nums) {  let write = 0;  for (let read = 0; read < nums.length; read++) {    if (nums[read] !== 0) {      [nums[write], nums[read]] = [nums[read], nums[write]];      write++;    }  }  return nums;}console.log(moveZeroes([0, 1, 0, 3, 12])); // [ 1, 3, 12, 0, 0 ]
Dry run · moveZeroes([0, 1, 0, 3, 12])
readnums[read]ActionArray afterwrite
00skip[0, 1, 0, 3, 12]0
11swap with index 0[1, 0, 0, 3, 12]1
20skip[1, 0, 0, 3, 12]1
33swap with index 1[1, 3, 0, 0, 12]2
412swap with index 2[1, 3, 12, 0, 0]3

Rotating by k: the reversal method

Rotating right by k moves the last k items to the front. For example, [1, 2, 3, 4, 5, 6, 7] rotated by 3 is [5, 6, 7, 1, 2, 3, 4]. There are three ways to do it. Moving every item one step, k times, costs O(n × k). Copying into a new array costs O(n) time but also O(n) extra space. The reversal method needs only O(n) time and O(1) space. It uses three reversals:

Dry run · rotate [1, 2, 3, 4, 5, 6, 7] by k = 3
StepArray
start[1, 2, 3, 4, 5, 6, 7]
1. reverse all[7, 6, 5, 4, 3, 2, 1]
2. reverse first k = 3[5, 6, 7, 4, 3, 2, 1]
3. reverse the rest[5, 6, 7, 1, 2, 3, 4]

Reversing everything puts the last k items at the front, but backwards. Steps 2 and 3 turn each block the right way round again.

JavaScript
function reverse(nums, i, j) {  while (i < j) {    [nums[i], nums[j]] = [nums[j], nums[i]];    i++;    j--;  }}function rotate(nums, k) {  const n = nums.length;  k %= n;                       // rotating by n changes nothing  reverse(nums, 0, n - 1);      // 1. reverse everything  reverse(nums, 0, k - 1);      // 2. reverse the first k  reverse(nums, k, n - 1);      // 3. reverse the rest  return nums;}console.log(rotate([1, 2, 3, 4, 5, 6, 7], 3)); // [ 5, 6, 7, 1, 2, 3, 4 ]
Remember k %= n

k can be larger than the array length. Rotating 7 items by 10 is the same as rotating by 3. The % operator gives the remainder after division (10 % 7 = 3). Without it, the reversal ranges would go past the end of the array.

Missing number and single number (XOR)

Lesson 11 found the missing number with “expected sum minus actual sum”. Here is a related classic. Every value appears twice, except one value. Find that one. A frequency map (a Map that counts each value) works, but it needs O(n) space. For O(1) space, use XOR.

XOR (written ^ in JavaScript) is an operator that compares two numbers bit by bit. A bit is one binary digit, 0 or 1. For each bit, XOR gives 1 if the two bits are different and 0 if they are the same. You only need three facts about it:

JavaScript
console.log(5 ^ 5);          // 0   a value XOR itself is 0console.log(5 ^ 0);          // 5   XOR with 0 changes nothingconsole.log(4 ^ 1 ^ 4);      // 1   order does not matter; the pair of 4s cancelsfunction singleNumber(nums) {  let x = 0;  for (const v of nums) x ^= v;  return x;}console.log(singleNumber([4, 1, 2, 1, 2])); // 4

Now XOR all the values together. Every pair cancels to 0, and only the single value is left. Lesson 58 explains XOR on the individual bits in more detail.

nums =
4
0
1
1
2
2
1
3
2
4
↑ index (position), always starting at 0
1 ^ 1 = 0 and 2 ^ 2 = 0, so XOR-ing all five values leaves 4.

Union of two sorted arrays

The union of two arrays holds every value that is in either array, each value once. If both arrays are sorted, walk through them together, like the merge step of merge sort (Lesson 17). Skip a value if it equals the last value you added:

JavaScript
function unionSorted(a, b) {  const out = [];  let i = 0, j = 0;  const add = (v) => { if (out[out.length - 1] !== v) out.push(v); };   // skip repeats  while (i < a.length && j < b.length) {    if (a[i] < b[j]) add(a[i++]);    else if (a[i] > b[j]) add(b[j++]);    else { add(a[i]); i++; j++; }  }  while (i < a.length) add(a[i++]);  while (j < b.length) add(b[j++]);  return out;}console.log(unionSorted([1, 2, 2, 4], [2, 3, 5])); // [ 1, 2, 3, 4, 5 ]

This takes O(n + m) time, where n and m are the two array lengths. The intersection (the values found in both arrays) works the same way. Add a value only when both front values are equal (Lesson 17, Question 4).

Practice questions

You can solve every question here in one or two passes. Aim for O(1) extra space when the question asks for it.

Q1Remove duplicates from a sorted arrayEasy

Remove the duplicates from a sorted array in place so each value appears once. Return the number k of unique values. The first k positions must hold those values in order.

Example 1
Input: [1, 1, 2]
Output: 2, nums = [1, 2, _]
Explanation: Two unique values. What is left after them does not matter.
Example 2
Input: [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]
Output: 5, nums = [0, 1, 2, 3, 4, …]
Explanation: Five unique values at the front.
Stuck? Open one hint at a time
Hint 1
Sorted means equal values sit next to each other.
Hint 2
Keep a write index. Copy a value only when it is different from the last value you kept.
Show all 2 solutions with explanations
Approach 1Set, then copy back
The idea

Collect the unique values with a Set (a collection that keeps each value once, in the order it was added). Then write them to the front.

JavaScript
function removeDuplicates(nums) {  const unique = [...new Set(nums)];  for (let i = 0; i < unique.length; i++) nums[i] = unique[i];  return unique.length;}const a = [1, 1, 2];console.log(removeDuplicates(a), a.slice(0, 2)); // 2 [ 1, 2 ]
How it works

This is O(n) time, but it uses O(n) extra space, so it breaks the “in place” rule.

Approach 2Read/write pointers
The idea

The traced algorithm from the lesson.

JavaScript
function removeDuplicates(nums) {  if (nums.length === 0) return 0;  let write = 1;  for (let read = 1; read < nums.length; read++) {    if (nums[read] !== nums[write - 1]) nums[write++] = nums[read];  }  return write;}const b = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4];const k = removeDuplicates(b);console.log(k, b.slice(0, k)); // 5 [ 0, 1, 2, 3, 4 ]
How it works

O(n) time, O(1) space. The code nums[write++] = … writes at position write and then adds 1 to it.

Which approach should you use?

The pointer version is the answer interviewers expect. (LeetCode 26.)

Q2Remove elementEasy

Remove every occurrence of val from nums in place, and return how many values remain.

Example 1
Input: nums = [3, 2, 2, 3], val = 3
Output: 2, nums = [2, 2, _, _]
Explanation: Two values remain after removing every 3.
Example 2
Input: nums = [0, 1, 2, 2, 3, 0, 4, 2], val = 2
Output: 5, nums = [0, 1, 3, 0, 4, …]
Explanation: The kept values stay in their original order.
Stuck? Open one hint at a time
Hint 1
Keep every value that is not val, using a write pointer.
Show the solution with explanations
Approach 1Read/write pointers
The idea

Copy each value that is not val to the write position.

JavaScript
function removeElement(nums, val) {  let write = 0;  for (const x of nums) {    if (x !== val) nums[write++] = x;  }  return write;}const a = [0, 1, 2, 2, 3, 0, 4, 2];const k = removeElement(a, 2);console.log(k, a.slice(0, k)); // 5 [ 0, 1, 3, 0, 4 ]
How it works

O(n) time, O(1) space. It has the same shape as remove-duplicates. Only the “keep it?” test is different.

Which approach should you use?

Once you know the read/write pattern, many “remove … in place” problems use the same few lines. (LeetCode 27.)

Q3Move zeroesEasy

Move all 0s to the end of nums in place, keeping the order of the other values.

Example 1
Input: [0, 1, 0, 3, 12]
Output: [1, 3, 12, 0, 0]
Explanation: Non-zero values keep their order; zeros go to the end.
Example 2
Input: [0]
Output: [0]
Explanation: Edge case.
Stuck? Open one hint at a time
Hint 1
Copy the non-zero values to the front, then fill the rest with zeros. Or swap as you go.
Show all 2 solutions with explanations
Approach 1Copy forward, then fill zeros
The idea

Two passes. First, pack the non-zero values at the front. Then write zeros after them.

JavaScript
function moveZeroes(nums) {  let write = 0;  for (const x of nums) if (x !== 0) nums[write++] = x;  while (write < nums.length) nums[write++] = 0;  return nums;}console.log(moveZeroes([0, 1, 0, 3, 12])); // [ 1, 3, 12, 0, 0 ]
How it works

O(n) time, O(1) space. It is easy to understand.

Approach 2Swap in one pass
The idea

This is the version from the lesson. Swap each non-zero value into the write position.

JavaScript
function moveZeroes(nums) {  let write = 0;  for (let read = 0; read < nums.length; read++) {    if (nums[read] !== 0) {      [nums[write], nums[read]] = [nums[read], nums[write]];      write++;    }  }  return nums;}console.log(moveZeroes([0, 1, 0, 3, 12])); // [ 1, 3, 12, 0, 0 ]console.log(moveZeroes([0]));              // [ 0 ]
How it works

One pass, O(1) space. It also does fewer writes when the array has few zeros.

Which approach should you use?

Both answers are accepted. (LeetCode 283.)

Q4Rotate arrayMedium

Rotate nums to the right by k steps, in place.

Example 1
Input: nums = [1, 2, 3, 4, 5, 6, 7], k = 3
Output: [5, 6, 7, 1, 2, 3, 4]
Explanation: The last 3 values move to the front.
Example 2
Input: nums = [-1, -100, 3, 99], k = 2
Output: [3, 99, -1, -100]
Explanation: The last 2 values move to the front.
Example 3
Input: nums = [1, 2], k = 5
Output: [2, 1]
Explanation: k is bigger than the length. 5 % 2 = 1, so rotate by 1.
Stuck? Open one hint at a time
Hint 1
Where does the value at index i end up? At index (i + k) % n.
Hint 2
For O(1) space: reverse everything, then reverse each of the two blocks.
Show all 2 solutions with explanations
Approach 1Extra array
The idea

Put each value at its new index in a new array. Then copy the new array back.

JavaScript
function rotate(nums, k) {  const n = nums.length;  const out = new Array(n);  for (let i = 0; i < n; i++) out[(i + k) % n] = nums[i];  for (let i = 0; i < n; i++) nums[i] = out[i];  return nums;}console.log(rotate([1, 2, 3, 4, 5, 6, 7], 3)); // [ 5, 6, 7, 1, 2, 3, 4 ]console.log(rotate([1, 2], 5));                // [ 2, 1 ]
How it works

O(n) time, O(n) space. The % n also works when k is larger than n.

Approach 2Three reversals
The idea

Reverse everything, then reverse the first k items, then reverse the rest.

JavaScript
function rotate(nums, k) {  const n = nums.length;  k %= n;  const rev = (i, j) => {    while (i < j) { [nums[i], nums[j]] = [nums[j], nums[i]]; i++; j--; }  };  rev(0, n - 1);  rev(0, k - 1);  rev(k, n - 1);  return nums;}console.log(rotate([1, 2, 3, 4, 5, 6, 7], 3)); // [ 5, 6, 7, 1, 2, 3, 4 ]console.log(rotate([-1, -100, 3, 99], 2));     // [ 3, 99, -1, -100 ]console.log(rotate([1, 2], 5));                // [ 2, 1 ]
How it works

O(n) time, O(1) space. Each item is swapped at most twice.

Which approach should you use?

Give the extra-array version first. Then show the reversal trick when the interviewer asks for O(1) space. Do not forget k %= n. (LeetCode 189.)

Q5Single numberEasy

Every value in nums appears twice except one. Find it in O(n) time and O(1) extra space.

Example 1
Input: [2, 2, 1]
Output: 1
Explanation: 1 appears once and 2 appears twice.
Example 2
Input: [4, 1, 2, 1, 2]
Output: 4
Explanation: Every other value comes in a pair.
Stuck? Open one hint at a time
Hint 1
A Map of counts works. Can you use only O(1) space?
Hint 2
XOR (^) has two useful facts: x ^ x = 0 and x ^ 0 = x.
Show all 2 solutions with explanations
Approach 1Frequency map
The idea

Count each value, then return the value whose count is 1.

JavaScript
function singleNumber(nums) {  const freq = new Map();  for (const x of nums) freq.set(x, (freq.get(x) ?? 0) + 1);  for (const [x, c] of freq) if (c === 1) return x;}console.log(singleNumber([4, 1, 2, 1, 2])); // 4
How it works

O(n) time, O(n) space.

Approach 2XOR everything
The idea

XOR all the values. The pairs cancel, and the single value is left.

JavaScript
function singleNumber(nums) {  let x = 0;  for (const v of nums) x ^= v;  return x;}console.log(singleNumber([2, 2, 1]));       // 1console.log(singleNumber([4, 1, 2, 1, 2])); // 4
How it works
Dry run · [4, 1, 2, 1, 2]
vx after x ^= v
44
14 ^ 1
24 ^ 1 ^ 2
14 ^ 2 (the 1s cancel)
24 (the 2s cancel)
Which approach should you use?

The XOR version is the O(1)-space answer that interviewers expect. (LeetCode 136.)

Q6Sorted and rotated?Easy

Return true if nums could be a sorted array (each value is the same or bigger than the one before) that was rotated by some number of positions.

Example 1
Input: [3, 4, 5, 1, 2]
Output: true
Explanation: [1, 2, 3, 4, 5] rotated by 3.
Example 2
Input: [2, 1, 3, 4]
Output: false
Explanation: No rotation of a sorted array can give this.
Example 3
Input: [1, 2, 3]
Output: true
Explanation: Rotated by 0.
Stuck? Open one hint at a time
Hint 1
A sorted array has no “drops”. A drop is a value that is smaller than the value before it.
Hint 2
A rotated sorted array has at most one drop, if you also compare the last value with the first value.
Show the solution with explanations
Approach 1Count the drops, circularly
The idea

Count the positions i where nums[i] > nums[(i + 1) % n]. At most one drop is allowed.

JavaScript
function check(nums) {  const n = nums.length;  let drops = 0;  for (let i = 0; i < n; i++) {    if (nums[i] > nums[(i + 1) % n]) drops++;  }  return drops <= 1;}console.log(check([3, 4, 5, 1, 2])); // trueconsole.log(check([2, 1, 3, 4]));    // falseconsole.log(check([1, 2, 3]));       // true
How it works

O(n) time, O(1) space. The code (i + 1) % n wraps from the last index back to 0, so the array is treated like a circle. You will see this trick again with circular arrays in Lesson 40.

Which approach should you use?

If you think of “rotated” as “sorted around a circle”, you do not need to find the rotation point. (LeetCode 1752.)

Q7Union of two sorted arraysEasy

Return the sorted union (each value once) of two sorted arrays.

Example 1
Input: [1, 2, 2, 4], [2, 3, 5]
Output: [1, 2, 3, 4, 5]
Explanation: Every value from either array, once.
Example 2
Input: [], [1, 1]
Output: [1]
Explanation: Edge case: one array is empty.
Stuck? Open one hint at a time
Hint 1
A Set plus a sort works. Because the input is already sorted, a merge-style walk can skip the sort.
Show all 2 solutions with explanations
Approach 1Set, then sort
The idea

Put both arrays into a Set (this removes repeats), then sort the result.

JavaScript
function unionSorted(a, b) {  return [...new Set([...a, ...b])].sort((x, y) => x - y);}console.log(unionSorted([1, 2, 2, 4], [2, 3, 5])); // [ 1, 2, 3, 4, 5 ]
How it works

O((n + m) log(n + m)) time. It does not use the fact that the inputs are sorted.

Approach 2Merge walk
The idea

Each time, take the smaller of the two front values. Skip it if it equals the last value you added.

JavaScript
function unionSorted(a, b) {  const out = [];  const add = (v) => { if (out[out.length - 1] !== v) out.push(v); };  let i = 0, j = 0;  while (i < a.length || j < b.length) {    if (j === b.length || (i < a.length && a[i] <= b[j])) add(a[i++]);    else add(b[j++]);  }  return out;}console.log(unionSorted([1, 2, 2, 4], [2, 3, 5])); // [ 1, 2, 3, 4, 5 ]console.log(unionSorted([], [1, 1]));              // [ 1 ]
How it works

O(n + m) time. The loop runs while either array still has items, so you do not need two extra loops at the end.

Which approach should you use?

When the inputs are sorted, a merge walk is almost always the best tool.

Make it stick

Make it stick — close this page, then
  1. Explain the read/write pointer pattern and write remove-duplicates from memory.
  2. Rotate [1, 2, 3, 4, 5] by k = 2 using the three reversals, on paper.
  3. State the three XOR facts that make the single-number trick work.
  4. Write the union of two sorted arrays without repeats.

Revisit schedule: solve two of the questions again tomorrow, again in 3 days, and once more in a week — without looking at the answers. Each time it will feel easier. That is the memory forming.

What's next

Lesson 20 introduces the prefix sum. It is one extra array of running totals. With it, you can get the sum of any range of items almost instantly. It is the first pattern in this series that turns a whole group of O(n²) problems into O(n).

Practise on LeetCode

Real interview problems that use exactly what this lesson teaches. Solve them in JavaScript after the practice questions above.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Related series