"주자 둘을 서로 다른 속도로 리스트에 풀어놔. 둘 사이 간격은 아무렇게나 벌어지는 게 아니야. 그 규칙성을 이용하면 가운데 찾기, 고리 잡기, 끝에서 n번째 짚기를 한 번에, 그것도 추가 메모리 없이 해낼 수 있어."
아이디어: 두 속도로 한 번에
연결 리스트는 인덱싱이 안 되니까 배열에서 쉽게 하던 일들, 가운데로 바로 점프한다든가 끝에서 n번째를 들여다본다든가 하는 게 두 번 훑지 않고는 안 될 것처럼 보여. 빠른 포인터와 느린 포인터 기법은 속도가 다른 포인터 둘을 달리게 하고 그 사이 관계를 읽어서 한 번에 답을 얻어. 배열과 문자열 트랙에서 본 투 포인터와 같은 집안인데, 앞으로 걷기만 되는 연결 리스트 세계에 맞게 특화된 버전이야.
가운데 찾기, 한 번의 순회로
slow는 한 번에 한 칸, fast는 한 번에 두 칸씩 움직여. fast가 끝에 닿는 순간 slow는 가운데에 서 있어. 길이가 짝수면 가운데가 둘이라는 걸 잊지 마. 반복 조건과 시작 위치에 따라 앞쪽 가운데를 줄지 뒤쪽 가운데를 줄지가 달라지니까, 어느 쪽을 반환할지 규약을 먼저 못 박아 둬야 해. 어쨌든 길이를 따로 세지 않고 한 번 훑어서 끝낸다는 이점은 그대로야.
플로이드 순환 검출: 그 유명한 기법
연결 리스트에 고리가 있는지, 그러니까 어떤 노드의 next가 앞쪽으로 되돌아가는지 알고 싶다고 하자. 단순한 답은 방문한 노드를 전부 set에 넣어두는 건데 그러면 공간이 O(n)이야. 플로이드의 토끼와 거북이는 이걸 O(1) 공간에 해내. slow는 한 칸씩, fast는 두 칸씩 달리게 해. 고리가 없으면 fast가 먼저 끝에서 떨어져(None에 닿아). 고리가 있으면 fast는 계속 돌다가 결국 slow와 정확히 같은 노드에서 만나. 포인터 둘이면 되고 추가 메모리는 없어. 추론도 정말 깔끔해. 고리 안에서는 두 칸씩 가는 쪽이 한 칸씩 가는 쪽과의 간격을 한 번에 하나씩 좁히니까, 언젠가는 반드시 따라잡게 돼 있거든.
세지 않고 끝에서 n번째
끝에서 n번째 노드가 필요한데 리스트 길이를 모른다면? fast만 먼저 n칸 보내 놓고, 그다음부터 fast가 끝에 닿을 때까지 fast와 slow를 나란히 움직여. fast가 처음부터 끝까지 정확히 n칸 앞서 있었으니 slow는 정확히 끝에서 n번째에 서게 돼. 한 번만 훑으면 되고 길이도 필요 없어. 고정된 간격이 세는 일을 대신해 준 거야.