You are on page 1of 4

CPSC 490 Dynamic Program ming: Memoization

Dynami c Program m i n g
Dynamicprogrammingisatechniquethatappearsinmanyformsinlotsofvastlydifferentsituations.
Thecoreideaisasimpleapplicationofdivideandconquer.First,wesplittheproblemintoseveral
parts,allofwhicharesmallerinstancesofthesameproblem.Thisgivesusarecursivesolution.And
thenwesimplyruntherecursivesolution,makingsurenevertosolvethesamesubproblemtwice.
Thisisbestshownwithanexample(orafew).

Catalan Numbers and Memoizati o n


Howmanybinarysearchtreesaretherethatcontainndifferentelements?Rememberthatabinary
searchtreealwaysobeystheruleofhavingsmallerelementstotheleftoflargerones.Forexample,
thereare2BST'swith2nodes(2choices fortheroot)and5BST's with3nodes.Letuscallthe
numberofBST'swithnnodes C n .

Whenever you have a problem involving trees, it's always a good idea to think about a recursive
solution.Hereisone.Wewillpickoneofthennodestobetheroot.Ifthenodesarenumberedfrom1
ton,imaginepickingnodenumberi.Thissplitstheproblemnicelyinto2subproblems.Intheleft
subtree, we have a BST on i1 nodes, and there are Ci1 ways of building those. In the right
subtree,wehaveninodesandso, C ni waysofbuildingaBST.Thetwosubtreesareindependent
ofeachother,sowesimplymultiplythetwonumbers.

Thatisifwehavechosennodeitobetheroot.Pickingadifferentrootissuretogiveusadifferent
tree,sotogetallthepossibletrees,wepickallthepossiblerootnodesandmultiplythenumbersfor
thetwosubtreesthatweget.Thisgivesusaneat,recursiveformula.
n
Cn =i=1 Ci1 C ni
Ofcourse,weneedsomebasecases.Simplysetting C0 tobe1isenoughthereisjustonewayto
makeatreewithzeronodes.

A mathematician would now try to get a closed form solution to this recurrence, but we have
computers.Whatwillhappenifwesimplywritearecursivefunctiontocalculate Cn ?Hereitis.

intC(intn){
if(n==0)return1;
intans=0;
for(inti=1;i<=n;i++)
ans+=C(i1)*C(ni);
returnans;
}

Thisworksfine,butthereisaslightproblemit'sridiculouslyslow.It'snotjustexponentialintime;
it'sworse.C(n)makes2nrecursivecalls.Itisevenworsethanfactorial!Thisseemswrong.Afterall,
Cn onlydependsonthenumbers C0 through Cn1 ,andtherearenofthesenumbers.There
mustbealotofrepeatingrecursivecallstoC(i)thatareresponsibleforthehorriblerunningtime.

Hereisasimplewaytofixthat.Wewillkeepatableofpreviouslycomputedvaluesof Ci .Thenif
thefunctionC()iscalledwithparameteri,andwehavealreadycomputedC(i)oncebefore,wewill
simplylookupitsvalueinthetableandavoidspendingalotofrunningtime(again).Wewillneedto
CPSC 490 Dynamic Program ming: Memoization

initializethecellsofthistablewithsomedummyvaluesthatwouldtelluswhetherwehavecomputed
thevalueofthatcellalreadyornot.Thistechniqueiscalledmemoization,andhereishowitwould
changethecode.

intCval[1024];
intC(intn){
if(Cval[n])!=1)returnCval[n];
intans=0;
if(n==0)ans=1;
for(inti=1;i<=n;i++)
ans+=C(i1)*C(ni);
returnCval[n]=ans;
}

intmain(){
for(inti=0;i<1024;i++)Cval[i]=1;
//callC(n)foranynupto1023
}

TherunningtimeofC(n)isnowquadraticinnbecausetocomputeC(n),wewillneedtocomputeall
oftheC(i)valuesbetween0andn1,andeachonewillbecomputedexactlyonce,inlineartime.
Unfortunately,wewillnotbeabletocomputeC(1000)thiseasilybecauseitishugeandwillcausean
overflow,butthat'saminordetail.:)

The Cn arecalledCatalannumbers,andthereisactuallyaniceformulaforthem.
2 n !
C n=
n ! n1 !
Theyappearallovertheplace,includingthenumberofwaysoftriangulatingaconvexpolygonand
thenumberofwaysofputtingbracketsintoanexpression.Youcanfindlotsofcoolfactshere:
http://mathworld.wolfram.com/CatalanNumber.html

Lucky Numbers
Thistechniqueofrememberingpreviouslycomputedanswersinatable,thustradingoffmemoryfora
gainincomputationtime,iscalledmemoization.[Idon'tknowanyonewhohasagoodexplanationof
whythereisno'r'in"memoization".]Hereisafunlittleproblemthatcanbesolvedusingthesame
technique.

13isanunluckynumber.Infact,anynumberthatcontains13isalsounlucky.Forexample,130,213
234513235and2451325areallunlucky.Alloftheothernumbersareluckybydefault.Howmany
luckynumbersaretherewithndigits?

We will try the same approach here. First, we will find a recursive solution, and then add a
memoization table to it in order to avoid calling the recursive function twice with the same
parameters.

Inanndigitnumber,thefirstdigitcanbeanythingbetween1and9,butallsubsequentdigitscanbe
anythingbetween0and9.Thisdifferenceisalittleinconvenient,sowewillletthefirstdigitbe0as
well.Thisway,insteadofcountingnumberswithndigits,wewillcountallthenumberswithupton
digits.ThenifT[n]isthenumber oflucky numbers withuptondigits,wecancountthosewith
CPSC 490 Dynamic Program ming: Memoization

exactlyndigitsbysubtractingT[n1]fromT[n],eliminatingallthosewithfewerthanndigits.

SohowdowecomputeT[n]thenumberofluckynumberswithuptondigits?Let'slookatthefirst
digit.Itcanbeanything;wehave10choicesforit.However,whenitis1,thentheseconddigitcan
notbe3(orthenumberwouldstartwith"13").HereisarecursivefunctionforT[n].
T[ n]=10T[ n1]11T[ n2]
Thefirstterm( 10T[ n1] )says,"pickanyofthe10valuesforthefirstdigitandmakesurethe
restofthenumberislucky."Thiscountsalltheluckynumberswithuptondigits,butwegetafew
unluckyonesthere,too.Namely,wealsocountthenumbersthatstartwith13andhavenoother13s
appearinganywhere.Thesecondtermsubtractsallthe"almostlucky"numberswehaveerroneously
counted.ThereareexactlyT[n2]ofthembecausetheyalllooklike13xxx,wherexxxisan(n2)digit
luckynumber.

SinceT[n]dependsonbothT[n1]andT[n2],weneedtwobasecaseshere.T[0]is1,andT[1]is10.
HereisasimplerecursiveimplementationthatusesamemoizationtabletostorethevaluesofT[n].

intT[1024];
intlucky(intn){
if(T[n]==1)
T[n]=10*lucky(n1)lucky(n2);
returnT[n];
}

intmain(){
T[0]=1;
T[1]=10;
for(inti=2;i<1024;i++)T[i]=1;
//Calllucky(x)tocomputetheanswerforx
}

Onceagain,weuse1tomarkthespacesinthememoizationtablethatarestilluncomputed.This
time,wetakecareofthebasecasesinthemain()function,whichmakeslucky()extremelyshort.Note
thatwehavetoactuallyuserecursivecallstolucky(n1)andlucky(n2)inorderforthistowork.We
cannotsimplyuseT[n1]andT[n2].Thistime,thefunctionworksinlineartime,computingT[i]for
allthevaluesbetween2andthetargetnumber,n.Eachvaluetakesconstanttimetocomputeandis
computedexactlyonce.Withoutmemoization,thelucky()functionwouldrequireexponentialtime
2 n recursivecalls.Thedifferenceinrunningtimeishuge.

Hamilto ni a n Path
Alotofhardproblemscanbesolvedusingmemoization.Sometimes,itiseasytocomeupwitha
O n ! bruteforcesolutiontoaproblem,andinmanycases,addingmemoizationwillimprovethe
runningtimeto O 2n ,whichmeansanimprovementfromn=10or11ton=25or30.

RecalltheHamiltonianpathproblem.Inanunweighted graph,findapathfromstotthatvisitseach
vertexexactlyonce. Thenaivebacktracking solution would start atsand tryallof its neighbours
recursively, making sure never to visit the same vertex twice. Branchandbound can improve the
runningtimeconsiderably,onaverage,butintheworstcase,thisapproachisstillnfactorialintime.
Thealgorithmwouldlooksomethinglikethis.
CPSC 490 Dynamic Program ming: Memoization

boolseen[32];
voidhpath(intu){
if(u==t){/*Checkthatwehaveseenallvertices.*/}
else{
for(intv=0;v<n;v++)if(!seen[v]&&graph[u][v]){
seen[v]=true;
hpath(v);
seen[v]=false;
}
}
}

Note,however,thatifwehaveapartialpathfromstouusingvertices s=v1 , v2 , ... , v k=u ,thenwe


don'tcareabouttheorderinwhichwevisitedtheseverticesinordertofigureoutwhichvertextovisit
next.Allthatweneedtoknowisthesetofverticeswehaveseen(theseen[]array)andwhichvertex
weareatrightnow(u).Thereare 2n possiblesetsofverticesandnchoicesforu.Inotherwords,
thereare 2 n possibleseen[]arraysandndifferentparameterstohpath().Whathpath()doesduring
anyparticularrecursivecalliscompletelydeterminedbytheseen[]arrayandtheparameter,u.

Whatwecandoischangetheseen[]arraytobeasingleinteger,usingonebitpervertex.Thenwe
can pass 'seen' as a second parameter to hpath(). Now if hpath() ever gets called with the same
parameters twice, we will not do the same work again. Instead, we will store the answer in a
memoizationtableandsimply returnit. Sincethereareonly n2n differentparameter pairs, and
each recursive call requires O n time (one 'for' loop), we will get a total running time of
O n2 2n muchbetterthan O n ! .

boolmemo[20][1<<20];
voidhpath(intu,intseen){
if(memo[u][seen])return;
memo[u][seen]=true;

if(u==t){/*checkthatseen==(1<<n)1(seeneveryvertex)*/}
else{
for(intv=0;v<n;v++)
if(!(seen&(1<<v))&&graph[u][v])
hpath(v,seen|(1<<v));
}
}

Initially,wewouldcallhpath()withu=sandseen=(1<<s)indicatingthatweareatsandhaveseen
salready.Thefirsttwolinesmakesurethathpath()isnevercalledtwicewiththesamevaluesofu
andseenthereisnoreasontodothesameworktwice.memo[][]isassumedtobeinitialisedtofalse
tostartwith.

Noteagainthatwearetradingmemory(20MBinthiscase)foranimprovementinrunningtime.This
isthemainadvantageofmemoization.Dynamicprogramming(DP)isatechniqueforfillingoutthe
memoizationtableiteratively,insteadofusingrecursion,andwewilllookatitnext.Manypeopleuse
thetermsDPandmemoizationinterchangeably.