Two pointers method

This commit is contained in:
Antti H S Laaksonen 2017-01-03 01:49:59 +02:00
parent 1d56f95355
commit 7f443cd64e
1 changed files with 111 additions and 132 deletions

View File

@ -1,41 +1,42 @@
\chapter{Amortized analysis} \chapter{Amortized analysis}
\index{tasoitettu analyysi@tasoitettu analyysi} \index{amortized analysis}
Monen algoritmin aikavaativuuden pystyy laskemaan Often the time complexity of an algorithm
suoraan katsomalla algoritmin rakennetta: is easy to analyze by looking at the structure
mitä silmukoita algoritmissa on ja miten monta of the algorithm:
kertaa niitä suoritetaan. what loops there are and how many times
Joskus kuitenkaan näin suoraviivainen analyysi ei they are performed.
riitä antamaan todellista kuvaa algoritmin tehokkuudesta. However, sometimes a straightforward analysis
doesn't give a true picture of the efficiency of the algorithm.
\key{Tasoitettu analyysi} soveltuu sellaisten \key{Amortized analysis} can be used for analyzing
algoritmien analyysiin, joiden osana on jokin operaatio, an algorithm that contains an operation whose
jonka ajankäyttö vaihtelee. time complexity varies.
Ideana on tarkastella yksittäisen operaation The idea is to consider all such operations during the
sijasta kaikkia operaatioita algoritmin execution of the algorithm instead of a single operation,
aikana ja laskea niiden ajankäytölle yhteinen raja. and estimate the total time complexity of the operations.
\section{Kahden osoittimen tekniikka} \section{Two pointers method}
\index{kahden osoittimen tekniikka} \index{two pointers method}
\key{Kahden osoittimen tekniikka} on taulukon käsittelyssä In the \key{two pointers method},
käytettävä menetelmä, jossa taulukkoa käydään läpi two pointers iterate through the elements in an array.
kahden osoittimen avulla. Both pointers can move during the algorithm,
Molemmat osoittimet liikkuvat algoritmin aikana, but the restriction is that each pointer can move
mutta rajoituksena on, että ne voivat liikkua vain to only one direction.
yhteen suuntaan, mikä takaa, että algoritmi toimii tehokkaasti. This ensures that the algorithm works efficiently.
Tutustumme seuraavaksi kahden osoittimen tekniikkaan We will next discuss two problems that can be solved
kahden esimerkkitehtävän kautta. using the two pointers method.
\subsubsection{Alitaulukon summa} \subsubsection{Subarray sum}
Annettuna on taulukko, jossa on $n$ positiivista kokonaislukua. Given an array that contains $n$ positive integers,
Tehtävänä on selvittää, onko taulukossa alitaulukkoa, our task is to find out if there is a subarray
jossa lukujen summa on $x$. where the sum of the elements is $x$.
Esimerkiksi taulukossa For example, the array
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1); \draw (0,0) grid (8,1);
@ -60,7 +61,7 @@ Esimerkiksi taulukossa
\node at (7.5,1.4) {$8$}; \node at (7.5,1.4) {$8$};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
on alitaulukko, jossa lukujen summa on 8: contains a subarray with sum 8:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (5,1); \fill[color=lightgray] (2,0) rectangle (5,1);
@ -87,17 +88,18 @@ on alitaulukko, jossa lukujen summa on 8:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Osoittautuu, että tämän tehtävän voi ratkaista It turns out that the problem can be solved in
ajassa $O(n)$ kahden osoittimen tekniikalla. $O(n)$ time using the two pointers method.
Ideana on käydä taulukkoa läpi kahden osoittimen The idea is to iterate through the array
avulla, jotka rajaavat välin taulukosta. using two pointers that define a range in the array.
Joka vuorolla vasen osoitin liikkuu On each turn, the left pointer moves one step
yhden askeleen eteenpäin ja oikea osoitin forward, and the right pointer moves forward
liikkuu niin kauan eteenpäin kuin summa on enintään $x$. as long as the sum is at most $x$.
Jos välin summaksi tulee tarkalleen $x$, ratkaisu on löytynyt. If the sum of the range becomes exactly $x$,
we have found a solution.
Tarkastellaan esimerkkinä algoritmin toimintaa As an example, we consider the following array
seuraavassa taulukossa, kun tavoitteena on muodostaa summa $x=8$: with target sum $x=8$:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1); \draw (0,0) grid (8,1);
@ -123,10 +125,10 @@ seuraavassa taulukossa, kun tavoitteena on muodostaa summa $x=8$:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Aluksi osoittimet rajaavat taulukosta välin, First, the pointers define a range with sum $1+3+2=6$.
jonka summa on $1+3+2=6$. The range can't be larger
Väli ei voi olla tätä suurempi, because the next number 5 would make the sum
koska seuraava luku 5 veisi summan yli $x$:n. larger than $x$.
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -157,9 +159,9 @@ koska seuraava luku 5 veisi summan yli $x$:n.
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Seuraavaksi vasen osoitin siirtyy askeleen eteenpäin. After this, the left pointer moves one step forward.
Oikea osoitin säilyy paikallaan, koska muuten The right pointer doesn't move because otherwise
summa kasvaisi liian suureksi. the sum would become too large.
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -190,10 +192,11 @@ summa kasvaisi liian suureksi.
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Vasen osoitin siirtyy taas askeleen eteenpäin Again, the left pointer moves one step forward,
ja tällä kertaa oikea osoitin siirtyy kolme askelta and this time the right pointer moves three
eteenpäin. Muodostuu summa $2+5+1=8$ eli taulukosta steps forward.
on löytynyt väli, jonka lukujen summa on $x$. The sum is $2+5+1=8$, so we have found a subarray
where the sum of the elements is $x$.
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -224,67 +227,42 @@ on löytynyt väli, jonka lukujen summa on $x$.
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
% Algoritmin toteutus näyttää seuraavalta: The time complexity of the algorithm depends on
% the number of steps the right pointer moves.
% \begin{lstlisting} There is no upper bound how many steps the
% int s = 0, b = 0; pointer can move on a single turn.
% for (int a = 1; a <= n; a++) { However, the pointer moves \emph{a total of}
% while (b<n && s+t[b+1] <= x) { $O(n)$ steps during the algorithm
% b++; because it only moves forward.
% s += t[b];
% }
% if (s == x) {
% // ratkaisu löytyi
% }
% s -= t[a];
% }
% \end{lstlisting}
%
% Muuttujat $a$ ja $b$ sisältävät vasemman ja oikean
% osoittimen kohdan.
% Muuttuja $s$ taas laskee lukujen summan välillä.
% Joka askeleella $a$ liikkuu askeleen eteenpäin
% ja $b$ liikkuu niin kauan kuin summa on enintään $x$.
Algoritmin aikavaativuus riippuu siitä, Since both the left and the right pointer
kauanko oikean osoittimen liikkuminen vie aikaa. move $O(n)$ steps during the algorithm,
Tämä vaihtelee, koska oikea osoitin voi liikkua the time complexity is $O(n)$.
minkä tahansa matkan eteenpäin taulukossa.
Kuitenkin oikea osoitin liikkuu \textit{yhteensä}
$O(n)$ askelta algoritmin aikana, koska se voi
liikkua vain eteenpäin.
Koska sekä vasen että oikea osoitin liikkuvat \subsubsection{Sum of two numbers}
$O(n)$ askelta algoritmin aikana,
algoritmin aikavaativuus on $O(n)$.
\subsubsection{Kahden luvun summa} \index{2SUM problem}
\index{2SUM-ongelma} Given an array of $n$ integers and an integer $x$,
our task is to find two numbers in array
whose sum is $x$ or report that there are no such numbers.
This problem is known as the \key{2SUM} problem,
and it can be solved efficiently using the
two pointers method.
Annettuna on taulukko, jossa on $n$ kokonaislukua, First, we sort the numbers in the array in
sekä kokonaisluku $x$. increasing order.
Tehtävänä on etsiä taulukosta kaksi lukua, After this, we iterate through the array using
joiden summa on $x$, tai todeta, two pointers that begin at both ends of the array.
että tämä ei ole mahdollista. The left pointer begins from the first element
Tämä ongelma tunnetaan tunnetaan nimellä and moves one step forward on each turn.
\key{2SUM} ja se ratkeaa tehokkaasti The right pointer begins from the last element
kahden osoittimen tekniikalla. and always moves backward until the sum of the range
defined by the pointers is at most $x$.
If the sum is exactly $x$, we have found a solution.
Taulukon luvut järjestetään ensin For example, consider the following array when
pienimmästä suurimpaan, minkä jälkeen our task is to find two elements whose sum is $x=12$:
taulukkoa aletaan käydä läpi kahdella osoittimella,
jotka lähtevät liikkelle taulukon molemmista päistä.
Vasen osoitin aloittaa taulukon alusta ja
liikkuu joka vaiheessa askeleen eteenpäin.
Oikea osoitin taas aloittaa taulukon lopusta
ja peruuttaa vuorollaan taaksepäin, kunnes osoitinten
määrittämän välin lukujen summa on enintään $x$.
Jos summa on tarkalleen $x$, ratkaisu on löytynyt.
Tarkastellaan algoritmin toimintaa
seuraavassa taulukossa, kun tavoitteena on muodostaa
summa $x=12$:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (8,1); \draw (0,0) grid (8,1);
@ -310,9 +288,10 @@ summa $x=12$:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Seuraavassa on algoritmin aloitustilanne. The initial positions of the pointers
Lukujen summa on $1+10=11$, joka on pienempi are as follows.
kuin $x$:n arvo. The sum of the numbers is $1+10=11$
that is smaller than $x$.
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -344,9 +323,9 @@ kuin $x$:n arvo.
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Seuraavaksi vasen osoitin liikkuu askeleen eteenpäin. Then the left pointer moves one step forward.
Oikea osoitin peruuttaa kolme askelta, minkä jälkeen The right pointer moves three steps backward,
summana on $4+7=11$. and the sum becomes $4+7=11$.
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -378,8 +357,9 @@ summana on $4+7=11$.
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Sitten vasen osoitin siirtyy jälleen askeleen eteenpäin. After this, the left pointer moves one step forward again.
Oikea osoitin pysyy paikallaan ja ratkaisu $5+7=12$ on löytynyt. The right pointer doesn't move, and the solution
$5+7=12$ has been found.
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -411,28 +391,27 @@ Oikea osoitin pysyy paikallaan ja ratkaisu $5+7=12$ on löytynyt.
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Algoritmin alussa taulukon järjestäminen vie At the beginning of the algorithm,
aikaa $O(n \log n)$. the sorting takes $O(n \log n)$ time.
Tämän jälkeen vasen osoitin liikkuu $O(n)$ askelta After this, the left pointer moves $O(n)$ steps
eteenpäin ja oikea osoitin liikkuu $O(n)$ askelta forward, and the right pointer moves $O(n)$ steps
taaksepäin, mihin kuluu aikaa $O(n)$. backward. Thus, the total time complexity
Algoritmin kokonaisaikavaativuus on siis $O(n \log n)$. of the algorithm is $O(n \log n)$.
Huomaa, että tehtävän voi ratkaista myös Note that it is possible to solve
toisella tavalla ajassa in another way in $O(n \log n)$ time using binary search.
$O(n \log n)$ binäärihaun avulla. In this solution, we iterate through the array
Tässä ratkaisussa jokaiselle taulukon luvulle and for each number, we try to find another
etsitään binäärihaulla toista lukua niin, number such that the sum is $x$.
että lukujen summa olisi yhteensä $x$. This can be done by performing $n$ binary searches,
Binäärihaku suoritetaan $n$ kertaa ja and each search takes $O(\log n)$ time.
jokainen binäärihaku vie aikaa $O(\log n)$.
\index{3SUM-ongelma} \index{3SUM-ongelma}
Hieman vaikeampi ongelma on \key{3SUM}, A somewhat more difficult problem is
jossa taulukosta tuleekin etsiä kolme lukua, the \key{3SUM} problem where our task is
joiden summa on $x$. to find \emph{three} numbers whose sum is $x$.
Tämä ongelma on mahdollista ratkaista ajassa $O(n^2)$. This problem can be solved in $O(n^2)$ time.
Keksitkö, miten se tapahtuu? Can you see how it is possible?
\section{Lähin pienempi edeltäjä} \section{Lähin pienempi edeltäjä}