<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="/atom.xsl" ?>

<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://blog.name666.top/</id>
    <title>静奢之境</title>
    <updated>2026-03-29T00:57:09.077Z</updated>
    <generator>Astro-Theme-Retypeset with Feed for Node.js</generator>
    <author>
        <name>LongDz</name>
        <uri>https://blog.name666.top</uri>
    </author>
    <link rel="alternate" href="https://blog.name666.top/"/>
    <link rel="self" href="https://blog.name666.top/atom.xml"/>
    <subtitle>欢迎来到我的思想角落。我是LongDz，一位在代码与文字间游走的探索者。这里并非喧闹的广场，而是一方精心耕耘的自留地。我书写历史的幽微回响，剖析哲学的锋利棱角，沉醉于文学的无垠宇宙。每一个观点，都经过时间的沉淀与反复的淬炼。</subtitle>
    <rights>Copyright © 2026 LongDz</rights>
    <entry>
        <title type="html"><![CDATA[136. 只出现一次的数字]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/136-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/136-/"/>
        <updated>2026-03-28T23:02:56.037Z</updated>
        <summary type="html"><![CDATA[找出数组中仅出现一次的元素，其余元素均出现两次，要求线性时间与常量空间。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/single-number/submissions/701096338/?envType=study-plan-v2&amp;envId=top-100-liked" title="136. 只出现一次的数字" difficulty="Easy"}</p>
<h2>解法：异或运算</h2>
<h3>思路</h3>
<p>利用异或运算的自反性（a^a=0）和恒等性（a^0=a）特性，将所有元素异或后成对元素抵消，剩余即为唯一元素。</p>
<p>该方法正确的原因是异或运算满足交换律和结合律。设数组为{a1,a1,a2,a2,...,an,an,x}，则异或结果为(a1^a1)^(a2^a2)^...^(an^an)^x = 0^0^...^0^x = x。无论元素顺序如何，该数学性质保证结果必然是唯一元素。</p>
<ul>
<li>Step 1: 初始化 ans = 0</li>
<li>Step 2: 遍历数组，执行 ans ^= nums[i]</li>
<li>Step 3: 返回 ans</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$</li>
<li>空间复杂度: $O(1)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int singleNumber(vector&lt;int&gt;&amp; nums) {
        int ans = 0;
        for(int i = 0;i&lt;nums.size();i++){
            ans ^= nums[i];
        }
        return ans;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T23:02:56.037Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[169. 多数元素]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/169-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/169-/"/>
        <updated>2026-03-28T23:02:16.865Z</updated>
        <summary type="html"><![CDATA[找出数组中出现次数超过一半的元素，假设数组非空且一定存在这样的元素。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/majority-element/submissions/701116907/?envType=study-plan-v2&amp;envId=top-100-liked" title="169. 多数元素" difficulty="Easy"}</p>
<h2>解法：投票算法</h2>
<h3>思路</h3>
<p>Boyer-Moore 投票算法，通过抵消不同的元素来找到多数元素。</p>
<p>该算法的正确性基于抵消思想：将多数元素记为 +1，其他元素记为 -1，总和必然大于 0。遍历数组时，维护当前候选元素及其计数。当计数归零时，说明之前的元素中候选元素与其他元素数量相等，相互抵消。由于多数元素总数超过一半，它最终一定会成为最后的候选元素。</p>
<ul>
<li>Step 1: 初始化计数器 cot 为 0</li>
<li>Step 2: 遍历数组中的每个元素 i</li>
<li>Step 3: 如果 cot 为 0，将当前元素 i 设为候选元素 now，cot 设为 1</li>
<li>Step 4: 否则，如果 i 等于 now，cot 加 1；否则 cot 减 1</li>
<li>Step 5: 返回最终的候选元素 now</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，只需一次线性遍历数组。</li>
<li>空间复杂度: $O(1)$，仅使用常数级别的额外变量。</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int majorityElement(vector&lt;int&gt;&amp; nums) {
        int now ;
        int cot = 0;
        for(auto i:nums){
            if(cot == 0){
                now = i;
                cot = 1;
            }
            else{
                cot += i==now?1:-1;
            }
        }
        return now;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T23:02:16.865Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[75. 颜色分类]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/75-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/75-/"/>
        <updated>2026-03-28T23:01:18.285Z</updated>
        <summary type="html"><![CDATA[原地对只含0、1、2的数组进行排序，使0在前1在中2在后]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/sort-colors/submissions/703223021/?envType=study-plan-v2&amp;envId=top-100-liked" title="75. 颜色分类" difficulty="Medium"}</p>
<h2>解法：三指针</h2>
<h3>思路</h3>
<p>第一段：使用三指针（low、mid、high）一次遍历完成排序，是经典的荷兰国旗问题解法。</p>
<p>第二段：算法维护四个区间的不变性：[0,low-1]全为0，[low,mid-1]全为1，[mid,high]未处理，[high+1,n-1]全为2。当mid遇到0时与low交换并前移双指针，确保0归位；遇到1时直接跳过；遇到2时与high交换并后移high指针，但不前移mid，因为交换后的元素可能仍是0或1需要再次处理。这种策略保证了每个元素最多被交换两次，且不会错过任何可能的正确位置。</p>
<p>第三段：</p>
<ul>
<li>Step 1: 初始化low=0, mid=0, high=n-1</li>
<li>Step 2: 当mid&lt;=high时循环处理</li>
<li>Step 3: 若nums[mid]==0，交换nums[low]和nums[mid]，low和mid都自增</li>
<li>Step 4: 若nums[mid]==1，仅mid自增</li>
<li>Step 5: 若nums[mid]==2，交换nums[mid]和nums[high]，high自减</li>
<li>Step 6: 循环结束，数组排序完成</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$</li>
<li>空间复杂度: $O(1)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    void sortColors(vector&lt;int&gt;&amp; nums) {
        int low = 0;
        int mid = 0;
        int high = nums.size() - 1;

        while(mid &lt;= high){
            if(nums[mid] == 0){
                swap(nums[low], nums[mid]);
                low++;
                mid++;
            }
            else if(nums[mid] == 1){
                mid++;
            }
            else{
                swap(nums[mid], nums[high]);
                high--;
            }
        }
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T23:01:18.285Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[31. 下一个排列]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/31-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/31-/"/>
        <updated>2026-03-28T23:00:19.602Z</updated>
        <summary type="html"><![CDATA[原地修改数组，找到其下一个字典序更大的排列，若不存在则重排为升序。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/next-permutation/submissions/703229175/?envType=study-plan-v2&amp;envId=top-100-liked" title="31. 下一个排列" difficulty="Medium"}</p>
<h2>解法：双指针</h2>
<h3>思路</h3>
<p>核心思想是从数组末尾向前寻找第一个满足 nums[i] &lt; nums[i+1] 的位置 i，然后交换 nums[i] 与后面大于它的最小元素，并反转后缀以获得下一个排列。</p>
<p>该方法正确是因为：若整个数组降序，则无更大排列，反转得最小；否则，i 是最后一个可增大的位置，后缀为降序。交换 nums[i] 与大于它的最小元素（从后往前找第一个大于的）确保增大最小幅度，反转后缀使其升序得到最小后缀，从而整体为紧挨着的下一个排列。</p>
<ul>
<li>Step 1: 从后向前遍历，寻找第一个满足 nums[i] &lt; nums[i+1] 的索引 i。</li>
<li>Step 2: 若找到，从末尾向前寻找第一个大于 nums[i] 的元素索引 j。</li>
<li>Step 3: 交换 nums[i] 和 nums[j]。</li>
<li>Step 4: 反转子数组 nums[i+1..end] 使其升序。</li>
<li>Step 5: 若未找到 i，反转整个数组。</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，其中 n 是数组长度，最坏情况下需要遍历数组两次并执行一次反转操作。</li>
<li>空间复杂度: $O(1)$，仅使用常数级别的额外空间存储索引和临时变量。</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    void nextPermutation(vector&lt;int&gt;&amp; nums) {
        for(int i = nums.size() - 2; i &gt;= 0; i--){
            if(nums[i] &lt; nums[i + 1]){
                int j = nums.size() - 1;
                while(nums[j] &lt;= nums[i]) j--;
                swap(nums[i],nums[j]);
                reverse(nums.begin() + i + 1, nums.end());
                return;
            }

        }
        reverse(nums.begin(), nums.end());
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T23:00:19.602Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[287. 寻找重复数]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/287-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/287-/"/>
        <updated>2026-03-28T22:59:24.919Z</updated>
        <summary type="html"><![CDATA[在包含 n+1 个 [1,n] 范围内整数的数组中，找出唯一的重复数字，要求不修改数组且使用 O(1) 空间。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/find-the-duplicate-number/submissions/703237122/?envType=study-plan-v2&amp;envId=top-100-liked" title="287. 寻找重复数" difficulty="Medium"}</p>
<h2>解法：快慢指针</h2>
<h3>思路</h3>
<p>将数组视为链表，利用 Floyd 判圈算法（龟兔赛跑）寻找环入口。数组索引作为节点，值作为指向下一节点的指针，由于必存在重复，因此会形成环，环入口即为重复数字。</p>
<p>该方法正确性基于鸽巢原理：n+1 个元素映射到 n 个取值范围必然产生环。设起点到环入口距离为 a，相遇点到入口距离为 b，环长为 L。快指针速度是慢指针两倍，相遇时满足 a + b = nL。重置一指针至起点后，两指针同步移动，当各走 a 步时必在环入口相遇，此时即为重复值。</p>
<ul>
<li>Step 1: 初始化 slow 和 fast 为 nums[0]</li>
<li>Step 2: 执行 do-while 循环，slow 走一步，fast 走两步，直到相遇</li>
<li>Step 3: 初始化 ptr 为 nums[0]</li>
<li>Step 4: ptr 和 slow 每次各走一步，直到相遇</li>
<li>Step 5: 返回相遇点值即为重复数</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$</li>
<li>空间复杂度: $O(1)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int findDuplicate(vector&lt;int&gt;&amp; nums) {
        int slow = nums[0];
        int fast = nums[0];
        do{
            slow = nums[slow];
            fast = nums[nums[fast]];
        }while(slow != fast);

        int ptr = nums[0];

        while(ptr != slow){
            ptr = nums[ptr];
            slow = nums[slow];
        }

        return ptr;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:59:24.919Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[72. 编辑距离]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/72-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/72-/"/>
        <updated>2026-03-28T22:58:20.659Z</updated>
        <summary type="html"><![CDATA[计算将 word1 转换为 word2 所需的最少编辑操作数（插入、删除、替换）。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/edit-distance/submissions/703246580/?envType=study-plan-v2&amp;envId=top-100-liked" title="72. 编辑距离" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>核心思想是动态规划，定义 dp[i][j] 表示 word1 前 i 个字符转换为 word2 前 j 个字符的最少操作数，通过状态转移方程递推求解。</p>
<p>正确性基于最优子结构和无后效性。对于任意位置 (i,j)，若字符相等则无需操作，直接继承 dp[i-1][j-1]；若不相等，则三种操作中取最小值：删除（dp[i-1][j]+1）、插入（dp[i][j-1]+1）、替换（dp[i-1][j-1]+1）。这种自底向上的方式确保每个子问题最优，从而保证全局最优。</p>
<ul>
<li>Step 1: 初始化 DP 表大小为 (m+1) × (n+1)，dp[i][0] = i（全删除），dp[0][j] = j（全插入）</li>
<li>Step 2: 遍历 i 从 1 到 m，j 从 1 到 n</li>
<li>Step 3: 若 word1[i-1] == word2[j-1]，则 dp[i][j] = dp[i-1][j-1]</li>
<li>Step 4: 否则 dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1</li>
<li>Step 5: 返回 dp[m][n]</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(m \times n)$</li>
<li>空间复杂度: $O(m \times n)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size();
        int n = word2.size();
        vector&lt;vector&lt;int&gt;&gt; dp(m + 1, vector&lt;int&gt;(n + 1, 0));
        for(int i = 1; i &lt;= m; i++){
            dp[i][0] = i;
        }
        for(int i = 1; i &lt;= n; i++){
            dp[0][i] = i;
        }
        for(int i = 1; i &lt;= m; i++){
            for(int j = 1; j &lt;= n; j++){
                if(word1[i-1] == word2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }
                else{
                    dp[i][j] = min( min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]) + 1;
                }
            }
        }
        return dp[m][n];
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:58:20.659Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[1143. 最长公共子序列]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/1143-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/1143-/"/>
        <updated>2026-03-28T22:57:52.725Z</updated>
        <summary type="html"><![CDATA[求两个字符串的最长公共子序列长度，子序列不要求连续但需保持相对顺序。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/longest-common-subsequence/submissions/703247808/?envType=study-plan-v2&amp;envId=top-100-liked" title="1143. 最长公共子序列" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>核心思想是动态规划，通过构建二维DP表存储所有前缀子串的最优解。</p>
<p>正确性基于最优子结构：每个状态 dp[i][j] 都表示 text1 前 i 个字符与 text2 前 j 个字符的最长公共子序列长度。当字符匹配时，问题可分解为子问题 dp[i-1][j-1] 加1；不匹配时，最优解必然包含于跳过 text1[i-1] 或 text2[j-1] 的两种情况之一，因此取最大值可保证不遗漏最优解。</p>
<ul>
<li>Step 1: 初始化 (m+1)×(n+1) 的DP数组，全部置0</li>
<li>Step 2: 双重循环遍历两个字符串的所有字符位置</li>
<li>Step 3: 若当前字符匹配，则 dp[i][j] = dp[i-1][j-1] + 1</li>
<li>Step 4: 若字符不匹配，则 dp[i][j] = max(dp[i-1][j], dp[i][j-1])</li>
<li>Step 5: 返回 dp[m][n] 作为最终结果</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(m \times n)$，其中 m 和 n 为两字符串长度，需填充整个DP表。</li>
<li>空间复杂度: $O(m \times n)$，用于存储二维DP数组。</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int m = text1.size();
        int n = text2.size();
        vector&lt;vector&lt;int&gt;&gt; dp(m + 1, vector&lt;int&gt;(n + 1, 0));
        
        for(int i = 1; i &lt;= m; i++){
            for(int j = 1; j &lt;= n; j++){
                if(text1[i-1] == text2[j-1]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }
                else{
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:57:52.725Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[5. 最长回文子串]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/5-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/5-/"/>
        <updated>2026-03-28T22:57:24.462Z</updated>
        <summary type="html"><![CDATA[寻找给定字符串中的最长回文子串。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/longest-palindromic-substring/submissions/700956919/?envType=study-plan-v2&amp;envId=top-100-liked" title="5. 最长回文子串" difficulty="Medium"}</p>
<h2>解法：Manacher算法</h2>
<h3>思路</h3>
<p>使用 Manacher 算法，通过预处理字符串和中心扩展法来线性求解最长回文子串。</p>
<p>算法的正确性基于回文串的对称性。通过插入特殊字符（#）统一处理奇偶长度回文，利用已计算的回文半径 p[i] 来避免重复计算。当 i 在右边界内时，p[i] 的值可以通过其关于中心点的对称点 p[mirror] 推导而来，因为回文具有对称性。如果对称点的回文范围超出了当前左边界，则 p[i] 只能取到右边界的值，否则可以直接使用对称点的值。然后继续中心扩展。这种方法确保每个字符只被扩展常数次，总体时间复杂度为 O(n)。</p>
<ul>
<li>Step 1: 预处理字符串，在字符间插入#，首尾添加不同字符@和%防止越界</li>
<li>Step 2: 初始化数组p存储每个位置的回文半径，center和right记录当前最右回文的中心和右边界</li>
<li>Step 3: 遍历处理后的字符串，对每个位置i计算初始p[i]值（若在右边界内则利用对称点信息）</li>
<li>Step 4: 尝试中心扩展，更新p[i]</li>
<li>Step 5: 如果扩展后右边界超出当前right，更新center和right</li>
<li>Step 6: 记录最长回文的长度和中心位置</li>
<li>Step 7: 根据maxCenter和maxLen从原字符串中提取结果</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$</li>
<li>空间复杂度: $O(n)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    string longestPalindrome(string s) {
        if(s.size() &lt; 2) return s;
        string ss = "@";
        for(auto c:s){
            ss += "#";
            ss += c;
        }
        ss += "#%";
        vector&lt;int&gt;p(ss.size(),0);
        int center = 0;int right = 0;
        int maxLen = 0;
        int maxCenter = 0;
        for(int i = 1; i &lt; ss.size() - 1; i++){
            p[i] = i&lt;right?min(right - i,p[2*center - i]):0;
            while(ss[i+p[i]+1] == ss[i-p[i]-1])
                p[i]++;
            if(i + p[i] &gt; right){
                center = i;
                right = i + p[i];
            }
            if(p[i] &gt; maxLen){
                maxLen = p[i];
                maxCenter = i;
            }
        }
        return s.substr((maxCenter - maxLen)/2,maxLen);
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:57:24.462Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[64. 最小路径和]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/64-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/64-/"/>
        <updated>2026-03-28T22:56:49.937Z</updated>
        <summary type="html"><![CDATA[从左上角到右下角找一条路径，只能向下或向右，使得路径上数字之和最小。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/minimum-path-sum/submissions/703253189/?envType=study-plan-v2&amp;envId=top-100-liked" title="64. 最小路径和" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>动态规划，每个格子的最小路径和等于其上方或左方格子的最小路径和加上当前格子的值。</p>
<p>因为只能向下或向右移动，到达每个格子的路径只能来自上方或左方，所以当前格子的最优解必然由上方或左方的最优解决定，这满足最优子结构性质。</p>
<ul>
<li>Step 1: 初始化 dp[0][0] = grid[0][0]</li>
<li>Step 2: 初始化第一列：dp[i][0] = dp[i-1][0] + grid[i][0]</li>
<li>Step 3: 初始化第一行：dp[0][j] = dp[0][j-1] + grid[0][j]</li>
<li>Step 4: 对于其他格子：dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]</li>
<li>Step 5: 返回 dp[m-1][n-1]</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(m \times n)$</li>
<li>空间复杂度: $O(m \times n)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int minPathSum(vector&lt;vector&lt;int&gt;&gt;&amp; grid) {

        int m = grid.size();
        int n = grid[0].size();

        vector&lt;vector&lt;int&gt;&gt; dp(m, vector&lt;int&gt;(n));

        dp[0][0] = grid[0][0];

        for(int i = 1; i &lt; m; i++)
            dp[i][0] = dp[i-1][0] + grid[i][0];

        for(int j = 1; j &lt; n; j++)
            dp[0][j] = dp[0][j-1] + grid[0][j];

        for(int i = 1; i &lt; m; i++){
            for(int j = 1; j &lt; n; j++){
                dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
            }
        }

        return dp[m-1][n-1];
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:56:49.937Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[62. 不同路径]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/62-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/62-/"/>
        <updated>2026-03-28T22:55:59.470Z</updated>
        <summary type="html"><![CDATA[计算从 m×n 网格左上角到右下角的唯一路径数量，每次只能向下或向右移动]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/unique-paths/submissions/703204867/?envType=study-plan-v2&amp;envId=top-100-liked" title="62. 不同路径" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>动态规划：到达每个格子的路径数等于从上方和左方到达的路径数之和。</p>
<p>该方法正确性基于数学归纳法：基础情况是第一行和第一列只有1条路径；递推关系成立是因为机器人只能从上方或左方到达当前格子，因此总路径数是这两个方向路径数的和。通过填充DP表，每个格子的值都正确表示了到达该位置的路径总数。</p>
<ul>
<li>Step 1: 初始化 m×n 的 DP 数组，所有元素为 0</li>
<li>Step 2: 设置第一行和第一列的所有值为 1</li>
<li>Step 3: 从 i=1 到 m-1，j=1 到 n-1 遍历</li>
<li>Step 4: dp[i][j] = dp[i-1][j] + dp[i][j-1]</li>
<li>Step 5: 返回 dp[m-1][n-1]</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(m \times n)$</li>
<li>空间复杂度: $O(m \times n)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int uniquePaths(int m, int n) {
        vector&lt;vector&lt;int&gt;&gt; dp(m, vector&lt;int&gt;(n, 0));
        for(int i = 0; i &lt; m; i++) dp[i][0] = 1;
        for(int i = 0; i &lt; n; i++) dp[0][i] = 1;
        for(int i = 1; i &lt; m; i++){
            for(int j = 1; j &lt; n; j++){
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:55:59.470Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[32. 最长有效括号]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/32-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/32-/"/>
        <updated>2026-03-28T22:54:52.705Z</updated>
        <summary type="html"><![CDATA[找出只包含 '(' 和 ')' 的字符串中最长有效括号子串的长度]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/longest-valid-parentheses/submissions/703517766/?envType=study-plan-v2&amp;envId=top-100-liked" title="32. 最长有效括号" difficulty="Hard"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>动态规划，定义 dp[i] 表示以位置 i 结尾的最长有效括号子串的长度。</p>
<p>当 s[i] == ')' 时，有两种情况：若 s[i-1]=='('，则形成 "()"，dp[i] = dp[i-2] + 2；若 s[i-1]==')'，则检查 s[i-dp[i-1]-1] 是否为 '('，若是则可扩展前面的有效串。这种方法能正确计算所有可能的有效括号子串长度，因为每个位置的状态都依赖于前面已计算的状态，通过状态转移覆盖了所有可能的匹配情况。</p>
<ul>
<li>Step 1: 初始化 dp 数组全为 0，ans 记录最大值</li>
<li>Step 2: 遍历字符串从索引 1 到 n-1</li>
<li>Step 3: 如果当前字符是 ')'</li>
<li>Step 4: 如果前一个字符是 '('，则 dp[i] = (i &gt;= 2 ? dp[i-2] : 0) + 2</li>
<li>Step 5: 如果前一个字符是 ')'，计算 j = i - dp[i-1] - 1，如果 j &gt;= 0 且 s[j] == '('，则 dp[i] = dp[i-1] + 2 + (j &gt;= 1 ? dp[j-1] : 0)</li>
<li>Step 6: 更新 ans = max(ans, dp[i])</li>
<li>Step 7: 返回 ans</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，其中 n 是字符串长度，只需要一次遍历</li>
<li>空间复杂度: $O(n)$，用于存储 dp 数组</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int longestValidParentheses(string s) {
        int n = s.size();
        vector&lt;int&gt; dp(n, 0);
        int ans = 0;

        for (int i = 1; i &lt; n; i++) {
            if (s[i] == ')') {
                if (s[i - 1] == '(') {
                    dp[i] = (i &gt;= 2 ? dp[i - 2] : 0) + 2;
                } else {
                    int j = i - dp[i - 1] - 1;
                    if (j &gt;= 0 &amp;&amp; s[j] == '(') {
                        dp[i] = dp[i - 1] + 2 + (j &gt;= 1 ? dp[j - 1] : 0);
                    }
                }
            }
            ans = max(ans, dp[i]);
        }

        return ans;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:54:52.705Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[416. 分割等和子集]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/416-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/416-/"/>
        <updated>2026-03-28T22:53:52.099Z</updated>
        <summary type="html"><![CDATA[判断数组是否能分割成两个等和子集，等价于是否存在子集和等于总和的一半]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/partition-equal-subset-sum/submissions/703510338/?envType=study-plan-v2&amp;envId=top-100-liked" title="416. 分割等和子集" difficulty="Medium"}</p>
<h2>解法：位集DP</h2>
<h3>思路</h3>
<p>核心思想是将问题转化为子集和问题，使用 bitset 优化的动态规划。第一段：将数组分割成两个等和子集的问题等价于判断是否存在一个子集，其元素和等于数组总和的一半。使用 bitset 进行状态压缩的动态规划，通过位运算高效地计算所有可达的和。第二段：正确性基于 0/1 背包问题的状态转移。dp[i] 为 true 表示可以从前 k 个元素中选出若干个数使其和为 i。对于每个数字 num，dp &lt;&lt; num 表示所有已可达的和加上 num 后变为新的可达和，dp |= (dp &lt;&lt; num) 将新可达的和合并到状态中。由于正整数不会导致和回退，这种更新方式不会遗漏任何可能解。第三段：</p>
<ul>
<li>Step 1: 计算数组总和，若为奇数直接返回 false</li>
<li>Step 2: 初始化 bitset dp，设置 dp[0] = 1</li>
<li>Step 3: 遍历每个数字 num，执行 dp |= (dp &lt;&lt; num)</li>
<li>Step 4: 返回 dp[target] 的结果</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n \times \text{target})$，其中 n 是数组长度，target 是总和的一半。主要耗时在遍历每个数字并更新 bitset</li>
<li>空间复杂度: $O(\text{target})$，使用大小为 10001 的 bitset 存储状态</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    bool canPartition(vector&lt;int&gt;&amp; nums) {
        int sum = 0;
        for(int x: nums) sum += x;

        if(sum % 2) return false;

        int target = sum/2;

        bitset&lt;10001&gt; dp;
        dp[0] = 1;

        for(int num: nums){
            dp |= (dp &lt;&lt; num);
        }

        return dp[target];
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:53:52.099Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[152. 乘积最大子数组]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/152-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/152-/"/>
        <updated>2026-03-28T22:52:40.496Z</updated>
        <summary type="html"><![CDATA[找出整数数组中乘积最大的连续子数组，返回其乘积。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/maximum-product-subarray/submissions/703498352/?envType=study-plan-v2&amp;envId=top-100-liked" title="152. 乘积最大子数组" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>维护以当前位置结尾的子数组的最大和最小乘积，通过动态规划处理负数符号翻转的情况。</p>
<p>由于负数的存在，当前的最大乘积可能在乘以负数后变成最小乘积，而最小乘积可能变成最大乘积。因此需要同时维护最大和最小两个状态。每一步我们考虑当前元素本身，或当前元素与之前最大/最小乘积的乘积，确保不会遗漏任何可能的子数组情况。这种动态规划方法保证了每个位置我们都掌握了最优解，最终答案就是所有位置最大乘积中的最大值。</p>
<ul>
<li>Step 1: 初始化 maxp、minp 和 ans 为第一个元素</li>
<li>Step 2: 遍历数组，若当前元素为负数则交换 maxp 和 minp</li>
<li>Step 3: 更新 maxp 为当前元素与 maxp*当前元素的较大值</li>
<li>Step 4: 更新 minp 为当前元素与 minp*当前元素的较小值</li>
<li>Step 5: 更新 ans 为当前 ans 与 maxp 的较大值</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，其中 $n$ 是数组长度，只需一次遍历。</li>
<li>空间复杂度: $O(1)$，只使用了常数个额外变量。</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int maxProduct(vector&lt;int&gt;&amp; nums) {
        int maxp = nums[0];
        int minp = nums[0];
        int ans = nums[0];

        for(int i = 1; i &lt; nums.size(); i++){
            if(nums[i] &lt; 0){
                swap(maxp, minp);
            }

            maxp = max(nums[i], maxp * nums[i]);
            minp = min(nums[i], minp * nums[i]);

            ans = max(ans, maxp);
        }

        return ans;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:52:40.496Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[300. 最长递增子序列]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/300-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/300-/"/>
        <updated>2026-03-28T22:52:08.931Z</updated>
        <summary type="html"><![CDATA[求数组中最长严格递增子序列的长度]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/longest-increasing-subsequence/submissions/703270525/?envType=study-plan-v2&amp;envId=top-100-liked" title="300. 最长递增子序列" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>动态规划，定义 dp[i] 为以 nums[i] 结尾的最长递增子序列长度。</p>
<p>该方法的正确性基于最优子结构。对于任意位置 i，若存在 j &lt; i 且 nums[j] &lt; nums[i]，则所有以 j 结尾的递增子序列都可以通过添加 nums[i] 扩展为以 i 结尾的子序列。dp[i] 取所有可扩展情况的最大值，因此必然记录了以 i 结尾的最长递增子序列长度。最终答案取所有 dp[i] 的最大值，因为全局最长递增子序列必然以某个元素结尾。</p>
<ul>
<li>Step 1: 初始化 dp 数组，每个元素为 1</li>
<li>Step 2: 遍历 i 从 0 到 n-1</li>
<li>Step 3: 对于每个 i，遍历 j 从 0 到 i-1</li>
<li>Step 4: 若 nums[i] &gt; nums[j]，更新 dp[i] = max(dp[i], dp[j] + 1)</li>
<li>Step 5: 返回 dp 数组的最大值</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n^2)$</li>
<li>空间复杂度: $O(n)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int lengthOfLIS(vector&lt;int&gt;&amp; nums) {
        int n = nums.size();
        vector&lt;int&gt; dp(n,1);
        for(int i = 0; i &lt; n; i++){
            for(int j = 0; j &lt; i; j++){
                if(nums[i] &gt; nums[j]){
                    dp[i] = max(dp[i],dp[j]+1);
                }
            }
        }
        return *max_element(dp.begin(),dp.end());
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:52:08.931Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[139. 单词拆分]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/139-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/139-/"/>
        <updated>2026-03-28T22:51:18.353Z</updated>
        <summary type="html"><![CDATA[判断字符串 s 是否能由字典 wordDict 中的单词（可重复使用）拼接而成]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/word-break/submissions/703271258/?envType=study-plan-v2&amp;envId=top-100-liked" title="139. 单词拆分" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>核心思想是动态规划，定义 dp[i] 表示前 i 个字符能否被拼接。</p>
<p>正确性基于最优子结构：若 s[0:i] 可被拼接，则必存在分割点 j，使得 s[0:j] 可被拼接且 s[j:i] 是字典单词；反之亦然。这保证了状态转移的完备性。</p>
<ul>
<li>Step 1: 构建哈希集合存储字典，实现 O(1) 查找</li>
<li>Step 2: 初始化 dp[0] = true，表示空字符串可拼接</li>
<li>Step 3: 遍历每个位置 i，尝试所有分割点 j</li>
<li>Step 4: 若 dp[j] 为 true 且子串 s[j:i] 在字典中，则设置 dp[i] = true</li>
<li>Step 5: 返回 dp[n] 作为最终结果</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n^2)$</li>
<li>空间复杂度: $O(n + m)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    bool wordBreak(string s, vector&lt;string&gt;&amp; wordDict) {

        unordered_set&lt;string&gt; dict(wordDict.begin(), wordDict.end());

        int n = s.size();
        vector&lt;bool&gt; dp(n + 1, false);

        dp[0] = true;

        for(int i = 1; i &lt;= n; i++){
            for(int j = 0; j &lt; i; j++){

                if(dp[j] &amp;&amp; dict.count(s.substr(j, i-j))){
                    dp[i] = true;
                    break;
                }
            }
        }

        return dp[n];
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:51:18.353Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[322. 零钱兑换]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/322-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/322-/"/>
        <updated>2026-03-28T22:50:29.278Z</updated>
        <summary type="html"><![CDATA[计算凑成总金额所需的最少硬币个数，若无法凑成则返回-1]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/coin-change/submissions/703200026/?envType=study-plan-v2&amp;envId=top-100-liked" title="322. 零钱兑换" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>核心思想是动态规划，通过构建DP数组记录每个金额所需的最少硬币数。</p>
<p>该方法正确是因为每个金额的最优解都依赖于更小金额的最优解，具有最优子结构。通过自底向上计算，确保每个状态都是最优的，且不会遗漏任何可能的硬币组合。</p>
<ul>
<li>Step 1: 初始化DP数组，dp[0]=0，其余为amount+1（表示无穷大）</li>
<li>Step 2: 遍历1到amount每个金额</li>
<li>Step 3: 对每个硬币面额，尝试使用并更新dp[i]</li>
<li>Step 4: 返回结果，若dp[amount]未更新则返回-1</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n × amount)$</li>
<li>空间复杂度: $O(amount)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int coinChange(vector&lt;int&gt;&amp; coins, int amount) {
        vector&lt;int&gt; dp(amount + 1, amount + 1);
        dp[0] = 0;
        for(int i = 1; i &lt;= amount; i++){
            for(int j = 0; j &lt; coins.size(); j++){
                if(i-coins[j] &gt;= 0)
                dp[i] = min(dp[i],dp[i-coins[j]]+1);
            }
        }
        return dp[amount] == amount+1 ? -1 : dp[amount];
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:50:29.278Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[279. 完全平方数]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/279-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/279-/"/>
        <updated>2026-03-28T22:50:06.601Z</updated>
        <summary type="html"><![CDATA[求和为给定整数 n 的完全平方数的最少数量]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/perfect-squares/submissions/703271856/?envType=study-plan-v2&amp;envId=top-100-liked" title="279. 完全平方数" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>第一段：使用动态规划，定义 dp[i] 表示凑出数值 i 所需的最少完全平方数个数，通过枚举所有可能的平方数进行状态转移。</p>
<p>第二段：该方法正确性的核心在于最优子结构性质。对于任意数值 i，若选择使用一个完全平方数 j²，则剩余部分 i-j² 的最优解加上当前这一个数，就构成了 i 的一个可行解。由于我们枚举了所有可能的 j² 并取最小值，因此 dp[i] 必然保存了全局最优解。dp[0]=0 作为基础边界条件，保证了递推链条的正确启动。</p>
<p>第三段：</p>
<ul>
<li>Step 1: 初始化 dp 数组，dp[0]=0，其余元素初始化为 INF</li>
<li>Step 2: 外层循环遍历 i 从 1 到 n</li>
<li>Step 3: 内层循环遍历 j 从 1 到 √i，枚举所有可用的完全平方数</li>
<li>Step 4: 状态转移：dp[i] = min(dp[i], dp[i - j*j] + 1)</li>
<li>Step 5: 返回 dp[n] 作为最终结果</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n \sqrt{n})$</li>
<li>空间复杂度: $O(n)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int numSquares(int n) {

        vector&lt;int&gt; dp(n + 1, INT_MAX);

        dp[0] = 0;

        for(int i = 1; i &lt;= n; i++){

            for(int j = 1; j * j &lt;= i; j++){

                dp[i] = min(dp[i], dp[i - j*j] + 1);

            }
        }

        return dp[n];
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:50:06.601Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[198. 打家劫舍]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/198-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/198-/"/>
        <updated>2026-03-28T22:49:34.969Z</updated>
        <summary type="html"><![CDATA[计算在不触发警报（不偷相邻房屋）的情况下，能偷到的最大金额。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/house-robber/submissions/703193777/?envType=study-plan-v2&amp;envId=top-100-liked" title="198. 打家劫舍" difficulty="Medium"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>动态规划，定义 dp[i][0/1] 表示前 i 个房屋在偷或不偷第 i 个房屋时的最大收益。</p>
<p>该问题的最优子结构性质体现在：对于第 i 个房屋，如果选择不偷，则最大收益为前 i-1 个房屋的最大收益（dp[i-1][0] 和 dp[i-1][1] 的较大值）；如果选择偷，则前一个房屋必须不偷，收益为 dp[i-1][0] + nums[i]。由于每个决策只依赖于前一个状态，且我们枚举了所有可能的选择，因此该动态规划方法能够得到全局最优解。</p>
<ul>
<li>Step 1: 初始化 dp[0][0] = 0（不偷第一个房屋），dp[0][1] = nums[0]（偷第一个房屋）</li>
<li>Step 2: 遍历 i 从 1 到 n-1，计算 dp[i][0] = max(dp[i-1][0], dp[i-1][1])</li>
<li>Step 3: 计算 dp[i][1] = dp[i-1][0] + nums[i]</li>
<li>Step 4: 返回 max(dp[n-1][0], dp[n-1][1])</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，其中 n 是房屋数量，只需一次遍历。</li>
<li>空间复杂度: $O(n)$，使用了二维数组 dp 存储状态（可优化至 $O(1)$）。</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int rob(vector&lt;int&gt;&amp; nums) {
        int dp[nums.size()][2];
        memset(dp,0,sizeof(dp));
        dp[0][0] = 0;
        dp[0][1] = nums[0];
        for(int i = 1; i &lt; nums.size(); i++){
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]);
            dp[i][1] = dp[i-1][0]+nums[i];
        }
        return max(dp[nums.size()-1][0],dp[nums.size()-1][1]);
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:49:34.969Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[118. 杨辉三角]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/118-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/118-/"/>
        <updated>2026-03-28T22:48:58.804Z</updated>
        <summary type="html"><![CDATA[生成杨辉三角的前 numRows 行，每个数是左上方和右上方两数之和。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/pascals-triangle/?envType=study-plan-v2&amp;envId=top-100-liked" title="118. 杨辉三角" difficulty="Easy"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>第一段：采用动态规划思想，逐行递推构造，每行的每个元素依赖于前一行相邻两个元素的和。</p>
<p>第二段：正确性基于数学归纳法。基础情况第0行[1]符合定义。假设前i-1行已正确生成，则第i行的首尾元素恒为1，中间元素通过ans[i-1][j-1] + ans[i-1][j]计算，这恰好满足杨辉三角的组合数学性质C(i,j) = C(i-1,j-1) + C(i-1,j)，因此每行都能被准确构造，不会遗漏任何正确结果。</p>
<p>第三段：</p>
<ul>
<li>Step 1: 初始化结果数组ans，从第0行开始迭代</li>
<li>Step 2: 第0行直接添加{1}</li>
<li>Step 3: 对于第i行(i&gt;0)，创建临时数组tmp</li>
<li>Step 4: 遍历j从0到i，首尾位置添加1，中间位置添加ans[i-1][j-1] + ans[i-1][j]</li>
<li>Step 5: 将tmp加入ans，继续下一行直到完成numRows行</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(numRows^2)$</li>
<li>空间复杂度: $O(numRows^2)$</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    vector&lt;vector&lt;int&gt;&gt; generate(int numRows) {
        vector&lt;vector&lt;int&gt;&gt;ans;
        for(int i = 0;i &lt; numRows;i++){
            if(i == 0){
               ans.push_back({1}); 
            }
            else{
                vector&lt;int&gt;tmp;
                for(int j=0;j &lt;= i;j++){
                    
                    if(j &gt; 0){
                        if(j == i){
                            tmp.push_back(ans[i-1][i-1]);
                        }
                        else{
                            tmp.push_back(ans[i-1][j-1] + ans[i-1][j]);
                        }
                    }
                    else{
                        tmp.push_back(1);
                    }
                    
                }
                ans.push_back(tmp);
            }
        }
        return ans;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:48:58.804Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[70. 爬楼梯]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/70-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/70-/"/>
        <updated>2026-03-28T22:47:46.080Z</updated>
        <summary type="html"><![CDATA[计算每次爬1或2阶楼梯到达楼顶的不同方法总数]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/climbing-stairs/?envType=study-plan-v2&amp;envId=top-100-liked" title="70. 爬楼梯" difficulty="Easy"}</p>
<h2>解法：动态规划</h2>
<h3>思路</h3>
<p>核心思想是动态规划，发现到达每阶楼梯的方法数遵循斐波那契数列递推规律。</p>
<p>正确性基于分类加法计数原理：对于第i阶，最后一步只能是1阶或2阶。若最后一步为1阶，则有dp[i-1]种方法；若为2阶，则有dp[i-2]种方法。这两种情况互斥且穷尽所有可能，因此dp[i] = dp[i-1] + dp[i-2]成立。初始条件dp[1]=1和dp[2]=2可通过枚举验证，递推关系保证了所有子问题解的正确性。</p>
<p>实现步骤：</p>
<ul>
<li>Step 1: 初始化dp数组，设置dp[1]=1, dp[2]=2</li>
<li>Step 2: 从i=3到n遍历，计算dp[i] = dp[i-1] + dp[i-2]</li>
<li>Step 3: 返回dp[n]作为最终结果</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，主要耗时在遍历计算每个台阶的方法数</li>
<li>空间复杂度: $O(n)$，使用了大小为n+2的数组存储中间结果</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int climbStairs(int n) {
        
        int dp[n+2];
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3;i&lt;=n;i++){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:47:46.080Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[763. 划分字母区间]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/763-/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/763-/"/>
        <updated>2026-03-28T22:46:33.598Z</updated>
        <summary type="html"><![CDATA[将字符串划分为最多片段，使得每个字母仅出现在一个片段中]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/partition-labels/?envType=study-plan-v2&amp;envId=top-100-liked" title="763. 划分字母区间" difficulty="Medium"}</p>
<h2>解法：贪心</h2>
<h3>思路</h3>
<p>核心思想是贪心策略：先统计每个字母的最后出现位置，再遍历字符串时维护当前片段的结束边界，当遍历到边界时立即切割。</p>
<p>正确性证明：对于任意片段，必须包含其中所有字母的最后出现位置，否则该字母会跨越片段。贪心策略选择当前已遍历字母的最后位置的最大值作为边界，确保边界内所有字母不会在后续出现，此时切割能得到最多片段数。若延迟切割只会减少片段数量，因此该策略是最优的。</p>
<ul>
<li>Step 1: 创建大小为26的数组<code>last</code>，记录每个字母在字符串中最后出现的下标</li>
<li>Step 2: 第一次遍历字符串，填充<code>last</code>数组</li>
<li>Step 3: 初始化<code>start=0</code>、<code>end=0</code>，用于标记当前片段的起止位置</li>
<li>Step 4: 第二次遍历字符串，更新<code>end = max(end, last[s[i]-'a'])</code></li>
<li>Step 5: 当<code>i == end</code>时，说明当前片段结束，记录长度并更新<code>start = end + 1</code></li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，其中n为字符串长度，需要两次线性遍历</li>
<li>空间复杂度: $O(1)$，使用了固定大小26的数组存储字母最后位置</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    vector&lt;int&gt; partitionLabels(string s) {
        int last[26] = {0};
        for(int i = 0; i &lt; s.size(); i++){
            last[s[i]-'a'] = i;
        }
        vector&lt;int&gt; ans;
        int start = 0;
        int end = 0;
        for(int i = 0; i &lt; s.size(); i++){
            end = max(end,last[s[i]-'a']);
            if(i == end){
                ans.push_back(end - start + 1);
                start = end + 1;
            }
        }
        return ans;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:46:33.598Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[45. 跳跃游戏 II]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/-ii/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/-ii/"/>
        <updated>2026-03-28T22:45:52.671Z</updated>
        <summary type="html"><![CDATA[使用BFS求到达数组末尾的最小跳跃次数]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/jump-game-ii/description/?envType=study-plan-v2&amp;envId=top-100-liked" title="跳跃游戏 II" difficulty="Medium"}</p>
<h2>解法：广度优先</h2>
<h3>思路</h3>
<p>使用广度优先搜索（BFS）按层遍历所有可达位置，首次到达终点时的层数即为最小跳跃次数。</p>
<p>BFS的正确性基于其按层扩展的特性：第k层的所有位置都恰好通过k次跳跃到达。由于每次跳跃可以到达任意可达位置，BFS能确保第一次访问到终点时，所用的跳跃次数是最小的。visited数组防止重复访问，保证每个位置只被处理一次，不会遗漏最优解。</p>
<ul>
<li>Step 1: 初始化队列和visited数组，将起点0入队</li>
<li>Step 2: 循环处理每一层，step记录当前跳跃次数</li>
<li>Step 3: 对于当前层的每个位置，尝试所有可能的跳跃距离</li>
<li>Step 4: 将未访问的下一个位置入队并标记为已访问</li>
<li>Step 5: 若到达终点则返回当前step</li>
<li>Step 6: 处理完一层后step自增</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，每个位置最多访问一次，每次最多尝试1000次跳跃（常数）</li>
<li>空间复杂度: $O(n)$，队列和visited数组的空间</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int jump(vector&lt;int&gt;&amp; nums) {

        int n = nums.size();
        queue&lt;int&gt; q;
        vector&lt;bool&gt; visited(n,false);

        q.push(0);
        visited[0] = true;

        int step = 0;

        while(!q.empty()){

            int size = q.size();

            for(int i = 0; i &lt; size; i++){

                int cur = q.front();
                q.pop();

                if(cur == n-1)
                    return step;

                for(int j = 1; j &lt;= nums[cur]; j++){

                    int next = cur + j;

                    if(next &lt; n &amp;&amp; !visited[next]){
                        visited[next] = true;
                        q.push(next);
                    }
                }
            }

            step++;
        }

        return -1;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:45:52.671Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[55. 跳跃游戏]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/jump-game/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/jump-game/"/>
        <updated>2026-03-28T22:39:49.813Z</updated>
        <summary type="html"><![CDATA[判断从数组起点出发，根据每个位置的跳跃长度，是否能够到达最后一个下标。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/jump-game/submissions/703204343/?envType=study-plan-v2&amp;envId=top-100-liked" title="跳跃游戏" difficulty="Medium"}</p>
<h2>解法：贪心</h2>
<h3>思路</h3>
<p>贪心算法，维护当前能到达的最远位置。</p>
<p>该方法正确性的核心在于贪心选择策略：在每一步都尽可能跳得最远。维护的不变量是'所有小于等于 i 的位置都是可达的'。如果 i &gt; max_reach，说明位置 i 不可达，那么 i 之后的所有位置也无法到达，因为无法跳过这个断点。反之，只要能够遍历到数组末尾，就说明每个位置都是可达的，因此可以到达最后一个下标。</p>
<ul>
<li>Step 1: 初始化 max_reach = 0，表示在起点能到达的最远位置</li>
<li>Step 2: 遍历数组，对于每个位置 i</li>
<li>Step 3: 如果 i &gt; max_reach，说明无法到达位置 i，返回 false</li>
<li>Step 4: 更新 max_reach = max(max_reach, i + nums[i])</li>
<li>Step 5: 如果遍历完成，说明可以到达最后一个下标，返回 true</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，其中 n 是数组长度。只需一次线性遍历。</li>
<li>空间复杂度: $O(1)$，仅使用常数级别的额外空间。</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    bool canJump(vector&lt;int&gt;&amp; nums) {
        int n = nums.size();
        int max_reach = 0;
        for(int i = 0; i &lt; n; i++){
            if(i &gt; max_reach) return false;
            max_reach = max(max_reach,i+nums[i]);
        }
        return true;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:39:49.813Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[121. 买卖股票的最佳时机]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/best-time-to-buy-and-sell-stock/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/best-time-to-buy-and-sell-stock/"/>
        <updated>2026-03-28T22:38:55.005Z</updated>
        <summary type="html"><![CDATA[给定股票价格数组，只能进行一次买卖，求最大利润。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/submissions/701124268/?envType=study-plan-v2&amp;envId=top-100-liked" title="买卖股票的最佳时机" difficulty="Easy"}</p>
<h2>解法：一次遍历</h2>
<h3>思路</h3>
<p>核心思想是在一次遍历中动态维护历史最低买入价，并计算每日卖出可获最大利润。</p>
<p>正确性证明：对于任意第 i 天，最优买入点必然是 i 之前（含 i）价格最低的那天。通过维护 mincost 可保证始终以最优价格买入，ans 记录所有卖出时机中的最大利润，故不会遗漏全局最优解。</p>
<ul>
<li>Step 1: 初始化 mincost 为第一天价格，ans 为 0</li>
<li>Step 2: 遍历价格数组中的每个价格</li>
<li>Step 3: 更新 mincost = min(mincost, 当前价格)</li>
<li>Step 4: 更新 ans = max(ans, 当前价格 - mincost)</li>
<li>Step 5: 返回 ans</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(n)$，其中 n 为价格数组长度，仅需一次遍历。</li>
<li>空间复杂度: $O(1)$，仅使用常数个额外变量。</li>
</ul>
<h3>代码</h3>
<pre><code>class Solution {
public:
    int maxProfit(vector&lt;int&gt;&amp; prices) {
        int mincost = prices[0];
        int ans = 0;
        for(auto i:prices){
            mincost = min(mincost,i);
            ans = max(ans,i-mincost);
        }
        return ans;
    }
};
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:38:55.005Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[295. 数据流的中位数]]></title>
        <id>https://blog.name666.top/posts/leetcode-hot100/find-median-from-data-stream/</id>
        <link href="https://blog.name666.top/posts/leetcode-hot100/find-median-from-data-stream/"/>
        <updated>2026-03-28T22:38:17.569Z</updated>
        <summary type="html"><![CDATA[实现一个数据结构，支持动态添加整数并快速查询当前所有元素的中位数。]]></summary>
        <content type="html"><![CDATA[<h2>题目</h2>
<p>::leetcode{url="https://leetcode.cn/problems/find-median-from-data-stream/submissions/703604378/?envType=study-plan-v2&amp;envId=top-100-liked" title="数据流的中位数" difficulty="Hard"}</p>
<h2>解法：对顶堆</h2>
<h3>思路</h3>
<p>使用两个堆（最大堆和最小堆）分别存储较小和较大的一半数，通过堆顶元素快速获取中位数。</p>
<p>该方法通过两个堆维护中位数：最大堆 lo 存储较小的一半数，最小堆 hi 存储较大的一半数。插入时先将元素放入 lo，再将 lo 的堆顶移到 hi 确保 lo 的所有元素 &lt;= hi 的所有元素；最后如果 lo 的大小小于 hi，则将 hi 的堆顶移回 lo，保证 lo 的大小 &gt;= hi 且两者大小差不超过 1。这样中位数要么是 lo 的堆顶（奇数个元素），要么是两堆堆顶的平均值（偶数个元素）。</p>
<ul>
<li>Step 1: 将新元素插入最大堆 lo</li>
<li>Step 2: 将 lo 的堆顶移到最小堆 hi</li>
<li>Step 3: 如果 lo 的大小小于 hi，将 hi 的堆顶移回 lo</li>
<li>Step 4: 查询时，若 lo 大小大于 hi 则返回 lo 堆顶，否则返回两堆堆顶的平均值</li>
</ul>
<h3>复杂度</h3>
<ul>
<li>时间复杂度: $O(log n)$ 每次操作，堆插入/删除耗时 $O(log n)$，查询中位数耗时 $O(1)$。</li>
<li>空间复杂度: $O(n)$，两个堆共同存储所有元素。</li>
</ul>
<h3>代码</h3>
<pre><code>class MedianFinder {
public:
    priority_queue&lt;int&gt; lo;                              // max heap
    priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; hi;   // min heap

    MedianFinder() {
    }
    
    void addNum(int num) {
        lo.push(num);                                    // Add to max heap

        hi.push(lo.top());                               // balancing step
        lo.pop();

        if (lo.size() &lt; hi.size()) {                     // maintain size property
            lo.push(hi.top());
            hi.pop();
        }
    }
    
    double findMedian() {
        return lo.size() &gt; hi.size()
               ? (double)lo.top()
               : (lo.top() + hi.top()) * 0.5;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj-&gt;addNum(num);
 * double param_2 = obj-&gt;findMedian();
 */
</code></pre>
]]></content>
        <author>
            <name>LongDz</name>
            <uri>https://blog.name666.top</uri>
        </author>
        <published>2026-03-28T22:38:17.569Z</published>
    </entry>
</feed>