<?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:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Zero to Expert: Enjoy Problem Solving]]></title><description><![CDATA[Enjoy Problem Solving]]></description><link>https://www.zerotoexpert.blog/s/enjoy-problem-solving</link><image><url>https://substackcdn.com/image/fetch/$s_!2hjI!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbf83b9-24f2-4bda-ace7-ea2ba9f53b1d_1024x1024.png</url><title>Zero to Expert: Enjoy Problem Solving</title><link>https://www.zerotoexpert.blog/s/enjoy-problem-solving</link></image><generator>Substack</generator><lastBuildDate>Wed, 08 Apr 2026 09:21:38 GMT</lastBuildDate><atom:link href="https://www.zerotoexpert.blog/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Gwonsoo Lee]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[zerotoexpert@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[zerotoexpert@substack.com]]></itunes:email><itunes:name><![CDATA[Gwonsoo Lee]]></itunes:name></itunes:owner><itunes:author><![CDATA[Gwonsoo Lee]]></itunes:author><googleplay:owner><![CDATA[zerotoexpert@substack.com]]></googleplay:owner><googleplay:email><![CDATA[zerotoexpert@substack.com]]></googleplay:email><googleplay:author><![CDATA[Gwonsoo Lee]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Leetcode Weekly Contest 437]]></title><description><![CDATA[Leetcode Weekly Contest 437 with solution]]></description><link>https://www.zerotoexpert.blog/p/leetcode-weekly-contest-437</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/leetcode-weekly-contest-437</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Sun, 16 Feb 2025 09:05:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99c48acd-da9b-46b0-a422-8e176f1658bb_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Problem 1: <a href="https://leetcode.com/problems/find-special-substring-of-length-k/description/">Find a Special Substring of Length K</a></h3><p>In this problem, we should find the substring with a fixed length that meets the following conditions.</p><ul><li><p>The substring should use only 1 character.</p></li><li><p>The character placed right before and after the substring must not be the same as the character in the substring. It means that if there is a substring whose range is [l,r], then s[l-1] and s[r+1] must not be the character of s[l].</p></li></ul><p>This is very easy because the length of the input string is really short. You can generate all possible substrings of length K and check if it is made with only 1 character. I used sorting to check this. After that, check if the same character exists before and after the substring. </p><pre><code>class Solution {
public:
    bool hasSpecialSubstring(string s, int k) {
        for (int i = 0; i &lt; s.size()-k+1; ++i) {
            string sub = s.substr(i, k);
            sort(sub.begin(), sub.end());
            if (sub[0] == sub.back() &amp;&amp; (i-1 &lt; 0 || s[i-1] != sub[0]) &amp;&amp;  (i + k &gt;= s.size() || s[i+k] != sub[0])) return true;
        }
        return false;
    }
};</code></pre><div><hr></div><h3>Problem 2: <a href="https://leetcode.com/problems/eat-pizzas/description/">Eat Pizzas!</a></h3><p>We should find the maximum weight we can get after eating all the pizzas. No matter how we eat the pizza daily, we only get the maximum or the second maximum based on the day's index. </p><p>This problem is easy if you find the greedy solution where you can greedily choose the maximum on the odd day and the second largest on the even days. However you should ensure that this algorithm really works in this problem.</p><p>Here&#8217;s why the greedy algorithm works.</p><ul><li><p>Let&#8217;s say there are piazzas like [1,2,3,4,5,6,7,8,9,10,11,12] (sorted order).</p></li><li><p>On the first day, we can choose a maximum of 12. (We should eat the three lightest pizzas together.) Then we only have [4,5,6,7,8,9,10,11]</p></li><li><p>On the second day, we choose the second largest one, 10. Then, we should choose 11 to make 10 the second largest. So, we only have [6,7,8,9].</p></li><li><p>On the third day, we gained 9 because it was the maximum weight. </p><ul><li><p>However, if we choose [4,5,9,10] to achieve weight 9, we can achieve 11 on the third day.</p></li><li><p>It means that If there are three values [X, Y, Z] where X &lt;= Y &lt;= Z, then we can choose (X, Y) or (X, Z).</p><ul><li><p>If we choose Y on the even-numbered day, then it will be (X, Y)</p></li><li><p>Otherwise, (X, Z).</p></li></ul></li><li><p>X + Z is always larger than X + Y. Therefore, to get the maximum number, we should choose the odd-numbered day first.</p></li></ul></li></ul><p>So, we can choose the largest (total days + 1 / 2) weights first for the odd-numbered day, and then the second largest one for (total days / 2) times.</p><pre><code>class Solution {
public:
    long long maxWeight(vector&lt;int&gt;&amp; pizzas) {
        const int n = (int)pizzas.size();
        sort(pizzas.rbegin(), pizzas.rend());
        int days = n / 4;
        int idx = 0;
        long long sum = 0LL;
        for (int i = 0; i &lt; (days+1) / 2; ++i) {
            sum += pizzas[idx++];
        }
        
        for (int i = 0; i &lt; days / 2; ++i) {
            sum += pizzas[++idx];
            ++idx;
        }
        return sum;
    }
};</code></pre><div><hr></div><h3>Problem 3: <a href="https://leetcode.com/problems/select-k-disjoint-special-substrings/description/">Select K Disjoint Special Substrings</a></h3><p>In this problem, one character should be in only one substring. If we choose the substring from the index [l, r], every character between them should not exist outside the range.</p><p>Fortunately, we have a maximum of 26 characters, so we can check all substrings that start with every character.</p><ul><li><p>Get the start and end position of every character.</p></li><li><p>Sort the intervals based on the start.</p></li><li><p>Iterate the character from the one that appears at the latest. This is for the number of possible disjoint substrings after the current index.</p><ul><li><p>For example, if the last index of the substring starting with index i is j, then we can get (dp[j+1] + 1) substrings. To get dp[j+1], we should calculate it before visiting the current index. So, we should iterate from the last.</p></li></ul></li><li><p>Every character has its start and end index. However, the end index should be changed if another character has a more extensive index than the end index. Also, if there is a character that should start before the current start index, then we can skip the current character because it will be checked later.</p><ul><li><p>For example, if the substring is &#8220;BABEAE&#8221; and the current character is A, the start index is 1, and the end index is 4.</p></li><li><p>However, E is between [1,4] and appears again after the end index of A. So, we should extend the end index to 5.</p></li><li><p>In this case, the character B exists between [1,4] and B should start before index 1. So, we have to skip to get the answer with a substring that begins with A because it is impossible.</p></li></ul></li></ul><p></p><p>This algorithm takes only O(26 * N) times because, for every character, we only iterate the input string at once.</p><pre><code>class Solution {
public:
    bool maxSubstringLength(string s, int k) {
        vector&lt;pair&lt;int,int&gt;&gt; intervals(26, {-1, -1});
        const int n = (int)s.size();
        for (int i = 0; i &lt; n; ++i) {
            int j = s[i]-'a';
            intervals[j].second = i;
            if (intervals[j].first == -1) intervals[j].first = i;
        }
        vector&lt;pair&lt;int,int&gt;&gt; arr;
        for (int i = 0; i &lt; intervals.size(); ++i) {
            if (intervals[i].first != -1) arr.push_back(intervals[i]);
        }
        sort(arr.begin(), arr.end());
        vector&lt;int&gt; dp(arr.size()+1, 0);
        int ans = 0;
        for (int i = arr.size()-1; i &gt;= 0; --i) {
            dp[i] = max(dp[i], dp[i+1]);
            int l = arr[i].first, r = arr[i].second;            
            int end = r;
            bool flag = true;
            for (int j = l+1; j &lt; r; ++j) {
                int ns = intervals[s[j]-'a'].first, ne = intervals[s[j]-'a'].second;
                if (ns &lt; l) {
                    flag = false;
                    break;
                }
                r = max(r, ne);
            }
            if (flag &amp;&amp; r - l + 1 &lt; s.size()) {
                int idx = lower_bound(arr.begin(), arr.end(), make_pair(r, r)) - arr.begin();
                dp[i] = max(dp[i], dp[idx] + 1);
            }
            ans = max(ans, dp[i]);
        }
        return ans &gt;= k;
    }
};</code></pre><div><hr></div><h3>Problem 4: <a href="https://leetcode.com/problems/length-of-longest-v-shaped-diagonal-segment/description/">Length of Longest V-Shaped Diagonal Segment</a></h3><p>In this problem, we should find the most extended length that can be made with a 1,2,0,2,0 sequence. Unlike the typical DFS problem, we should move in a diagonal direction. Also, we can change the direction to one way and at most once. </p><p>The solution is basically the same as for the typical DFS problem. However, before proceeding, we should consider the following two cases.</p><ul><li><p>Choose the same direction. </p></li><li><p>Choose to take a 90-degree clockwise turn. In this case, we should not change the direction before.</p></li><li><p>The value difference between the current and the following positions should be 2 for both cases.</p></li></ul><p>So, for every position (x, y), we can keep track of the direction and the changed times. With this information, we can make all the possible moves. </p><p>However, if the grid size increases, we might visit the same position with the same condition more than once. So, we can use memoization to save the previous result and then return if we visit again. This can be possible because we have at most 500 * 500 * 4 * 2 cases. (500 - m, 500 - n, 4 - direction, 2 - the number of turns)</p><p>We don&#8217;t need to keep track of the previous visit during DFS because we cannot go back because we cannot change the direction 180 degrees. We cannot change the direction twice, so revisiting the same node within the single DFS traverse is impossible.</p><pre><code>class Solution {
public:
    int dx[4] = {1,1,-1,-1};
    int dy[4] = {1,-1,-1,1};
    int dp[501][501][4][2];
    bool pos(int x, int y, int nx, int ny, vector&lt;vector&lt;int&gt;&gt; &amp; grid) {
        return nx &gt;= 0 &amp;&amp; ny &gt;= 0 &amp;&amp; nx &lt; grid.size() &amp;&amp; ny &lt; grid[0].size() &amp;&amp; abs(grid[x][y] - grid[nx][ny]) == 2;
    }

    int dfs(vector&lt;vector&lt;int&gt;&gt; &amp; grid, int x, int y, int d, int changed) {
        if (dp[x][y][d][changed] != -1) return dp[x][y][d][changed];
        // follow the direction
        int ans = 1;
        if (pos(x, y, x + dx[d], y + dy[d], grid)) {
            ans = max(ans, dfs(grid, x + dx[d], y + dy[d], d, changed) + 1);
        }
        // change the direction
        if (changed == 0) {
            int nd = (d + 1) % 4;
            if (pos(x, y, x + dx[nd], y + dy[nd], grid)) {
                ans = max(ans, dfs(grid, x + dx[nd], y + dy[nd], nd, changed+1) + 1);
            }
        }
        return dp[x][y][d][changed] = ans;
    }
    int lenOfVDiagonal(vector&lt;vector&lt;int&gt;&gt;&amp; grid) {
        const int n = (int)grid.size();
        const int m = (int)grid[0].size();
        int ans = 0;
        memset(dp, -1, sizeof(dp));
        for (int i = 0; i &lt; n; ++i) {
            for (int j = 0; j &lt; m; ++j) {
                if (grid[i][j] == 1) {
                    ans = max(ans, 1);
                    for (int k = 0; k &lt; 4; ++k) {
                        int nx = i + dx[k], ny = j + dy[k];
                        if (nx &lt; 0 || ny &lt; 0 || nx &gt;= n || ny &gt;= m || grid[nx][ny] != 2) continue;
                        ans = max(ans, dfs(grid, nx, ny, k, 0) + 1);
                    }
                }
            }
        }
        return ans;
    }
};</code></pre><p></p>]]></content:encoded></item><item><title><![CDATA[Lowest Common Ancestor(LCA) Problems]]></title><description><![CDATA[Lowest Common Ancestor problems & solutions]]></description><link>https://www.zerotoexpert.blog/p/lowest-common-ancestorlca-problems</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/lowest-common-ancestorlca-problems</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Mon, 10 Feb 2025 17:45:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbf83b9-24f2-4bda-ace7-ea2ba9f53b1d_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Problem1: <a href="https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/">Lowest Common Ancestor of a Binary Search Tree</a></h3><p>In the problem, we should find the common ancestor of p and q. Unlike in typical LCA problems, BST is used here, so we can take advantage of it. </p><ul><li><p>If the current node is larger than p and q, it cannot be the ancestor. The right ancestor will be in the left tree.</p></li><li><p>If the current node is smaller than p and q, the answer should be in the right tree.</p></li><li><p>Otherwise, the current value should be the lowest common ancestor because one might be on the left and the other on the right. (Or the current node can be one of p and q)</p></li></ul><pre><code>class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        while(true) {
            if(root-&gt;val &lt; p-&gt;val &amp;&amp; root-&gt;val &lt; q-&gt;val) root = root-&gt;right;
            else if (root-&gt;val &gt; p-&gt;val &amp;&amp; root-&gt;val &gt; q-&gt;val) root = root-&gt;left;
            else break;
        }
        return root;
    }
};</code></pre><div><hr></div><h3>Problem2: <a href="https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/">Lowest Common Ancestor of a Binary Tree</a></h3><p>Now, we should find the LCA from Binary Tree. This is not the BST, so we cannot decide which child node will contain the answer. It means that we should find the left and right child together.</p><p>The important thing is to determine which case can be the ancestor. Let&#8217;s say we already checked the left and right nodes. </p><ul><li><p>If both the left and right nodes find something, the current node should be the common ancestor. </p></li><li><p>If not, there are two cases.</p><ul><li><p>1) Find p or q, not both.</p></li><li><p>2) Find nothing.</p></li><li><p>In both cases, <strong>the current node may be either p or q</strong>. If so, we can return it. The current node will be the answer if it is the first case. In the second case, the current node will indicate to the parent that it finds p or q.</p></li></ul></li><li><p>If the current node is not p and q, then we can return the found node from the result of the left and right nodes. </p></li></ul><pre><code>class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr) return nullptr;

        TreeNode* left = lowestCommonAncestor(root-&gt;left, p, q);
        TreeNode* right = lowestCommonAncestor(root-&gt;right, p, q);

        if ((left != nullptr &amp;&amp; right != nullptr) || root == p || root == q) {
            return root;
        }

        return left == nullptr ? right : left;
    }
};</code></pre><div><hr></div><h3>Problem3: <a href="https://leetcode.com/problems/lowest-common-ancestor-of-deepest-leaves/description/">Lowest Common Ancestor of Deepest Leaves</a></h3><p>In this problem, we should find the LCA of deepest leaves. If there are more than 1 leaves that have the deepest depth, we should find the LCA for all of them. This is tricky, even though we search with DFS, we cannot know if it is the deepest leaves or not.</p><p>So, when we search the left and right nodes, we should get the deepest depth information as well. If the deepest depth of left tree is larger than that of right tree, then we should return LCA of left tree. It&#8217;s because the right tree must not be the answer. Of course, in this case, we are unsure that the LCA from left tree is the correct answer. There might be another LCA that has deeper depth.</p><ul><li><p>To make the logic work, we should return the empty node&#8217;s depth with -1. This will work because if left node is empty and the right node is not, right node&#8217;s depth is always larger than that of the left node.</p></li></ul><p>If the depth of left and right nodes are equal, then we should return the current node as the LCA. If it is the leaf node, then left and right node depth will be same and the value is -1. In this case, we should return the current depth to let parent know the deepest depth it has.</p><pre><code>class Solution {
public:
    pair&lt;TreeNode*, int&gt; findLCADeepestLeaves(TreeNode* root, int depth) {
        if (root == nullptr) return {nullptr,  -1};

        auto left = findLCADeepestLeaves(root-&gt;left, depth + 1);
        auto right = findLCADeepestLeaves(root-&gt;right, depth + 1);

        if (left.second == right.second) {
            return {root, left.second == -1 ? depth : left.second };
        }
        return left.second &lt; right.second ? right : left;
    }

    TreeNode* lcaDeepestLeaves(TreeNode* root) {
        return findLCADeepestLeaves(root, 0).first;
    }
};</code></pre><div><hr></div><h3>Problem4: <a href="https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree-ii/">Lowest Common Ancestor of a Binary Tree II</a></h3><p>This problem is a bit harder one than problem2. It&#8217;s because p or q might not be in the tree. We should return the LCA if and only if we find both of them in the tree.</p><p>Basic logic is the same as the problem2,  but I added the boolean value that indicates returned node is the LCA. If it is false, then the node is just one of p or q, not the LCA.</p><p>If one of the left or right node found the answer, then we can return it. Unless, we should check the current node can be the answer. </p><ul><li><p>If both left node and right node finds the LCA but those are not the answer, then p and q is found from different child. So the LCA of them is the current node.</p></li><li><p>If one of them finds the LCA and the current node is either p or q, then it also means that the current node is LCA.</p></li><li><p>In those two cases, the current node is the answer, so we should return true for the second return value.</p></li></ul><p>Even though the current node is not the LCA, it can be one of p or q. If not, then the current node is useless, so return the non-empty node between left and right. (Both can be null, but it&#8217;s okay)</p><pre><code>class Solution {
public:
    pair&lt;TreeNode*, bool&gt; lowestCommonAncestorHelper(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (!root) return {nullptr, false};

        auto left = lowestCommonAncestorHelper(root-&gt;left, p, q);
        auto right = lowestCommonAncestorHelper(root-&gt;right, p, q);
        if ((left.first != nullptr &amp;&amp; right.first != nullptr) || ((root == p || root == q) &amp;&amp; (left.first != nullptr || right.first != nullptr))) {
            return {root, true};
        }

        if (left.second) return left;
        if (right.second) return right;

        if (root == p || root == q) return {root, false};

        return left.first == nullptr ? right : left;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        auto answer = lowestCommonAncestorHelper(root, p, q);
        return answer.second ? answer.first : nullptr;
    }
};</code></pre><div><hr></div><h3>Problem5: <a href="https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree-iii/description/">Lowest Common Ancestor of a Binary Tree III</a></h3><p>In this problem, we don&#8217;t know the root and only p and q are provided. However, each node has the parent infromation. So we can find the parent if we keep following the parent node.</p><p>To find the LCA, we can look through all parents of p and q. </p><ul><li><p>Store all parents of p, including p itself until it reach to the root node.</p></li><li><p>Find the LCA by checking if the parent of q exists.</p></li><li><p>If nothing is found, then return nullptr.</p></li></ul><pre><code>class Solution {
public:
    Node* lowestCommonAncestor(Node* p, Node * q) {
        unordered_set&lt;Node*&gt; parents;
        while(p != nullptr) {
            parents.insert(p);
            p = p-&gt;parent;
        }
        while(q != nullptr) {
            if (parents.find(q) != parents.end()) return q;
            q = q-&gt;parent;
        }
        return nullptr;
    }
};</code></pre><p>This problem can be solved without storing the parent information. No matter where p and q is located in the tree, they have each own depth from the root node. If the depth is equal, then we can simply iterate the parent one by one until both get the same parent. </p><p>However, the problem happens when the depth is different. Let&#8217;s seem the following graph. </p><ul><li><p>If p and q moves to each parent at the same speed, then the shorter one will arrive at the root node first. In this case, p is the shorter one and it will iterate A + B times.  q also iterates A + B times. It is the C - A far from the root.</p></li><li><p>p is now starting again from original q position. Then it should iterate C times more to get to the LCA. However, before iterating C, q will arrive at the root node after C - A iteration. p also moves C - A times so it will be A far from the LCA.</p></li><li><p>if q starts from original p postion, both of them should move A times. In conclustion, both p and q moves A + B + C times to get to the LCA node.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wM-H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wM-H!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png 424w, https://substackcdn.com/image/fetch/$s_!wM-H!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png 848w, https://substackcdn.com/image/fetch/$s_!wM-H!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png 1272w, https://substackcdn.com/image/fetch/$s_!wM-H!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wM-H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png" width="1174" height="935" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:935,&quot;width&quot;:1174,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:122226,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wM-H!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png 424w, https://substackcdn.com/image/fetch/$s_!wM-H!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png 848w, https://substackcdn.com/image/fetch/$s_!wM-H!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png 1272w, https://substackcdn.com/image/fetch/$s_!wM-H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc478148a-5bca-4c00-9857-26f8ab470d7b_1174x935.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><pre><code>class Solution {
public:
    Node* lowestCommonAncestor(Node* p, Node * q) {
        Node* l = p;
        Node* r = q;
        while(l != r) {
            l = l-&gt;parent == nullptr ? q : l-&gt;parent;
            r = r-&gt;parent == nullptr ? p : r-&gt;parent;
        }
        return l;
    }
};</code></pre><div><hr></div><h3>Problem6: <a href="https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree-iv/description/">Lowest Common Ancestor of a Binary Tree IV</a></h3><p>In this problem, we should find the LCA of multiple nodes. We can find all LCA one by one, but it will take too long time if array size is big. However, if you try to find the LCA with this method, then you will get one possible optimization idea.</p><p>Let&#8217;s assume that we find the LCA of A and B. (let&#8217;s call it X)  If we want to find LCA of A,B,C, then we can find the LCA of X and C. </p><p>What if C is located in the child tree of X? In this case, we don&#8217;t need to consider C. It means that if we find a node, then we don&#8217;t need to think about the LCA of other nodes that are located in the child tree. So, we just can return the current node as the LCA.</p><p>So, the basic logic is mostly the same as the problem 2. One thing different is that, if the current node is in the list of nodes that we have to find, then we just can return the current node ans the LCA, no matter what result of left or right.</p><pre><code>class Solution {
public:
    TreeNode* lowestCommonAncestorHelper(TreeNode* root, unordered_set&lt;TreeNode*&gt; &amp; s) {
        if (root == nullptr) return nullptr;

        TreeNode* left = lowestCommonAncestorHelper(root-&gt;left, s);
        TreeNode* right = lowestCommonAncestorHelper(root-&gt;right, s);

        if ((left != nullptr &amp;&amp; right != nullptr) || s.find(root) != s.end()) {
            return root;
        }

        return left == nullptr ? right : left;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, vector&lt;TreeNode*&gt; &amp;nodes) {
        unordered_set&lt;TreeNode*&gt; s;
        for (auto &amp; node: nodes) s.insert(node);

        return lowestCommonAncestorHelper(root, s);
    }
};</code></pre>]]></content:encoded></item><item><title><![CDATA[Optimal Partition of String]]></title><description><![CDATA[Greedy Algorithm, Hash Table.]]></description><link>https://www.zerotoexpert.blog/p/optimal-partition-of-string</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/optimal-partition-of-string</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Mon, 10 Feb 2025 14:05:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99c48acd-da9b-46b0-a422-8e176f1658bb_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://leetcode.com/problems/optimal-partition-of-string/">Original Link</a></p><h1>Solution</h1><p>In this problem, we should find the minimum possible partitions that can divide the input string into multiple(or single) substring(s). The same character should not be in each substring. </p><p>We should create a substring containing as many characters as possible. Otherwise, we might need more partitions to split the duplicated characters. Thus, a greedy algorithm simply works here.</p><pre><code>class Solution {
public:
    int partitionString(string s) {
        int n = (int)s.size();
        int ans = 1;
        vector&lt;int&gt; cnt(26, 0);
        for (int i = 0; i &lt; n; ++i) {
            if (cnt[s[i]-'a'] == 1) {
                ++ans;
                fill(cnt.begin(), cnt.end(), 0);
            }
            cnt[s[i]-'a']++;
        }
        return ans;
    }
};</code></pre><p></p><h3><strong>Then, why does the greedy algorithm work here?</strong></h3><p>Let&#8217;s say that we get the answer without using a greedy algorithm. There are two partitions: A and B.</p><ul><li><p>A partition can contain more characters in the B partitions. If not, then we already solved it with a greedy algorithm.</p></li><li><p>A partition can contain some characters or all characters in the B partitions. It means that a greedy solution gets the same or fewer partitions. So, the greedy algorithm works here.</p></li></ul>]]></content:encoded></item><item><title><![CDATA[The kth Factor of n]]></title><description><![CDATA[Math, Number Theory]]></description><link>https://www.zerotoexpert.blog/p/the-kth-factor-of-n</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/the-kth-factor-of-n</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Mon, 10 Feb 2025 12:49:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99c48acd-da9b-46b0-a422-8e176f1658bb_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://leetcode.com/problems/the-kth-factor-of-n/description/">Original Link</a></p><h1>Solution</h1><p>In this problem, we should find all factors that meet n % i == 0. Before getting all possible factors, we are unsure if there is a kth number. The easy solution is to check all possible numbers if n is divisible by that number. The input size is too small, so we can solve this problem with O(N) time complexity.</p><pre><code>class Solution {
public:
    int kthFactor(int n, int k) {
        for (int i = 1; i &lt;= n; ++i) {
            if (n % i == 0) --k;
            if (k == 0) return i;
        }
        return -1;
    }
};</code></pre><p></p><p>However, this solution won&#8217;t pass if the size of N becomes large. To make it more effective, we should consider the divisor's aspect. When a number X can divide the number N, then we know that N/X is also the divisor of N. For example, if we know that 3 is the divisor of 15, 5 is also the divisor of 15 automatically. </p><p>Then, we don&#8217;t need to check all numbers smaller than N. We only need to check until ceil(sqrt(N)). It&#8217;s because that sqrt(N) * sqrt(N) = N. If the previous sqrt(N) is larger, the later sqrt(N) should be smaller. It was already checked before we checked sqrt(N).</p><pre><code>class Solution {
public:
    int kthFactor(int n, int k) {
        vector&lt;int&gt; ans;
        for (int i = 1; i * i &lt;= n; ++i) {
            if (n % i == 0) ans.push_back(i);
            if (n % i == 0 &amp;&amp; i != n / i) ans.push_back(n/i);
        }
        sort(ans.begin(), ans.end());
        k--;
        return ans.size() &gt; k ? ans[k] : -1;
    }
};</code></pre>]]></content:encoded></item><item><title><![CDATA[Number of Possible Sets of Closing Branches]]></title><description><![CDATA[Shortest Path]]></description><link>https://www.zerotoexpert.blog/p/number-of-possible-sets-of-closing</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/number-of-possible-sets-of-closing</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Sun, 09 Feb 2025 16:23:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99c48acd-da9b-46b0-a422-8e176f1658bb_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://leetcode.com/problems/number-of-possible-sets-of-closing-branches/description/">Original Link</a></p><h1>Solution</h1><p>In the problem, we should find all possible ways to close the branches. All the branches that survive must be connected to each other, and the distance between them should be no longer than the maximum distance. </p><p>The maximum input of n is 10, so there are only 1024 states to select the survived branches. Also, we should find all the shortest paths from each branch to the others. It will take N*NlogN times to detect whether the current state is valid. So, it will be O(2^N * N^2logN). This value is really low even with N = 10.</p><p></p><ul><li><p>Iterates all possible ways to select the survived branches.</p></li><li><p>Iterates all survived branches as the starting branch to find the shortest path to other branches. </p></li><li><p>Check if there is any case that one branch cannot connect to the other branch. If there is any, then this cannot be possible.</p></li></ul><pre><code>#include &lt;vector&gt;
#include &lt;unordered_map&gt;
#include &lt;queue&gt;
using namespace std;

class Solution {
public:
    int numberOfSets(int n, int maxDistance, vector&lt;vector&lt;int&gt;&gt;&amp; roads) {
        unordered_map&lt;int, vector&lt;pair&lt;int, int&gt;&gt;&gt; graph;

        // Build adjacency list representation of the graph
        for (const auto&amp; road : roads) {
            int u = road[0], v = road[1], w = road[2];
            graph[u].push_back({v, w});
            graph[v].push_back({u, w});
        }

        int validSets = 1; // At least one valid set (empty set)
        
        // Iterate over all subsets of branches (bitmasking)
        for (int mask = 1; mask &lt; (1 &lt;&lt; n); ++mask) {
            vector&lt;int&gt; openBranches;

            // Extract the branches that are open in this subset
            for (int j = 0; j &lt; n; ++j) {
                if (mask &amp; (1 &lt;&lt; j)) { // Check if branch `j` is included
                    openBranches.push_back(j);
                }
            }

            bool isValid = true;

            // Run Dijkstra's algorithm for each branch in the subset
            for (auto&amp; startNode : openBranches) {
                vector&lt;int&gt; dist(n, -1); // Distance array initialized to -1
                dist[startNode] = 0;

                priority_queue&lt;pair&lt;int, int&gt;&gt; pq;
                pq.push({0, startNode}); // {distance, node}

                while (!pq.empty()) {
                    auto [negDistance, node] = pq.top();
                    pq.pop();
                    int currDistance = -negDistance; // Convert back to positive distance

                    // If this path is not optimal, skip
                    if (dist[node] != -1 &amp;&amp; dist[node] &lt; currDistance) continue;

                    // Relax edges
                    for (auto&amp; [neighbor, weight] : graph[node]) {
                        if (mask &amp; (1 &lt;&lt; neighbor)) { // Only consider nodes in the subset
                            int newDistance = currDistance + weight;
                            if ((dist[neighbor] == -1 || dist[neighbor] &gt; newDistance) &amp;&amp; newDistance &lt;= maxDistance) {
                                dist[neighbor] = newDistance;
                                pq.push({-newDistance, neighbor}); // Push with negative distance for min-heap
                            }
                        }
                    }
                }

                // Check if all selected branches are reachable
                for (auto&amp; branch : openBranches) {
                    if (dist[branch] == -1) {
                        isValid = false;
                        break;
                    }
                }

                if (!isValid) break;
            }

            if (isValid) validSets++; // Count this subset if it's valid
        }

        return validSets;
    }
};
</code></pre><h3><strong>Time Complexity Analysis</strong></h3><ul><li><p><strong>Generating subsets:</strong> O(2^n) (Iterating over all subsets)</p></li><li><p><strong>Dijkstra's Algorithm:</strong> O(nlog&#8289;n) per subset (Priority queue operations)</p></li><li><p><strong>Checking All Subsets:</strong> O(n) in worst case</p></li></ul><p>Thus, the overall complexity is <strong>O(2^n&#8901;n^2&#8901;log n)</strong>, making this approach feasible only for small <code>n</code> (typically n&#8804;20).</p>]]></content:encoded></item><item><title><![CDATA[Leetcode Weekly Contest 436]]></title><description><![CDATA[Solutions for Weekly Contest 436]]></description><link>https://www.zerotoexpert.blog/p/weekly-contest-436</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/weekly-contest-436</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Sun, 09 Feb 2025 08:31:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99c48acd-da9b-46b0-a422-8e176f1658bb_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>Problem1: <a href="https://leetcode.com/problems/sort-matrix-by-diagonals/description/">Sort Matrix by Diagonals</a></h3><p>In this problem, we need to sort the diagonals. First, we should sort the numbers of bottom-left matrix in the non-increasing order. To sort the diagonals, we can start the first index of each row. And then we can move row + 1, col + 1 to get all values in the same diagonal. After sorting the values, then reassign the values to the matrix with the same logic.</p><p>For the top-right matrix, we can start the first index of each column except for the first column. First column will be calculated in the bottom-left case. The logic is the same as the bottom-left case.</p><pre><code>class Solution {
public:
    vector&lt;vector&lt;int&gt;&gt; sortMatrix(vector&lt;vector&lt;int&gt;&gt;&amp; grid) {
        int n = grid.size();

        // Sort diagonals in bottom-left (including middle diagonal) in non-increasing order
        for (int i = n - 1; i &gt;= 0; --i) {
            int row = i, col = 0;
            vector&lt;int&gt; diagonal;

            // Extract diagonal elements
            while (row &lt; n &amp;&amp; col &lt; n) {
                diagonal.push_back(grid[row][col]);
                row++, col++;
            }

            // Sort in descending order
            sort(diagonal.rbegin(), diagonal.rend());

            // Put sorted values back into the matrix
            row = i, col = 0;
            for (int val : diagonal) {
                grid[row++][col++] = val;
            }
        }

        // Sort diagonals in top-right in non-decreasing order
        for (int i = 1; i &lt; n; ++i) {
            int row = 0, col = i;
            vector&lt;int&gt; diagonal;

            // Extract diagonal elements
            while (row &lt; n &amp;&amp; col &lt; n) {
                diagonal.push_back(grid[row][col]);
                row++, col++;
            }

            // Sort in ascending order
            sort(diagonal.begin(), diagonal.end());

            // Put sorted values back into the matrix
            row = 0, col = i;
            for (int val : diagonal) {
                grid[row++][col++] = val;
            }
        }

        return grid;
    }
};
</code></pre><h3><strong>Time Complexity Analysis</strong></h3><ul><li><p><strong>Extracting diagonals</strong>: O(n)per diagonal.</p></li><li><p><strong>Sorting diagonals</strong>: O(nlog&#8289;n) per diagonal.</p></li><li><p><strong>Placing elements back</strong>: O(n) per diagonal.</p></li><li><p>There are approximately 2n&#8722;1 diagonals.</p></li></ul><p>Thus, the total complexity is <strong>O(n2log&#8289;n)</strong>.</p><div><hr></div><h3>Problem2: <a href="https://leetcode.com/problems/assign-elements-to-groups-with-constraints/description/">Assign Elements to Groups with Constraints</a></h3><p>In this problem, we should assign the divisible elements to the value in the groups. We cannot check all elements to all values because the number of groups and elements is 10^5. It should be TLE.</p><p>I remove the duplicated elements by choosing only earlier one. Also, I sorted the group values to reduce the search range after getting unique values. I can store the indexes of each value with the hashmap. When I should find the divisible values with the element, find the lower_bound from the distinct value array. This only cost O(logN). </p><p>To reduce the time complexity, I calculated the element value 1, because it will be applied to all groups by default. </p><pre><code>class Solution {
public:
    vector&lt;int&gt; assignElements(vector&lt;int&gt;&amp; groups, vector&lt;int&gt;&amp; elements) {
        unordered_map&lt;int, vector&lt;int&gt;&gt; groupToIndices;
        
        // Store indices of each group size
        for (int i = 0; i &lt; groups.size(); ++i) {
            groupToIndices[groups[i]].push_back(i);
        }

        // Extract unique group sizes and sort them
        vector&lt;int&gt; sortedGroups;
        for (auto &amp;entry : groupToIndices) {
            sortedGroups.push_back(entry.first);
        }
        sort(sortedGroups.begin(), sortedGroups.end());

        // Track the smallest index for each element in elements
        unordered_map&lt;int, int&gt; elementIndex;
        for (int i = elements.size() - 1; i &gt;= 0; --i) {
            elementIndex[elements[i]] = i; // Store the smallest index for each element
        }

        // Map to store the smallest valid element index for each group
        unordered_map&lt;int, int&gt; bestAssignment;
        
        // Iterate over each element and check its multiples in sortedGroups
        for (auto &amp;[element, index] : elementIndex) {
            int multiple = element;
            while (multiple &gt; 1) {
                auto it = lower_bound(sortedGroups.begin(), sortedGroups.end(), multiple);
                if (it == sortedGroups.end()) break; // No more valid groups

                if (*it == multiple &amp;&amp; (bestAssignment.count(multiple) == 0 || bestAssignment[multiple] &gt; index)) {
                    bestAssignment[*it] = index; // Store the smallest index
                }
                multiple += element;
            }
        }

        // Initialize answer array with -1 (default unassigned state)
        vector&lt;int&gt; result(groups.size(), -1);
        
        // Special case: If '1' is present in elements, assign its index to all groups
        if (elementIndex.count(1) &gt; 0) {
            fill(result.begin(), result.end(), elementIndex[1]);
        }

        // Assign best matching element indices to corresponding groups
        for (auto &amp;[groupSize, assignedIndex] : bestAssignment) {
            for (auto &amp;groupIndex : groupToIndices[groupSize]) {
                if (result[groupIndex] == -1 || result[groupIndex] &gt; assignedIndex) {
                    result[groupIndex] = assignedIndex;
                }
            }
        }
        
        return result;
    }
};
</code></pre><h3><strong>Time Complexity Analysis</strong></h3><ul><li><p><strong>Sorting the unique group sizes:</strong> O(Glog&#8289;G)(where G is the number of unique group sizes).</p></li><li><p><strong>Traversing </strong><code>elements</code><strong> and populating </strong><code>els</code><strong>:</strong> O(E)</p></li><li><p><strong>Checking divisibility using a sieve-like approach:</strong> O(Elog&#8289;G).</p></li><li><p><strong>Final assignment process:</strong> O(G).</p></li></ul><p>Thus, the overall complexity is approximately <strong>O(Elog&#8289;G+Glog&#8289;G)</strong>, which is efficient.</p><div><hr></div><h3>Problem3: <a href="https://leetcode.com/problems/count-substrings-divisible-by-last-digit/description/">Count Substrings Divisible By Last Digit</a></h3><p>In this problem, we should find the substring that is divisible by the last digit. The string size is too long, so that we cannot check all substrings within the limited time.</p><p>In this case, we can only find the substring by calculating the remainder.  Let&#8217;s say dp[i] means the number of substrings whose remainder is i in the current index. If we move to the next index, then every substring will become X * 10 + (next value). It means that every remainders should be recalculated by (X * 10 + next value)  % divisor.</p><ul><li><p>For example, if  we divide the substring &#8220;124&#8221; by 3, then the remainder will be 1.</p></li></ul><ul><li><p>If the substring is &#8220;1243&#8221;, which is the 124 * 10 + 3, then the remainder will be (1 * 10 + 3) % 3 == 1. It means that If calculate the next remainder with current value x, then we can find the divisible substring counts with dp[0].</p></li></ul><p>If the current value is the divisor, then we should add dp[0] to answer.</p><pre><code>class Solution {
public:
    long long countSubstrings(string s) {
        long long totalCount = 0; // Stores the total valid substrings count
        
        // Iterate over possible last digits (1 to 9)
        for (int divisor = 1; divisor &lt;= 9; ++divisor) {
            vector&lt;long long&gt; dp(divisor, 0LL); // DP array to track remainders

            // Iterate over the string to form substrings
            for (char c : s) {
                int currentDigit = c - '0'; // Convert char to int
                vector&lt;long long&gt; newDP(divisor, 0LL); // Temporary DP array

                // Update DP table for substrings ending at this digit
                for (int remainder = 0; remainder &lt; divisor; ++remainder) {
                    int newRemainder = (remainder * 10 + currentDigit) % divisor;
                    newDP[newRemainder] += dp[remainder]; // Carry forward counts
                }

                // Count single-digit substrings
                newDP[currentDigit % divisor]++;
                
                // Update DP table
                dp = newDP;

                // If the substring ending here is divisible by its last digit, count it
                if (currentDigit == divisor) {
                    totalCount += dp[0];
                }
            }
        }
        
        return totalCount;
    }
};
</code></pre><h3><strong>Time Complexity Analysis</strong></h3><ul><li><p><strong>Outer Loop:</strong> Iterate over <strong>digits 1 to 9</strong> &#8594; O(9)=O(1) (constant).</p></li><li><p><strong>Inner Loop:</strong> Traverse the string of length n &#8594; O(n).</p></li><li><p><strong>Modular DP Update:</strong> Runs in O(9) time for each character.</p></li></ul><p>Thus, the total complexity is <strong>O(n)</strong>, which is optimal for this problem.</p><div><hr></div><h3>Problem4: <a href="https://leetcode.com/problems/maximize-the-minimum-game-score/description/">Maximize the Minimum Game Score</a></h3><p>In this problem, we should find the maximum of minimum possible value after, at most, m moves. In this problem, if you can make all points to K within m moves, then you can say that all points are over K-1, K-2, etc. So, we can simply check if random X is okay for the answer. If yes, we only need to check the values above X. Otherwise, check those below X. </p><p>Then, we need to solve the problem of whether it is possible to make all values larger than or equal to X if X is given. Now the X is given, we can calculate how many times we should visit every index.</p><ul><li><p><code>cnt[i] = (X + points[i] -1) / points[i]</code></p></li></ul><p>Now, we can visit the index i greedily. No matter how far you go from i, you should visit the current index(i) at cnt[i] times. In this problem you should visit all indexes to get back to the index i and every move counts. So, we just can visit the current cnt[i] times first, then we can go to the next index.</p><ul><li><p>To visit index i, we can start from the previous index. [move i-1 to i] </p></li><li><p>And then, we should go to the next index and get back to the current as many times as needed. [move i to i+1 and move i+1 to i] * (cnt[i]-1 times). </p><ul><li><p>We already visit once from the previous index, so we just need the cnt[i] - 1 times visit more.</p></li><li><p>In this case, we should reduce the cnt[i+1] by (cnt[i]-1). </p></li></ul></li><li><p>If cnt[i] ==0 , even though we don&#8217;t need to visit more, then we need to move to i to visit the i+1 index.</p><ul><li><p>However, if the cnt[i] == 0 and the i == n-1, which means that we don&#8217;t need any more visit after i, then we can stop moving.</p></li></ul></li></ul><pre><code>class Solution {
public:
    // Helper function to check if a given minimum value can be achieved
    bool isPossible(vector&lt;int&gt;&amp; points, long long targetValue, int maxMoves) {
        int n = points.size();
        vector&lt;long long&gt; requiredUpdates(n);
        long long totalMoves = 0;

        // Calculate how many times each index needs to be updated to reach targetValue
        for (int i = 0; i &lt; n; ++i) {
            requiredUpdates[i] = (targetValue + points[i] - 1) / points[i]; // Ceiling division

            // If previous index was updated, adjust current index accordingly
            if (i &gt; 0 &amp;&amp; requiredUpdates[i - 1] &gt; 0) {
                totalMoves += 2 * requiredUpdates[i - 1] - 1;
                requiredUpdates[i] = max(0LL, requiredUpdates[i] - (requiredUpdates[i - 1] - 1));
            } 
            // Otherwise, if we are in the middle of the array, count a move
            else if (i &gt; 0 &amp;&amp; i != n - 1) {
                totalMoves++;
            }
        }

        // Handle last index separately
        if (requiredUpdates[n - 1] &gt; 0) {
            totalMoves += requiredUpdates[n - 1] * 2;
            if (requiredUpdates[n - 2] &gt; 0) {
                totalMoves--; // Reduce one move if second last index was updated
            }
        }

        return totalMoves &lt;= maxMoves; // Check if we can achieve the target value within maxMoves
    }

    // Binary search to find the maximum possible minimum value
    long long maxScore(vector&lt;int&gt;&amp; points, int m) {
        long long left = 1, right = 1e15, answer = 0;

        while (left &lt;= right) {
            long long mid = left + (right - left) / 2;

            // Check if it's possible to maintain a minimum score of 'mid'
            if (isPossible(points, mid, m)) {
                answer = mid; // Store the valid answer
                left = mid + 1; // Try for a higher minimum value
            } else {
                right = mid - 1; // Reduce the search space
            }
        }
        
        return answer; // Maximum minimum possible value
    }
};
</code></pre><h3><strong>Time Complexity Analysis</strong></h3><ul><li><p><strong>Binary Search:</strong> Runs in <strong>O(log 1e15) &#8776; O(50)</strong></p></li><li><p><strong>Greedy Checking Function (</strong><code>pos</code><strong>)</strong>: Iterates over <code>n</code> elements &#8594; <strong>O(n)</strong></p></li><li><p><strong>Total Complexity:</strong> O(nlog&#8289;1e15)&#8776; O(n&#8901;50) &#8594; <strong>O(n)</strong> in practical cases.</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Find Edges in Shortest Paths]]></title><description><![CDATA[Shortest Path, Dijkstra&#8217;s algorithm, Backtracking]]></description><link>https://www.zerotoexpert.blog/p/find-edges-in-shortest-paths</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/find-edges-in-shortest-paths</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Sat, 08 Feb 2025 17:05:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99c48acd-da9b-46b0-a422-8e176f1658bb_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://leetcode.com/problems/find-edges-in-shortest-paths/description/">Original Link</a></p><h1>Solution</h1><p>In this problem, we should find the shortest path from 0 to n-1 and all edges that can be used as the shortest path. </p><p>If you track all the paths while getting the shortest path, this problem seems complicated. We are not sure the current path is the shortest path until we reach the Node n-1.</p><p>However, we know that Dijkstra&#8217;s algorithm returns the shortest path to all nodes from the starting node. It means that if we get all shortest paths from Node 0 to all nodes, then we can detect the edge from that node is used as the shortest path by comparing the <code>dist[currentNode] + cost == dist[targetNode]</code>. </p><p>For example, </p><ul><li><p>The shortest path to Node 1 costs 5. </p></li><li><p>And the shortest path to Node 2 costs 7. </p></li><li><p>Then, if the edge connecting Node 1 and Node 2 costs 2, it can be used as the shortest path to Node 2. </p><ul><li><p>If it costs more than 2, it must not be the shortest path because there is another way to get to Node 2.</p></li></ul></li></ul><p></p><p>We should iterate from the destination to determine whether the edge can be used. We wouldn&#8217;t know the following possible nodes from the start. If we find the possible nodes from the destination, we can get all possible edges to those nodes similarly.</p><pre><code>class Solution {
public:
    vector&lt;bool&gt; findAnswer(int n, vector&lt;vector&lt;int&gt;&gt;&amp; edges) {
        // Step 1: Build the graph as an adjacency list
        unordered_map&lt;int, vector&lt;pair&lt;int, int&gt;&gt;&gt; graph;
        for (int i = 0; i &lt; edges.size(); ++i) {
            int u = edges[i][0], v = edges[i][1];
            graph[u].push_back({v, i}); // Store {neighbor, edge index}
            graph[v].push_back({u, i});
        }

        // Step 2: Use Dijkstra's algorithm to find shortest distances from node 0
        vector&lt;int&gt; dist(n, 1e9);  // Initialize distances with a large value
        dist[0] = 0;
        
        // Min-heap (priority queue) for Dijkstra's algorithm
        priority_queue&lt;pair&lt;int, int&gt;&gt; pq;
        pq.push({0, 0});  // {distance, node}

        while (!pq.empty()) {
            auto current = pq.top();
            pq.pop();
            int d = -current.first;  // Convert back to positive distance
            int node = current.second;

            // Ignore outdated distances (optimization)
            if (dist[node] &lt; d) continue;

            // Explore neighbors
            for (auto &amp;neighborData : graph[node]) {
                int neighbor = neighborData.first;
                int edgeIndex = neighborData.second;
                int cost = edges[edgeIndex][2];  // Edge weight

                // Relaxation step: Update shortest distance if a better path is found
                if (dist[neighbor] &gt; d + cost) {
                    dist[neighbor] = d + cost;
                    pq.push({-dist[neighbor], neighbor});  // Push with negative for min-heap behavior
                }
            }
        }

        // Step 3: Backtrack from node n-1 to find edges used in shortest paths
        vector&lt;bool&gt; answer(edges.size(), false);
        vector&lt;bool&gt; seen(n, false);
        
        queue&lt;int&gt; q;
        q.push(n - 1);
        seen[n - 1] = true;

        while (!q.empty()) {
            int node = q.front();
            q.pop();

            // Traverse backwards to find shortest path edges
            for (auto &amp;neighborData : graph[node]) {
                int neighbor = neighborData.first;
                int edgeIndex = neighborData.second;
                int cost = edges[edgeIndex][2];

                // If the edge belongs to a shortest path
                if (dist[neighbor] + cost == dist[node]) {
                    answer[edgeIndex] = true;
                    if (!seen[neighbor]) {
                        seen[neighbor] = true;
                        q.push(neighbor);
                    }
                }
            }
        }

        return answer;
    }
};</code></pre><h3><strong>Time Complexity Analysis</strong></h3><ul><li><p><strong>Graph Construction:</strong> <code>O(m)</code>, where <code>m</code> is the number of edges.</p></li><li><p><strong>Dijkstra&#8217;s Algorithm:</strong> <code>O((n + m) log n)</code>, since each node and edge are processed at most once in a priority queue.</p></li><li><p><strong>Backtracking using BFS:</strong> <code>O(n + m)</code>, since each node and edge are visited once.</p></li></ul><p><strong>Overall Complexity:</strong> <code>O((n + m) log n)</code>, which is efficient for large graphs.</p>]]></content:encoded></item><item><title><![CDATA[Second Minimum Time to Reach Destination]]></title><description><![CDATA[Shortest Path, Dijkstra&#8217;s algorithm]]></description><link>https://www.zerotoexpert.blog/p/second-minimum-time-to-reach-destination</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/second-minimum-time-to-reach-destination</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Sat, 08 Feb 2025 15:27:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F99c48acd-da9b-46b0-a422-8e176f1658bb_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://leetcode.com/problems/second-minimum-time-to-reach-destination/description">Original Link</a></p><h1>Solution</h1><p>In this problem, we should find the second minimum shortest path to the destination. If the problem wants the minimum shortest path, it would be solved with Dijkstra's algorithm. However, we should do more to find the second one.</p><p>One thing we can do is to put all possible paths until we reach the destination twice at different times. Unlike Dijkstra&#8217;s algorithm, we don&#8217;t need to compare the minimum times to get to the next vertex. If we use min-heap, the answer will be returned as expected.</p><p>The solution above takes too long because it contains too many duplicates to reach each vertex. So, we reduce the duplication to make it faster. </p><p><strong>If we get to the vertex X at time Y, then we don&#8217;t need to revisit X with time Y because the answer after that will be the same</strong>. For example, if the minimum path from 1 to N is 1 &#8594; 4 &#8594; N and we visited vertex 4 at time 10. Then, we don&#8217;t need to revisit vertex 4 at time 10 from vertice other than 1 because even though the routes are different, time to N will be the same.</p><p>So, we can remove the case where we visited the vertex X with the time Y. Also, we only need to get the second minimum, meaning we don&#8217;t need to get the third minimum time to visit each vertex. </p><pre><code>class Solution {
public:
    // Adjacency list representation of the graph
    unordered_map&lt;int, vector&lt;int&gt;&gt; graph;

    int secondMinimum(int n, vector&lt;vector&lt;int&gt;&gt;&amp; edges, int time, int change) {   
        // Build the graph from the given edge list
        for (auto &amp; edge : edges) {
            graph[edge[0]].push_back(edge[1]);
            graph[edge[1]].push_back(edge[0]);
        }

        // Priority queue for Dijkstra-like traversal (min-heap)
        priority_queue&lt;pair&lt;int, int&gt;&gt; pq;
        pq.push({0, 1});  // {travel time, node}

        // Store up to two shortest arrival times at each node
        vector&lt;vector&lt;int&gt;&gt; arrivalTimes(n + 1);
        arrivalTimes[1].push_back(0);  // Start at node 1 with time 0

        while (!pq.empty()) { 
            auto current = pq.top();
            pq.pop();
            int travelTime = -current.first;  // Negating since we use max-heap simulation
            int node = current.second;

            // If we reach the destination and recorded the second arrival time, return it
            if (node == n &amp;&amp; arrivalTimes[node].size() == 2) {
                return arrivalTimes[node].back();
            }

            // Handle traffic signal: Wait if the light is red
            if ((travelTime / change) % 2 == 1) {
                travelTime = change * ((travelTime / change) + 1);  // Wait until green
            }

            // Explore all adjacent nodes
            for (auto &amp; neighbor : graph[node]) {
                int newTime = travelTime + time;

                // If the neighbor already has two recorded times, ignore
                if (arrivalTimes[neighbor].size() &gt; 1) continue;

                // Ensure we record two **distinct** times per node
                if (!arrivalTimes[neighbor].empty() &amp;&amp; arrivalTimes[neighbor].back() == newTime) continue;

                // Store new travel time and push to queue
                arrivalTimes[neighbor].push_back(newTime);
                pq.push({-newTime, neighbor});  // Use negative to simulate min-heap
            }
        }

        return -1;  // Should never be reached if a second minimum path exists
    }
};</code></pre><h3><strong>Time Complexity Analysis:</strong></h3><ul><li><p><strong>Graph Construction:</strong> <code>O(E)</code>, where <code>E</code> is the number of edges.</p></li><li><p><strong>Priority Queue Operations:</strong></p><ul><li><p>Each node can be pushed into the queue <strong>at most twice</strong> (<code>O(2N log N) ~ O(N log N)</code>).</p></li><li><p>Each edge is processed at most twice (<code>O(2E)</code>).</p></li></ul></li><li><p><strong>Overall Complexity:</strong> <code>O((N + E) log N)</code>, which is efficient for large graphs.</p></li></ul>]]></content:encoded></item><item><title><![CDATA[Leetcode Weekly Contest 435]]></title><description><![CDATA[Solutions for the Weekly contest 435.]]></description><link>https://www.zerotoexpert.blog/p/leetcode-weekly-contest-435</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/leetcode-weekly-contest-435</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Sun, 02 Feb 2025 12:54:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbf83b9-24f2-4bda-ace7-ea2ba9f53b1d_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>[Problem 1] <a href="https://leetcode.com/problems/maximum-difference-between-even-and-odd-frequency-i/description/">Maximum Difference Between Even and Odd Frequency I</a></h3><p>To get the correct answer, we should find the character that appears most at odd times and the one that appears least at even times. </p><p>So, I iterate the input string and record the frequency for all characters. Then, I calculate the maximum odd frequency and minimum even frequency. In this case, we cannot use characters that are not shown.</p><pre><code>class Solution {
public:
    int maxDifference(string s) {
        vector&lt;int&gt; cnt(26, 0);
        for (auto &amp;c:s)cnt[c-'a']++;
        int maxOdd = 0, minEven = 1e9;
        for (auto &amp; x:cnt) {
            if (x == 0) continue;
            if (x % 2 == 0) minEven = min(minEven, x);
            else maxOdd = max(maxOdd, x);
        }
        return maxOdd - minEven;
    }
};</code></pre><div><hr></div><h3>[Problem 2] <a href="https://leetcode.com/problems/maximum-manhattan-distance-after-k-changes/description/">Maximum Manhattan Distance After K Changes</a></h3><p>In this problem, we should find the maximum Manhattan distance from (0, 0). We can calculate the Manhattan distance by [X1 - X0| + [Y1 - Y0|. If we want it to be maximum, we should minimize the opposite moves, such as N-S and W-E.</p><p>So, we can simply choose a maximum of one from N/S and one from W/E. Then, we can change the direction at most K from both minimum directions. </p><ul><li><p>For example, N = 6, S = 4, E = 5, W = 2, K = 5</p><ul><li><p>Then, we can choose N and E.</p></li><li><p>We change 4 S to 4 N and 1 W to 1 E (Total K times).</p></li><li><p>Then it became N= 10, S = 0, E = 6, W = 1, so the answer is 15.</p></li></ul></li></ul><p></p><p>We should check the answer at each index. The maximum can be found in the middle of the moves.</p><pre><code>class Solution {
public:
    int maxDistance(string S, int k) {
        int n = 0, w = 0, s = 0, e = 0, ans = 0;
        for (auto &amp; c:S) {
            if (c == 'N') n++;
            else if (c == 'W') w++;
            else if (c == 'S') s++;
            else e++;

            int curr = max(n, s) + max(w, e), minus = min(n, s) + min(w, e);
            int changed = min(minus, k);
            minus -= changed;
            curr += changed;
            curr -= minus;
            ans = max(ans, curr);
        }
        return ans;
    }
};</code></pre><div><hr></div><h3>[Problem 3] <a href="https://leetcode.com/problems/minimum-increments-for-target-multiples-in-an-array/description/">Minimum Increments for Target Multiples in an Array</a></h3><p>In this problem, we should find the minimum increments to meet the condition. The critical point is that we only can increase the element. By this restriction, we can calculate the minimum value to make nums[i] to be the multiple of targets[j]. </p><p>However, we don&#8217;t know which number will be the multiple of which target. Also, one number can be the multiple of two different targets. We cannot consider all of these cases one by one.</p><p>Fortunately, we only have at most 4 targets. It means that if we calculate all the cases that nums[i] cover, it is at most 2^4 = 16. So, it is fast enough to iterate all cases for each index i of nums.</p><p>I precalculated the&nbsp;<a href="https://en.wikipedia.org/wiki/Least_common_multiple">Least Common Multiple</a>&nbsp;for each target combination to get the minimum increment faster. I record the state to find which targets are covered. If all targets are covered before iterating all nums, then return 0. If iterating all numbers finished before covering all targets, return an invalid value.</p><p>I also used memoization to skip the duplicated case.</p><pre><code>class Solution {
public:
    int n, m;
    unordered_map&lt;int, long long&gt; mp;
    int dp[50001][1&lt;&lt;4];
    long long func(vector&lt;int&gt; &amp; nums, vector&lt;int&gt; &amp; target, int i, int state) {
        if (state == (1&lt;&lt;m) - 1) {
            return 0;
        }
        if (i == n) {
            return 1e9;
        }

        if (dp[i][state] != -1) return dp[i][state];
        
        long long ans = 1e9;
        for (int x = 0; x &lt; (1 &lt;&lt; m); ++x) {
            long long l = mp[x];
            long long diff = ((nums[i] + l-1) / l) * l - nums[i];
            ans = min(ans, func(nums, target, i+1, state | x) + diff);
        }

        return dp[i][state] = ans;
    }

    int minimumIncrements(vector&lt;int&gt;&amp; nums, vector&lt;int&gt;&amp; target) {
        n = (int)nums.size();
        m = (int)target.size();
        memset(dp, -1,sizeof(dp));
        mp[0] = 1;
        for (int x = 1; x &lt; (1 &lt;&lt; m); ++x) {
            vector&lt;long long&gt; a;
            for (int j = 0; j &lt; m; ++j) {
                if (x &amp; (1 &lt;&lt; j)) {
                    a.push_back(target[j]);
                }
            }
            long long l = a[0];
            for (int i = 1; i &lt; a.size(); ++i) {
                l = l * a[i] / gcd(l, a[i]);
            }
            mp[x] = l;
        }

        return func(nums, target, 0, 0);
    }
};</code></pre><p></p><div><hr></div><h3>[Problem 4] <a href="https://leetcode.com/problems/maximum-difference-between-even-and-odd-frequency-ii/description/">Maximum Difference Between Even and Odd Frequency II</a></h3><p>We should find the maximum difference between odd frequency and even frequency. This problem is the hard version of problem 1. The reason why the first problem is easy is that the size of the string is fixed. We only need to care about the frequency of each character. </p><p>However, we can't use the same strategy in this problem because we don&#8217;t know the size. If we fix the size, we should iterate every character of the input string for each possible size larger than K. If K == 1, then it should take O((26) * N^2) times. So, this won&#8217;t work.</p><p>Fortunately, we know that there are only 5 characters that can be shown in the input string. We don&#8217;t know which one will be the answer, but the answer case will use only 2 characters: <strong>One for the odd frequency and one for the even frequency.</strong> Also, there are only 20 possible combinations. Assuming the characters for counting and getting the answer within O(N) or O(NlogN) time complexity, iterating 20 times wouldn&#8217;t be the problem.</p><p>So, I tried to consider the case when only two characters exist. Let&#8217;s ignore the size of substring K.</p><p></p><h4>How to calculate the frequency for a range query.</h4><p>To get the correct answer, we should find the exact case where an odd frequency character should appear at odd times and an even frequency character should appear at even times. But we are not sure if <code>index i</code> will meet the conditions. We should remove the character from the start if it does not meet. <strong>In short, we should calculate the count for each character within the range of [left, right].</strong></p><p>To calculate it faster, I precalculate the prefix sum, which contains the frequency data of the odd-frequency character and the even-frequency character within [0, i]. To find out the counting status of [i, j], we can simply subtract the prefixSum[i-1] from the prefixSum[j].</p><p></p><h4>How can we ensure the proper case for the answer?</h4><p>Now, we should find the right index i where [i, j] is the possible case for the answer. It means that when we iterate the input string, we should find the left index that will meet the condition. This is pretty complex.</p><p>What I thought of was the following conditions:</p><ul><li><p>We have already set the odd-frequency character and even-frequency character, and my goal is to find the case where an odd-frequency character appears at odd times, and the even-frequency character appears at even times. </p></li><li><p>If both characters appear at even times at index i, I should find the left index where an odd-frequency character appears at odd times and the even-frequency character appears at even times.</p><ul><li><p>For example, both characters appear 6 times at index i. If we found the j where j &lt; i and odd-frequency character appeared 3 times and even-frequency character appeared 2 times.</p></li><li><p>If we remove the characters [0,j] from [0, i], then [j+1, i] will meet the condition because odd-frequency appeared 3 times(6 - 3), and even-frequency appeared 4 times(6 -2).</p></li></ul></li><li><p>In short, we can record each counting status at each index i and then find the correct index for the right counting status type.</p><ul><li><p>There will be four different statuses.</p><ul><li><p>(Odd, Even) = odd-frequency appears <strong>odd</strong> times, even-frequency appears <strong>even</strong> times,</p></li><li><p>(Even, Even) = odd-frequency appears <strong>even</strong> times, even-frequency appears <strong>even</strong> times,</p></li><li><p>(Odd, Odd) = odd-frequency appears <strong>odd</strong> times, even-frequency appears <strong>odd</strong> times,</p></li><li><p>(Even, Odd) = odd-frequency appears <strong>even</strong> times, even-frequency appears <strong>odd</strong> times,</p></li></ul></li><li><p>To get the (Odd, Even) case, we can find the correct status for removal.</p><ul><li><p>(Odd, Even) - <strong>(Even, Even)</strong> = (Odd, Even)</p></li><li><p>(Even, Even) - <strong>(Odd, Even)</strong> = (Odd, Even)</p></li><li><p>(Odd, Odd) - <strong>(Even,  Odd)</strong> = (Odd, Even)</p></li><li><p>(Even, Odd) - <strong>(Odd, Odd)</strong> = (Odd, Even)</p></li></ul></li></ul></li><li><p>This can be implemented by recording the types at each index i and enqueuing them to each type queue. We can then find the index j for removal from the matching status type queue at index i.</p><ul><li><p>If (Odd, Even) is at index i, find the index from the (Even, Even) queue.</p></li></ul></li></ul><p></p><h4>How can we ensure the size of the substring?</h4><p>To ensure the size of the substring, we can use a pointer to follow the index to keep the size of the substring and enqueue the value for each type of queue. We cannot calculate the answer before we enqueue the value to the queue. This means we should ensure that the enqueued items are okay to get the answer. </p><p>So, if the i&#8212;(left index) &gt;= K meets, we can enqueue the left index item to the queue and then move the left index to the right. </p><pre><code>class Solution {
public:
    int func(string &amp; s, int k, char &amp; oddChar, char &amp; evenChar) {
        const int n = (int)s.size();
        vector&lt;vector&lt;int&gt;&gt; cnt(n, vector&lt;int&gt;(2));
        int oddCharCnt = 0, evenCharCnt = 0;
        vector&lt;int&gt; T(n);
        for (int i = 0; i &lt; n; ++i) {
            if (s[i] == oddChar) oddCharCnt++;
            else if (s[i] == evenChar) evenCharCnt++;
            cnt[i][0] = oddCharCnt, cnt[i][1] = evenCharCnt;
            if (oddCharCnt % 2 == 0 &amp;&amp; evenCharCnt % 2 == 1) T[i] = 0;
            else if (oddCharCnt % 2 == 0 &amp;&amp; evenCharCnt % 2 == 0) T[i] = 1;
            else if (oddCharCnt % 2 == 1 &amp;&amp; evenCharCnt % 2 == 0) T[i] = 2;
            else T[i] = 3;
        }
        int ans = -1e9, curr = 0, l = 0;

        auto getOppositeIndex  = [&amp;] (int t) {
            if (t == 0) return 3;
            if (t == 1) return 2;
            if (t == 2) return 1;
            return 0;
        };

        auto isPossible = [&amp;] (int i, int j) {
            int oddCharCnt = cnt[i][0] - (j == -1 ? 0 : cnt[j][0]);
            int evenCharCnt = cnt[i][1] - (j == -1 ? 0 : cnt[j][1]);
            return oddCharCnt &gt; 0 &amp;&amp; evenCharCnt &gt; 0 &amp;&amp; oddCharCnt % 2 == 1 &amp;&amp; evenCharCnt % 2 == 0 &amp;&amp; i - j &gt;= k;
        };

        vector&lt;int&gt; minimumCanRemove(4, 1e9);
        queue&lt;int&gt; q[4];
        for (int i = 0; i &lt; n; ++i) {
            int curr = cnt[i][0] - cnt[i][1];
            while(i - l &gt;= k) {
                q[T[l]].push(l);
                ++l;
            }
            int oppositeType = getOppositeIndex(T[i]);
            while(!q[oppositeType].empty() &amp;&amp; isPossible(i, q[oppositeType].front())) {
                int prevIdx = q[oppositeType].front();
                q[oppositeType].pop();
                minimumCanRemove[oppositeType] = min(minimumCanRemove[oppositeType], cnt[prevIdx][0] - cnt[prevIdx][1]);
            }
            ans = max(ans, curr - minimumCanRemove[oppositeType]);
            if (T[i] == 2 &amp;&amp; isPossible(i, -1)) ans = max(ans, curr);
        }

        return ans;
    }

    int maxDifference(string s, int k) {
        int ans = INT_MIN;
        for (char i = '0'; i &lt;= '4'; ++i) {
            for (char j = '0'; j &lt;= '4'; ++j) {
                if (i == j) continue;
                ans = max(ans, func(s, k, i, j));
            }
        }
        return ans;
    }
};</code></pre>]]></content:encoded></item><item><title><![CDATA[Monotonic Queue Questions]]></title><description><![CDATA[Let's solve the problem related to the monotonic queue patterns.]]></description><link>https://www.zerotoexpert.blog/p/monotonic-queue-questions</link><guid isPermaLink="false">https://www.zerotoexpert.blog/p/monotonic-queue-questions</guid><dc:creator><![CDATA[Gwonsoo Lee]]></dc:creator><pubDate>Tue, 28 Jan 2025 10:49:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbf83b9-24f2-4bda-ace7-ea2ba9f53b1d_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A monotonic queue means the queue that contains the elements is sorted in increasing or decreasing order. This is used to determine the window's minimum or maximum elements. </p><p>A monotonic queue keeps track of elements in increasing or decreasing order, so this should be used when we don&#8217;t need to insert the elements between the elements in the queue. This means we should push the current element back to the queue after dropping the smaller or larger elements from the back to keep the order. This implies that we don&#8217;t need to use the dropped elements anymore.</p><p></p><p>Let&#8217;s solve some problems to practice a monotonic queue.</p><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.zerotoexpert.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.zerotoexpert.blog/subscribe?"><span>Subscribe now</span></a></p><p></p><h2>Problem 1: <a href="https://leetcode.com/problems/sliding-window-maximum/description/">Sliding Window Maximum</a></h2><div><hr></div><p>In this problem, we should find the maximum elements of a fixed window. This means we should keep track of the largest element as we iterate the elements. We should find a way to find the next largest element if we drop the current largest element.</p><p>Let&#8217;s see the example. </p><p><code>nums = [1, -1, 10, 2, 11, 3, 7, 5] , k = 3</code></p><ul><li><p>The window size is 3, so we can start getting the maximum value since we iterate at least 3 elements.</p><ul><li><p>1st window is <code>[1, -1, 10]</code>. The maximum value is 10.</p></li><li><p>2nd window is <code>[-1, 10, 2]</code>. The maximum value is 10.</p></li><li><p>3rd window is <code>[10, 2, 11]</code>. The maximum value is 11.</p></li><li><p>4th window is <code>[2, 11, 3]</code>. The maximum value is 11.</p></li><li><p>5th window is <code>[11, 3, 7]</code>. The maximum value is 11.</p></li><li><p>6th window is <code>[3, 7, 5]</code>. The maximum value is 7.</p></li></ul></li><li><p>So, the answer should be <code>[10, 10, 11, 11, 11, 7]</code>.</p></li></ul><p></p><p>To solve the problem, we can use a monotonic queue for keeping the largest numbers.</p><ul><li><p>For every element, check if any elements in the queue are smaller than or equal to nums[i]. It&#8217;s because after we put nums[i] to the window, elements smaller than or equal to nums[i]  won&#8217;t be used as the maximum value. </p></li><li><p>Put nums[i] to the back of the monotonic queue. If the queue is empty, then nums[i] is the largest number in the window. If not, there is an element larger than nums[i]. </p></li><li><p>Check if the position of the first element in the queue is out of the window. If the largest number is out of the window, we should pop it from the queue. We can check this by adding the index(i), not the value(nums[i]) to the queue.</p></li></ul><pre><code>class Solution {
public:
    vector&lt;int&gt; maxSlidingWindow(vector&lt;int&gt;&amp; nums, int k) {
        vector&lt;int&gt; ans;
        const int n = (int)nums.size();
        deque&lt;int&gt; q;
        for (int i = 0; i &lt; n; ++i) {
            while(!q.empty() &amp;&amp; nums[q.back()] &lt;= nums[i]) {
                q.pop_back();
            }
            q.push_back(i);
            while(!q.empty() &amp;&amp; q.front() &lt; i-k+1) {
                q.pop_front();
            }
            if (i &gt;= k-1) {
                ans.push_back(nums[q.front()]);
            }
        }
        return ans;
    }
};</code></pre><p></p><h2>Problem2: <a href="https://leetcode.com/problems/k-empty-slots/description/">K Empty Slots</a></h2><div><hr></div><p>In this problem, you should find the minimum number of days when two bulbs are turned on and all bulbs between them are turned off. </p><p>This problem seems tricky if you keep track of the bulbs&#8217; status. We cannot track the status of every bulb between two turned-on bulbs. However, we can think of the final status of what we want to find.</p><p>What we want to find is the following situation.</p><ul><li><p>If K == 3, then <code>[On, Off, Off, Off, On].</code></p></li><li><p>If K == 0, then <code>[On, On].</code></p></li></ul><p>So, we can ensure this happens when the minimum number of days to turn on any of the K bulbs is later than the maximum number of days to turn on both the left and right bulbs. Based on the input, we can calculate the days to turn on each bulb.</p><p>For example, let&#8217;s assume that the array that represents the days to turn on the bulbs is <code>[1,3,4,5,2]</code>. It means that it takes 1 day to turn on the first bulb and 3 days to turn on the second day.</p><ul><li><p>On the first day, the bulb status is <code>[On, Off, Off, Off, Off]</code>.</p></li><li><p>On the second day, the bulb status is <code>[On, Off, Off, Off, On]</code>.</p></li><li><p>This can be proved by calculating the following condition.</p><ul><li><p> <code>min({arr[1], arr[2], arr[3]}) &gt; max(arr[0], arr[4])</code></p></li></ul></li></ul><pre><code>class Solution {
public:
    int kEmptySlots(vector&lt;int&gt;&amp; bulbs, int k) {
        const int n = (int)bulbs.size();
        vector&lt;int&gt; on(n);
        for (int i = 0; i &lt; n; ++i) {
            on[bulbs[i]-1] = i+1;
        }

        int ans = 1e9;
        deque&lt;int&gt; q;
        for (int i = 0; i &lt; n; ++i) {
            while(!q.empty() &amp;&amp; on[q.back()] &gt;= on[i]) {
                q.pop_back();
            }
            q.push_back(i);
            while(!q.empty() &amp;&amp; q.front() &lt;= i-k) {
                q.pop_front();
            }
            if (i &gt;= k &amp;&amp; i-k &gt;= 0 &amp;&amp; i+1 &lt; n){
                int maxOn = max(on[i-k],  on[i+1]);
                if (k == 0 || maxOn &lt; on[q.front()]) {
                    ans = min(ans, maxOn);
                }
            }
        }
        return ans == 1e9 ? -1 : ans;
    }
};</code></pre><p></p><h2>Problem3: <a href="https://leetcode.com/problems/minimum-number-of-coins-for-fruits-ii/">Minimum Number of Coins for Fruits II</a></h2><div><hr></div><p>We should find the minimum number of coins needed to buy all the fruits. If we buy the fruit in the index X, we can get the following (X+1) fruits for free. This might be helpful because buying expensive fruits with a small number of coins is possible. </p><p>We can use the dynamic programming technique to get the minimum number of coins at each index. </p><p>Suppose that <strong>dp[i] means the minimum number of coins needed to buy all fruits after (including) index i</strong>. Then, we make the following formula.</p><ul><li><p>dp[i] = min(dp[j] + prices[i]) for all j where i&lt;= j &lt;= min(i+i+2, n).</p><ul><li><p>dp[n] should be 0. This is needed because we can buy the fruits at index i, and all fruits after i are free. </p></li></ul></li></ul><p>This technique works when the price length (n) is small. <a href="https://leetcode.com/problems/minimum-number-of-coins-for-fruits/description/">Here&#8217;s the question</a> that you can try with dynamic programming.</p><p>However, in this problem, the maximum size of n is 10^5, so it is not fast enough to use dynamic programming. So, we should find another way. </p><p>Looking at the formula above, you will see that we don&#8217;t need to know all the dp[j] values. We need the minimum dp[j] within the valid window. For example, if the current index is 5, then we should know the minimum of dp[j] where <code>6 &lt;= j &lt;= 12(5 + 5 + 2)</code>.</p><p>So, we can use the monotonic queue to keep track of the minimum value of dp[j] and remove the elements outside the window before calculating the dp[i] value. To make this work, we should iterate from the back.</p><pre><code>class Solution {
public:
    int minimumCoins(vector&lt;int&gt;&amp; prices) {
        const int n = (int)prices.size();
        vector&lt;int&gt; dp(n+1, 0);
        deque&lt;int&gt; q;
        q.push_back(n);
        for (int i = n-1; i&gt;=0; --i) {
            while(!q.empty() &amp;&amp; q.front() &gt; i + i + 2) q.pop_front();
            dp[i] = prices[i] + dp[q.front()];
            while(!q.empty() &amp;&amp; dp[q.back()] &gt;= dp[i]) {
                q.pop_back();
            }
            q.push_back(i);
        }
        return dp[0];
    }
};</code></pre><p></p><h2>Problem4:  <a href="https://leetcode.com/problems/count-non-decreasing-subarrays-after-k-operations/description/">Count Non-Decreasing Subarrays After K Operations</a></h2><div><hr></div><p>We should find the non-decreasing subarrays after most K operations. In this case, the operation is increasing one of the elements by 1 in the subarray. This means that we should make every element bigger within the subarray.</p><p>Let&#8217;s assume that the nums = <code>[6,3,1,2]</code>.</p><ul><li><p>If the subarray is [6,3], it should be [6,6], and the number of operations is 3.</p><ul><li><p>To make 3 to 6 &#8594; 3 operations.</p></li></ul></li><li><p>If the subarray is [6,3,1], it should be [6,6,6], and the number of operations is 8.</p><ul><li><p>To make 3 to 6 &#8594; 3 operations.</p></li><li><p>To make 1 to 6 &#8594; 5 operations.</p></li></ul></li><li><p>If the subarray is [3,1,2], it should be [3,3,3], and the number of operations is 3.</p><ul><li><p>To make 1 to 3 &#8594; 2 operations.</p></li><li><p>To make 2 to 3 &#8594; 1 operations.</p></li></ul></li></ul><p>So, when we iterate the elements, we should know how many operations we need to make <code>nums[i]</code> to something. We can use the monotonic queue to store the maximum numbers within the window and add <code>maximum number - nums[i]</code> operations. If the current value is the maximum, the result will be 0.</p><p>If the current sum of operations is larger than K, all subarrays containing the current window are invalid. For example, if the nums = [A, B, C, D, E], and the subarray [A, B, C] is invalid. Then  [A, B, C, D] and  [A, B, C, D, E] are also invalid because they contain the invalid subarray. To calculate the number of invalid subarrays, we can subtract the current index from N(the size of nums).</p><p>The hard part is calculating the operations when we reduce the window size. If we find the invalid subarray, we should remove the leftmost value from the window to minimize the operations. In this case, all operation counts should be changed because some of the elements in the window are larger than the next leftmost element.</p><p>Let&#8217;s say the leftmost element index is L.</p><ul><li><p>If nums[L] &lt;= nums[L+1], then we don&#8217;t need to re-calculate the number of operations because nothing will be changed.</p></li><li><p>If nums[L] &gt; nums[L+1], then we should change the number of operations.</p></li></ul><p>For example, the invalid subarray is <code>[6,3,1,5,10]</code>, and L = 0.</p><ul><li><p>Now, nums[1] (3) &lt; num[0] (6). </p></li><li><p>Find the next element&#8217;s index whose value is larger than or equal to nums[0]. It should be 4(nums[4] = 10).</p><ul><li><p>Reduce all operations added from 0 &lt;= i &lt; 4 to make elements to 6.</p></li></ul></li><li><p>Now, reduce the window size by changing L to 1.</p></li><li><p>We should re-calculate operations to make it non-descreasing within 1 &lt;= i &lt; 4.</p><ul><li><p>Find the next element&#8217;s index whose value is larger than or equal to nums[1]. It should be 3(nums[3] = 5).</p></li><li><p>Add all operations to make elements to 3 within 1 &lt;= i &lt; 3.</p></li><li><p>Add all operations to make elements to 5 within 3 &lt;= i &lt; 4.</p></li></ul></li></ul><p>To calculate the operations faster, we can pre-calculate the prefixSum array and the right-closest index array to contain the index whose value is larger than or equal to the current value.</p><ul><li><p>To calculate the whole operations from L &lt;= i &lt;= R.</p><ul><li><p><code>nums[L] * (R - L + 1) - (PrefixSum[R] - PrefixSum[L-1])</code></p></li><li><p>This calculation is only valid if the elements within the range are non-increasing.</p></li></ul></li></ul><pre><code>class Solution {
public:
    long long countNonDecreasingSubarrays(vector&lt;int&gt;&amp; nums, int k) {
        const int n = (int)nums.size();
        vector&lt;long long&gt; pref(n+1, 0LL);
        vector&lt;int&gt; R(n, n);
        stack&lt;int&gt; st;
        for (int i = 1; i &lt;= n; ++i) {
            pref[i] += pref[i-1] + nums[i-1];
            while(!st.empty() &amp;&amp; nums[st.top()] &lt;= nums[i-1]) {
                R[st.top()] = i-1;
                st.pop();
            }
            st.push(i-1);
        }

        auto calc = [&amp;] (int l, int r) {
            long long cnt = r - l + 1;
            return nums[l] * cnt - (pref[r+1] - pref[l]);
        };

        deque&lt;int&gt; q;
        long long tot = ((long long) n * (n + 1)) / 2;
        long long invalid = 0LL;
        int l = 0, cur = 0;
        for (int i = 0; i &lt; n; ++i) {
            while(!q.empty() &amp;&amp; nums[q.back()] &lt;= nums[i]) {
                q.pop_back();
            }
            q.push_back(i);
            cur += nums[q.front()] - nums[i];
            while (cur &gt; k) {
                invalid += n-i;
                int nx = min(R[l], i + 1);
                cur -= calc(l, nx-1);
                int j = l+1;
                while(j &lt; nx) {
                    cur += calc(j, min(R[j], i + 1)-1);
                    j = min(R[j], i + 1);
                }
                l++;
            }
            while(!q.empty() &amp;&amp; q.front() &lt; l) {
                q.pop_front();
            }
        }

        return tot - invalid;
    }
};</code></pre><p></p><h2>Problem5: <a href="https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/description/">Shortest Subarray with Sum at Least K</a></h2><div><hr></div><p>In this problem, we should find the shortest subarray among subarrays where the sum of elements is larger than or equal to K. </p><p>To make it easier, we can pre-calculate the prefix sum and find the matching subarrays by calculating the <code>prefixSum[i] - prefixSum[j]</code>. But this algorithm takes too long because we should iterate the input array for each index i. The time complexity is O(N^2). </p><p>What we should do in the solution above is to find the maximum of j where the condition meets(<code>prefixSum[i] - prefixSum[j] &gt;= k)</code>. The important thing is that if we find the j, we no longer need the prefixSum[j] value. It&#8217;s because if we should use it again, then it would be from prefixSum[x] where i &lt; x, and that can&#8217;t be the answer. </p><p>Also, we don&#8217;t need to consider the case where prefixSum[x] &gt;= prefixSum[y] (x &lt; y) when we find the index j. It&#8217;s because if x is possible to use, then y is also possible, and y will be used to get the correct answer as we need the shortest subarray.</p><p>So, we can use a monotonic queue to keep track of the prefixSum values in increasing order.</p><ul><li><p>If the current prefixSum[i] is smaller than the largest value in the element, then pop the last value from the queue.</p></li><li><p>If prefixSum[i] - (smallest value from the queue) &gt;= k, then calculate the subarray size. </p><ul><li><p>Pop the smallest value from the queue and check if the condition meets with the next smallest value. </p></li><li><p>We should keep doing this until the condition is met.</p></li><li><p>This is because the smallest prefixSum doesn&#8217;t ensure the shortest subarray.</p></li></ul></li></ul><pre><code>class Solution {
public:
    int shortestSubarray(vector&lt;int&gt;&amp; nums, int k) {
        const int n = (int)nums.size();
        vector&lt;long long&gt; pref(n+1, 0LL);
        for (int i = 1; i &lt;= n; ++i) {
            pref[i] = pref[i-1] + nums[i-1];
        }
        deque&lt;int&gt; q;
        q.push_back(0);
        int ans = 1e9;
        for (int i = 1; i &lt;= n; ++i) {
            while(!q.empty() &amp;&amp; pref[q.back()] &gt;= pref[i]) {
                q.pop_back();
            }

            q.push_back(i);
            while(!q.empty() &amp;&amp; pref[i] - pref[q.front()] &gt;= k) {
                ans = min(ans, i - q.front());
                q.pop_front();
            }
        }
        return ans == 1e9 ? -1: ans;
    }
};</code></pre><p></p><h2>Problem6: <a href="https://leetcode.com/problems/constrained-subsequence-sum/description/">Constrained Subsequence Sum</a></h2><div><hr></div><p>We should find the maximum sum of subsequences that meet the condition. The condition is simple. We cannot choose two elements consecutively where the difference between two indices is larger than K. (j - I &lt;= k, where i &lt; j).</p><p>This means we should find the maximum sum from the index range <code>[i-k, i-1]</code> to get the maximum sum in the index i. So, we can use the monotonic queue to keep track of the maximum sum in each index in decreasing order. </p><ul><li><p>We can drop the element from the front if the index is smaller than i-k. </p></li><li><p>We need to consider that use the nums[i] itself without adding something from the queue. This is valid if the largest sum is negative.</p></li><li><p>If the maximum sum in the current index is larger than any element in the queue, remove that element from the queue.</p></li></ul><pre><code>class Solution {
public:
    int constrainedSubsetSum(vector&lt;int&gt;&amp; nums, int k) {
        const int n = (int)nums.size();
        deque&lt;int&gt; q;
        int ans = -1e9;
        for (int i = 0; i &lt; n; ++i) {
            while(!q.empty() &amp;&amp; q.front() &lt; i-k) {
                q.pop_front();
            }

            nums[i] += (q.empty() ? 0 : max(0, nums[q.front()]));
            ans = max(ans, nums[i]);
            while(!q.empty() &amp;&amp; nums[q.back()] &lt;= nums[i]) {
                q.pop_back();
            }
            q.push_back(i);
        }
        return ans;
    }
};</code></pre><div><hr></div><h3><strong>Useful courses</strong></h3><ul><li><p><strong><a href="https://www.designgurus.io/course/grokking-the-coding-interview?aff=nozayu">Grokking the Coding Interview: Patterns for Coding Questions</a></strong></p></li><li><p><strong><a href="https://www.designgurus.io/course/grokking-advanced-coding-patterns-for-interviews?aff=nozayu">Grokking Advanced Coding Patterns for Interviews</a></strong></p></li></ul>]]></content:encoded></item></channel></rss>