{"id":"dsa-complexity-two-pointers","slug":"two-pointers","title":"Two pointers","text":"Binary search used the order of a sorted list to drop half of it at a time. **Two pointers** use the order in another way: two indexes walk along the data, and at each step the order tells which one to move. Each one only ever moves in one direction, so together they take O(n) steps.\n\n### A pair with a given sum\n\nIn the step on sets and dicts, you found two values that add up to a target with a set: O(n) time and O(n) memory. If the list is sorted, you can do it with no extra memory. Start `left` at the smallest value and `right` at the largest, and look at their sum:\n\n```python\ndef has_pair(values, target):\n    left = 0\n    right = len(values) - 1\n    while left < right:\n        total = values[left] + values[right]\n        print(values[left], \"+\", values[right], \"=\", total)\n        if total == target:\n            return True\n        if total < target:\n            left += 1\n        else:\n            right -= 1\n    return False\n\n\nprint(has_pair([1, 4, 6, 9, 13, 15], 22))\n```\n\n```output\n1 + 15 = 16\n4 + 15 = 19\n6 + 15 = 21\n9 + 15 = 24\n9 + 13 = 22\nTrue\n```\n\n**Why it's right:** when the sum is too small, `values[left]` can't be in any pair. Its biggest possible partner, `values[right]`, isn't enough, and every other partner still in play is smaller. So dropping it loses nothing. In the same way, when the sum is too big, `values[right]` is too big even with the smallest value still in play. Each round drops one value that can't be in a pair, and after at most n - 1 rounds the pointers meet: O(n) time, O(1) memory. If the list isn't sorted, sorting it first costs O(n log n), and then a set is faster.\n\n### Merging two sorted lists\n\nTo merge two sorted lists into one, put a pointer at the front of each. The smallest value left is at one of the two pointers, so take the smaller one and move that pointer on. When one list runs out, the rest of the other comes after, already in order:\n\n```python\ndef merge(a, b):\n    result = []\n    i = 0\n    j = 0\n    while i < len(a) and j < len(b):\n        if a[i] <= b[j]:\n            result.append(a[i])\n            i += 1\n        else:\n            result.append(b[j])\n            j += 1\n    result += a[i:]\n    result += b[j:]\n    return result\n\n\nprint(merge([2, 5, 8, 9], [1, 5, 7, 12, 15]))\n```\n\n```output\n[1, 2, 5, 5, 7, 8, 9, 12, 15]\n```\n\nEvery round appends one value, so it's O(n + m) for lists of n and m values. Taking the values with `a.pop(0)` instead would be O(n) per value, since every value after it moves one place.\n\n### Removing duplicates, and a trap\n\nA first try at removing repeats often removes items from a list while a loop goes over the same list. That skips items:\n\n```python\nvalues = [2, 5, 5, 5, 5, 8]\nfor value in values:\n    if values.count(value) > 1:\n        values.remove(value)\nprint(values)\n```\n\n```output\n[2, 5, 5, 8]\n```\n\nThe loop goes by position. Each `remove` shifts the later items one place to the left, so the item that moves into the current place is never looked at. It's also O(n²), since `count` and `remove` each go through the list.\n\nIn everyday Python, you don't remove repeats from the list itself: you make a new list. `sorted(set(values))` gives each different value once, in order, in O(n log n). `dict.fromkeys(values)` makes a dict whose keys are the values, each once, in the order they first appear, so `list(dict.fromkeys(values))` drops the repeats and keeps the order, in O(n):\n\n```pycon\n>>> values = [5, 2, 5, 8, 2, 9]\n>>> sorted(set(values))\n[2, 5, 8, 9]\n>>> list(dict.fromkeys(values))\n[5, 2, 8, 9]\n```\n\nInterviews ask for the version by hand, when the list itself must change, with O(1) extra memory. For a sorted list, two pointers do it in place: `read` looks at every value, and `write` is where the next value to keep goes. A value is new exactly when it differs from the last value kept, `values[write - 1]`:\n\n```python\ndef remove_duplicates(values):\n    write = 0\n    for read in range(len(values)):\n        if write == 0 or values[read] != values[write - 1]:\n            values[write] = values[read]\n            write += 1\n    del values[write:]\n\n\nvalues = [2, 5, 5, 5, 5, 8, 8, 9]\nremove_duplicates(values)\nprint(values)\n```\n\n```output\n[2, 5, 8, 9]\n```\n\n`read` moves every round and `write` only when a value is kept, never back: O(n) time and O(1) extra memory. The `del` at the end cuts off what's left over.\n","task":"Fill in the blanks so that each case moves the correct pointer, and the function returns the pair of values that adds up to `target`, or `None`. The program should print:\n\n```\n(4, 18)\n(9, 11)\nNone\n```\n","kind":"fill","seed":"def pair_with_sum(values, target):\n    left = 0\n    right = len(values) - 1\n    while left < right:\n        total = values[left] + values[right]\n        if total == target:\n            return (values[left], values[right])\n        if total < target:\n            [[left += 1‖left = left + 1]]\n        else:\n            [[right -= 1‖right = right - 1]]\n    return None\n\n\nnumbers = [1, 4, 9, 11, 15, 18]\nprint(pair_with_sum(numbers, 22))\nprint(pair_with_sum(numbers, 20))\nprint(pair_with_sum(numbers, 3))\n","solution":"def pair_with_sum(values, target):\n    left = 0\n    right = len(values) - 1\n    while left < right:\n        total = values[left] + values[right]\n        if total == target:\n            return (values[left], values[right])\n        if total < target:\n            left += 1\n        else:\n            right -= 1\n    return None\n\n\nnumbers = [1, 4, 9, 11, 15, 18]\nprint(pair_with_sum(numbers, 22))\nprint(pair_with_sum(numbers, 20))\nprint(pair_with_sum(numbers, 3))\n","hints":["When the sum is too small, the smaller value can't be in any pair: drop it by moving `left` up. When it's too big, drop the larger value by moving `right` down.","The first blank is `left += 1` and the second `right -= 1`."],"tests":[{"name":"Output","stdin":"","expect":"(4, 18)\n(9, 11)\nNone","hidden":false}],"require":[],"forbid":[],"more":[{"task":"Write `common(a, b)`: a sorted list of the values that are in both `a` and `b`. Both lists are sorted, and neither has a repeated value. Walk along both with two pointers, as in merging, for O(n + m): the hidden test has 200,000 values in each list.\n\nFor example, `common([1, 4, 6, 9, 13], [2, 4, 5, 9, 10, 13])` returns:\n\n```\n[4, 9, 13]\n```\n","kind":"code","seed":"def common(a, b):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(common([1, 4, 6, 9, 13], [2, 4, 5, 9, 10, 13]))\n","solution":"def common(a, b):\n    result = []\n    i = 0\n    j = 0\n    while i < len(a) and j < len(b):\n        if a[i] == b[j]:\n            result.append(a[i])\n            i += 1\n            j += 1\n        elif a[i] < b[j]:\n            i += 1\n        else:\n            j += 1\n    return result\n\n\nif __name__ == \"__main__\":\n    print(common([1, 4, 6, 9, 13], [2, 4, 5, 9, 10, 13]))\n","hints":["Start a pointer at the front of each list. If the two values are equal, it's a common value: keep it and move both pointers on.","If `a[i]` is smaller than `b[j]`, then `a[i]` can't be in `b`: everything from `b[j]` on is greater. Move `i` on. In the same way, move `j` on when `b[j]` is the smaller one.","Stop as soon as one list runs out: `while i < len(a) and j < len(b)`."],"tests":[{"name":"common([1, 4, 6, 9, 13], [2, 4, 5, 9, 10, 13])","stdin":"","call":"common([1, 4, 6, 9, 13], [2, 4, 5, 9, 10, 13])\n","expect":"[4, 9, 13]","hidden":false,"returns":true},{"name":"common([], [1, 2])","stdin":"","call":"common([], [1, 2])\n","expect":"[]","hidden":true,"returns":true},{"name":"common([1, 2, 3], [4, 5])","stdin":"","call":"common([1, 2, 3], [4, 5])\n","expect":"[]","hidden":true,"returns":true},{"name":"common([5, 6, 7], [5, 6, 7])","stdin":"","call":"common([5, 6, 7], [5, 6, 7])\n","expect":"[5, 6, 7]","hidden":true,"returns":true},{"name":"common([-3, 0, 8], [-3, 8, 20])","stdin":"","call":"common([-3, 0, 8], [-3, 8, 20])\n","expect":"[-3, 8]","hidden":true,"returns":true},{"name":"len(common(list(range(0, 600_000, 3)), list(range(0, 400_000, 2))))","stdin":"","call":"len(common(list(range(0, 600_000, 3)), list(range(0, 400_000, 2))))\n","expect":"66667","hidden":true,"big":true,"returns":true}],"require":[],"forbid":[]},{"task":"Write `count_pairs(values, limit)`: how many pairs of positions `i < j` have `values[i] + values[j] <= limit`. The list is sorted. The hidden test has 200,000 values, and checking every pair is O(n²): use two pointers.\n\nFor example, `count_pairs([1, 2, 4, 5, 9], 7)` returns (1 + 2, 1 + 4, 1 + 5, 2 + 4 and 2 + 5):\n\n```\n5\n```\n","kind":"code","seed":"def count_pairs(values, limit):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(count_pairs([1, 2, 4, 5, 9], 7))\n","solution":"def count_pairs(values, limit):\n    count = 0\n    left = 0\n    right = len(values) - 1\n    while left < right:\n        if values[left] + values[right] <= limit:\n            count += right - left\n            left += 1\n        else:\n            right -= 1\n    return count\n\n\nif __name__ == \"__main__\":\n    print(count_pairs([1, 2, 4, 5, 9], 7))\n","hints":["Start `left` at 0 and `right` at the last index. If `values[right]` is too big even with `values[left]`, the smallest value, it's too big for every pair: move `right` down.","If `values[left] + values[right] <= limit`, then `values[left]` also works with every value between `left` and `right`, which are smaller than `values[right]`. That's `right - left` pairs at once.","After counting those pairs, every pair with `values[left]` is counted: move `left` up. Stop when the pointers meet."],"tests":[{"name":"count_pairs([1, 2, 4, 5, 9], 7)","stdin":"","call":"count_pairs([1, 2, 4, 5, 9], 7)\n","expect":"5","hidden":false,"returns":true},{"name":"count_pairs([], 10)","stdin":"","call":"count_pairs([], 10)\n","expect":"0","hidden":true,"returns":true},{"name":"count_pairs([3], 10)","stdin":"","call":"count_pairs([3], 10)\n","expect":"0","hidden":true,"returns":true},{"name":"count_pairs([1, 1, 1], 2)","stdin":"","call":"count_pairs([1, 1, 1], 2)\n","expect":"3","hidden":true,"returns":true},{"name":"count_pairs([5, 6, 7], 4)","stdin":"","call":"count_pairs([5, 6, 7], 4)\n","expect":"0","hidden":true,"returns":true},{"name":"count_pairs([-4, -1, 2, 3, 8], 1)","stdin":"","call":"count_pairs([-4, -1, 2, 3, 8], 1)\n","expect":"4","hidden":true,"returns":true},{"name":"count_pairs(list(range(0, 600_000, 3)), 500_000)","stdin":"","call":"count_pairs(list(range(0, 600_000, 3)), 500_000)\n","expect":"6944472222","hidden":true,"big":true,"returns":true}],"require":[],"forbid":[]}]}