blog/assets/js/1cd58e77.1abe79ba.js
github-actions[bot] df70c7553e deploy: 1b7d0b6199
2023-12-28 17:55:58 +00:00

1 line
No EOL
13 KiB
JavaScript

"use strict";(self.webpackChunkfi=self.webpackChunkfi||[]).push([[1547],{32090:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>o,metadata:()=>r,toc:()=>d});var s=n(85893),i=n(11151);const o={id:"bottom-up-dp",slug:"/recursion/pyramid-slide-down/bottom-up-dp",title:"Bottom-up DP solution",description:"Bottom-up DP solution of the Pyramid Slide Down.\n",tags:["java","dynamic-programming","bottom-up-dp"],last_updated:{date:new Date("2023-08-17T00:00:00.000Z")}},a="Bottom-up dynamic programming",r={id:"recursion/2023-08-17-pyramid-slide-down/bottom-up-dp",title:"Bottom-up DP solution",description:"Bottom-up DP solution of the Pyramid Slide Down.\n",source:"@site/algorithms/04-recursion/2023-08-17-pyramid-slide-down/04-bottom-up-dp.md",sourceDirName:"04-recursion/2023-08-17-pyramid-slide-down",slug:"/recursion/pyramid-slide-down/bottom-up-dp",permalink:"/algorithms/recursion/pyramid-slide-down/bottom-up-dp",draft:!1,unlisted:!1,editUrl:"https://github.com/mfocko/blog/tree/main/algorithms/04-recursion/2023-08-17-pyramid-slide-down/04-bottom-up-dp.md",tags:[{label:"java",permalink:"/algorithms/tags/java"},{label:"dynamic-programming",permalink:"/algorithms/tags/dynamic-programming"},{label:"bottom-up-dp",permalink:"/algorithms/tags/bottom-up-dp"}],version:"current",lastUpdatedAt:1703786024,formattedLastUpdatedAt:"Dec 28, 2023",sidebarPosition:4,frontMatter:{id:"bottom-up-dp",slug:"/recursion/pyramid-slide-down/bottom-up-dp",title:"Bottom-up DP solution",description:"Bottom-up DP solution of the Pyramid Slide Down.\n",tags:["java","dynamic-programming","bottom-up-dp"],last_updated:{date:"2023-08-17T00:00:00.000Z"}},sidebar:"autogeneratedBar",previous:{title:"Top-down DP solution",permalink:"/algorithms/recursion/pyramid-slide-down/top-down-dp"},next:{title:"Red-Black Trees",permalink:"/algorithms/category/red-black-trees"}},l={},d=[{value:"Time complexity",id:"time-complexity",level:2},{value:"Memory complexity",id:"memory-complexity",level:2}];function c(e){const t={a:"a",admonition:"admonition",annotation:"annotation",code:"code",em:"em",h1:"h1",h2:"h2",hr:"hr",li:"li",math:"math",mdxAdmonitionTitle:"mdxAdmonitionTitle",mi:"mi",mn:"mn",mo:"mo",mrow:"mrow",ol:"ol",p:"p",pre:"pre",section:"section",semantics:"semantics",span:"span",strong:"strong",sup:"sup",...(0,i.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.h1,{id:"bottom-up-dynamic-programming",children:"Bottom-up dynamic programming"}),"\n",(0,s.jsxs)(t.p,{children:["If you try to think in depth about the top-down DP solution, you might notice\nthat the ",(0,s.jsx)(t.em,{children:"core"})," of it stands on caching the calculations that have been already\ndone on the lower \u201clevels\u201d of the pyramid. Our bottom-up implementation will be\nusing this fact!"]}),"\n",(0,s.jsxs)(t.admonition,{type:"tip",children:[(0,s.jsxs)(t.p,{children:["As I have said in the ",(0,s.jsx)(t.em,{children:"top-down DP"})," section, it is the easiest way to implement\nDP (unless the cached function has complicated parameters, in that case it might\nget messy)."]}),(0,s.jsx)(t.p,{children:"Bottom-up dynamic programming can be more effective, but may be more complicated\nto implement right from the beginning."})]}),"\n",(0,s.jsx)(t.p,{children:"Let's see how we can implement it:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-java",children:"public static int longestSlideDown(int[][] pyramid) {\n // In the beginning we declare new array. At this point it is easier to just\n // work with the one dimension, i.e. just allocating the space for the rows.\n int[][] slideDowns = new int[pyramid.length][];\n\n // Bottom row gets just copied, there's nothing else to do\u2026 It's the base\n // case.\n slideDowns[pyramid.length - 1] = Arrays.copyOf(pyramid[pyramid.length - 1],\n pyramid[pyramid.length - 1].length);\n\n // Then we need to propagate the found slide downs for each of the levels\n // above.\n for (int y = pyramid.length - 2; y >= 0; --y) {\n // We start by copying the values lying in the row we're processing.\n // They get included in the final sum and we need to allocate the space\n // for the precalculated slide downs anyways.\n int[] row = Arrays.copyOf(pyramid[y], pyramid[y].length);\n\n // At this we just need to \u201cfetch\u201d the partial results from \u201cneighbours\u201d\n for (int x = 0; x < row.length; ++x) {\n // We look under our position, since we expect the rows to get\n // shorter, we can safely assume such position exists.\n int under = slideDowns[y + 1][x];\n\n // Then we have a look to the right, such position doesn't have to\n // exist, e.g. on the right edge, so we validate the index, and if\n // it doesn't exist, we just assign minimum of the \u2039int\u203a which makes\n // sure that it doesn't get picked in the \u2039Math.max()\u203a call.\n int toRight = x + 1 < slideDowns[y + 1].length\n ? slideDowns[y + 1][x + 1]\n : Integer.MIN_VALUE;\n\n // Finally we add the best choice at this point.\n row[x] += Math.max(under, toRight);\n }\n\n // And save the row we've just calculated partial results for to the\n // \u201ctable\u201d.\n slideDowns[y] = row;\n }\n\n // At the end we can find our seeked slide down at the top cell.\n return slideDowns[0][0];\n}\n"})}),"\n",(0,s.jsx)(t.p,{children:"I've tried to explain the code as much as possible within the comments, since it\nmight be more beneficial to see right next to the \u201coffending\u201d lines."}),"\n",(0,s.jsxs)(t.p,{children:["As you can see, in this approach we go from the other side",(0,s.jsx)(t.sup,{children:(0,s.jsx)(t.a,{href:"#user-content-fn-1",id:"user-content-fnref-1","data-footnote-ref":!0,"aria-describedby":"footnote-label",children:"1"})}),", the bottom of\nthe pyramid and propagate the partial results up."]}),"\n",(0,s.jsxs)(t.admonition,{type:"info",children:[(0,s.jsxs)(t.mdxAdmonitionTitle,{children:["How is this different from the ",(0,s.jsx)(t.em,{children:"greedy"})," solution???"]}),(0,s.jsxs)(t.p,{children:["If you try to compare them, you might find a very noticable difference. The\ngreedy approach is going from the top to the bottom without ",(0,s.jsx)(t.strong,{children:"any"})," knowledge of\nwhat's going on below. On the other hand, bottom-up DP is going from the bottom\n(",(0,s.jsx)(t.em,{children:"DUH\u2026"}),") and ",(0,s.jsx)(t.strong,{children:"propagates"})," the partial results to the top. The propagation is\nwhat makes sure that at the top I don't choose the best ",(0,s.jsx)(t.strong,{children:"local"})," choice, but\nthe best ",(0,s.jsx)(t.strong,{children:"overall"})," result I can achieve."]})]}),"\n",(0,s.jsx)(t.h2,{id:"time-complexity",children:"Time complexity"}),"\n",(0,s.jsx)(t.p,{children:"Time complexity of this solution is rather simple. We allocate an array for the\nrows and then for each row, we copy it and adjust the partial results. Doing\nthis we get:"}),"\n",(0,s.jsx)(t.span,{className:"katex-display",children:(0,s.jsxs)(t.span,{className:"katex",children:[(0,s.jsx)(t.span,{className:"katex-mathml",children:(0,s.jsx)(t.math,{xmlns:"http://www.w3.org/1998/Math/MathML",display:"block",children:(0,s.jsxs)(t.semantics,{children:[(0,s.jsxs)(t.mrow,{children:[(0,s.jsx)(t.mi,{mathvariant:"script",children:"O"}),(0,s.jsx)(t.mo,{stretchy:"false",children:"("}),(0,s.jsx)(t.mi,{children:"r"}),(0,s.jsx)(t.mi,{children:"o"}),(0,s.jsx)(t.mi,{children:"w"}),(0,s.jsx)(t.mi,{children:"s"}),(0,s.jsx)(t.mo,{children:"+"}),(0,s.jsx)(t.mn,{children:"2"}),(0,s.jsx)(t.mi,{children:"n"}),(0,s.jsx)(t.mo,{stretchy:"false",children:")"})]}),(0,s.jsx)(t.annotation,{encoding:"application/x-tex",children:"\\mathcal{O}(rows + 2n)"})]})})}),(0,s.jsxs)(t.span,{className:"katex-html","aria-hidden":"true",children:[(0,s.jsxs)(t.span,{className:"base",children:[(0,s.jsx)(t.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,s.jsx)(t.span,{className:"mord mathcal",style:{marginRight:"0.02778em"},children:"O"}),(0,s.jsx)(t.span,{className:"mopen",children:"("}),(0,s.jsx)(t.span,{className:"mord mathnormal",children:"ro"}),(0,s.jsx)(t.span,{className:"mord mathnormal",style:{marginRight:"0.02691em"},children:"w"}),(0,s.jsx)(t.span,{className:"mord mathnormal",children:"s"}),(0,s.jsx)(t.span,{className:"mspace",style:{marginRight:"0.2222em"}}),(0,s.jsx)(t.span,{className:"mbin",children:"+"}),(0,s.jsx)(t.span,{className:"mspace",style:{marginRight:"0.2222em"}})]}),(0,s.jsxs)(t.span,{className:"base",children:[(0,s.jsx)(t.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,s.jsx)(t.span,{className:"mord",children:"2"}),(0,s.jsx)(t.span,{className:"mord mathnormal",children:"n"}),(0,s.jsx)(t.span,{className:"mclose",children:")"})]})]})]})}),"\n",(0,s.jsx)(t.p,{children:"Of course, this is an upper bound, since we iterate through the bottom row only\nonce."}),"\n",(0,s.jsx)(t.h2,{id:"memory-complexity",children:"Memory complexity"}),"\n",(0,s.jsxs)(t.p,{children:["We're allocating an array for the pyramid ",(0,s.jsx)(t.strong,{children:"again"})," for our partial results, so\nwe get:"]}),"\n",(0,s.jsx)(t.span,{className:"katex-display",children:(0,s.jsxs)(t.span,{className:"katex",children:[(0,s.jsx)(t.span,{className:"katex-mathml",children:(0,s.jsx)(t.math,{xmlns:"http://www.w3.org/1998/Math/MathML",display:"block",children:(0,s.jsxs)(t.semantics,{children:[(0,s.jsxs)(t.mrow,{children:[(0,s.jsx)(t.mi,{mathvariant:"script",children:"O"}),(0,s.jsx)(t.mo,{stretchy:"false",children:"("}),(0,s.jsx)(t.mi,{children:"n"}),(0,s.jsx)(t.mo,{stretchy:"false",children:")"})]}),(0,s.jsx)(t.annotation,{encoding:"application/x-tex",children:"\\mathcal{O}(n)"})]})})}),(0,s.jsx)(t.span,{className:"katex-html","aria-hidden":"true",children:(0,s.jsxs)(t.span,{className:"base",children:[(0,s.jsx)(t.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,s.jsx)(t.span,{className:"mord mathcal",style:{marginRight:"0.02778em"},children:"O"}),(0,s.jsx)(t.span,{className:"mopen",children:"("}),(0,s.jsx)(t.span,{className:"mord mathnormal",children:"n"}),(0,s.jsx)(t.span,{className:"mclose",children:")"})]})})]})}),"\n",(0,s.jsxs)(t.admonition,{type:"tip",children:[(0,s.jsx)(t.p,{children:"If we were writing this in C++ or Rust, we could've avoided that, but not\nreally."}),(0,s.jsxs)(t.p,{children:["C++ would allow us to ",(0,s.jsx)(t.strong,{children:"copy"})," the pyramid rightaway into the parameter, so we\nwould be able to directly change it. However it's still a copy, even though we\ndon't need to allocate anything ourselves. It's just implicitly done for us."]}),(0,s.jsxs)(t.p,{children:["Rust is more funny in this case. If the pyramids weren't used after the call of\n",(0,s.jsx)(t.code,{children:"longest_slide_down"}),", it would simply ",(0,s.jsx)(t.strong,{children:"move"})," them into the functions. If they\nwere used afterwards, the compiler would force you to either borrow it, or\n",(0,s.jsx)(t.em,{children:"clone-and-move"})," for the function."]}),(0,s.jsx)(t.hr,{}),(0,s.jsxs)(t.p,{children:["Since we're doing it in Java, we get a reference to the ",(0,s.jsx)(t.em,{children:"original"})," array and we\ncan't do whatever we want with it."]})]}),"\n",(0,s.jsx)(t.h1,{id:"summary",children:"Summary"}),"\n",(0,s.jsxs)(t.p,{children:["And we've finally reached the end. We have seen 4 different \u201csolutions\u201d",(0,s.jsx)(t.sup,{children:(0,s.jsx)(t.a,{href:"#user-content-fn-2",id:"user-content-fnref-2","data-footnote-ref":!0,"aria-describedby":"footnote-label",children:"2"})})," of\nthe same problem using different approaches. Different approaches follow the\norder in which you might come up with them, each approach influences its\nsuccessor and represents the way we can enhance the existing implementation."]}),"\n",(0,s.jsx)(t.hr,{}),"\n",(0,s.jsx)(t.admonition,{title:"source",type:"info",children:(0,s.jsxs)(t.p,{children:["You can find source code referenced in the text\n",(0,s.jsx)(t.a,{href:"pathname:///files/algorithms/recursion/pyramid-slide-down.tar.gz",children:"here"}),"."]})}),"\n",(0,s.jsxs)(t.section,{"data-footnotes":!0,className:"footnotes",children:[(0,s.jsx)(t.h2,{className:"sr-only",id:"footnote-label",children:"Footnotes"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{id:"user-content-fn-1",children:["\n",(0,s.jsxs)(t.p,{children:["definitely not an RHCP reference ","\ud83d\ude09 ",(0,s.jsx)(t.a,{href:"#user-content-fnref-1","data-footnote-backref":"","aria-label":"Back to reference 1",className:"data-footnote-backref",children:"\u21a9"})]}),"\n"]}),"\n",(0,s.jsxs)(t.li,{id:"user-content-fn-2",children:["\n",(0,s.jsxs)(t.p,{children:["one was not correct, thus the quotes ",(0,s.jsx)(t.a,{href:"#user-content-fnref-2","data-footnote-backref":"","aria-label":"Back to reference 2",className:"data-footnote-backref",children:"\u21a9"})]}),"\n"]}),"\n"]}),"\n"]})]})}function h(e={}){const{wrapper:t}={...(0,i.a)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},11151:(e,t,n)=>{n.d(t,{Z:()=>r,a:()=>a});var s=n(67294);const i={},o=s.createContext(i);function a(e){const t=s.useContext(o);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),s.createElement(o.Provider,{value:t},e.children)}}}]);