<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Pooja Das on Medium]]></title>
        <description><![CDATA[Stories by Pooja Das on Medium]]></description>
        <link>https://medium.com/@poojadas053?source=rss-6de368060ada------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Q-C9Yp6v2D_Tgv84P5dlfQ.jpeg</url>
            <title>Stories by Pooja Das on Medium</title>
            <link>https://medium.com/@poojadas053?source=rss-6de368060ada------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 19 May 2026 19:29:45 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@poojadas053/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Don’t Use`JSON.stringify()` and `{…obj}`, OnBoard`structuredClone()`!]]></title>
            <link>https://medium.com/@poojadas053/dont-use-json-stringify-and-obj-onboard-structuredclone-eb5bf9084aa8?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/eb5bf9084aa8</guid>
            <category><![CDATA[json]]></category>
            <category><![CDATA[json-deep-copy]]></category>
            <category><![CDATA[javascript-tips]]></category>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 08 Sep 2024 17:55:35 GMT</pubDate>
            <atom:updated>2024-09-08T18:14:12.681Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://dev.to/t/webdev">#webdev</a><a href="https://dev.to/t/javascript">#javascript</a><a href="https://dev.to/t/beginners">#beginners</a><a href="https://dev.to/t/programming">#programming</a></p><p><strong>What is </strong><strong>structuredClone()?</strong></p><ul><li>structuredClone() is a global function introduced in 2022 that enables deep cloning of JavaScript objects. Unlike traditional methods like JSON.stringify() and JSON.parse(), which struggle with complex structures and circular references, structuredClone() handles these challenges effortlessly.</li></ul><p><strong>Why is it a game-changer?</strong></p><ul><li>It’s a robust tool for creating true deep clones, preserving the integrity of nested objects and circular references without the need for extra logic or workarounds. Plus, it’s available in modern environments, including Web Workers.</li></ul><h4>1. Simple Object Cloning: The Basics</h4><ul><li><strong>Using </strong><strong>{...obj} (Shallow Copy)</strong></li></ul><pre>const original = { name: &quot;Alice&quot;, details: { age: 25 } };<br>  const shallowCopy = { ...original };</pre><pre>  shallowCopy.details.age = 30;</pre><pre>  console.log(original.details.age); // 30<br>  console.log(shallowCopy.details.age); // 30</pre><p>The spread operator {...obj} only creates a shallow copy. The details object is not deeply cloned, so changes to shallowCopy.details affect the original details as well.</p><p><strong>Using </strong><strong>JSON.stringify() + </strong><strong>JSON.parse() (Deep Copy)</strong></p><pre>const original = { name: &quot;Alice&quot;, details: { age: 25 } };<br>  const deepCopy = JSON.parse(JSON.stringify(original));</pre><pre>  deepCopy.details.age = 30;</pre><pre>  console.log(original.details.age); // 25<br>  console.log(deepCopy.details.age); // 30</pre><p>This method creates a deep copy, but it has limitations: it cannot handle functions, undefined, or circular references.</p><p><strong>Using </strong><strong>structuredClone() (Deep Copy)</strong></p><pre>const original = { name: &quot;Alice&quot;, details: { age: 25 } };<br>  const clone = structuredClone(original);</pre><pre>  clone.details.age = 30;</pre><pre>  console.log(original.details.age); // 25<br>  console.log(clone.details.age); // 30</pre><p>structuredClone() creates a deep clone, preserving the structure without any of the limitations of JSON.stringify() and handling complex data types like circular references and undefined.</p><h4>2. Handling Circular References: A Challenge</h4><ul><li><strong>Circular Reference with </strong><strong>{...obj}</strong></li></ul><pre>const original = { name: &quot;Alice&quot; };<br>  original.self = original;</pre><pre>  // This will cause an error:<br>  const shallowCopy = { ...original }; // TypeError: Converting circular structure to JSON</pre><p>{...obj} can’t handle circular references, resulting in an error.</p><ul><li><strong>Circular Reference with </strong><strong>JSON.stringify()</strong></li></ul><pre>const original = { name: &quot;Alice&quot; };<br>  original.self = original;</pre><pre>  // This will cause an error:<br>  const jsonCopy = JSON.parse(JSON.stringify(original)); // TypeError: Converting circular structure to JSON</pre><p>JSON.stringify() also fails with circular references, throwing an error.</p><ul><li><strong>Circular Reference with </strong><strong>structuredClone()</strong></li></ul><pre>const original = { name: &quot;Alice&quot; };<br>  original.self = original;</pre><pre>  const clone = structuredClone(original);</pre><pre>  console.log(clone !== original); // true<br>  console.log(clone.self === clone); // true</pre><p>structuredClone() seamlessly handles circular references, creating a proper deep clone without errors.</p><h4>3. Cloning with Functions and undefined: Another Test</h4><ul><li><strong>Using </strong><strong>{...obj}</strong></li></ul><pre>const original = { name: &quot;Alice&quot;, greet: () =&gt; &quot;Hello!&quot;, value: undefined };<br>  const shallowCopy = { ...original };</pre><pre>  console.log(shallowCopy.greet()); // &quot;Hello!&quot;<br>  console.log(shallowCopy.value); // undefined</pre><p>{...obj} copies functions and undefined as expected, but only shallowly.</p><ul><li><strong>Using </strong><strong>JSON.stringify()</strong></li></ul><pre>const original = { name: &quot;Alice&quot;, greet: () =&gt; &quot;Hello!&quot;, value: undefined };<br>  const jsonCopy = JSON.parse(JSON.stringify(original));</pre><pre>  console.log(jsonCopy.greet); // undefined<br>  console.log(jsonCopy.value); // undefined</pre><p>JSON.stringify() cannot serialize functions or undefined, resulting in their loss in the cloned object.</p><ul><li><strong>Using </strong><strong>structuredClone()</strong></li></ul><pre>const original = { name: &quot;Alice&quot;, greet: () =&gt; &quot;Hello!&quot;, value: undefined };<br>  const clone = structuredClone(original);</pre><pre>  console.log(clone.greet); // undefined<br>  console.log(clone.value); // undefined</pre><p>structuredClone() also does not clone functions but preserves undefined values, making it more reliable than JSON.stringify() for complex objects.</p><h4>4. Speed and Efficiency: A Performance Note</h4><ul><li><strong>Efficiency with Large Data</strong></li></ul><pre>const largeArray = new Array(1e6).fill({ key: &quot;value&quot; });</pre><pre>  console.time(&quot;structuredClone&quot;);<br>  const clone = structuredClone(largeArray);<br>  console.timeEnd(&quot;structuredClone&quot;);</pre><pre>  console.time(&quot;JSON.stringify + JSON.parse&quot;);<br>  const jsonCopy = JSON.parse(JSON.stringify(largeArray));<br>  console.timeEnd(&quot;JSON.stringify + JSON.parse&quot;);</pre><p>structuredClone() is often faster than JSON.stringify() + JSON.parse() for large, complex data, and avoids the pitfalls of serializing and deserializing.</p><h4>5. Conclusion: Why structuredClone() is the Future</h4><ul><li><strong>Reliability</strong>: Handles circular references, functions, and undefined values more predictably.</li><li><strong>Efficiency</strong>: Performs deep cloning faster for large datasets and doesn’t require workarounds.</li><li><strong>Simplicity</strong>: One method to rule them all — no more choosing between {...obj}, JSON.stringify(), or custom deep clone functions.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eb5bf9084aa8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Word loud]]></title>
            <link>https://medium.com/@poojadas053/word-loud-ee77e83a89f4?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/ee77e83a89f4</guid>
            <category><![CDATA[word-cloud]]></category>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:41:43 GMT</pubDate>
            <atom:updated>2024-08-18T18:31:40.938Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Word Cloud</strong></h3><p><strong>You want to build a word cloud, an infographic where the size of a word corresponds to how often it appears in the body of text.</strong></p><p>To do this, you’ll need data. Write code that takes a long string and builds its word cloud data in a hash map, where the keys are words and the values are the number of times the words occurred.</p><p><strong>Think about capitalized words</strong>. For example, look at these sentences:</p><pre>&quot;After beating the eggs, Dana read the next step:&quot;<br>&quot;Add milk and eggs, then add flour and sugar.&quot;</pre><p>What do we want to do with “After”, “Dana”, and “add”? In this example, your final hash map should include <em>one</em> “Add” or “add” with a value of 22. Make <em>reasonable</em> (not necessarily <em>perfect</em>) decisions about cases like “After” and “Dana”.</p><p>Assume the input will only contain words and standard punctuation.</p><p>You could make a reasonable argument to use <strong>regex</strong> in your solution. We won’t, mainly because performance is difficult to measure and can get pretty bad.</p><p>Are you sure your code handles hyphenated words and standard punctuation?</p><p>Are you sure your code <strong>reasonably handles the same word with different capitalization?</strong></p><p>Try these sentences:</p><pre>&quot;We came, we saw, we conquered...then we ate Bill&#39;s (Mille-Feuille) cake.&quot;<br>&quot;The bill came to five dollars.&quot;</pre><p>We can do this in O(n)<em>O</em>(<em>n</em>) runtime and space.</p><p>The final hash map we return should be the <strong><em>only</em></strong> data structure whose length is tied to n<em>n</em>.</p><p>We should only iterate through our input string <strong>once</strong>.</p><h3>Breakdown</h3><p>We’ll have to go through the entire input string, and we’re returning a hash map with every unique word. In the worst case every word is different, so our runtime and space cost will both be at least O(n)<em>O</em>(<em>n</em>).</p><p>This challenge has several parts. Let’s break them down.</p><ol><li><strong>Splitting the words</strong> from the input string</li><li><strong>Populating the hash map</strong> with each word</li><li><strong>Handling words that are both uppercase and lowercase</strong> in the input string</li></ol><p>How would you start the first part?</p><p>We could use the built-in String.split() method to separate our words, but if we just split on spaces we’d have to iterate over all the words before or after splitting to clean up the punctuation. And consider em dashes or ellipses, which <em>aren’t</em> surrounded by spaces but nonetheless separate words. Instead, we’ll make our <em>own</em> splitWords() method, which will let us iterate over the input string only once.</p><p>We’ll check if each character is a letter with Character.isLetter().</p><p>Now how can we split each word? Let’s assume, for now, that our helper method will return a list of words.</p><p>We’ll iterate over all the characters in the input string. How can we identify when we’ve reached the end of a word?</p><p>Here’s a simple example. It doesn’t work perfectly yet — you’ll need to add code to handle the end of the input string, hyphenated words, punctuation, and edge cases.</p><pre>public static List&lt;String&gt; splitWords(String inputString) {<br>List&lt;String&gt; words = new ArrayList&lt;&gt;();<br>    int currentWordStartIndex = 0;<br>    int currentWordLength = 0;<br>    for (int i = 0; i &lt; inputString.length(); i++) {<br>        char c = inputString.charAt(i);<br>        if (Character.isLetter(c)) {<br>            if (currentWordLength == 0) {<br>                currentWordStartIndex = i;<br>            }<br>            currentWordLength++;<br>        } else {<br>            String currentWord = inputString.substring(currentWordStartIndex,<br>                currentWordStartIndex + currentWordLength);<br>            words.add(currentWord);<br>            currentWordLength = 0;<br>        }<br>    }<br>    return words;<br>}</pre><p>Careful — if you thought of building up the word character by character (using +=), you’d be doing a lot more work than you probably think. Every time we append a character to a string, Java makes a whole new string. If our input is one long word, then creating all these copies is O(n2)<em>O</em>(<em>n</em>2) time.</p><p>Instead, we keep track of the <em>index</em> where our word starts and its current length. Once we hit a space, we can extract the substring with our word and append it to the list. That keeps our split method at O(n)<em>O</em>(<em>n</em>) time.</p><p>Another option would be to use a StringBuilder. Lots of different ways to work with strings.</p><p>Now we’ve solved the first part of the challenge, splitting the words. The next part is <strong>populating our hash map with unique words.</strong> What do we do with each word?</p><p>If the word is in the hash map, we’ll increment its count. Otherwise, we’ll add it to the hash map with a count of 1.</p><pre>private Map&lt;String, Integer&gt; wordsToCounts = new HashMap&lt;&gt;();<br>public void addWordToHashMap(String word) {<br>    if (wordsToCounts.containsKey(word)) {<br>        wordsToCounts.put(word, wordsToCounts.get(word) + 1);<br>    } else {<br>        wordsToCounts.put(word, 1);<br>    }<br>}</pre><p>Alright, last part! <strong>How should we handle words that are uppercase and lowercase?</strong></p><p>Consider these sentences:</p><pre>&quot;We came, we saw, we ate cake.&quot;<br>&quot;Friends, Romans, countrymen! Let us eat cake.&quot;<br>&quot;New tourists in New York often wait in long lines for cronuts.&quot;</pre><p><strong>Take some time to think of possible approaches.</strong> What are some other sentences you might run into. What are all your options?</p><p>When are words that <em>should be</em> lowercase <em>not</em>?<br><em>Why</em> not?<br>What are the <em>ideal</em> cases we’d want in our hash map?</p><p>Here are a few options:</p><ol><li>Only make a word uppercase in our hash map if it is <em>always</em> uppercase in the original string.</li><li>Make a word uppercase in our hash map if it is <em>ever</em> uppercase in the original string.</li><li>Make a word uppercase in our hash map if it is ever uppercase in the original string <em>in a position that is not the first word of a sentence.</em></li><li>Use an API or other tool that identifies proper nouns.</li><li>Ignore case entirely and make every word lowercase.</li></ol><p><strong>What are the pros and cons for each one?</strong></p><p>Pros and cons include:</p><ol><li><strong>Only make a word uppercase in our hash map if it is <em>always</em> uppercase in the original string:</strong> this will have reasonable accuracy in very long strings where words are more likely to be included multiple times, but words that <em>only</em> ever occur as the first word in a sentence will always be included as uppercase.</li><li><strong>Make a word uppercase in our hash map if it is <em>ever</em> uppercase in the original string:</strong> this will ensure proper nouns are <em>always</em> uppercase, but any words that are <em>ever</em> at the start of sentences will always be uppercase too.</li><li><strong>Make a word uppercase in our hash map if it is ever uppercase in the original string <em>in a position that is not the first word of a sentence:</em></strong> this addresses the problem with option (2), but proper nouns that are <em>only</em> ever at the start of sentences will be made lowercase.</li><li><strong>Use an API or other tool that identifies proper nouns:</strong> this has a lot of potential to give us a high level of accuracy, but we’ll give up control over decisions, we’ll be relying on code we didn’t write, and our practical runtime may be significantly increased.</li><li><strong>Ignore case entirely and make every word lowercase:</strong> this will give us simplicity and consistency, but we’ll lose all accuracy for words that should be uppercase.</li></ol><p>Any of these could be considered reasonable. Importantly, <strong>none of them are perfect</strong>. They all have tradeoffs, and it is very difficult to write a highly accurate algorithm. Consider “cliff” and “bill” in these sentences:</p><pre>&quot;Cliff finished his cake and paid the bill.&quot;<br>&quot;Bill finished his cake at the edge of the cliff.&quot;</pre><p>You can choose whichever of the options you’d like, or another option you thought of. For this breakdown, we’re going to choose option (1).</p><p>Now, how do we update our addWordToHashMap() method to avoid duplicate words?</p><p>Think about the different possibilities:</p><ol><li>The word is <strong>uppercase or lowercase.</strong></li><li>The word is <strong>already in the hash map</strong> or not.</li><li><strong>A different case of the word is already in the hash map</strong> or not.</li></ol><p>Moving forward, we can either:</p><ol><li>Check for words that are in the hash map in <strong>both</strong> cases <em>when we’re done populating the hash map.</em> If we add “Vanilla” three times and “vanilla” eight times, we’ll combine them into <em>one</em> “vanilla” at the end with a value 1111.</li><li><em>Avoid </em><strong><em>ever</em></strong><em> having a word in our hash map that’s both uppercase and lowercase.</em> As we add “Vanilla”s and “vanilla”s, we’d <em>always only ever have one version</em> in our hash map.</li></ol><p>We’ll choose the second approach since it will save us a walk through our hash map. How should we start?</p><p>If the word we’re adding is already in the hash map in its current case, let’s increment its count. What if it’s <em>not</em> in the hash map?</p><p>There are three possibilities:</p><ol><li><strong>A lowercase version is in the hash map</strong> (in which case we <em>know</em> our input word is uppercase, because if it is lowercase and already in the hash map it would have passed our first check and we’d have just incremented its count)</li><li><strong>An uppercase version is in the hash map</strong> (so we <em>know</em> our input word is lowercase)</li><li><strong>The word is not in the hash map</strong> in any case</li></ol><p>Let’s start with the first possibility. What do we want to do?</p><p>Because we only include a word as uppercase if it is always uppercase, we simply increment the lowercase version’s count.</p><pre>// current hash map<br>// {blue=3}<br>// adding<br>// &quot;Blue&quot;<br>// code<br>int lowerCaseCount = wordsToCounts.get(word.toLowerCase());<br>wordsToCounts.put(word.toLowerCase(), lowerCaseCount + 1);<br>// updated hash map<br>// {blue=4}</pre><p>What about the second possibility?</p><p>This is a little more complicated. We need to remove the uppercase version from our hash map if we encounter a lowercase version. <strong>But we still need the uppercase version’s count!</strong></p><pre>// current hash map<br>// {Yellow=6}<br>// adding<br>// &quot;yellow&quot;<br>// code (we will write our &quot;capitalize()&quot; method later)<br>int capitalizedCount = wordsToCounts.get(capitalize(word));<br>wordsToCounts.put(word, capitalizedCount + 1);<br>wordsToCounts.remove(capitalize(word));<br>// updated hash map<br>// {yellow=7}</pre><p>Finally, what if the word is not in the hash map at all?</p><p>Easy — we add it and give it a count of 1.</p><pre>// current hash map<br>// {purple=2}<br>// adding<br>// &quot;indigo&quot;<br>// code<br>wordsToCounts.put(word, 1);<br>// updated hash map<br>// {purple=2, indigo=1}</pre><p>Now we have all our pieces! We can split words, add them to a hash map, and track the number of times each word occurs without having duplicate words of the same case. Can we improve our solution?</p><p>Let’s look at our runtime and space cost. We iterate through every character in the input string once and then every word in our list once. That’s a runtime of O(n)<em>O</em>(<em>n</em>), which is the best we can achieve for this challenge (we <em>have</em> to look at the entire input string). The space we’re using includes a list for each word and a hash map for every unique word. Our worst case is that every word is different, so our space cost is also O(n)<em>O</em>(<em>n</em>), which is also the best we can achieve for this challenge (we <em>have</em> to return a hash map of words).</p><p><strong>But we can still make some optimizations!</strong></p><p>How can we make our <em>space cost</em> even smaller?</p><p>We’re storing all our split words in a separate list. That at least doubles the memory we use! How can we eliminate the need for that list?</p><p>Right now, we store each word in our list <em>as we split them</em>. Instead, let’s just immediately populate each word in our hash map!</p><h3>Solution</h3><p>In our solution, we make three decisions:</p><ul><li><strong>We use a class.</strong> This allows us to tie our methods together, calling them on instances of our class instead of passing references.</li><li>To handle duplicate words with different cases, <strong>we choose to make a word uppercase in our hash map only if it is <em>always</em> uppercase in the original string</strong>. While this is a reasonable approach, it is <em>imperfect</em> (consider proper nouns that are also lowercase words, like “Bill” and “bill”).</li><li><strong>We build our own splitWords() method</strong> instead of using a built-in one. This allows us to pass each word to our addWordToHashMap() method <em>as it was split</em>, and to split words and eliminate punctuation in <em>one</em> iteration.</li></ul><p>To split the words in the input string and populate a hash map of the unique words to the number of times they occurred, we:</p><ol><li><strong>Split words</strong> by spaces, em dashes, and ellipses — making sure to include hyphens surrounded by characters. We also include all apostrophes (which will handle contractions nicely but will break possessives into separate words).</li><li><strong>Populate the words in our hash map</strong> as they are identified, checking if the word is already in our hash map in its current case or another case.</li></ol><p>If the input word is <em>uppercase</em> and <em>there’s a lowercase version in the hash map</em>, we increment the lowercase version’s count. If the input word is <em>lowercase</em> and <em>there’s an uppercase version in the hash map</em>, we “demote” the uppercase version by adding the lowercase version and giving it the uppercase version’s count.</p><pre>import java.util.Collections;<br>import java.util.HashMap;<br>import java.util.Map;<br>public class WordCloudData {<br>    private Map&lt;String, Integer&gt; wordsToCounts = new HashMap&lt;&gt;();<br>    public WordCloudData(String inputString) {<br>        populateWordsToCounts(inputString);<br>    }<br>    public Map&lt;String, Integer&gt; getWordsToCounts() {<br>        return wordsToCounts;<br>    }<br>    private void populateWordsToCounts(String inputString) {<br>        // iterates over each character in the input string, splitting<br>        // words and passing them to addWordToHashMap()<br>        int currentWordStartIndex = 0;<br>        int currentWordLength = 0;<br>        for (int i = 0; i &lt; inputString.length(); i++) {<br>            char character = inputString.charAt(i);<br>            // if we reached the end of the string we check if the last<br>            // character is a letter and add the last word to our hash map<br>            if (i == inputString.length() - 1) {<br>                if (Character.isLetter(character)) {<br>                    currentWordLength++;<br>                }<br>                if (currentWordLength &gt; 0) {<br>                    String currentWord = inputString.substring(currentWordStartIndex,<br>                        currentWordStartIndex + currentWordLength);<br>                    addWordToHashMap(currentWord);<br>                }<br>            // if we reach a space or emdash we know we&#39;re at the end of a word<br>            // so we add it to our hash map and reset our current word<br>            } else if (character == &#39; &#39; || character == &#39;\u2014&#39;) {<br>                if (currentWordLength &gt; 0) {<br>                    String currentWord = inputString.substring(currentWordStartIndex,<br>                        currentWordStartIndex + currentWordLength);<br>                    addWordToHashMap(currentWord);<br>                    currentWordLength = 0;<br>                }<br>            // we want to make sure we split on ellipses so if we get two periods in<br>            // a row we add the current word to our hash map and reset our current word<br>            } else if (character == &#39;.&#39;) {<br>                if (i &lt; inputString.length() - 1 &amp;&amp; inputString.charAt(i + 1) == &#39;.&#39;) {<br>                    if (currentWordLength &gt; 0) {<br>                        String currentWord = inputString.substring(currentWordStartIndex,<br>                            currentWordStartIndex + currentWordLength);<br>                        addWordToHashMap(currentWord);<br>                        currentWordLength = 0;<br>                    }<br>                }<br>            // if the character is a letter or an apostrophe, we add it to our current word<br>            } else if (Character.isLetter(character) || character == &#39;\&#39;&#39;) {<br>                if (currentWordLength == 0) {<br>                    currentWordStartIndex = i;<br>                }<br>                currentWordLength++;<br>            // if the character is a hyphen, we want to check if it&#39;s surrounded by letters<br>            // if it is, we add it to our current word<br>            } else if (character == &#39;-&#39;) {<br>                if (i &gt; 0 &amp;&amp; Character.isLetter(inputString.charAt(i - 1))<br>                        &amp;&amp; Character.isLetter(inputString.charAt(i + 1))) {<br>                    currentWordLength++;<br>                } else {<br>                    if (currentWordLength &gt; 0) {<br>                        String currentWord = inputString.substring(currentWordStartIndex,<br>                            currentWordStartIndex + currentWordLength);<br>                        addWordToHashMap(currentWord);<br>                        currentWordLength = 0;<br>                    }<br>                }<br>            }<br>        }<br>    }<br>    private void addWordToHashMap(String word) {<br>        // if the word is already in the hash map we increment its count<br>        if (wordsToCounts.containsKey(word)) {<br>            wordsToCounts.put(word, wordsToCounts.get(word) + 1);<br>        // if a lowercase version is in the hash map, we know our input word must be uppercase<br>        // but we only include uppercase words if they&#39;re always uppercase<br>        // so we just increment the lowercase version&#39;s count<br>        } else if (wordsToCounts.containsKey(word.toLowerCase())) {<br>            int newCount = wordsToCounts.get(word.toLowerCase()) + 1;<br>            wordsToCounts.put(word.toLowerCase(), newCount);<br>        // if an uppercase version is in the hash map, we know our input word must be lowercase.<br>        // since we only include uppercase words if they&#39;re always uppercase, we add the<br>        // lowercase version and give it the uppercase version&#39;s count<br>        } else if (wordsToCounts.containsKey(capitalize(word))) {<br>            int newCount = wordsToCounts.get(capitalize(word)) + 1;<br>            wordsToCounts.put(word, newCount);<br>            wordsToCounts.remove(capitalize(word));<br>        // otherwise, the word is not in the hash map at all, lowercase or uppercase<br>        // so we add it to the hash map<br>        } else {<br>            wordsToCounts.put(word, 1);<br>        }<br>    }<br>    private String capitalize(String word) {<br>        return word.substring(0, 1).toUpperCase() + word.substring(1);<br>    }<br>}</pre><p>Complexity</p><p>Runtime and memory cost are both O(n)<em>O</em>(<em>n</em>). This is the best we can do because we have to look at <em>every</em> character in the input string and we have to return a hash map of <em>every</em> unique word. We optimized to only make <em>one</em> pass over our input and have only <em>one</em> O(n)<em>O</em>(<em>n</em>) data structure.</p><h3>Bonus</h3><ol><li>We haven’t explicitly talked about how to handle more complicated character sets. How would you make your solution work with more unicode characters? What changes need to be made to handle silly sentences like these:</li><li>I’m singing ♬ on a ☔ day.</li><li>☹ + ☕ = ☺.</li><li>We limited our input to letters, hyphenated words and punctuation. How would you expand your functionality to include numbers, email addresses, twitter handles, etc.?</li><li>How would you add functionality to identify phrases or words that belong together but aren’t hyphenated? (“Fire truck” or “Interview Cake”)</li><li>How could you improve your capitalization algorithm?</li><li>How would you avoid having duplicate words that are just plural or singular possessives?</li></ol><h3>What We Learned</h3><p>To handle capitalized words, there were lots of heuristics and approaches we could have used, each with their own strengths and weaknesses. Open-ended questions like this can really separate good engineers from great engineers.</p><p>Good engineers will come up with <em>a solution</em>, but great engineers will come up with <em>several solutions</em>, weigh them carefully, and choose the best solution for the given context. So as you’re running practice questions, challenge yourself to keep thinking even after you have a first solution. See how many solutions you can come up with. This will grow your ability to quickly see multiple ways to solve a problem, so you can figure out the <em>best</em> solution. And use the hints and gotchas on each Interview Cake question — they’re designed to help you cultivate this skill.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ee77e83a89f4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reverse Words]]></title>
            <link>https://medium.com/@poojadas053/reverse-words-05cc18103587?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/05cc18103587</guid>
            <category><![CDATA[reverse-words-in-a-string]]></category>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:37:34 GMT</pubDate>
            <atom:updated>2024-08-23T17:28:05.174Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>You’re working on a secret team solving coded transmissions.</strong></p><p>Your team is scrambling to decipher a recent message, worried it’s a plot to break into a major European National Cake Vault. The message has been <em>mostly</em> deciphered, but all the words are backward! Your colleagues have handed off the last step to you.</p><p>Write a method reverseWords() that takes a message as an array of characters and reverses the order of the words in place.</p><p>Why an array of characters instead of a string?</p><p>The goal of this question is to practice manipulating strings <em>in place</em>. Since we’re modifying the message, we need a <strong>mutable ↴ </strong>type like an array, instead of Java’s <em>immutable</em> strings.</p><p>For example:</p><pre>char[] message = { &#39;c&#39;, &#39;a&#39;, &#39;k&#39;, &#39;e&#39;, &#39; &#39;,<br>                   &#39;p&#39;, &#39;o&#39;, &#39;u&#39;, &#39;n&#39;, &#39;d&#39;, &#39; &#39;,<br>                   &#39;s&#39;, &#39;t&#39;, &#39;e&#39;, &#39;a&#39;, &#39;l&#39; };<br>reverseWords(message);<br>System.out.println(message);<br>// prints: &quot;steal pound cake&quot;</pre><p>When writing your method, assume the message contains only letters and spaces, and all words are separated by one space.</p><h3>Gotchas</h3><p>We can do this in O(1)<em>O</em>(1) space. Remember, <em>in place.</em></p><p>We can do this in O(n)<em>O</em>(<em>n</em>) time.</p><p>If you’re swapping individual words one at a time, consider what happens when the words are different lengths. Isn’t <em>each swap</em> O(n)<em>O</em>(<em>n</em>) time in the worst case?</p><h3>Breakdown</h3><p>Let’s start with a simpler problem. What if we wanted to <strong>reverse all the characters</strong> in the message?</p><p>Well, we could swap the first character with the last character, then the second character with the second to last character, and so on, moving towards the middle. Can you implement this in code?</p><pre>public static void reverseCharacters(char[] message) {<br>int leftIndex = 0;<br>    int rightIndex = message.length - 1;<br>    // walk towards the middle, from both sides<br>    while (leftIndex &lt; rightIndex) {<br>        // swap the left char and right char<br>        char temp = message[leftIndex];<br>        message[leftIndex] = message[rightIndex];<br>        message[rightIndex] = temp;<br>        leftIndex++;<br>        rightIndex--;<br>    }<br>}</pre><p>Ok, looks good. <strong>Does this help us?</strong></p><p>Can we use the same concept but apply it to <em>words</em> instead of <em>characters</em>?</p><p>Probably. We’ll have to figure out a couple things:</p><ol><li>How do we figure out where words begin and end?</li><li>Once we know the start and end indices of two words, how do we <em>swap</em> those two words?</li></ol><p>We could attack either of those first, but I’m already seeing a potential problem in terms of runtime. Can you guess what it is?</p><p>What happens when you swap two words that <em>aren’t</em> the same length?</p><pre>// the eagle has landed<br>{ &#39;t&#39;, &#39;h&#39;, &#39;e&#39;, &#39; &#39;, &#39;e&#39;, &#39;a&#39;, &#39;g&#39;, &#39;l&#39;, &#39;e&#39;, &#39; &#39;,<br>  &#39;h&#39;, &#39;a&#39;, &#39;s&#39;, &#39; &#39;, &#39;l&#39;, &#39;a&#39;, &#39;n&#39;, &#39;d&#39;, &#39;e&#39;, &#39;d&#39; };</pre><p>Supposing we already knew the start and end indices of ‘the’ and ‘landed’, how long would it take to swap them?</p><p>O(n)<em>O</em>(<em>n</em>) time, where n<em>n</em> is the total length of the message!</p><p>Why? Notice that in addition to moving ‘the’ to the back and moving ‘landed’ to the front, we have to “scoot over” <em>everything in between</em>, since ‘landed’ is longer than ‘the’.</p><p>So what’ll be the <em>total</em> time cost with this approach? Assume we’ll be able to learn the start and end indices of all of our words in just one pass (O(n)<em>O</em>(<em>n</em>) time).</p><p>O(n2)<em>O</em>(<em>n</em>2) total time. Why? In the worst case we have almost as many words as we have characters, and we’re always swapping words of different lengths. For example:</p><pre>// a bb c dd e ff g hh<br>{ &#39;a&#39;, &#39; &#39;, &#39;b&#39;, &#39;b&#39;, &#39; &#39;, &#39;c&#39;, &#39; &#39;, &#39;d&#39;, &#39;d&#39;, &#39; &#39;,<br>  &#39;e&#39;, &#39; &#39;, &#39;f&#39;, &#39;f&#39;, &#39; &#39;, &#39;g&#39;, &#39; &#39;, &#39;h&#39;, &#39;h&#39; };</pre><p>We take O(n)<em>O</em>(<em>n</em>) time to swap the first and last words (we have to move all n<em>n</em> characters):</p><pre>// input: a bb c dd e ff g hh<br>{ &#39;a&#39;, &#39; &#39;, &#39;b&#39;, &#39;b&#39;, &#39; &#39;, &#39;c&#39;, &#39; &#39;, &#39;d&#39;, &#39;d&#39;, &#39; &#39;,<br>  &#39;e&#39;, &#39; &#39;, &#39;f&#39;, &#39;f&#39;, &#39; &#39;, &#39;g&#39;, &#39; &#39;, &#39;h&#39;, &#39;h&#39; };<br>// first swap: hh bb c dd e ff g a<br>{ &#39;h&#39;, &#39;h&#39;, &#39; &#39;, &#39;b&#39;, &#39;b&#39;, &#39; &#39;, &#39;c&#39;, &#39; &#39;, &#39;d&#39;, &#39;d&#39;,<br>  &#39; &#39;, &#39;e&#39;, &#39; &#39;, &#39;f&#39;, &#39;f&#39;, &#39; &#39;, &#39;g&#39;, &#39; &#39;, &#39;a&#39; };</pre><p>Then for the second swap:</p><pre>// input: a bb c dd e ff g hh<br>{ &#39;a&#39;, &#39; &#39;, &#39;b&#39;, &#39;b&#39;, &#39; &#39;, &#39;c&#39;, &#39; &#39;, &#39;d&#39;, &#39;d&#39;, &#39; &#39;,<br>  &#39;e&#39;, &#39; &#39;, &#39;f&#39;, &#39;f&#39;, &#39; &#39;, &#39;g&#39;, &#39; &#39;, &#39;h&#39;, &#39;h&#39; };<br>// first swap: hh bb c dd e ff g a<br>{ &#39;h&#39;, &#39;h&#39;, &#39; &#39;, &#39;b&#39;, &#39;b&#39;, &#39; &#39;, &#39;c&#39;, &#39; &#39;, &#39;d&#39;, &#39;d&#39;,<br>  &#39; &#39;, &#39;e&#39;, &#39; &#39;, &#39;f&#39;, &#39;f&#39;, &#39; &#39;, &#39;g&#39;, &#39; &#39;, &#39;a&#39; };<br>// second swap: hh g c dd e ff bb a<br>{ &#39;h&#39;, &#39;h&#39;, &#39; &#39;, &#39;g&#39;, &#39; &#39;, &#39;c&#39;, &#39; &#39;, &#39;d&#39;, &#39;d&#39;,<br>  &#39; &#39;, &#39;e&#39;, &#39; &#39;, &#39;f&#39;, &#39;f&#39;, &#39; &#39;, &#39;b&#39;, &#39;b&#39;, &#39; &#39;, &#39;a&#39; };</pre><p>We have to move all n<em>n</em> characters <em>except</em> the first and last words, and a couple spaces. So we move n−5<em>n</em>−5 characters in total.</p><p>For the third swap, we have another 55 characters we don’t have to move. So we move n−10<em>n</em>−10 in total. We’ll end up with a series like this:</p><p>n+(n−5)+(n−10)+(n−15)+…+6+1<em>n</em>+(<em>n</em>−5)+(<em>n</em>−10)+(<em>n</em>−15)+…+6+1</p><p>This is a subsection of the common triangular series. ↴ We’re just skipping 4 terms between each term!</p><p>So we have the sum of “every fifth number” from that triangular series. That means our sum will be about a fifth the sum of the original series! But in big O notation dividing by 5 is a constant, so we can throw it out. The original triangular series is O(n2)<em>O</em>(<em>n</em>2), and so is our series with every fifth element!</p><p>Okay, so O(n2)<em>O</em>(<em>n</em>2) time. That’s pretty bad. It’s <em>possible</em> that’s the best we can do…but maybe we can do better?</p><p>Let’s try manipulating a sample input by hand.</p><p>And remember what we did for our character-level reversal…</p><p>Look what happens when we do a character-level reversal:</p><pre>// input: the eagle has landed<br>{ &#39;t&#39;, &#39;h&#39;, &#39;e&#39;, &#39; &#39;, &#39;e&#39;, &#39;a&#39;, &#39;g&#39;, &#39;l&#39;, &#39;e&#39;, &#39; &#39;,<br>  &#39;h&#39;, &#39;a&#39;, &#39;s&#39;, &#39; &#39;, &#39;l&#39;, &#39;a&#39;, &#39;n&#39;, &#39;d&#39;, &#39;e&#39;, &#39;d&#39; };<br>// character-reversed: dednal sah elgae eht<br>{ &#39;d&#39;, &#39;e&#39;, &#39;d&#39;, &#39;n&#39;, &#39;a&#39;, &#39;l&#39;, &#39; &#39;, &#39;s&#39;, &#39;a&#39;, &#39;h&#39;, &#39; &#39;,<br>  &#39;e&#39;, &#39;l&#39;, &#39;g&#39;, &#39;a&#39;, &#39;e&#39;, &#39; &#39;, &#39;e&#39;, &#39;h&#39;, &#39;t&#39; };</pre><p>Notice anything?</p><p>What if we put it up against the desired output:</p><pre>// input: the eagle has landed<br>{ &#39;t&#39;, &#39;h&#39;, &#39;e&#39;, &#39; &#39;, &#39;e&#39;, &#39;a&#39;, &#39;g&#39;, &#39;l&#39;, &#39;e&#39;, &#39; &#39;,<br>  &#39;h&#39;, &#39;a&#39;, &#39;s&#39;, &#39; &#39;, &#39;l&#39;, &#39;a&#39;, &#39;n&#39;, &#39;d&#39;, &#39;e&#39;, &#39;d&#39; };<br>// character-reversed: dednal sah elgae eht<br>{ &#39;d&#39;, &#39;e&#39;, &#39;d&#39;, &#39;n&#39;, &#39;a&#39;, &#39;l&#39;, &#39; &#39;, &#39;s&#39;, &#39;a&#39;, &#39;h&#39;, &#39; &#39;,<br>  &#39;e&#39;, &#39;l&#39;, &#39;g&#39;, &#39;a&#39;, &#39;e&#39;, &#39; &#39;, &#39;e&#39;, &#39;h&#39;, &#39;t&#39; };<br>// word-reversed (desired output): landed has eagle the<br>{ &#39;l&#39;, &#39;a&#39;, &#39;n&#39;, &#39;d&#39;, &#39;e&#39;, &#39;d&#39;, &#39; &#39;, &#39;h&#39;, &#39;a&#39;, &#39;s&#39;, &#39; &#39;,<br>  &#39;e&#39;, &#39;a&#39;, &#39;g&#39;, &#39;l&#39;, &#39;e&#39;, &#39; &#39;, &#39;t&#39;, &#39;h&#39;, &#39;e&#39; };</pre><p>The character reversal reverses the words! It’s a great first step. From there, we just have to “unscramble” each word.</p><p>More precisely, we just have to re-reverse each word!</p><h3>Solution</h3><p>We’ll write a helper method reverseCharacters() that reverses all the characters between a leftIndex and rightIndex. We use it to:</p><ol><li>Reverse <strong>all the characters in the entire message</strong>, giving us the correct <em>word order</em> but with <em>each word backward</em>.</li><li>Reverse <strong>the characters in each individual word</strong>.</li></ol><pre>public static void reverseWords(char[] message) {<br>// first we reverse all the characters in the entire message array<br>    // this gives us the right word order<br>    // but with each word backward<br>    reverseCharacters(message, 0, message.length - 1);<br>    // now we&#39;ll make the words forward again<br>    // by reversing each word&#39;s characters<br>    // we hold the index of the *start* of the current word<br>    // as we look for the *end* of the current word<br>    int currentWordStartIndex = 0;<br>    for (int i = 0; i &lt;= message.length; i++) {<br>        // found the end of the current word!<br>        if (i == message.length || message[i] == &#39; &#39;) {<br>            // if we haven&#39;t exhausted the array, our<br>            // next word&#39;s start is one character ahead<br>            reverseCharacters(message, currentWordStartIndex, i - 1);<br>            currentWordStartIndex = i + 1;<br>        }<br>    }<br>}<br>private static void reverseCharacters(char[] message, int leftIndex, int rightIndex) {<br>    // walk towards the middle, from both sides<br>    while (leftIndex &lt; rightIndex) {<br>        // swap the left char and right char<br>        char temp = message[leftIndex];<br>        message[leftIndex] = message[rightIndex];<br>        message[rightIndex] = temp;<br>        leftIndex++;<br>        rightIndex--;<br>    }<br></pre><p>Complexity</p><p>O(n)<em>O</em>(<em>n</em>) time and O(1)<em>O</em>(1) space!</p><p>Hmm, the team used your method to finish deciphering the message. There definitely seems to be a heist brewing, but no specifics on where. Any ideas?</p><h3>Bonus</h3><p>How would you handle punctuation?</p><h3>What We Learned</h3><p>The naive solution of reversing the words one at a time had a worst-case O(n2)<em>O</em>(<em>n</em>2) runtime. That’s because swapping words with <em>different lengths</em> required “scooting over” all the other characters to make room.</p><p>To get around this “scooting over” issue, we reversed all the <em>characters</em> in the message first. This put all the words in the correct spots, but with the characters in each word backward. So to get the final answer, we reversed the characters in each word. This all takes two passes through the message, so O(n)<em>O</em>(<em>n</em>) time total.</p><p>This might seem like a blind insight, but we derived it by using a clear strategy:</p><p><strong>Solve a <em>simpler</em> version of the problem (in this case, reversing the characters instead of the words), and see if that gets us closer to a solution for the original problem.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=05cc18103587" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Greedy Algorithms]]></title>
            <link>https://medium.com/@poojadas053/greedy-algorithms-aebd1170ef36?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/aebd1170ef36</guid>
            <category><![CDATA[greedy-algorithms]]></category>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:33:53 GMT</pubDate>
            <atom:updated>2024-08-11T04:33:53.543Z</atom:updated>
            <content:encoded><![CDATA[<p>A <strong>greedy</strong> algorithm builds up a solution by choosing the option that looks the best at every step.</p><p>Say you’re a cashier and need to give someone 67 cents (US) using as few coins as possible. How would you do it?</p><p>Whenever picking which coin to use, you’d take the highest-value coin you could. A quarter, another quarter, then a dime, a nickel, and finally two pennies. That’s a <em>greedy</em> algorithm, because you’re always <em>greedily</em> choosing the coin that covers the biggest portion of the remaining amount.</p><p>Some other places where a greedy algorithm gets you the best solution:</p><ul><li>Trying to fit as many overlapping meetings as possible in a conference room? At each step, schedule the meeting that <em>ends</em> earliest.</li><li>Looking for a minimum spanning tree in a graph? At each step, greedily pick the cheapest edge that reaches a new vertex.</li></ul><p><strong>Careful: sometimes a greedy algorithm <em>doesn’t</em> give you an optimal solution:</strong></p><ul><li>When filling a duffel bag with cakes of different weights and values, choosing the cake with the highest value per pound doesn’t always produce the best haul.</li><li>To find the cheapest route visiting a set of cities, choosing to visit the cheapest city you haven’t been to yet doesn’t produce the cheapest overall itinerary.</li></ul><p>Validating that a greedy strategy always gets the best answer is tricky. Either prove that the answer produced by the greedy algorithm is as good as an optimal answer, or run through a rigorous set of test cases to convince your interviewer (and yourself) that it’s correct.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aebd1170ef36" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Binary Search Algorithm]]></title>
            <link>https://medium.com/@poojadas053/binary-search-algorithm-0b7037feaede?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/0b7037feaede</guid>
            <category><![CDATA[binary-search-algorithm]]></category>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:32:00 GMT</pubDate>
            <atom:updated>2024-08-11T04:32:00.196Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>A binary search algorithm finds an item in a <em>sorted</em> array in O(lg(n))<em>O</em>(<em>lg</em>(<em>n</em>)) time.</strong></p><p>A brute force search would walk through the whole array, taking O(n)<em>O</em>(<em>n</em>) time in the worst case.</p><p>Let’s say we have a sorted array of numbers. To find a number with a binary search, we:</p><ol><li><strong>Start with the middle number: is it bigger or smaller than our target number?</strong> Since the array is sorted, this tells us if the target would be in the <em>left</em> half or the <em>right</em> half of our array.</li><li><strong>We’ve effectively divided the problem in half</strong>. We can “rule out” the whole half of the array that we know doesn’t contain the target number.</li><li><strong>Repeat the same approach (of starting in the middle) on the new half-size problem</strong>. Then do it again and again, until we either find the number or “rule out” the whole set.</li></ol><p>We can do this recursively, or iteratively. Here’s an iterative version:</p><pre>public static boolean binarySearch(int target, int[] nums) {<br>// we think of floorIndex and ceilingIndex as &quot;walls&quot; around<br>    // the possible positions of our target, so by -1 below we mean<br>    // to start our wall &quot;to the left&quot; of the 0th index<br>    // (we *don&#39;t* mean &quot;the last index&quot;)<br>    int floorIndex = -1;<br>    int ceilingIndex = nums.length;<br>    // if there isn&#39;t at least 1 index between floor and ceiling,<br>    // we&#39;ve run out of guesses and the number must not be present<br>    while (floorIndex + 1 &lt; ceilingIndex) {<br>        // find the index ~halfway between the floor and ceiling<br>        // we use integer division, so we&#39;ll never get a &quot;half index&quot;<br>        int distance = ceilingIndex - floorIndex;<br>        int halfDistance = distance / 2;<br>        int guessIndex = floorIndex + halfDistance;<br>        int guessValue = nums[guessIndex];<br>        if (guessValue == target) {<br>            return true;<br>        }<br>        if (guessValue &gt; target) {<br>            // target is to the left, so move ceiling to the left<br>            ceilingIndex = guessIndex;<br>        } else {<br>            // target is to the right, so move floor to the right<br>            floorIndex = guessIndex;<br>        }<br>    }<br>    return false;<br>}<br>    // see if target appears in nums</pre><p><strong>How did we know the time cost of binary search was O(lg(n))<em>O</em>(<em>lg</em>(<em>n</em>))?</strong> The only non-constant part of our time cost is the number of times our while loop runs. Each step of our while loop cuts the range (dictated by floorIndex and ceilingIndex) in half, until our range has just one element left.</p><p><strong>So the question is, “how many times must we divide our original array size (n<em>n</em>) in half until we get down to 1?”</strong></p><p>n∗12∗12∗12∗12∗…=1<em>n</em>∗21​∗21​∗21​∗21​∗…=1</p><p>How many 1221​’s are there? We don’t know yet, but we can call that number x<em>x</em>:</p><p>n∗(12)x=1<em>n</em>∗(21​)<em>x</em>=1</p><p>Now we solve for x<em>x</em>:</p><p>n∗1x2x=1<em>n</em>∗2<em>x</em>1<em>x</em>​=1n∗12x=1<em>n</em>∗2<em>x</em>1​=1n2x=12<em>xn</em>​=1n=2x<em>n</em>=2<em>x</em></p><p>Now to get the x<em>x</em> out of the exponent. How do we do that? Logarithms.</p><p><strong>Recall that log⁡10100log10​100 means, “what power must we raise 10 to, to get 100”? The answer is 2.</strong></p><p>So in this case, if we take the log⁡2log2​ of both sides…</p><p>log⁡2n=log⁡22xlog2​<em>n</em>=log2​2<em>x</em></p><p>The right hand side asks, “what power must we raise 22 to, to get 2x2<em>x</em>?” Well, that’s just x<em>x</em>.</p><p>log⁡2n=xlog2​<em>n</em>=<em>x</em></p><p>So there it is. The number of times we must divide n<em>n</em> in half to get down to 1 is log2n<em>log</em>2​<em>n</em>. So our total time cost is O(lg(n))<em>O</em>(<em>lg</em>(<em>n</em>))</p><p><strong>Careful: we can only use binary search if the input array is <em>already sorted</em>.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0b7037feaede" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Bottom-Up Algorithms]]></title>
            <link>https://medium.com/@poojadas053/bottom-up-algorithms-be4879c4ca07?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/be4879c4ca07</guid>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:29:41 GMT</pubDate>
            <atom:updated>2024-08-11T04:29:41.526Z</atom:updated>
            <content:encoded><![CDATA[<p>Going <strong>bottom-up</strong> is a way to avoid recursion, saving the <strong>memory cost</strong> that recursion incurs when it builds up the <strong>call stack</strong>.</p><p>Put simply, a bottom-up algorithm “starts from the beginning,” while a recursive algorithm often “starts from the end and works backwards.”</p><p>For example, if we wanted to multiply all the numbers in the range 1..n1..<em>n</em>, we could use this cute, <strong>top-down</strong>, recursive one-liner:</p><pre>public static int product1ToN(int n) {<br>    // we assume n &gt;= 1<br>    return (n &gt; 1) ? (n * product1ToN(n-1)) : 1;<br>}</pre><p>This approach has a problem: it builds up a <strong>call stack</strong> of size O(n)<em>O</em>(<em>n</em>), which makes our total memory cost O(n)<em>O</em>(<em>n</em>). This makes it vulnerable to a <strong>stack overflow error</strong>, where the call stack gets too big and runs out of space.</p><p>To avoid this, we can instead go <strong>bottom-up</strong>:</p><pre>public static int product1ToN(int n) {<br>    // we assume n &gt;= 1<br>int result = 1;<br>    for (int num = 1; num &lt;= n; num++) {<br>        result *= num;<br>    }<br>    return result;<br>}</pre><p>This approach uses O(1)<em>O</em>(1) space (O(n)<em>O</em>(<em>n</em>) time).</p><p><em>Some</em> compilers and interpreters will do what’s called <strong>tail call optimization</strong> (TCO), where it can optimize <em>some</em> recursive methods to avoid building up a tall call stack. Python and Java decidedly do not use TCO. Some Ruby implementations do, but most don’t. Some C implementations do, and the JavaScript spec recently <em>allowed</em> TCO. Scheme is one of the few languages that <em>guarantee</em> TCO in all implementations. In general, best not to assume your compiler/interpreter will do this work for you.</p><p>Going bottom-up is a common strategy for <strong>dynamic programming</strong> problems, which are problems where the solution is composed of solutions to the same problem with smaller inputs (as with multiplying the numbers 1..n1..<em>n</em>, above). The other common strategy for dynamic programming problems is <a href="https://www.interviewcake.com/concept/memoization"><strong>memoization</strong></a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=be4879c4ca07" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Memoization]]></title>
            <link>https://medium.com/@poojadas053/memoization-d4bfbdcca1c0?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/d4bfbdcca1c0</guid>
            <category><![CDATA[memoization]]></category>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:28:29 GMT</pubDate>
            <atom:updated>2024-08-11T04:28:29.103Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Memoization</strong> ensures that a method doesn’t run for the same inputs more than once by keeping a record of the results for the given inputs (usually in a hash map).</p><p>For example, a simple recursive method for computing the n<em>n</em>th Fibonacci number:</p><pre>public static int fib(int n) {<br>if (n &lt; 0) {<br>        throw new IllegalArgumentException(<br>            &quot;Index was negative. No such thing as a negative index in a series.&quot;);<br>    }<br>    // base cases<br>    if (n == 0 || n == 1) {<br>        return n;<br>    }<br>    System.out.printf(&quot;computing fib(%d)\n&quot;, n);<br>    return fib(n - 1) + fib(n - 2);<br>}Will run on the same inputs multiple times:</pre><pre>// output for fib(5)<br>computing fib(5)<br>computing fib(4)<br>computing fib(3)<br>computing fib(2)<br>computing fib(2)<br>computing fib(3)<br>computing fib(2)<br>5</pre><p>We can imagine the recursive calls of this method as a tree, where the two children of a node are the two recursive calls it makes. We can see that the tree quickly branches out of control:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/0*u5FXMC50TBidjfZk" /></figure><p>To avoid the duplicate work caused by the branching, we can wrap the method in a class that stores an instance variable, memo, that maps inputs to outputs. Then we simply</p><ol><li>check memo to see if we can avoid computing the answer for any given input, and</li><li>save the results of any calculations to memo.</li></ol><pre>import java.util.Map;<br>import java.util.HashMap;<br>class Fibber {<br>    private Map&lt;Integer, Integer&gt; memo = new HashMap&lt;&gt;();<br>    public int fib(int n) {<br>        if (n &lt; 0) {<br>            throw new IllegalArgumentException(<br>                &quot;Index was negative. No such thing as a negative index in a series.&quot;);<br>        // base cases<br>        } else if (n == 0 || n == 1) {<br>            return n;<br>        }<br>        // see if we&#39;ve already calculated this<br>        if (memo.containsKey(n)) {<br>            System.out.printf(&quot;grabbing memo[%d]\n&quot;, n);<br>            return memo.get(n);<br>        }<br>        System.out.printf(&quot;computing fib(%d)\n&quot;, n);<br>        int result = fib(n - 1) + fib(n - 2);<br>        // memoize<br>        memo.put(n, result);<br>        return result;<br>    }<br>}</pre><p>We save a bunch of calls by checking the memo:</p><pre>// output of new Fibber().fib(5)<br>computing fib(5)<br>computing fib(4)<br>computing fib(3)<br>computing fib(2)<br>grabbing memo[2]<br>grabbing memo[3]<br>5</pre><p>Now in our recurrence tree, no node appears more than twice:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/0*_-_4aLWQqEcuhlqf" /></figure><p>Memoization is a common strategy for <strong>dynamic programming</strong> problems, which are problems where the solution is composed of solutions to the same problem with smaller inputs (as with the Fibonacci problem, above). The other common strategy for dynamic programming problems is <a href="https://www.interviewcake.com/concept/bottom-up"><strong>going bottom-up</strong></a>, which is usually cleaner and often more efficient.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d4bfbdcca1c0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Overlapping Subproblems]]></title>
            <link>https://medium.com/@poojadas053/overlapping-subproblems-55dea4e61b57?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/55dea4e61b57</guid>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:26:52 GMT</pubDate>
            <atom:updated>2024-08-11T04:26:52.246Z</atom:updated>
            <content:encoded><![CDATA[<p>A problem has <strong>overlapping subproblems</strong> if finding its solution involves solving the <em>same</em> subproblem multiple times.</p><p>As an example, let’s look at the Fibonacci sequence (the series where each number is the sum of the two previous ones — 0, 1, 1, 2, 3, 5, 8, …).</p><p>If we wanted to compute the n<em>n</em>th Fibonacci number, we could use this simple recursive algorithm:</p><pre>public static int fib(int n) {<br>    if (n == 0 || n == 1) {<br>        return n;<br>    }<br>    return fib(n-1) + fib(n-2);<br>}</pre><p>CC#C++JavaJavaScriptObjective-CPHPPython 2.7Python 3.6RubySwift</p><p>We’d call fib(n-1) and fib(n-2) <strong>subproblems</strong> of fib(n).</p><p>Now let’s look at what happens when we call fib(5):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/0*LMr4fxkDUkN8RTrt" /></figure><p>Our method ends up recursively calling fib(2) <strong><em>three times</em></strong>. So the problem of finding the n<em>n</em>th Fibonacci number has overlapping subproblems.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=55dea4e61b57" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[You are a renowned thief who has recently switched from stealing precious metals to stealing cakes…]]></title>
            <link>https://medium.com/@poojadas053/you-are-a-renowned-thief-who-has-recently-switched-from-stealing-precious-metals-to-stealing-cakes-57325f274cbb?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/57325f274cbb</guid>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:25:32 GMT</pubDate>
            <atom:updated>2024-08-11T04:25:32.302Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>You are a renowned thief who has recently switched from stealing precious metals to stealing cakes because of the insane profit margins. You end up hitting the jackpot, breaking into the world’s largest privately owned stock of cakes — the vault of the Queen of England.</strong></h3><p>While Queen Elizabeth has a <em>limited number of types of cake</em>, she has an <em>unlimited supply of each type</em>.</p><p>Each type of cake has a weight and a value, stored in objects of a CakeType class:</p><pre>public class CakeType {<br>final int weight;<br>    final int value;<br>    public CakeType(int weight, int value) {<br>        this.weight = weight;<br>        this.value  = value;<br>    }<br>}</pre><p>For example:</p><pre>// weighs 7 kilograms and has a value of 160 shillings<br>new CakeType(7, 160);<br>// weighs 3 kilograms and has a value of 90 shillings<br>new CakeType(3, 90);</pre><p>You brought a duffel bag that can hold limited weight, and you want to make off with the most valuable haul possible.</p><p>Write a method maxDuffelBagValue() that takes <strong>an array of cake type objects </strong>and <strong>a weight capacity</strong>, and returns <strong>the <em>maximum monetary value</em> the duffel bag can hold.</strong></p><p>For example:</p><pre>CakeType[] cakeTypes = new CakeType[] {<br>    new CakeType(7, 160),<br>    new CakeType(3, 90),<br>    new CakeType(2, 15),<br>};<br>int capacity = 20;<br>maxDuffelBagValue(cakeTypes, capacity);<br>// returns 555 (6 of the middle type of cake and 1 of the last type of cake)</pre><p><strong>Weights and values may be any non-negative integer.</strong> Yes, it’s weird to think about cakes that weigh nothing or duffel bags that can’t hold anything. But we’re not just super mastermind criminals — we’re also meticulous about keeping our algorithms flexible and comprehensive.</p><h3>Gotchas</h3><p>Does your method work if the duffel bag’s weight capacity is 0 kg?</p><p>Does your method work if any of the cakes weigh 0 kg? Think about a cake whose weight and value are <em>both</em> 0.</p><p>We can do this in O(n∗k)<em>O</em>(<em>n</em>∗<em>k</em>) time and O(k)<em>O</em>(<em>k</em>) space, where n<em>n</em> is the number of types of cakes and k<em>k</em> is the duffel bag’s capacity!</p><h3>Breakdown</h3><p>The <strong>brute force approach</strong> is to try <em>every</em> combination of cakes, but that would take a really long time — you’d surely be captured.</p><p>What if we just look at <strong>the cake with the <em>highest value?</em></strong></p><p>We could keep putting the cake with the highest value into our duffel bag until adding one more would go over our weight capacity. Then we could look at the cake with the <em>second</em> highest value, and so on until we find a cake that’s not too heavy to add.</p><p><strong>Will this work?</strong></p><p>Nope. Let’s say our capacity is <strong>100 kg</strong> and these are our two cakes:</p><pre>new CakeType(1, 30);<br>new CakeType(50, 200);</pre><p>With our approach, we’ll put in two of the second type of cake for a total value of <em>400 shillings</em>. But we could have put in a <em>hundred</em> of the first type of cake, for a total value of <em>3000 shillings!</em></p><p>Just looking at the cake’s values won’t work. <strong>Can we improve our approach?</strong></p><p>Well, <em>why</em> didn’t it work?</p><p>We didn’t think about the <strong>weight!</strong> How can we factor that in?</p><p>What if instead of looking at the <strong>value</strong> of the cakes, we looked at their <strong>value/weight ratio?</strong> Here are our example cakes again:</p><pre>new CakeType(1, 30);<br>new CakeType(50, 200);</pre><p>The second cake has a higher value, but look at the value <strong>per kilogram</strong>.</p><p>The second type of cake is worth 4 shillings/kg (200/50200/50), but the first type of cake is worth 30 shillings/kg (30/130/1)!</p><p>Ok, can we just change our algorithm to use the highest value/weight ratio instead of the highest value? We know it would work in our example above, but try some more tests to be safe.</p><p>We might run into problems if the weight of the cake with the highest value/weight ratio doesn’t fit evenly into the capacity. Say we have these two cakes:</p><pre>new CakeType(3, 40);<br>new CakeType(5, 70);</pre><p>If our capacity is <strong>8 kg</strong>, no problem. Our algorithm chooses one of each cake, giving us a haul worth <strong>110 shillings</strong>, which is optimal.</p><p>But if the capacity is <strong>9 kg</strong>, we’re in trouble. Our algorithm will again choose one of each cake, for a total value of <strong>110 shillings</strong>. But the <em>actual optimal value</em> is <strong>120 shillings</strong> — three of the first type of cake!</p><p>So even looking at the value/weight ratios doesn’t always give us the optimal answer!</p><p>Let’s step back. <strong>How can we <em>ensure</em> we get the <em>optimal</em> value we can carry?</strong></p><p>Try thinking small. How can we calculate the maximum value for a duffel bag with a weight capacity of <strong>1 kg</strong>? (Remember, all our weights and values are integers.)</p><p><strong>If the capacity is 1 kg</strong>, we’ll only care about cakes that weigh 1 kg (for simplicity, let’s ignore zeroes for now). And we’d just want the one with the <em>highest</em> value.</p><p>We could go through every cake, using a greedy ↴ approach to keep track of the max value we’ve seen so far.</p><p>Here’s an example solution:</p><pre>public static long maxDuffelBagValueWithCapacity1(CakeType[] cakeTypes) {<br>long maxValueAtCapacity1 = 0L;<br>    for (CakeType cakeType : cakeTypes) {<br>        if (cakeType.weight == 1) {<br>            maxValueAtCapacity1 = Math.max(maxValueAtCapacity1, cakeType.value);<br>        }<br>    }<br>    return maxValueAtCapacity1;<br>}</pre><p>(We’re using long because we’re looking for a <em>max</em> value.)</p><p>Ok, <strong>now what if the capacity is 2 kg</strong>? We’ll need to be a bit more clever.</p><p>It’s <em>pretty</em> similar. Again we’ll track a max value, let’s say with a variable maxValueAtCapacity2. But now we care about cakes that weigh 1 <em>or</em> 2 kg. What do we do with each cake? And keep in mind, <strong>we can lean on the code we used to get the max value at weight capacity 1 kg.</strong></p><ol><li><strong>If the cake weighs 2 kg</strong>, it would fill up our whole capacity if we just took one. So we just need to see if the cake’s value is higher than our current maxValueAtCapacity2.</li><li><strong>If the cake weighs 1 kg</strong>, we could take one, and we’d still have 1 kg of capacity left. How do we know the best way to fill that extra capacity? We can use the max value at capacity 1. We’ll see if adding the cake’s value to the max value at capacity 1 is better than our current maxValueAtCapacity2.</li></ol><p>Does this apply more generally? If we can use the max value at capacity 1 to get the max value at capacity 2, can we use the max values at capacity 1 and 2 to get the max value at capacity 3?</p><p>Looks like this problem might have overlapping subproblems! ↴</p><p>Let’s see if we can build up to the <em>given</em> weight capacity, <em>one capacity at a time</em>, using the max values from <em>previous</em> capacities. How can we do this?</p><p>Well, <strong>let’s try one more weight capacity by hand — 3 kg.</strong> So we already know the max values at capacities 1 and 2. And just like we did with maxValueAtCapacity1 and maxValueAtCapacity2, now we’ll track maxValueAtCapacity3 and loop through every cake:</p><pre>long maxValueAtCapacity3 = 0L;<br>for (CakeType cakeType : cakeTypes) {<br>    // only care about cakes that weigh 3 kg or less<br>    ...<br>}</pre><p><strong>What do we do for each cake?</strong></p><p>If the current cake weighs 3 kg, easy — we see if it’s more valuable than our current maxValueAtCapacity3.</p><p><strong>What if the current cake weighs 2 kg?</strong></p><p>Well, let’s see what our max value would be <em>if we used the cake.</em> How can we calculate that?</p><p>If we include the current cake, we can only carry 1 more kilogram. What would be the max value we can carry?</p><p>We already know the maxValueAtCapacity1! We can just add that to the current cake’s value!</p><p>Now we can see which is higher — our <em>current</em> maxValueAtCapacity3, or the <em>new</em> max value if we use the cake:</p><pre>long maxValueUsingCake = maxValueAtCapacity1 + cakeType.value;<br>maxValueAtCapacity3 = Math.max(maxValueAtCapacity3, maxValueUsingCake);</pre><p>Finally, <strong>what if the current cake weighs 1 kg?</strong></p><p>Basically the same as if it weighs 2 kg:</p><pre>long maxValueUsingCake = maxValueAtCapacity2 + cakeType.value;<br>maxValueAtCapacity3 = Math.max(maxValueAtCapacity3, maxValueUsingCake);</pre><p>There’s gotta be a pattern here. We can keep building up to higher and higher capacities until we reach our input capacity. Because the max value we can carry at each capacity is calculated using the max values at <em>previous</em> capacities, we’ll need to solve the max value for <em>every</em> capacity from 0 up to our duffel bag’s actual weight capacity.</p><p>Can we write a method to handle <strong>all the capacities?</strong></p><p>To start, <strong>we’ll need a way to store and update <em>all</em> the max monetary values for each capacity</strong>.</p><p>We could use a hash map, ↴ where the keys represent capacities and the values represent the max possible monetary values at those capacities. Hash maps are <em>built on</em> arrays, ↴ so we can save some overhead by just using an array.</p><pre>public static long maxDuffelBagValue(CakeType[] cakeTypes, int weightCapacity) {<br>// array to hold the maximum possible value at every<br>    // integer capacity from 0 to weightCapacity<br>    // starting each index with value 0 long<br>    long[] maxValuesAtCapacities = new long[weightCapacity + 1];<br>}</pre><p>What do we do next?</p><p>We’ll need to work with every capacity up to the input weight capacity. That’s an easy loop:</p><pre>// every integer from 0 to the input weightCapacity<br>for (int currentCapacity = 0; currentCapacity &lt;= weightCapacity; currentCapacity++) {<br>    ...<br>}</pre><p>What will we do inside this loop? This is where it gets a little tricky.</p><p>We care about any cakes that weigh <em>the current capacity or less</em>. Let’s try putting <em>each cake</em> in the bag and seeing how valuable of a haul we could fit from there.</p><p>So we’ll write a loop through all the cakes (ignoring cakes that are too heavy to fit):</p><pre>for (CakeType cakeType : cakeTypes) {<br>// if the cake weighs as much or less than the current capacity<br>    // see what our max value could be if we took it!<br>    if (cakeType.weight &lt;= currentCapacity) {<br>        // find maxValueUsingCake<br>        ...<br>    }<br>}</pre><p>And put it in our method body so far:</p><pre>public static long maxDuffelBagValue(CakeType[] cakeTypes, int weightCapacity) {<br>// we make an array to hold the maximum possible value at every<br>    // duffel bag weight capacity from 0 to weightCapacity<br>    // starting each index with value 0<br>    long[] maxValuesAtCapacities = new long[weightCapacity + 1];<br>    for (int currentCapacity = 0; currentCapacity &lt;= weightCapacity; currentCapacity++) {<br>        for (CakeType cakeType : cakeTypes) {<br>            // if the cake weighs as much or less than the current capacity<br>            // see what our max value could be if we took it!<br>            if (cakeType.weight &lt;= currentCapacity) {<br>                // find maxValueUsingCake<br>                ...<br>            }<br>        }<br>    }<br>}</pre><p>How do we compute maxValueUsingCake?</p><p>Remember when we were calculating the max value at capacity 3kg and we “hard-coded” the maxValueUsingCake for cakes that weigh 3 kg, 2kg, and 1kg?</p><pre>// cake weighs 3 kg<br>long maxValueUsingCake = cakeType.value;<br>// cake weighs 2 kg<br>long maxValueUsingCake = maxValueAtCapacity1 + cakeType.value;<br>// cake weighs 1 kg<br>long maxValueUsingCake = maxValueAtCapacity2 + cakeType.value;</pre><p>How can we generalize this? With our new method body, look at the variables we have in scope:</p><ul><li>maxValuesAtCapacities</li><li>currentCapacity</li><li>cakeType</li></ul><p>Can we use these to get maxValueUsingCake for <em>any cake</em>?</p><p>Well, let’s figure out how much space would be left in the duffel bag after putting the cake in:</p><pre>int remainingCapacityAfterTakingCake = currentCapacity - cakeType.weight;</pre><p>CC#C++JavaJavaScriptObjective-CPHPPython 2.7Python 3.6RubySwift</p><p>So maxValueUsingCake is:</p><ol><li>the current cake’s value, <em>plus</em></li><li>the best value we can fill the remainingCapacityAfterTakingCake with</li></ol><pre>int remainingCapacityAfterTakingCake = currentCapacity - cakeType.weight;<br>long maxValueUsingCake = cakeType.value + maxValuesAtCapacities[remainingCapacityAfterTakingCake];</pre><p>We can squish this into one line:</p><pre>long maxValueUsingCake = cakeType.value + maxValuesAtCapacities[currentCapacity - cakeType.weight];</pre><p>Since remainingCapacityAfterTakingCake is a <em>lower</em> capacity, we’ll have <em>always</em> already computed its max value and stored it in our maxValuesAtCapacities!</p><p>Now that we know the max value <em>if we include the cake</em>, <strong>should we include it?</strong> How do we know?</p><p>Let’s allocate a variable currentMaxValue that holds the highest value we can carry at the current capacity. We can start it at zero, and as we go through all the cakes, any time the value <em>using</em> a cake is higher than currentMaxValue, we’ll update currentMaxValue!</p><pre>currentMaxValue = Math.max(maxValueUsingCake, currentMaxValue);</pre><p>What do we <em>do</em> with each value for currentMaxValue? What do we need to do for each <em>capacity</em> when we finish looping through all the cakes?</p><p>We save each currentMaxValue in the maxValuesAtCapacities array. We’ll also need to make sure we set currentMaxValue to zero in the right place in our loops — we want it to reset every time we start a new capacity.</p><p>So here’s our method so far:</p><pre>public static long maxDuffelBagValue(CakeType[] cakeTypes, int weightCapacity){<br>// we make an array to hold the maximum possible value at every<br>    // duffel bag weight capacity from 0 to weightCapacity<br>    // starting each index with value 0<br>    long[] maxValuesAtCapacities = new long[weightCapacity + 1];<br>    for (int currentCapacity = 0; currentCapacity &lt;= weightCapacity; currentCapacity++) {<br>        // set a variable to hold the max monetary value so far for currentCapacity<br>        long currentMaxValue = 0;<br>        for (CakeType cakeType : cakeTypes) {<br>            // if the current cake weighs as much or less than the current weight capacity<br>            // it&#39;s possible taking the cake would get a better value<br>            if (cakeType.weight &lt;= currentCapacity) {<br>                // so we check: should we use the cake or not?<br>                // if we use the cake, the most kilograms we can include in addition to the cake<br>                // we&#39;re adding is the current capacity minus the cake&#39;s weight. we find the max<br>                // value at that integer capacity in our array maxValuesAtCapacities<br>                long maxValueUsingCake = cakeType.value<br>                    + maxValuesAtCapacities[currentCapacity - cakeType.weight];<br>                // now we see if it&#39;s worth taking the cake. how does the<br>                // value with the cake compare to the currentMaxValue?<br>                currentMaxValue = Math.max(maxValueUsingCake, currentMaxValue);<br>            }<br>        }<br>        // add each capacity&#39;s max value to our array so we can use them<br>        // when calculating all the remaining capacities<br>        maxValuesAtCapacities[currentCapacity] = currentMaxValue;<br>    }<br>} </pre><p>Looking good! But <strong>what’s our final answer?</strong></p><p>Our final answer is maxValuesAtCapacities[weightCapacity]!</p><p>Okay, this seems complete. <strong>What about edge cases?</strong></p><p>Remember, weights and values can be any non-negative integer. What about zeroes? How can we handle duffel bags that can’t hold anything and cakes that weigh nothing?</p><p>Well, if our duffel bag can’t hold anything, we can just return 0. And if a cake weighs 0 kg, we return <em>infinity</em>. Right?</p><p>Not that simple!</p><p>What if our duffel bag holds 0 kg, and we have a cake that weighs 0 kg. What do we return?</p><p>And what if we have a cake that weighs 0 kg, but its value is <em>also</em> 0. If we have other cakes with positive weights and values, what do we return?</p><p>If a cake’s weight and value are both 0, it’s reasonable to not have that cake affect what we return at all.</p><p>If we have a cake that weighs 0 kg and has a positive value, it’s reasonable to return infinity, even if the capacity is 0.</p><p>For returning infinity, we have a couple choices. We could return:</p><ul><li><strong>The highest possible Long.</strong> In Java, that’d be Long.MAX_VALUE.</li><li><strong>Raise an exception</strong> indicating the answer is infinity.</li></ul><p>What are the advantages and disadvantages of each option?</p><p>For the <strong>first option</strong> the advantage is the highest possible Long will <em>behave</em> like infinity in a few ways. For example, it’ll be greater than any other integer. But it’s a still a <em>specific</em> number, which can be an advantage or disadvantage — we might want our result to always be the same <em>type</em>, but representing infinity as a specific number is “lossy” — it won’t be clear if we’re talking about an actual value or the special case of infinity.</p><p>The <strong>second option</strong> is a good choice if we decide infinity is usually an “unacceptable” answer. For example, we might decide an infinite answer means we’ve probably entered our inputs wrong. Then, if we <em>really</em> wanted to “accept” an infinite answer, we could always “catch” this exception when we call our method.</p><p>Either option <em>could</em> be reasonable. We’ll go with the second one here.</p><h3>Solution</h3><p>This is a classic computer science puzzle called <strong>“the unbounded knapsack problem.”</strong></p><p>We use a bottom-up ↴ approach to find the max value at our duffel bag’s weightCapacity by finding the max value at <em>every</em> capacity from 0 to weightCapacity.</p><p>We allocate an array maxValuesAtCapacities where the indices are capacities and each value is the max value <em>at that capacity</em>.</p><p>For each capacity, we want to know the max monetary value we can carry. To figure that out, we go through each cake, checking to see if we should take that cake.</p><p>The best monetary value we can get if we take a given cake is simply:</p><ol><li>that cake’s value, plus</li><li>the best monetary value we can carry in our remaining duffel bag capacity after taking the cake — which we’ll already have stored in maxValuesAtCapacities</li></ol><p>To handle weights and values of zero, we throw an infinity error <em>only</em> if a cake weighs nothing and has a positive value.</p><pre>public static class InfinityException extends RuntimeException {<br>    public InfinityException() {<br>        super(&quot;Max value is infinity!&quot;);<br>    }<br>}<br>public static long maxDuffelBagValue(CakeType[] cakeTypes, int weightCapacity) {<br>    // we make an array to hold the maximum possible value at every<br>    // duffel bag weight capacity from 0 to weightCapacity<br>    // starting each index with value 0<br>    long[] maxValuesAtCapacities = new long[weightCapacity + 1];<br>    for (int currentCapacity = 0; currentCapacity &lt;= weightCapacity; currentCapacity++) {<br>        // set a variable to hold the max monetary value so far for currentCapacity<br>        long currentMaxValue = 0;<br>        for (CakeType cakeType : cakeTypes) {<br>            // if a cake weighs 0 and has a positive value the value of our duffel bag is infinite!<br>            if (cakeType.weight == 0 &amp;&amp; cakeType.value != 0) {<br>                throw new InfinityException();<br>            }<br>            // if the current cake weighs as much or less than the current weight capacity<br>            // it&#39;s possible taking the cake would get a better value<br>            if (cakeType.weight &lt;= currentCapacity) {<br>                // so we check: should we use the cake or not?<br>                // if we use the cake, the most kilograms we can include in addition to the cake<br>                // we&#39;re adding is the current capacity minus the cake&#39;s weight. we find the max<br>                // value at that integer capacity in our array maxValuesAtCapacities<br>                long maxValueUsingCake = cakeType.value<br>                    + maxValuesAtCapacities[currentCapacity - cakeType.weight];<br>                // now we see if it&#39;s worth taking the cake. how does the<br>                // value with the cake compare to the currentMaxValue?<br>                currentMaxValue = Math.max(maxValueUsingCake, currentMaxValue);<br>            }<br>        }<br>        // add each capacity&#39;s max value to our array so we can use them<br>        // when calculating all the remaining capacities<br>        maxValuesAtCapacities[currentCapacity] = currentMaxValue;<br>    }<br>    return maxValuesAtCapacities[weightCapacity];<br>}</pre><p>Complexity</p><p>O(n∗k)<em>O</em>(<em>n</em>∗<em>k</em>) time, and O(k)<em>O</em>(<em>k</em>) space, where n<em>n</em> is number of types of cake and k<em>k</em> is the capacity of the duffel bag. We loop through each cake (n<em>n</em> cakes) for every capacity (k<em>k</em> capacities), so our runtime is O(n∗k)<em>O</em>(<em>n</em>∗<em>k</em>), and maintaining the array of k+1<em>k</em>+1 capacities gives us the O(k)<em>O</em>(<em>k</em>) space.</p><p><strong>Congratulations!</strong> Because of dynamic programming, you have successfully stolen the Queen’s cakes and made it big.</p><p><strong>Keep in mind:</strong> in some cases, it might <em>not</em> be worth using our optimal dynamic programming solution. It’s a pretty slow algorithm — without any context (not knowing how many cake types we have, what our weight capacity is, or just how they compare) it’s easy to see O(n∗k)<em>O</em>(<em>n</em>∗<em>k</em>) growing out of control quickly if n<em>n</em> or k<em>k</em> is large.</p><p>If we cared about <em>time</em>, like if there was an alarm in the vault and we had to move quickly, it might be worth using a <em>faster algorithm that gives us a </em><strong><em>good</em></strong><em> answer, even if it’s not always the </em><strong><em>optimal</em></strong><em> answer</em>. Some of our first ideas in the breakdown were to look at cake values or value/weight ratios. Those algorithms would probably be faster, taking O(nlg⁡n)<em>O</em>(<em>n</em>lg<em>n</em>) time (we’d have to start by sorting the input).</p><p><strong>Sometimes an efficient, <em>good</em> answer might be more <em>practical</em> than an inefficient, <em>optimal</em> answer.</strong></p><h3>Bonus</h3><ol><li>We know the <em>max value we can carry</em>, but <strong>which cakes should we take, and how many?</strong> Try adjusting your answer to return this information as well.</li><li>What if we check to see if all the cake weights have a <strong>common denominator</strong>? Can we improve our algorithm?</li><li>A cake that’s both <em>heavier</em> and <em>worth less</em> than another cake would <em>never</em> be in the optimal solution. This idea is called <strong>dominance relations</strong>. Can you apply this idea to save some time? Hint: dominance relations can apply to <em>sets of cakes</em>, not just individual cakes.</li><li>What if we had an object for <em>every individual cake</em> instead of <em>types of cakes</em>? So now there’s not an unlimited supply of a type of cake — there’s exactly one of each. This is a <em>similar but harder</em> problem, known as the <strong>0/1 Knapsack</strong> problem.</li></ol><h3>What We Learned</h3><p>This question is our spin on the famous “unbounded knapsack problem” — a classic dynamic programming question.</p><p>If you’re struggling with dynamic programming, we have reference pages for the two main dynamic programming strategies: memoization and going bottom-up.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=57325f274cbb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Write a method to see if a binary tree ↴ is “superbalanced” (a new tree property we just made up)]]></title>
            <link>https://medium.com/@poojadas053/write-a-method-to-see-if-a-binary-tree-is-superbalanced-a-new-tree-property-we-just-made-up-26d8d5e8aed7?source=rss-6de368060ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/26d8d5e8aed7</guid>
            <category><![CDATA[binary-search-tree]]></category>
            <dc:creator><![CDATA[Pooja Das]]></dc:creator>
            <pubDate>Sun, 11 Aug 2024 04:15:19 GMT</pubDate>
            <atom:updated>2024-08-11T04:15:19.193Z</atom:updated>
            <content:encoded><![CDATA[<p>A tree is “superbalanced” if the difference between the depths of any two leaf nodes ↴ is no greater than one.</p><p>Here’s a sample binary tree node class:</p><pre>public class BinaryTreeNode {<br>public int value;<br>    public BinaryTreeNode left;<br>    public BinaryTreeNode right;<br>    public BinaryTreeNode(int value) {<br>        this.value = value;<br>    }<br>    public BinaryTreeNode insertLeft(int leftValue) {<br>        this.left = new BinaryTreeNode(leftValue);<br>        return this.left;<br>    }<br>    public BinaryTreeNode insertRight(int rightValue) {<br>        this.right = new BinaryTreeNode(rightValue);<br>        return this.right;<br>    }<br>}</pre><p>Gotchas</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ht-E8xmQ1KHGIvLqdCgcmg.jpeg" /></figure><p>Your first thought might be to write a recursive method, thinking, “the tree is balanced if the left subtree is balanced and the right subtree is balanced.” This kind of approach works well for some other tree problems.</p><p><strong>But this isn’t quite true</strong>. Counterexample: suppose that from the root of our tree:</p><ul><li>The left subtree only has leaves at depths 10 and 11.</li><li>The right subtree only has leaves at depths 11 and 12.</li></ul><p>Both subtrees are balanced, but from the root we will have leaves at 3 different depths.</p><p>We could instead have our recursive method get the array of distinct leaf depths for each subtree. That could work fine. But let’s come up with an iterative solution instead. It’s usually better to use an iterative solution instead of a recursive one because it avoids stack overflow. ↴</p><p>We can do this in O(n)<em>O</em>(<em>n</em>) time and O(n)<em>O</em>(<em>n</em>) space.</p><p>What about a tree with only one leaf node? Does your method handle that case properly?</p><h3>Breakdown</h3><p>Sometimes it’s good to start by rephrasing or “simplifying” the problem.</p><p>The requirement of “the difference between the depths of any two leaf nodes is no greater than 1” implies that we’ll have to compare the depths of <em>all possible pairs</em> of leaves. That’d be expensive — if there are n<em>n</em> leaves, there are O(n2)<em>O</em>(<em>n</em>2) possible pairs of leaves.</p><p><strong>But we can simplify this requirement to require less work.</strong> For example, we could equivalently say:</p><ul><li>“The difference between the min leaf depth and the max leaf depth is 1 or less”</li><li>“There are at most two distinct leaf depths, and they are at most 1 apart”</li></ul><p>If you’re having trouble with a recursive approach, try using an iterative one.</p><p>To get to our leaves and measure their depths, we’ll have to traverse the tree somehow. <strong>What methods do we know for traversing a tree?</strong></p><p>Depth-first ↴ and breadth-first ↴ are common ways to traverse a tree. Which one should we use here?</p><p>The worst-case time and space costs of both are the same — you could make a case for either.</p><p>But one characteristic of our algorithm is that it could <strong>short-circuit</strong> and return false as soon as it finds two leaves with depths more than 1 apart. So maybe we should <strong>use a traversal that will hit leaves as quickly as possible…</strong></p><p>Depth-first traversal will generally hit leaves before breadth-first, so let’s go with that. How could we write a depth-first walk that also keeps track of our depth?</p><h3>Solution</h3><p>We do a depth-first walk ↴ through our tree, keeping track of the depth as we go. When we find a leaf, we add its depth to a list of depths <em>if</em> we haven’t seen that depth already.</p><p>Each time we hit a leaf with a new depth, there are two ways that our tree might now be unbalanced:</p><ol><li>There are more than 2 different leaf depths</li><li>There are exactly 2 leaf depths and they are more than 1 apart.</li></ol><p>Why are we doing a depth-first walk and not a breadth-first ↴ one? You could make a case for either. We chose depth-first because it reaches leaves faster, which allows us to short-circuit earlier in some cases.</p><pre>import java.util.ArrayDeque;<br>import java.util.ArrayList;<br>import java.util.Deque;<br>import java.util.List;<br>private static class NodeDepthPair {<br>    BinaryTreeNode node;<br>    int depth;<br>    NodeDepthPair(BinaryTreeNode node, int depth) {<br>        this.node = node;<br>        this.depth = depth;<br>    }<br>}<br>public static boolean isBalanced(BinaryTreeNode treeRoot) {<br>    // a tree with no nodes is superbalanced, since there are no leaves!<br>    if (treeRoot == null) {<br>        return true;<br>    }<br>    // we short-circuit as soon as we find more than 2<br>    List&lt;Integer&gt; depths = new ArrayList&lt;&gt;(3);<br>    Deque&lt;NodeDepthPair&gt; nodes = new ArrayDeque&lt;&gt;();<br>    nodes.push(new NodeDepthPair(treeRoot, 0));<br>    while (!nodes.isEmpty()) {<br>        // pop a node and its depth from the top of our stack<br>        NodeDepthPair nodeDepthPair = nodes.pop();<br>        BinaryTreeNode node = nodeDepthPair.node;<br>        int depth = nodeDepthPair.depth;<br>        // case: we found a leaf<br>        if (node.left == null &amp;&amp; node.right == null) {<br>            // we only care if it&#39;s a new depth<br>            if (!depths.contains(depth)) {<br>                depths.add(depth);<br>                // two ways we might now have an unbalanced tree:<br>                //   1) more than 2 different leaf depths<br>                //   2) 2 leaf depths that are more than 1 apart<br>                if (depths.size() &gt; 2 || (depths.size() == 2<br>                        &amp;&amp; Math.abs(depths.get(0) - depths.get(1)) &gt; 1)) {<br>                    return false;<br>                }<br>            }<br>        // case: this isn&#39;t a leaf - keep stepping down<br>        } else {<br>            if (node.left != null) {<br>                nodes.push(new NodeDepthPair(node.left, depth + 1));<br>            }<br>            if (node.right != null) {<br>                nodes.push(new NodeDepthPair(node.right, depth + 1));<br>            }<br>        }<br>    }<br>    return true;<br>}</pre><p>Complexity</p><p>O(n)<em>O</em>(<em>n</em>) time and O(n)<em>O</em>(<em>n</em>) space.</p><p>For time, the worst case is the tree <em>is</em> balanced and we have to iterate over <em>all </em>n<em>n nodes</em> to make sure.</p><p>For the space cost, we have two data structures to watch: depths and nodes.</p><p>depths will never hold more than three elements, so we can write that off as O(1)<em>O</em>(1).</p><p>Because we’re doing a depth first search, nodes will hold at most d<em>d</em> nodes where d<em>d</em> is the depth of the tree (the number of levels in the tree from the root node down to the lowest node). So we <em>could</em> say our space cost is O(d)<em>O</em>(<em>d</em>).</p><p>But we can also relate d<em>d</em> to n<em>n</em>. <a href="https://www.interviewcake.com/concept/binary-tree#property2">In a balanced tree, d<em>d</em> is O(log⁡2(n))<em>O</em>(log2​(<em>n</em>))</a>. And the <em>more unbalanced</em> the tree gets, the closer d<em>d</em> gets to n<em>n</em>.</p><p>In the worst case, the tree is a straight line of right children from the root where every node in that line also has a left child. The traversal will walk down the line of right children, adding a new left child to nodes at each step. When the traversal hits the rightmost node, nodes will hold <em>half</em> of the n<em>n</em> total nodes in the tree. Half is O(n)<em>O</em>(<em>n</em>), so our worst case space cost is O(n)<em>O</em>(<em>n</em>).</p><h3>What We Learned</h3><p>This is an intro to some tree basics. If this is new to you, don’t worry — it can take a few questions for this stuff to come together. We have a few more coming up.</p><p>Particular things to note:</p><p>Focus on <strong>depth-first ↴ vs breadth-first ↴ traversal</strong>. You should be very comfortable with the differences between the two and the strengths and weaknesses of each.</p><p>You should also be very comfortable coding each of them up.</p><p>One tip: <strong>Remember that breadth-first uses a queue and depth-first uses a stack </strong>(could be the call stack or an actual stack object). That’s not just a clue about implementation, it also helps with figuring out the differences in behavior. Those differences come from whether we visit nodes in the order we see them (first in, first out) or we visit the last-seen node first (last in, first out).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=26d8d5e8aed7" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>