<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Elysium&apos;s Blog</title><description>FullStack / DevOps。</description><link>https://elysium2020.github.io/</link><language>zh-CN</language><item><title>串联所有单词的子串</title><link>https://elysium2020.github.io/blog/substring_with_concatenation_of_all_words/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/substring_with_concatenation_of_all_words/</guid><description>LeetCode 30 题解析</description><pubDate>Sun, 21 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题和 438 题类似，都是利用滑动窗口来解决，思路也大抵一样。
不过需要留意：438 题中是针对单个字符，而此题是针对整个字符串。
因此， &lt;code&gt;differ&lt;/code&gt; 也从一个数组变成了一个哈希表。
我们令 n 为每个单词的长度， m 为数组 &lt;code&gt;words&lt;/code&gt; 的长度， &lt;code&gt;s_len&lt;/code&gt; 为字符串 s 的长度。
因为我们要逐个对比字符串中的字符，所以可以用 $m \times n$ 来摊平需要比较的字符，
用 &lt;code&gt;i&lt;/code&gt; 作为偏移量比较对这些窗口逐个比对。
因此，我们可以设置一个初始为 $0$ 的循环。结束条件为偏移量小于单词长度并小于等于窗口长度。
随后，我们可以设置一个哈希表 &lt;code&gt;differ&lt;/code&gt;。
接下来的步骤和 438 题类似：
先用 &lt;code&gt;differ&lt;/code&gt; 统计第一个窗口中各个单词出现的次数。
然后遍历 &lt;code&gt;words&lt;/code&gt; 数组，减去 &lt;code&gt;differ&lt;/code&gt; 中对应单词的计数。
此时，&lt;code&gt;differ&lt;/code&gt; 变成了「窗口与 &lt;code&gt;words&lt;/code&gt; 差异」的统计。&lt;/p&gt;
&lt;p&gt;现在让我们开始滑动窗口。
用 &lt;code&gt;start&lt;/code&gt; 表示窗口索引，步长为 $n$。
接着我们可以套用 438 题的逻辑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在窗口最左侧移除旧单词（&lt;code&gt;differ[word]--&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;在窗口右侧加入新单词（&lt;code&gt;differ[word]++&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;如果某个单词的计数为 $0$，那么删除它（&lt;code&gt;differ.erase(word)&lt;/code&gt;）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后，如果 &lt;code&gt;differ&lt;/code&gt; 为空，若为空，那么 &lt;code&gt;start&lt;/code&gt; 就是一个答案，加入答案数组。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  vector&amp;lt;int&amp;gt; findSubstring(string s, vector&amp;lt;string&amp;gt; &amp;amp;words) {
    int m = words.size(), n = words[0].size(), s_len = s.size();
    vector&amp;lt;int&amp;gt; ans;

    for (auto i = 0; i &amp;lt; n &amp;amp;&amp;amp; i + m * n &amp;lt;= s_len; ++i) {
      unordered_map&amp;lt;string, int&amp;gt; differ;
      for (auto j = 0; j &amp;lt; m; ++j)
        ++differ[s.substr(i + j * n, n)];

      for (auto &amp;amp;word : words)
        if (--differ[word] == 0)
          differ.erase(word);

      for (auto start = i; start &amp;lt; s_len - m * n + 1; start += n) {
        if (start != i) {
          auto word = s.substr(start - n, n);
          if (--differ[word] == 0)
            differ.erase(word);

          word = s.substr(start + (m - 1) * n, n);
          if (++differ[word] == 0)
            differ.erase(word);
        }

        if (differ.empty())
          ans.emplace_back(start);
      }
    }

    return ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>找到字符串中所有字母异位词</title><link>https://elysium2020.github.io/blog/find_all_anagrams_in_a_string/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/find_all_anagrams_in_a_string/</guid><description>LeetCode 438 题解析</description><pubDate>Sat, 20 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题是适合使用滑动窗口来解决。
一开始，我们可以先比较字符串 &lt;code&gt;s&lt;/code&gt; 和 &lt;code&gt;p&lt;/code&gt; 的长度。
当 &lt;code&gt;s.length() &amp;lt; p.length()&lt;/code&gt; 时，直接返回空数组。
接下来，我们可以定义两个长度为26的数组 &lt;code&gt;s_count[]&lt;/code&gt; 和 &lt;code&gt;p_count[]&lt;/code&gt;。
并将 &lt;code&gt;p.length()&lt;/code&gt; 个字符分别加入 &lt;code&gt;s_count[]&lt;/code&gt; 和 &lt;code&gt;p_count[]&lt;/code&gt; 中。
若此时 &lt;code&gt;s_count[]&lt;/code&gt; 和 &lt;code&gt;p_count[]&lt;/code&gt; 相等，
则意味着我们找到了第一个异位词，起始索引为 $0$。
此时，我们将 $0$ 压入数组 &lt;code&gt;ans&lt;/code&gt; 中。
随后，我们开始滑动窗口。
先弹出最左侧字符 &lt;code&gt;--s_count[s[i] - &apos;a&apos;]&lt;/code&gt;，
然后将 &lt;code&gt;s[i + p.length()]&lt;/code&gt; 加入 &lt;code&gt;s_count[]&lt;/code&gt; 中。
此时我们比较 &lt;code&gt;s_count[]&lt;/code&gt; 和 &lt;code&gt;p_count[]&lt;/code&gt; 是否相等。
若相等则说明我们找到了一个异位词，起始索引为 $i + 1$，将其压入数组 &lt;code&gt;ans&lt;/code&gt; 中。
随后，返回数组 &lt;code&gt;ans&lt;/code&gt; 即可。&lt;/p&gt;
&lt;p&gt;上面这个方法有个问题：每次都需要比较 &lt;code&gt;s_count[]&lt;/code&gt; 和 &lt;code&gt;p_count[]&lt;/code&gt; 是否完全相等，
这浪费了不少时间。
因此我们可以考虑使用一个变量 &lt;code&gt;differ&lt;/code&gt; 以及一个数组 &lt;code&gt;count[]&lt;/code&gt;。
用 &lt;code&gt;count[]&lt;/code&gt; 存储窗口内字符与 &lt;code&gt;p&lt;/code&gt; 字符数的差。
用 &lt;code&gt;differ&lt;/code&gt; 记录 &lt;code&gt;count[]&lt;/code&gt; 的非零元素个数，即当前窗口与字符串 &lt;code&gt;p&lt;/code&gt; 中数量不同的字母的个数。
然后我们统计一下情况&lt;/p&gt;
&lt;p&gt;$$
\begin{cases}
\text{字符 c 离开窗口} &amp;amp; \mathrm{count[c]} - 1 = &amp;amp;&lt;/p&gt;
&lt;p&gt;\begin{cases}
0 &amp;amp; \text{一个多余字符被移除，differ} - 1 \
-1 &amp;amp; \text{平衡被打破，有新的差异项。differ} + 1
\end{cases} \&lt;/p&gt;
&lt;p&gt;\text{字符 c 进入窗口} &amp;amp; \mathrm{count[c]} + 1 = &amp;amp;&lt;/p&gt;
&lt;p&gt;\begin{cases}
0 &amp;amp; \text{一个缺失的字符被添加，differ} - 1 \
1 &amp;amp; \text{平衡被打破，有新的差异项。differ} + 1
\end{cases}
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;也就是说，我们可以仅考虑 &lt;code&gt;differ&lt;/code&gt; 的变化来判断当前窗口是否满足要求。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  vector&amp;lt;int&amp;gt; findAnagrams(string s, string p) {
    int s_len = s.length(), p_len = p.length();
    if (s_len &amp;lt; p_len)
      return vector&amp;lt;int&amp;gt;{};

    vector&amp;lt;int&amp;gt; count(26), ans;

    for (auto i = 0; i &amp;lt; p_len; ++i) {
      ++count[s[i] - &apos;a&apos;];
      --count[p[i] - &apos;a&apos;];
    }

    auto differ =
        count_if(count.begin(), count.end(), [](int c) { return c != 0; });

    if (differ == 0)
      ans.emplace_back(0);

    for (auto i = 0; i &amp;lt; s_len - p_len; ++i) {
      if (count[s[i] - &apos;a&apos;] == 1)
        --differ;
      else if (count[s[i] - &apos;a&apos;] == 0)
        ++differ;

      --count[s[i] - &apos;a&apos;];

      if (count[s[i + p_len] - &apos;a&apos;] == -1)
        --differ;
      else if (count[s[i + p_len] - &apos;a&apos;] == 0)
        ++differ;

      ++count[s[i + p_len] - &apos;a&apos;];

      if (differ == 0)
        ans.emplace_back(i + 1);
    }

    return ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>无重复字符的最长子串</title><link>https://elysium2020.github.io/blog/longest_substring_without_repeating_characters/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/longest_substring_without_repeating_characters/</guid><description>LeetCode 3 题解析</description><pubDate>Tue, 26 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题主要考验滑动窗口的使用。
为了记录字符是否出现过，我们可以使用哈希表来存储每个字符。
用左右指针标明滑动窗口界限。
其中右指针初始值为 -1，左指针初始值为 0。
从而表示此时的滑动窗口为空。&lt;/p&gt;
&lt;p&gt;随后我们开始调整滑动窗口。
首先我们可以固定左指针，
当左指针不为0时，左指针会在滑动前滑出指向的字符，从而允许该字符能在右边再次出现。
随后我们开始调整右指针。
右指针会尽可能的向右移动，直到遇到重复的字符或者到达字符串的末尾。
在这期间，程序会更新滑动窗口的最大长度。
从而得出答案。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int lengthOfLongestSubstring(string s) {
    unordered_set&amp;lt;char&amp;gt; persist;
    auto right = -1, ans = 0;
    int n = s.size();

    for (auto left = 0; left &amp;lt; n; ++left) {
      if (left != 0)
        persist.erase(s[left - 1]);

      while (right + 1 &amp;lt; n &amp;amp;&amp;amp; !persist.count(s[right + 1]))
        persist.insert(s[++right]);

      ans = max(ans, right - left + 1);
    }

    return ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>长度最小的子数组</title><link>https://elysium2020.github.io/blog/minimum_size_subarray_sum/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/minimum_size_subarray_sum/</guid><description>LeetCode 209 题解析</description><pubDate>Mon, 04 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;如果仅追求能跑的话，这道题其实很简单。
两轮循环即可。
但在实际中数据量很大。&lt;/p&gt;
&lt;p&gt;因此我们可以考虑在两轮循环的基础上使用二分查找。
而如何改写成二分查找呢？
我们可以利用前缀和的思想。
我们先创建个辅助数组 &lt;code&gt;sums&lt;/code&gt; 用于保存前缀和。
为了方便计算，我们在 &lt;code&gt;sums&lt;/code&gt; 的最前面插入一个 0。
随后我们可以先固定一个指针 &lt;code&gt;i&lt;/code&gt;，然后对整个数组进行二分查找。
注意：我们要找的目标是 &lt;code&gt;target + suns[i - 1]&lt;/code&gt;。
其中 &lt;code&gt;suns[i - 1]&lt;/code&gt; 就是前 &lt;code&gt;i - 1&lt;/code&gt; 个元素的前缀和。
而 &lt;code&gt;target + suns[i - 1]&lt;/code&gt; 就是目标值的前缀和。
随后我们要对结果进行处理，因为我们二分查找的是前缀和，所以我们要将结果减去 &lt;code&gt;suns[i - 1]&lt;/code&gt;，
并与现有的结果进行比较，取最小值。
其中，二分查找可以用现成的函数，但更推荐自己实现。
在此给出一种针对该题的实现方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto binary_search(const vector&amp;lt;int&amp;gt; &amp;amp;nums, int i, int j, const int target) {
  auto ans = -1;

  while (i &amp;lt;= j) {
    auto mid = i + ((j - i) &amp;gt;&amp;gt; 1);

    if (nums[mid] &amp;gt;= target) {
      ans = mid;
      j = mid - 1;
    } else
      i = mid + 1;
  }

  return ans;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，这道题最优的解法还是使用滑动窗口的办法。
前两种方法都是固定一个指针然后进行查找。
而我们可以通过指定两个指针 &lt;code&gt;start ,end&lt;/code&gt; 来表示滑动窗口的范围。
初始情况下，两指针均为 0。
在 &lt;code&gt;end&lt;/code&gt; 指针移动过程中，我们先求出 &lt;code&gt;end&lt;/code&gt; 指针的前缀和，
然后通过移动 &lt;code&gt;start&lt;/code&gt; 指针来缩小滑动窗口。
从而找出满足条件的子数组。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int minSubArrayLen(int target, vector&amp;lt;int&amp;gt; &amp;amp;nums) {
    int n = nums.size();

    if (n == 0)
      return 0;

    auto ans = INT_MAX, start = 0, end = 0, sum = 0;

    while (end &amp;lt; n) {
      sum += nums[end];

      while (sum &amp;gt;= target) {
        ans = min(ans, end - start + 1);
        sum -= nums[start++];
      }

      end++;
    }

    return ans == INT_MAX ? 0 : ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>三数之和</title><link>https://elysium2020.github.io/blog/3sum/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/3sum/</guid><description>LeetCode 15 题解析</description><pubDate>Fri, 01 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题的关键不在于复杂的数据结构，而在于如何避免重复计算与重复答案。
一开始想着先利用两轮循环确定两个数字，然后使用二分来寻找剩余的数字中是否有满足条件的数字。
随后想到这其中可能有大量的重复计算，遂考虑 DP。
但在实现过程中发现该方法时间复杂度 $O(n^2\log{n})$，似乎不太像 LeetCode 的答案。&lt;/p&gt;
&lt;p&gt;官方解答前几个步骤和我前面想法类似。
都是先用两轮循环固定住两个数字，但官方解答在寻找第三个数的时候使用了额外的指针。
由于题目要求 $i \ne j,\ j \ne k,\ i \ne k$，
所以我们在定义第二个和第三个指针的时候要确保内容不能与前一个指针重复。
且第三个指针必须要在第二个指针的后面。&lt;/p&gt;
&lt;p&gt;当第二个指针和第三个指针相遇时，就不会有 $a + b + c = 0$ 且 $b &amp;lt; c$ 的情况。
此时我们可以跳过。
当找到满足条件的 c 时，我们可以将 &lt;code&gt;{a, b, c}&lt;/code&gt; 压入答案数组 &lt;code&gt;res&lt;/code&gt; 中。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; threeSum(vector&amp;lt;int&amp;gt; &amp;amp;nums) {
    int n = nums.size();
    sort(nums.begin(), nums.end());
    vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; res;

    for (auto i = 0; i &amp;lt; n; i++) {
      if (i &amp;gt; 0 &amp;amp;&amp;amp; nums[i] == nums[i - 1])
        continue;

      auto k = n - 1, target = -nums[i];

      for (auto j = i + 1; j &amp;lt; n; j++) {
        if (j &amp;gt; i + 1 &amp;amp;&amp;amp; nums[j] == nums[j - 1])
          continue;
        while (j &amp;lt; k &amp;amp;&amp;amp; nums[j] + nums[k] &amp;gt; target)
          k--;
        if (j == k)
          break;
        if (nums[j] + nums[k] == target)
          res.push_back({nums[i], nums[j], nums[k]});
      }
    }

    return res;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>盛最多水的容器</title><link>https://elysium2020.github.io/blog/container_with_most_water/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/container_with_most_water/</guid><description>LeetCode 11 题解析</description><pubDate>Mon, 28 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题如果有双指针的思想难度不大。
我们只需要设立左右指针，
让他们从两端往中间扫描每个元素，
并确保面积始终为最大值即可。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int maxArea(vector&amp;lt;int&amp;gt; &amp;amp;height) {

    int num = height.size();
    auto left = 0, right = num - 1, ans = 0;

    while (left &amp;lt; right) {
      auto min_height = min(height[left], height[right]);
      ans = max((right - left) * min_height, ans);

      if (height[left] &amp;gt;= height[right])
        right--;
      else
        left++;
    }

    return ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>两数之和 II - 输入有序数组</title><link>https://elysium2020.github.io/blog/two_sum_ii_-_input_array_is_sorted/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/two_sum_ii_-_input_array_is_sorted/</guid><description>LeetCode 167 题解析</description><pubDate>Sun, 27 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题算不上特别难。
基本上一眼就能得出双指针的方法。
但在介绍双指针方法之前，先来看一看以二分的思路是如何完成的。&lt;/p&gt;
&lt;p&gt;二分的思路是先固定一个数字，
然后在右侧二分寻找满足 &lt;code&gt;numbers[left] + numbers[right] == target&lt;/code&gt; 的 &lt;code&gt;right&lt;/code&gt;。
换句话说，在固定好一个数字 &lt;code&gt;numbers[i]&lt;/code&gt; 后，我们需要寻找一个满足&lt;code&gt;target - numbers[i]&lt;/code&gt; 的 &lt;code&gt;numbers[mid]&lt;/code&gt;。
由于答案唯一，所以我们在找到后可以直接返回。
需要注意的是，二分查找在计算中间值时更重要的是避免潜在溢出，因此通常会写成 &lt;code&gt;left + (right - left) / 2&lt;/code&gt; 这样的形式。&lt;/p&gt;
&lt;p&gt;而双指针法则更简单。
我们只需要两个指针 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt;。
在 &lt;code&gt;numbers[left] + numbers[right] = target&lt;/code&gt; 之前对 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt; 进行向中间移动即可。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  vector&amp;lt;int&amp;gt; twoSum(vector&amp;lt;int&amp;gt; &amp;amp;numbers, int target) {
    int left = 0, right = numbers.size() - 1;

    while (left &amp;lt; right) {
      auto sum = numbers[left] + numbers[right];

      if (sum == target)
        return {left + 1, right + 1};
      else if (sum &amp;gt; target)
        right--;
      else
        left++;
    }

    return {-1, -1};
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>判断子序列</title><link>https://elysium2020.github.io/blog/is_subsequence/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/is_subsequence/</guid><description>LeetCode 392 题解析</description><pubDate>Thu, 24 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题非常简单，双指针即可轻松判断。
因为是子序列，所以后一个字符必须在前一个字符之后出现。
因此我们可以在一个循环内完成判断：当慢指针移动到 &lt;code&gt;s&lt;/code&gt; 的末尾时，说明 &lt;code&gt;s&lt;/code&gt; 已经匹配完成，可以直接返回 &lt;code&gt;true&lt;/code&gt;。
如果遍历完整个 &lt;code&gt;t&lt;/code&gt; 后慢指针仍未到达 &lt;code&gt;s&lt;/code&gt; 的末尾，则返回 &lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;当然，这道题还可以用动态规划的方法来解决。
但动态规划方法对于这道题而言过于复杂。
因为每个位置的状态依赖后续位置的状态，所以我们通过倒序来装填 &lt;code&gt;dp[][]&lt;/code&gt;。
首先，我们要确定状态转移的边界。
令 &lt;code&gt;m = t.size()&lt;/code&gt;。
那我们可以让 &lt;code&gt;dp[m][i]&lt;/code&gt; 全为 &lt;code&gt;m&lt;/code&gt; 来表示虚拟边界。
其次，我们写出状态转移方程。&lt;/p&gt;
&lt;p&gt;$$
\mathrm{dp[i][j]} =
\begin{cases}
i &amp;amp; \mathrm{t[i]} = j \
\mathrm{dp[i + 1][j]} &amp;amp; \mathrm{t[i]} \ne j
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;根据以上方程，我们即可完成 &lt;code&gt;dp[][]&lt;/code&gt; 的填充。&lt;/p&gt;
&lt;p&gt;现在我们开始查找：
我们先定义一个 &lt;code&gt;add&lt;/code&gt; 用于表示搜索起始位置。
令 &lt;code&gt;c = s[i]&lt;/code&gt;，则我们判断 &lt;code&gt;dp[add][c - &apos;a&apos;]&lt;/code&gt; 是否等于 &lt;code&gt;m&lt;/code&gt;。
若等于则说明未能找到字符 &lt;code&gt;c&lt;/code&gt;，即不符合子序列的要求。
若不等于，则可以将 &lt;code&gt;add&lt;/code&gt; 更新为 &lt;code&gt;dp[add][c - &apos;a&apos;] + 1&lt;/code&gt;。
直到遍历结束。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  bool isSubsequence(string s, string t) {
    if (s.size() &amp;gt; t.size())
      return false;

    int m = t.size();
    vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; dp(m + 1, vector&amp;lt;int&amp;gt;(26, 0));

    for (auto i = 0; i &amp;lt; 26; ++i)
      dp[m][i] = m;

    for (auto i = m - 1; i &amp;gt;= 0; --i)
      for (auto j = 0; j &amp;lt; 26; ++j)
        dp[i][j] = t[i] == j + &apos;a&apos; ? i : dp[i + 1][j];

    auto add = 0;

    for (auto c : s) {
      if (dp[add][c - &apos;a&apos;] == m)
        return false;
      add = dp[add][c - &apos;a&apos;] + 1;
    }

    return true;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>文本左右对齐</title><link>https://elysium2020.github.io/blog/text_justification/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/text_justification/</guid><description>LeetCode 68 题解析</description><pubDate>Mon, 21 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题相对同组其他题来说比较繁琐，
究其原因是因为这更像道业务逻辑题而不是算法题。
我们只需要抓住三种情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前行是最后一行：单词左对齐，单词间有且仅有一个空格。
其余空格在末尾补充从而对齐。&lt;/li&gt;
&lt;li&gt;当前行不是最后一行，但只有一个单词：该单词左对齐，在末尾填充空格从而对齐。&lt;/li&gt;
&lt;li&gt;当前行不是最后一行且不止一个单词：设当前单词数为 &lt;code&gt;num_words&lt;/code&gt;，
单词间空格数为 &lt;code&gt;num_spaces&lt;/code&gt; 。
因为我们要在单词周围均匀分配空格，
所以单词之间至少有空格
$\mathrm{avg_spaces} = \frac{\mathrm{num_spaces}}{\mathrm{num_words - 1}}$
对于多出来的空格
$\mathrm{extra_spaces} = \mathrm{num_spaces}\bmod{(\mathrm{num_words} - 1)}$，
应该填充在前 &lt;code&gt;extra_spaces&lt;/code&gt; 个单词的之间。
也就是说，前 &lt;code&gt;extra_spaces&lt;/code&gt; 个单词应该填充 &lt;code&gt;avg_spaces + 1&lt;/code&gt; 个空格，其余的单词应该填充 &lt;code&gt;avg_spaces&lt;/code&gt; 个空格。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
private:
  auto blank(int n) { return string(n, &apos; &apos;); }
  auto join(const vector&amp;lt;string&amp;gt; &amp;amp;words, int left, int right,
            const string &amp;amp;sep) {
    auto s = words[left];
    for (auto i = left + 1; i &amp;lt; right; ++i)
      s += sep + words[i];

    return s;
  }

public:
  vector&amp;lt;string&amp;gt; fullJustify(vector&amp;lt;string&amp;gt; &amp;amp;words, int maxWidth) {
    vector&amp;lt;string&amp;gt; ans;
    auto right = 0;
    int n = words.size();
    while (true) {
      auto left = right;
      auto sum_len = 0;

      while (right &amp;lt; n &amp;amp;&amp;amp;
             sum_len + words[right].length() + right - left &amp;lt;= maxWidth)
        sum_len += words[right++].length();

      if (right == n) {
        auto s = join(words, left, n, &quot; &quot;);
        ans.emplace_back(s + blank(maxWidth - s.length()));
        return ans;
      }

      auto num_words = right - left;
      auto num_spaces = maxWidth - sum_len;

      if (num_words == 1) {
        ans.emplace_back(words[left] + blank(num_spaces));
        continue;
      }

      auto avg_spaces = num_spaces / (num_words - 1);
      auto extra_spaces = num_spaces % (num_words - 1);
      auto s1 =
          join(words, left, left + extra_spaces + 1, blank(avg_spaces + 1));
      auto s2 = join(words, left + extra_spaces + 1, right, blank(avg_spaces));
      ans.emplace_back(s1 + blank(avg_spaces) + s2);
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>验证回文串</title><link>https://elysium2020.github.io/blog/valid_palindrome/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/valid_palindrome/</guid><description>LeetCode 125 题解析</description><pubDate>Mon, 21 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题最直观的方法就是利用双指针进行前后遍历。
我们可以先利用 &lt;code&gt;erase()&lt;/code&gt; 配合 &lt;code&gt;remove_if()&lt;/code&gt; 以及 &lt;code&gt;isalnum()&lt;/code&gt; 移除所有非法字符。
然后利用 &lt;code&gt;transform()&lt;/code&gt; 将所有字符转换为小写。
接着就可以使用双指针进行前后遍历判断是否回文了。&lt;/p&gt;
&lt;p&gt;当然，我们还可以直接在原字符串上判断，从而节省额外空间。
我们可以利用上文提到的 &lt;code&gt;isalnum()&lt;/code&gt; 函数，在遇见非法字符时进行跳过。
然后直接利用 &lt;code&gt;tolower()&lt;/code&gt; 对字符进行立即转换并判断。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  bool isPalindrome(string s) {
    int n = s.size();
    auto left = 0, right = n - 1;

    while (left &amp;lt; right) {
      while (left &amp;lt; right &amp;amp;&amp;amp; !isalnum(s[left]))
        left++;
      while (left &amp;lt; right &amp;amp;&amp;amp; !isalnum(s[right]))
        right--;
      if (left &amp;lt; right)
        if (tolower(s[left++]) != tolower(s[right--]))
          return false;
    }

    return true;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>接雨水</title><link>https://elysium2020.github.io/blog/trapping_rain_water/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/trapping_rain_water/</guid><description>LeetCode 42 题解析</description><pubDate>Wed, 16 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;要解答这题，首先要明确什么情况下可以接到雨水。
从题目给出的示意图不难看出：只有当当前位置左右两侧都存在更高的挡板时，该位置才可能积水。
因此，我们可以参照 135 题那样的办法，通过前后两次遍历来解决。
首先，我们从左往右遍历，记录每个位置的最大高度 &lt;code&gt;max(height[i], max_left[i-1])&lt;/code&gt;。
然后，我们再从右往左遍历，记录每个位置的最大高度 &lt;code&gt;max(height[i], max_right[i+1])&lt;/code&gt;。
最后，我们取两侧最大高度的较小值，从而计算出每个位置可以接到的雨水，即 &lt;code&gt;min(max_left[i], max_right[i]) - height[i]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我们也可以使用单调栈的方法来解决这个问题。
在此题中，我们用单调栈存储下标。
当遍历到下标 &lt;code&gt;i&lt;/code&gt; 时，我们做以下判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果当前柱子高度不高于栈顶柱子，则直接将 &lt;code&gt;i&lt;/code&gt; 压入栈。&lt;/li&gt;
&lt;li&gt;如果当前柱子高于栈顶柱子，说明此时一个储水区域已经形成。这时：
&lt;ul&gt;
&lt;li&gt;栈顶元素 &lt;code&gt;top&lt;/code&gt; 为区域底部。&lt;/li&gt;
&lt;li&gt;当前元素 &lt;code&gt;i&lt;/code&gt; 为区域右墙。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;top&lt;/code&gt; 出栈的栈顶元素 &lt;code&gt;left&lt;/code&gt; 为区域左墙。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，该区域的宽度为 $\mathrm{i} - \mathrm{left} - 1$，
高度为 $\min{(\mathrm{height}[\mathrm{left}], \mathrm{height}[\mathrm{i}])} - \mathrm{height}[\mathrm{top}]$。
由于右墙 &lt;code&gt;i&lt;/code&gt; 可能为递增序列，因此我们需要重复出栈和计算的过程。
直到该单调栈为空或者找到一个低于当前右墙高度的柱子为止。&lt;/p&gt;
&lt;p&gt;同 135 题一样，我们也可以在两次遍历的基础上使用双指针进行优化。
经过观察，不难发现某个位置能接到的雨水取决于其左侧最高挡板和右侧最高挡板中的较小值。
我们可以将这两个数组优化为指针，并分别设置两个变量记录当前两侧的最大高度。
随后，两个指针分别从最左侧或最右侧开始向中间遍历。
此时有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\mathrm{height}[\mathrm{left}]&amp;lt;\mathrm{height}[\mathrm{right}]$。
此时可以优先确定 &lt;code&gt;left&lt;/code&gt; 位置的积水量，因此 &lt;code&gt;left&lt;/code&gt; 处能接到的雨水为 &lt;code&gt;max_left - height[left]&lt;/code&gt;。
并将 &lt;code&gt;left&lt;/code&gt; 向右移动。&lt;/li&gt;
&lt;li&gt;$\mathrm{height}[\mathrm{left}]\ge\mathrm{height}[\mathrm{right}]$。
此时可以优先确定 &lt;code&gt;right&lt;/code&gt; 位置的积水量，因此 &lt;code&gt;right&lt;/code&gt; 处能接到的雨水为 &lt;code&gt;max_right - height[right]&lt;/code&gt;。
并将 &lt;code&gt;right&lt;/code&gt; 向左移动。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当两个指针相遇时，即可得到最终结果。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int trap(vector&amp;lt;int&amp;gt; &amp;amp;height) {
    auto ans = 0, left = 0, max_left = 0, max_right = 0;
    int right = height.size() - 1;

    while (left &amp;lt; right) {
      max_left = max(max_left, height[left]);
      max_right = max(max_right, height[right]);

      ans += height[left] &amp;lt; height[right] ? max_left - height[left++]
                                          : max_right - height[right--];
    }

    return ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>分发糖果</title><link>https://elysium2020.github.io/blog/candy/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/candy/</guid><description>LeetCode 135 题解析</description><pubDate>Sat, 12 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题难道中等。
核心问题在于如何正确处理两种情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;比左边的人分数高&lt;/li&gt;
&lt;li&gt;比右边的人分数高&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了解决这个问题。
我们可以进行两次遍历，一次从左往右遍历且只考虑情况 1，一次从右往左遍历且只考虑情况 2。
在从右往左便利的过程中，比较当前结果和从左往右遍历至当前位置的结果，取最大值。
这样，我们就能保证时间复杂度和空间复杂度均为 $O(2N) = $O(N)$。&lt;/p&gt;
&lt;p&gt;那有没有更好的办法呢？
我们可以从情况 1 的角度考虑：
如果当前分数大于等于左边人的分数，
那证明此时我们处于一个递增的序列当中。
那我们可以采用上一个题从左往右便利的思路去做,
使用变量 &lt;code&gt;pre&lt;/code&gt; 来记录上一个人所分的糖果数，用变量 &lt;code&gt;inc&lt;/code&gt; 来记录当前递增序列的长度。
那么当不满足情况 1 的时候，那证明我们在递减序列中。
我们可以定义一个变量 &lt;code&gt;des&lt;/code&gt; 。
通过 &lt;code&gt;des++&lt;/code&gt; 来记录当前递减序列的长度。&lt;/p&gt;
&lt;p&gt;现在有个新的问题：假设存在这么一个数列： $[1, 3, 2, 1]$，且我们已经给第一位分配了一颗糖果。
现在我们开始遍历：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当 &lt;code&gt;idx = 3&lt;/code&gt; 时，因为 $3 &amp;gt; 1$，所以我们给第二位分配两颗糖果。&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;idx = 2&lt;/code&gt; 时，因为 $2 &amp;lt; 3$，所以我们给第三位分配一颗糖果。&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;idx = 1&lt;/code&gt; 时，因为 $1 &amp;lt; 2$，所以我们给第四位分配 &lt;code&gt;award[2] - 1&lt;/code&gt; 颗糖果。
但根据第二步， $\mathrm{award}[2] = 1$，所以不符合题意。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;那么我们该怎么修复这个 bug 呢？&lt;/p&gt;
&lt;p&gt;回想一下，我们定义了一些变量：&lt;code&gt;pre, inc, dec&lt;/code&gt;.
&lt;code&gt;pre&lt;/code&gt; 是用来记录上一个人分的糖果，在递增序列中我们要给当前人分配 &lt;code&gt;pre + 1&lt;/code&gt; 个糖果。
&lt;code&gt;inc&lt;/code&gt; 用于记录当前递增序列的长度，同样也是当前序列的最大值。
&lt;code&gt;dec&lt;/code&gt; 用于记录递减序列的长度。
现在我们分三种情况情况来考虑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\mathrm{rating}[\mathrm{idx}] &amp;gt; \mathrm{rating}[\mathrm{idx} - 1]$。
此时我们处于递增的序列当中，在递增序列中我们要给当前人分配 &lt;code&gt;pre + 1&lt;/code&gt; 个糖果。
因此 &lt;code&gt;inc&lt;/code&gt; 与 &lt;code&gt;pre&lt;/code&gt; 需要自增。&lt;/li&gt;
&lt;li&gt;$\mathrm{rating}[\mathrm{idx}] = \mathrm{rating}[\mathrm{idx} - 1]$。
此时我们处于一个序列的断裂点。
因此 &lt;code&gt;inc&lt;/code&gt; 和 &lt;code&gt;pre&lt;/code&gt; 都会被重置为 1。&lt;/li&gt;
&lt;li&gt;$\mathrm{rating}[\mathrm{idx}] &amp;lt; \mathrm{rating}[\mathrm{idx} - 1]$。
此时我们处于一个递减序列当中。
因为 &lt;code&gt;dec&lt;/code&gt; 是个变量，所以我们不需要严格遵守「有序地求出从左往右对应糖果数量」。
在分配之前， 我们先让 &lt;code&gt;dec++&lt;/code&gt;，然后做以下判断：
&lt;ul&gt;
&lt;li&gt;如果 $\mathrm{dec} &amp;lt; \mathrm{inc}$，那么证明此时我们处于一个比前一个序列短的递减序列中。
当前人所分的糖果就是自增后的 &lt;code&gt;dec&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果 $\mathrm{dec} = \mathrm{inc}$，那么此时递减序列的长度与递增序列相等。
就像例子中的最后一项一样。
这时我们应该在 &lt;code&gt;dec++&lt;/code&gt; 的基础上再次 &lt;code&gt;dec++&lt;/code&gt;。
第一次的自增和上述情况一样，是表示当前人所分的糖果数。
再次自增则保证了 rating 数组最大值的人能得到最多的糖果。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此时为了维护 &lt;code&gt;des&lt;/code&gt;，我们需要在非递减序列中令 &lt;code&gt;des = 0&lt;/code&gt;。
从而保证 &lt;code&gt;des&lt;/code&gt; 不会大于 &lt;code&gt;inc&lt;/code&gt;，即保证不会超过 rating 数组最大值的人能得到最多的糖果。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int candy(vector&amp;lt;int&amp;gt; &amp;amp;ratings) {
    auto n = ratings.size();
    auto ans = 1;
    auto inc = 1;
    auto dec = 0;
    auto pre = 1;

    for (auto i = 1; i &amp;lt; n; ++i) {
      if (ratings[i] &amp;gt;= ratings[i - 1]) {
        dec = 0;
        pre = ratings[i] == ratings[i - 1] ? 1 : pre + 1;
        ans += pre;
        inc = pre;
      } else {
        dec++;
        if (dec == inc)
          dec++;
        pre = 1;
        ans += dec;
      }
    }

    return ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>找出字符串中第一个匹配项的下标</title><link>https://elysium2020.github.io/blog/find_the_index_of_the_first_occurrence_in_a_string/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/find_the_index_of_the_first_occurrence_in_a_string/</guid><description>LeetCode 28 题解析</description><pubDate>Wed, 09 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题虽然标是 easy，但仅在使用暴力时为 easy。
如果要用 KMP 算法的思路去做那么难度将会大大上升。&lt;/p&gt;
&lt;p&gt;先从暴力搜索的思路去看，
因为我们是匹配字符串，所以要确保相应的字符串是连续且完全匹配的。
现在我们定义两个变量如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto n = haystack.size();
auto m = needle.size();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为我们要确保字符串连续且相同，所以外层循环中结束条件应为 &lt;code&gt;i + m &amp;lt;= n&lt;/code&gt;。
内层循环中，为了减少开始位置的判断，我们可以始终令 &lt;code&gt;j = 0&lt;/code&gt;。
而具体的判断的索引则为 &lt;code&gt;i + j&lt;/code&gt;。
当匹配过程中发现不匹配时，应该立即打断而不是返回 -1。
因为可能存在诸如被匹配项为 &lt;code&gt;aaaaaaaab&lt;/code&gt;，匹配项为 &lt;code&gt;aaab&lt;/code&gt; 的情况。&lt;/p&gt;
&lt;p&gt;关于 KMP 解法。
我们要先算出 PM 数组。
然后使用 PM 数组和要匹配的字符串进行比较。
当遍历到 PM 最后一项且相等时，匹配成功。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int strStr(string haystack, string needle) {
    if (needle.size() == 0)
      return 0;

    auto n = haystack.size();
    auto m = needle.size();

    vector&amp;lt;int&amp;gt; pm(m);

    for (auto i = 1, j = 0; i &amp;lt; m; ++i) {
      while (j &amp;gt; 0 &amp;amp;&amp;amp; needle[i] != needle[j])
        j = pm[j - 1];
      if (needle[i] == needle[j])
        ++j;
      pm[i] = j;
    }

    for (auto i = 0, j = 0; i &amp;lt; n; ++i) {
      while (j &amp;gt; 0 &amp;amp;&amp;amp; haystack[i] != needle[j])
        j = pm[j - 1];
      if (haystack[i] == needle[j])
        ++j;
      if (j == m)
        return i - m + 1;
    }

    return -1;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Z 字形变换</title><link>https://elysium2020.github.io/blog/zigzag_conversion/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/zigzag_conversion/</guid><description>LeetCode 6 题解析</description><pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题题目误导性有点强。
准确说来说应该是个反向的 N 而不是 Z。
现在让我们重新来看一下题目给出的例子。
在压缩不必要的空行后，不难看出，
这个排列是自上往下，从 0 递增到 &lt;code&gt;numRows - 1&lt;/code&gt;。
当到达 &lt;code&gt;numRows - 1&lt;/code&gt; 时，改变遍历方向，
从下往上递减回 0。
因此，我们不难想到利用一个参数 flag。
在当前索引为 0 或者 &lt;code&gt;numRows - 1&lt;/code&gt; 时，改变 flag 的正负。
从而实现控制遍历方向的功能。&lt;/p&gt;
&lt;p&gt;当然，这样的解法还有优化的空间。
在此之前，我们先需要推导一下这个反向 N 的变化逻辑。
还是以题目的例子为例。
我们先需要向下遍历 $r = 3$ 行（一开始为空所以需要从 0 开始遍历），
然后向上遍历回到 $r - 2 = 1$ 行。
因此周期为 $t = r + r - 2 = 2r - 2$。
而每个周期会占用 $1 + r - 2 = r - 1$ 列。&lt;/p&gt;
&lt;p&gt;假设最后一个周期为完整的周期，那我们不难的得出总共有 $\begin{bmatrix} n \ t \end{bmatrix}$ 个周期。
乘上每个周期的列数，则得到矩阵的列数为 $\begin{bmatrix} n \ t \end{bmatrix} \cdot (r - 1)$。&lt;/p&gt;
&lt;p&gt;因此，对于第一行非空字符，其索引均为周期的倍数。
令索引为 $\mathrm{idx}$，则有 $\mathrm{idx} = 0\pmod{t}$。
最后一行则有 $\mathrm{idx} = r - 1\pmod{t}$。
而对于其余行，假设行号为 $i$。
在题目的例子中每个周期内中间行有两个字符。
因此分别满足 $\mathrm{idx}=i\pmod{t}$ 和 $\mathrm{idx} = t - i\pmod{t}$。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  string convert(string s, int numRows) {
    if (numRows &amp;lt; 2)
      return s;

    int n = s.size();
    auto r = numRows;

    string ans;

    auto t = r * 2 - 2;

    for (auto i = 0; i &amp;lt; r; ++i)
      for (auto j = 0; j + i &amp;lt; n; j += t) {
        ans += s[j + i];
        if (i &amp;gt; 0 &amp;amp;&amp;amp; i &amp;lt; r - 1 &amp;amp;&amp;amp; j + t - i &amp;lt; n)
          ans += s[j + t - i];
      }

    return ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>反转字符串中的单词</title><link>https://elysium2020.github.io/blog/reverse_words_in_a_string/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/reverse_words_in_a_string/</guid><description>LeetCode 151 题解析</description><pubDate>Mon, 07 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题难点在于如何正确处理空格。
我的思路是从后往前遍历，
先像 58 题那样处理尾空格，
然后再利用 &lt;code&gt;substr&lt;/code&gt; 来截取单词。
并根据 &lt;code&gt;result&lt;/code&gt; 数组是否为空来决定是否加入前置空格。&lt;/p&gt;
&lt;p&gt;当然，这道题可以利用 C++ 中的可变字符串特性来解决。
在开始前，我们先反转整个字符串。
然后先退空格，然后利用快慢指针截取单词并翻转。
其中满指针可以通过判断是否值为 0 来决定是否添加空格。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  string reverseWords(string s) {
    auto n = s.size();
    auto idx = 0;

    reverse(s.begin(), s.end());

    for (auto start = 0; start &amp;lt; n; ++start) {
      if (s[start] != &apos; &apos;) {
        if (idx != 0)
          s[idx++] = &apos; &apos;;

        auto end = start;
        while (end &amp;lt; n &amp;amp;&amp;amp; s[end] != &apos; &apos;)
          s[idx++] = s[end++];

        reverse(s.begin() + idx - (end - start), s.begin() + idx);

        start = end;
      }
    }

    s.erase(s.begin() + idx, s.end());
    return s;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>最后一个单词的长度</title><link>https://elysium2020.github.io/blog/length_of_last_word/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/length_of_last_word/</guid><description>LeetCode 58 题解析</description><pubDate>Sat, 28 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这题难度不大。
既然只需要计算最后一个单词的长度，那么只需要从后向前遍历一次就可以了。
但需要注意题目中给的例子后面部分有不等数量的空格，
所以在正式计数前我们要先过滤空格。
随后计算遇到下一个空格的位置即可。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int lengthOfLastWord(string s) {
    int idx = s.size() - 1;

    while (s[idx] == &apos; &apos;)
      idx--;

    auto num = 0;

    while (idx &amp;gt;= 0 &amp;amp;&amp;amp; s[idx] != &apos; &apos;) {
      num++;
      idx--;
    }

    return num;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>最长公共前缀</title><link>https://elysium2020.github.io/blog/longest_common_prefix/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/longest_common_prefix/</guid><description>LeetCode 14 题解析</description><pubDate>Sat, 28 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题其实非常简单。
按照常规思维来看，我们只需要对字符串进行纵向扫描，
分别对比字符串的每个字母，当字母不一样时停止扫描并返回当前存储的结果。&lt;/p&gt;
&lt;p&gt;但是，这道题的思路可以进一步拓展。
首先，假设字符串成员为 &lt;code&gt;strs = [&apos;leet&apos;, &apos;leetcode&apos;, &apos;lee&apos;, &apos;le&apos;]&lt;/code&gt;。
我们可以对字符串进行分治。即分别对 &lt;code&gt;strs = [&apos;leet&apos;, &apos;leetcode&apos;]&lt;/code&gt; 和 &lt;code&gt;strs = [&apos;lee&apos;, &apos;le&apos;]&lt;/code&gt; 进行分治。
求出 &lt;code&gt;strs = [&apos;leet&apos;, &apos;leetcode&apos;]&lt;/code&gt; 的最长公共前缀为 &lt;code&gt;leet&lt;/code&gt;，
&lt;code&gt;strs = [&apos;lee&apos;, &apos;le&apos;]&lt;/code&gt; 的最长公共前缀为 &lt;code&gt;le&lt;/code&gt;。
随后，我们对这两个字符串进行比较，得到 &lt;code&gt;leet&lt;/code&gt; 与 &lt;code&gt;le&lt;/code&gt; 的最长公共前缀为 &lt;code&gt;le&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;当然，此题还可以利用二分查找的思路进行解答。
显然，最长公共前缀小于等于最短字符串的长度，
所以我们先找出最短字符串的长度 &lt;code&gt;min_len&lt;/code&gt;。
然后，我们取字符串中间值 &lt;code&gt;mid&lt;/code&gt;，
先比较每个字符串的前 &lt;code&gt;mid&lt;/code&gt; 个字符，若都相同则以 &lt;code&gt;mid+1&lt;/code&gt; 为起点比较后 &lt;code&gt;len - (mid + 1)&lt;/code&gt; 个字符。
从而将搜索范围缩小，得出答案。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  string longestCommonPrefix(vector&amp;lt;string&amp;gt; &amp;amp;strs) {
    if (!strs.size())
      return &quot;&quot;;

    auto min_len = min_element(strs.begin(), strs.end(),
                               [](const string &amp;amp;s, const string &amp;amp;t) {
                                 return s.size() &amp;lt; t.size();
                               })
                       -&amp;gt;size();
    auto low = 0;
    auto high = min_len;

    while (low &amp;lt; high) {
      auto mid = (high - low + 1) / 2 + low;

      if (isCommonPrefix(strs, mid))
        low = mid;
      else
        high = mid - 1;
    }

    return strs[0].substr(0, low);
  }

  bool isCommonPrefix(const vector&amp;lt;string&amp;gt; &amp;amp;strs, int len) {
    auto str0 = strs[0].substr(0, len);
    auto cnt = strs.size();

    for (auto i = 0; i &amp;lt; cnt; ++i) {
      auto str = strs[i];

      for (auto j = 0; j &amp;lt; len; ++j)
        if (str[j] != str0[j])
          return false;
    }

    return true;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>整数转罗马数字</title><link>https://elysium2020.github.io/blog/integer_to_roman/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/integer_to_roman/</guid><description>LeetCode 12 题解析</description><pubDate>Tue, 24 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;一开始我以为和 13 题一样从后往前处理即可，后来发现直接按位拆分会更清晰。&lt;/p&gt;
&lt;p&gt;答案中给了两种方法。
第一种是从大到小先建立一个映射表 &lt;code&gt;table&lt;/code&gt; 并开始遍历，
当发现当前值 &lt;code&gt;num&lt;/code&gt; 不小于 &lt;code&gt;table[i]&lt;/code&gt; 时，就输出对应字符并减去相应的值。
从而得出答案。
但这样的方法有个问题：虽然时间复杂度为 $O(1)$，但实际运行会受到映射大小的影响。
本题条件最大的循环次数不会超过15。&lt;/p&gt;
&lt;p&gt;第二种虽然也是通过映射实现，但比第一种考虑得更周到：
通过归纳，我们不难发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;千位只能由 M 表示。&lt;/li&gt;
&lt;li&gt;百位只能由 C, CD, D, CM 表示。&lt;/li&gt;
&lt;li&gt;十位只能由 X，XL，L 和 XC 表示。&lt;/li&gt;
&lt;li&gt;个位只能由 I，IV，V 和 IX 表示。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，我们可以将其分为四组，且每组没有公共符号。
因此，每个数字我们都可以进行单独处理。
即，我们可以根据每个位上的数字来寻找对应的罗马字符。
随后拼接在一起，即为所求。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  string intToRoman(int num) {
    const string thousands[] = {&quot;&quot;, &quot;M&quot;, &quot;MM&quot;, &quot;MMM&quot;};
    const string hundreds[] = {&quot;&quot;,  &quot;C&quot;,  &quot;CC&quot;,  &quot;CCC&quot;,  &quot;CD&quot;,
                               &quot;D&quot;, &quot;DC&quot;, &quot;DCC&quot;, &quot;DCCC&quot;, &quot;CM&quot;};
    const string tens[] = {&quot;&quot;,  &quot;X&quot;,  &quot;XX&quot;,  &quot;XXX&quot;,  &quot;XL&quot;,
                           &quot;L&quot;, &quot;LX&quot;, &quot;LXX&quot;, &quot;LXXX&quot;, &quot;XC&quot;};
    const string ones[] = {&quot;&quot;,  &quot;I&quot;,  &quot;II&quot;,  &quot;III&quot;,  &quot;IV&quot;,
                           &quot;V&quot;, &quot;VI&quot;, &quot;VII&quot;, &quot;VIII&quot;, &quot;IX&quot;};

    return thousands[num / 1000] + hundreds[num % 1000 / 100] +
           tens[num % 100 / 10] + ones[num % 10];
  }
};

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>罗马数字转整数</title><link>https://elysium2020.github.io/blog/roman_to_integer/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/roman_to_integer/</guid><description>LeetCode 13 题解析</description><pubDate>Tue, 24 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这题按题意直接写 &lt;code&gt;if-else&lt;/code&gt; 也能完成，但那样的写法可读性一般，可扩展性也较差。
但官方解答也有可取之处：
从左往右的角度看，小的数字位于大的左边，则 &lt;code&gt;ans&lt;/code&gt; 减去当前值。
比如 $IV$，因为 $I &amp;lt; V$，所以我们应该 $\mathrm{ans} - 1 + 5$。
反之则 &lt;code&gt;ans += val&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int romanToInt(string s) {
    unordered_map&amp;lt;char, int&amp;gt; symbol_values = {
        {&apos;I&apos;, 1},   {&apos;V&apos;, 5},   {&apos;X&apos;, 10},  {&apos;L&apos;, 50},
        {&apos;C&apos;, 100}, {&apos;D&apos;, 500}, {&apos;M&apos;, 1000}};
    auto ans = 0;
    auto n = s.length();

    for (auto i = 0; i &amp;lt; n; ++i) {
      auto val = symbol_values[s[i]];
      if (i &amp;lt; n - 1 &amp;amp;&amp;amp; val &amp;lt; symbol_values[s[i + 1]])
        ans -= val;
      else
        ans += val;
    }

    return ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>加油站</title><link>https://elysium2020.github.io/blog/gas_station/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/gas_station/</guid><description>LeetCode 134 题解析</description><pubDate>Tue, 10 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;此题核心思路在于如何减少遍历的次数。
依据题意，我们不难得出需要针对 &lt;code&gt;(i + 1) % n&lt;/code&gt; 进行两次遍历。
假设符合题意的起点加油站为 $x$，终点站为 $y$。$z$ 为两者中间的任意一个加油站。
则我们耐心观察后就会得出以下两个等式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\sum^y_{i=x} \mathrm{gas[i]} &amp;lt; \sum^y_{i=x}{\mathrm{cost[i]}}$。
即到达最后的加油站后，前往下一个加油站所需要消耗的油量比现有油量要多&lt;/li&gt;
&lt;li&gt;$\sum^y_{i=x} \mathrm{gas[i]} \ge \sum^j_{i=x}{\mathrm{cost[i]}}$。
即在到最后的加油站以及之前所需的油量比现有油量少。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，我们可以做以下变形：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\sum^y_{i=z}\mathrm{gas[i]} &amp;amp; = \sum^y_{i=x}\mathrm{gas[i]}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;\sum^{z-1}&lt;em&gt;{i=x}\mathrm{gas[i]} \
&amp;amp; \le \sum^y&lt;/em&gt;{i=x}\mathrm{cost[i]}&lt;/li&gt;
&lt;li&gt;\sum^{z-1}&lt;em&gt;{i=x}\mathrm{gas[i]} \
&amp;amp; \le \sum^y&lt;/em&gt;{i=x}\mathrm{cost[i]}&lt;/li&gt;
&lt;li&gt;\sum^{z-1}&lt;em&gt;{i=x}\mathrm{cost[i]} \
&amp;amp; = \sum^y&lt;/em&gt;{i=z}\mathrm{cost[i]}
\end{aligned}
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;即 $x$ 与 $y$ 之间，不存在任何一个加油站，使得汽车能到达 $y$ 后下一个加油站。&lt;/p&gt;
&lt;p&gt;因此，我们可以先从 0 号加油站开始检查并判断能否环绕一周。
如果不能则从第一个失败的加油站重新开始检查。
以此类推，直到得出结果。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int canCompleteCircuit(vector&amp;lt;int&amp;gt; &amp;amp;gas, vector&amp;lt;int&amp;gt; &amp;amp;cost) {
    auto n = gas.size();
    auto i = 0;

    while (i &amp;lt; n) {
      auto sum_cost = 0;
      auto sum_gas = 0;
      auto cnt = 0;

      while (cnt &amp;lt; n) {
        auto j = (i + cnt) % n;
        sum_gas += gas[j];
        sum_cost += cost[j];

        if (sum_cost &amp;gt; sum_gas)
          break;

        cnt++;
      }

      if (cnt == n)
        return i;
      else
        i = i + cnt + 1;
    }

    return -1;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>除自身以外数组的乘积</title><link>https://elysium2020.github.io/blog/product_of_array_except_self/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/product_of_array_except_self/</guid><description>LeetCode 238 题解析</description><pubDate>Sun, 25 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;解决本题的难点在于如何在不不使用除法也能完成题目的要求。&lt;/p&gt;
&lt;p&gt;一种方法是另外建立两个新数组 &lt;code&gt;L[len], R[len]&lt;/code&gt;，分别存储 $i - 1$、$i + 1$ 的乘积。
然后在 &lt;code&gt;ans&lt;/code&gt; 数组中再相乘。
这样做虽然保证了时间复杂度为 $O(N)$，但空间复杂度也为 $O(N)$。&lt;/p&gt;
&lt;p&gt;那有什么办法优化呢？我们可以把 &lt;code&gt;ans[]&lt;/code&gt; 先当成 &lt;code&gt;L[]&lt;/code&gt; 来进行初始化与计算。
而将 R 数组变成一个跟踪右边元素的乘积。
并实时更新 R 的值。
为了让 R 能正确存储 $i + 1$ 的乘积.
R 应该从 &lt;code&gt;len - 1&lt;/code&gt; 开始索引。解答如下节所示。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  vector&amp;lt;int&amp;gt; productExceptSelf(vector&amp;lt;int&amp;gt; &amp;amp;nums) {
    auto length = nums.size();
    vector&amp;lt;int&amp;gt; answer(length);

    answer[0] = 1;
    for (auto i = 1; i &amp;lt; length; ++i)
      answer[i] = nums[i - 1] * answer[i - 1];

    auto R = 1;
    for (int i = length - 1; i &amp;gt;= 0; --i) {
      answer[i] = answer[i] * R;
      R *= nums[i];
    }

    return answer;
  };
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>O(1) 时间插入、删除和获取随机元素</title><link>https://elysium2020.github.io/blog/insert_delete_getrandom_o1/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/insert_delete_getrandom_o1/</guid><description>LeetCode 380 题解析</description><pubDate>Thu, 22 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;看见插入和删除都要求 $O(1)$，很自然会先想到使用数组。
不过，仅靠数组虽然可以做到按下标随机访问为 $O(1)$，
但删除任意元素通常需要移动后续元素，因此难以保证删除操作也是 $O(1)$。
因此我们需要引入额外的数据结构来配合实现。&lt;/p&gt;
&lt;p&gt;注意到题目中并没有限制空间复杂度。
因此我们可以通过引入哈希表，用哈希表记录数组下标。
通过哈希表判断元素是否存在并记录其下标，就可以配合数组满足题目的时间复杂度要求。
删除操作中，由于直接删除会涉及到后续元素的前移，
所以我们可以交换目标元素与数组最后一个元素的位置。
从而在常数时间里移除元素。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class RandomizedSet {
private:
  vector&amp;lt;int&amp;gt; nums;
  unordered_map&amp;lt;int, int&amp;gt; indices;
  mt19937 rng;
  uniform_int_distribution&amp;lt;int&amp;gt; dist;

public:
  RandomizedSet() {}

  bool insert(int val) {
    if (indices.count(val))
      return false;

    auto index = nums.size();
    nums.emplace_back(val);
    indices[val] = index;

    return true;
  }

  bool remove(int val) {
    if (!indices.count(val))
      return false;

    auto index = indices[val];
    auto last = nums.back();
    nums[index] = last;
    indices[last] = index;
    nums.pop_back();
    indices.erase(val);

    return true;
  }

  int getRandom() {
    uniform_int_distribution&amp;lt;int&amp;gt;::param_type parm(0, nums.size() - 1);

    return nums[dist(rng, parm)];
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>H 指数</title><link>https://elysium2020.github.io/blog/h-index/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/h-index/</guid><description>LeetCode 274 题解析</description><pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这道题的难点在于如何高效找到 H 指数。
一开始我是想到了利用计数排序来实现：
通过引入一个额外的数组 &lt;code&gt;nums[]&lt;/code&gt;，用类似我们之前建立 Hash Table 的方法来记录引用次数。
其中，&lt;code&gt;nums[i]&lt;/code&gt; 用于表示引用次数恰好为 $i$ 的论文数。
考虑到 H 指数不可能大于论文发表数，
所以我们可以将所有引用次数超过论文发表数的算入总发表数（即 &lt;code&gt;nums[nums.length]&lt;/code&gt;）。
接着，我们可以逆序遍历这个数组，当“引用次数至少为当前值”的论文数量不少于当前值时，
即为我们所求的 H 指数。&lt;/p&gt;
&lt;p&gt;当然，这个方法虽然很快，但要引入一个辅助数组，这增加了空间复杂度。
因此，我们需要找到一个更好的方法。
我们可以将问题转换为寻找「有 h 篇论文引用次数至少为 h」的最大值。
因此，我们可以利用二分查找快速寻找出这个最大值。
当 &lt;code&gt;left = right&lt;/code&gt; 时，此时 &lt;code&gt;left&lt;/code&gt; 即为所求。
解答如下文所示。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int hIndex(vector&amp;lt;int&amp;gt; &amp;amp;citations) {
    auto left = 0;
    auto right = citations.size();

    while (left &amp;lt; right) {
      auto mid = (left + right + 1) &amp;gt;&amp;gt; 1;
      auto cnt = count_if(citations.begin(), citations.end(),
                          [mid](auto val) { return val &amp;gt;= mid; });
      if (cnt &amp;gt;= mid)
        left = mid;
      else
        right = mid - 1;
    }
    return left;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>搭建监控平台（二）</title><link>https://elysium2020.github.io/blog/monitoring_platform_setup_2/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/monitoring_platform_setup_2/</guid><description>完成 Prometheus 的监控告警与 Alertmanager 的自动处理告警</description><pubDate>Wed, 14 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在上一篇博文中，我们完成了监控平台的初步搭建。
但光有监控还不够，在 OOM、磁盘即将满、BTRFS 出现错误等情况时，
我们可能会因为未能及时监控到而导致服务器出现异常。
为了解决这个问题，我们可以利用 Prometheus 的告警功能，对一些常见异常情况设置告警规则。
并经由 Alertmanager 进行统一管理并转发。
本博文拟针对一些常见情况设置自动告警，并经由 Alertmanager 往指定邮箱发送告警邮件。&lt;/p&gt;
&lt;h2&gt;配置 Prometheus&lt;/h2&gt;
&lt;h3&gt;关联 Alertmanager&lt;/h3&gt;
&lt;p&gt;在正式设置警告规则前，我们应该关联 Prometheus 与 Alertmanager，
让 Prometheus 正确向 Alertmanager 发送告警信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#/etc/prometheus/prometheus.yml
alerting:
  alertmanagers:
    - static_configs:
        - targets: [&apos;localhost:9093&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置告警规则&lt;/h3&gt;
&lt;p&gt;首先，应该创建一个子目录用于维护告警规则：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /etc/prometheus/rules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在配置文件引入这个目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#/etc/prometheus/prometheus.yml
rule_files:
  - &apos;/etc/prometheus/rules/*.yml&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着我们创建相关的告警规则：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#/etc/prometheus/rules/system.yml
groups:
  - name: hostStatsAlert
    rules:
      - alert: CPUHighUse
        expr: &apos;(1 - avg by (instance) (rate(node_cpu_seconds_total{mode=&quot;idle&quot;}[5m]))) * 100 &amp;gt; 85&apos;
      - alert: DiskFull
        expr: &apos;node_filesystem_avail_bytes{mountpoint=&quot;/&quot;} / node_filesystem_size_bytes{mountpoint=&quot;/&quot;} &amp;lt; 0.15&apos;
      - alert: MemoryLow
        expr: &apos;node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes &amp;lt; 0.15&apos;
      - alert: NodeDown
        expr: &apos;up == 0&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后重启 Prometheus：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart prometheus
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;安装并配置 Alertmanager&lt;/h2&gt;
&lt;p&gt;Alertmanager 同样可以直接通过发行版仓库安装。&lt;/p&gt;
&lt;p&gt;紧接着对其进行简单配置，让它通过指定的 Gmail 邮箱向目标邮箱发送告警邮件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#/etc/alertmanager/alertmanager.yml
global:
  smtp_smarthost: smtp.gmail.com:587
  smtp_from: &amp;lt;发送邮箱地址&amp;gt;
  smtp_auth_username: &amp;lt;发送邮箱用户名&amp;gt;
  smtp_auth_password: &amp;lt;发送邮箱密码&amp;gt;

route:
  group_by: [&apos;alertname&apos;]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 1h
  receiver: &apos;email&apos;

receivers:
  - name: email
    email_configs:
      - to: &amp;lt;接收邮箱地址&amp;gt;
        send_resolved: true

inhibit_rules:
  - source_match:
      severity: &apos;critical&apos;
    target_match:
      severity: &apos;warning&apos;
    equal: [&apos;alertname&apos;, &apos;dev&apos;, &apos;instance&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后启动 Alertmanager：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl enable --now alertmanager
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>搭建监控平台（一）</title><link>https://elysium2020.github.io/blog/monitoring_platform_setup_1/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/monitoring_platform_setup_1/</guid><description>在 Linux 下搭建基于 Prometheus 和 Grafana 的监控平台。完成初步安装与设置</description><pubDate>Sun, 11 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;选型&lt;/h2&gt;
&lt;p&gt;最近终于有时间折腾一下 homelab。
我准备了一台 &lt;code&gt;4C + 4G + 60G&lt;/code&gt; 的虚拟机，用来实践 Linux 环境下的监控方案。&lt;/p&gt;
&lt;p&gt;现在第一步就是搭建一个检测虚拟机状态的平台。
在搜索后决定用 Prometheus + Grafana 的架构来搭建。&lt;/p&gt;
&lt;h2&gt;流程&lt;/h2&gt;
&lt;h3&gt;安装及初启动&lt;/h3&gt;
&lt;p&gt;首先我们需要安装上述软件。为了同时监控系统资源，还需要额外安装 &lt;code&gt;node_exporter&lt;/code&gt;。
我在选择的发行版中官方仓库已经有相关包，这里直接选择使用包管理器安装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dnf install grafana prometheus node_exporter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后启动这些服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl enable --now grafana prometheus node_exporter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们就完成了安装以及初次启动。&lt;/p&gt;
&lt;h3&gt;配置&lt;/h3&gt;
&lt;p&gt;上文提到， Prometheus 如果要监控系统资源，要额外安装 node_exporter。
现在我们对 Prometheus 进行配置来启用这个插件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/prometheus/prometheus.yml
scrape_configs:
  - job_name: &apos;prometheus&apos;
    static_configs:
      - targets: [&apos;localhost:9090&apos;]
        labels:
          app: &apos;prometheus&apos;
  - job_name: &apos;node&apos;
    static_configs:
      - targets: [&apos;localhost:9100&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后重启服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart prometheus
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即可完成插件启用。&lt;/p&gt;
&lt;p&gt;现在让我们测试一下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ sudo ss -tulnp | grep -E &quot;9090|9100&quot;
[sudo] password for elysium:
tcp   LISTEN 0      4096       127.0.0.1:9090       0.0.0.0:*    users:((&quot;prometheus&quot;,pid=54760,fd=6))
tcp   LISTEN 0      4096               *:9100             *:*    users:((&quot;node_exporter&quot;,pid=54759,fd=3))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时相关服务已经正常运行。&lt;/p&gt;
&lt;p&gt;现在让我们看看 Grafana。
Grafana 的 Web 界面已经提供了较完整的引导，我们只需要按提示导入 Prometheus 数据源即可。
关于 Dashboard，官方内置模板相对基础，
我这里更推荐使用 &lt;a href=&quot;https://grafana.com/grafana/dashboards/1860-node-exporter-full/&quot;&gt;Node Exporter Full&lt;/a&gt;。
配置时输入 1860 导入即可。&lt;/p&gt;
&lt;h3&gt;路由转发&lt;/h3&gt;
&lt;p&gt;在实际环境中，通常不会直接将 &lt;code&gt;3000&lt;/code&gt; 端口暴露到互联网。
因此，我们通常会通过反向代理暴露一个受控的访问路径，供管理员使用。
比如域名为 &lt;code&gt;foo.bar&lt;/code&gt;，则需要访问 &lt;code&gt;foo.bar/monitor&lt;/code&gt; 来进行访问。&lt;/p&gt;
&lt;p&gt;为了实现这样的功能，我们需要对 grafana 设置如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/grafana/grafana.ini
[server]
domain = foo.bar
root_url = %(protocol)s://%(domain)s/monitor/
serve_from_sub_path = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后对 nginx 设置如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /etc/nginx/nginx.conf
server {
  location /monitor/ {
    proxy_pass http://localhost:3000/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后重启这两个服务&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart nginx grafana
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们可以通过访问 &lt;code&gt;https://foo.bar/monitor/&lt;/code&gt; 来访问 Grafana。&lt;/p&gt;
</content:encoded></item><item><title>跳跃游戏</title><link>https://elysium2020.github.io/blog/jump_game/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/jump_game/</guid><description>LeetCode 55 题解析</description><pubDate>Sun, 20 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这题的核心在于维护“当前能够到达的最远位置”。&lt;/p&gt;
&lt;p&gt;更合适的做法是使用贪心思路来解答。
当 &lt;code&gt;max_jump&lt;/code&gt; 的值大于等于 &lt;code&gt;i&lt;/code&gt; 时，意味着此时 &lt;code&gt;i&lt;/code&gt; 可达。
此时可通过比较 &lt;code&gt;max_jump&lt;/code&gt; 和 &lt;code&gt;i + nums[i]&lt;/code&gt; 的最大值，
并将结果存储在 &lt;code&gt;max_jump&lt;/code&gt; 中。
而当 &lt;code&gt;max_jump&lt;/code&gt; 大于等于数组最大索引时，说明最后一个位置可达，返回 &lt;code&gt;true&lt;/code&gt;。
如果遍历过程中出现 &lt;code&gt;i &amp;gt; max_jump&lt;/code&gt;，则当前位置不可达，应返回 &lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  bool canJump(vector&amp;lt;int&amp;gt; &amp;amp;nums) {
    auto max_jump = 0;
    auto n = nums.size();

    if (n == 1)
      return true;

    for (auto i = 0; i &amp;lt; n; ++i) {
      if (i &amp;lt;= max_jump) {
        max_jump = max(max_jump, i + nums[i]);

        if (max_jump &amp;gt;= n - 1)
          return true;
      } else
        break;
    }

    return false;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>跳跃游戏2</title><link>https://elysium2020.github.io/blog/jump_game2/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/jump_game2/</guid><description>LeetCode 45 题解析</description><pubDate>Sun, 20 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;个人比起 55 题更难了。
这题的重点在于如何进行「贪心」地向后查找。
以 &lt;code&gt;[2, 3, 1, 1, 4]&lt;/code&gt; 为例：
&lt;code&gt;i = 0&lt;/code&gt; 时，最大跳跃距离为 2， &lt;code&gt;i = 1&lt;/code&gt; 时，最大跳跃距离为 3。
因此，我们可以维护一个当前可到达的边界 &lt;code&gt;max_jump&lt;/code&gt;。
到达边界时，更新边界值并增加跳跃次数。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int jump(vector&amp;lt;int&amp;gt; &amp;amp;nums) {
    auto count = 0;
    auto end = 0;
    auto max_jump = 0;
    auto n = nums.size();

    for (auto i = 0; i &amp;lt; n - 1; ++i) {
      max_jump = max(max_jump, nums[i] + i);

      if (max_jump &amp;gt;= n - 1)
        return count + 1;

      if (i == end) {
        end = max_jump;
        count++;
      }
    }

    return count;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>买卖股票的最佳时机2</title><link>https://elysium2020.github.io/blog/best_time_to_buy_and_sell_stock2/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/best_time_to_buy_and_sell_stock2/</guid><description>LeetCode 122 题解析</description><pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这题相比于 121 题，关键在于多了一个条件「可以当天卖出的同时买入，但同时只能持有一股」。
所以问题从「找出最小值，并在此之后找到最大值」变成了「尽可能找到一系列不相交的区间，记录所有区间的左右差值（右减左）大于 $0$
的区间」。&lt;/p&gt;
&lt;p&gt;因此这题有两种方案实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;动态规划。因为当日盈亏情况均由昨日决定。
所以我们可以通过变量记录昨天是否持有股票（&lt;code&gt;dp0, dp1&lt;/code&gt;），
然后通过方程 &lt;code&gt;dp0_now=max{dp0,dp1+price}&lt;/code&gt; 来记录不持有的情况下收益最大化的转移方程。
并通过 &lt;code&gt;dp1_now=max{dp1,dp0-price}&lt;/code&gt; 来记录持有股票的情况下损失最小化的转移方程。
最后一天要同时做到利益最大化以及损失最小化，所以必然为 &lt;code&gt;dp0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;贪心。我们只需要寻找 n 个区间为 $1$，且卖出 $&amp;gt;$ 买入的区间就行。具体代码如下所示。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int maxProfit(vector&amp;lt;int&amp;gt; &amp;amp;prices) {
    auto n = prices.size();
    auto ans = 0;

    for (auto i = 1; i &amp;lt; n; ++i)
      ans += max(0, prices[i] - prices[i - 1]);

    return ans;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>买卖股票的最佳时机</title><link>https://elysium2020.github.io/blog/best_time_to_buy_and_sell_stock/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/best_time_to_buy_and_sell_stock/</guid><description>LeetCode 121 题解析</description><pubDate>Wed, 16 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这题抓住一个点：找到历史最低点购入，并在这之后找到利润最高点。
即求「卖出价格-买入价格」的最大值。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int maxProfit(vector&amp;lt;int&amp;gt; &amp;amp;prices) {
    auto buy = prices[0];
    auto profit = 0;

    for (auto price : prices) {
      profit = max(profit, price - buy);
      buy = min(buy, price);
    }

    return profit;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>多数元素</title><link>https://elysium2020.github.io/blog/majority_element/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/majority_element/</guid><description>LeetCode 169 题解析</description><pubDate>Mon, 14 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;由于多数元素占比超过 $\frac{n}{2}$，
所以我们可以直接通过调用 &lt;code&gt;sort()&lt;/code&gt; 来排序，
并返回索引为 $\frac{n}{2}$ 的元素。&lt;/p&gt;
&lt;p&gt;当然，如果不考虑进阶要求空间复杂度为 $O(1)$，我们也可以通过调用哈希表。
利用键值对来统计每个元素及其出现次数。
并通过类似打擂台的方式来维护最大值。&lt;/p&gt;
&lt;p&gt;本题中，最佳办法是官方解法中的 Boyer-Moore 投票算法。
具体算法正确性可见 LeetCode 官方题解。
其原理为：
定义为一个候选众数 &lt;code&gt;candidate&lt;/code&gt;，并定义出现次数 &lt;code&gt;count = 0&lt;/code&gt;，当前数为 $x$。&lt;/p&gt;
&lt;p&gt;当 &lt;code&gt;count &amp;lt; 0&lt;/code&gt; 时，定义 $x$ 为 &lt;code&gt;candidate&lt;/code&gt;。
然后做以下判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;candidate = x; count++;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;candidate != x; count--;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为众数数量超过数组长度的 $\frac{1}{2}$。
令众数数量为 $n$，则在次情况下总共有 &lt;code&gt;n - num.size()/2&lt;/code&gt; 的数没有消除。
也就是说，最后得出的 &lt;code&gt;candidate&lt;/code&gt; 自然为众数。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int majorityElement(vector&amp;lt;int&amp;gt; &amp;amp;nums) {
    auto candidate = -1;
    auto count = 0;

    for (auto num : nums) {
      if (num == candidate)
        ++count;
      else if (--count &amp;lt; 0) {
        candidate = num;
        count = 1;
      }
    }

    return candidate;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>旋转数组</title><link>https://elysium2020.github.io/blog/rotate_array/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/rotate_array/</guid><description>LeetCode 189 题解析</description><pubDate>Mon, 14 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这题其实还算比较简单。
令当前位置为 $i$，数组长度为 $n$，目标位置为 $j$。
那么只需要知道 $j = (i + k) % n$ 就能利用一个辅助数组快速写出一个空间复杂度为 $O(N)$ 的解法。
若想不出，也可以向我一开始那样通过两个循环先将需要往前移动的数放入数组，
然后再利用 $j = n - i$ 来将剩余元素复制到新数组中。&lt;/p&gt;
&lt;p&gt;不过，综合时间复杂度和空间复杂度的情况，这题最优解法为翻转三次数组。
第一次翻转整个数组。此时数组将以倒序方式排列。
随后依次翻转 $[0, k-1], [k, n]$ 区间的元素。
此时所有元素位置均已达目标位置。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
private:
  void reserve(vector&amp;lt;int&amp;gt; &amp;amp;nums, int start, int end) {
    while (start &amp;lt; end)
      swap(nums[start++], nums[end--]);
  }

public:
  void rotate(vector&amp;lt;int&amp;gt; &amp;amp;nums, int k) {
    k %= nums.size();
    reserve(nums, 0, nums.size() - 1);
    reserve(nums, 0, k - 1);
    reserve(nums, k, nums.size() - 1);
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>移除元素2</title><link>https://elysium2020.github.io/blog/remove_element2/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/remove_element2/</guid><description>LeetCode 80 题解析</description><pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这题核心思想在于快慢指针开始的位置和比较位置。
当我们从 0 开始时，对于 $[0,0,1,1,1]$ 以及 $[0,0,0,1,1]$，
我们只能不断修改判断条件去满足这种避免情况。
但如果我们从 2 开始，则只需要判断 &lt;code&gt;slow - 2 != fast&lt;/code&gt; 的情况即可。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int removeDuplicates(vector&amp;lt;int&amp;gt; &amp;amp;nums) {
    auto n = nums.size();
    if (n &amp;lt; 2)
      return n;
    auto slow = 2;

    for (auto fast = 2; fast &amp;lt; n; ++fast) {
      if (nums[fast] != nums[slow - 2]) {
        nums[slow++] = nums[fast];
      }
    }

    return slow;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>移除元素</title><link>https://elysium2020.github.io/blog/remove_element/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/remove_element/</guid><description>LeetCode 27 题解析</description><pubDate>Fri, 28 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这题的核心就是快慢指针，思路本身并不复杂。&lt;/p&gt;
&lt;p&gt;我们可以取两个指针，慢指针用于逐个读取元素，快指针在 &lt;code&gt;nums[fast] = val&lt;/code&gt; 时跳过。
然后我们仅需返回 slow 的值。
这样一来时间复杂度为 $O(N)$，空间复杂度为 $O(1)$。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  int removeElement(vector&amp;lt;int&amp;gt; &amp;amp;nums, int val) {
    auto slow = 0;

    for (auto fast = 0; fast &amp;lt; nums.size(); ++fast) {
      if (nums[fast] != val)
        nums[slow++] = nums[fast];
    }

    return slow;
  };
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>合并两个有序数组</title><link>https://elysium2020.github.io/blog/merge_sorted_array/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/merge_sorted_array/</guid><description>LeetCode 88 题解析</description><pubDate>Thu, 27 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;这题一眼就能看出要用双指针，不过如何灵活运用就是另外一回事了。&lt;/p&gt;
&lt;p&gt;我首先想到的是创建一个新的数组，然后利用两层循环逐个比较。
这种暴力做法显然不是最优解，光时间复杂度就已经达到了 $O(m\times n)$。
此时问题自然而然地集中在如何合并成一个循环上。
该如何合并呢？
经过提示不难想出我们可以利用&lt;strong&gt;已排列数组&lt;/strong&gt;这个特性，通过比较 &lt;code&gt;p1&lt;/code&gt;、&lt;code&gt;p2&lt;/code&gt; 所指向的值，
从而有选择地插入。
这样下来，时间复杂度被压缩到 $O(m + n)$ ，空间复杂度为 $O(m + n)$。&lt;/p&gt;
&lt;p&gt;还能不能进一步优化呢？&lt;/p&gt;
&lt;p&gt;仔细观察题意，不难看出 &lt;code&gt;nums1&lt;/code&gt; 的总长度为 &lt;code&gt;m + n&lt;/code&gt;，其后半部分预留了足够空间用于合并结果。
再回想下我们上一个解法中，创建一个新数组是为了避免值被覆盖。
那如果我们从后面开始逆序遍历，就可以优先填写最终数组的尾部，因此即使覆盖 &lt;code&gt;nums1&lt;/code&gt; 中原有元素，也不会影响尚未比较的内容。[^1]
我们仅需要再添加一个指针 tail 用于存储 &lt;code&gt;m + n - 1&lt;/code&gt; ，让其分别与 $m$、$n$ 比较即可。
由于这是从后面开始遍历，所以开始值与结束条件我们也需要变换。
此时，空间复杂度压缩到了 $O(1)$。&lt;/p&gt;
&lt;p&gt;在这基础上，我们可以做一些小优化：
由于当 &lt;code&gt;p2 &amp;lt; 0&lt;/code&gt; 时，&lt;code&gt;nums2&lt;/code&gt; 已经全部合并完成，所以我们实际上只需要继续判断 &lt;code&gt;p2 &amp;gt;= 0&lt;/code&gt; 的情况即可。
在这样的情况下，我们也无需在控制语句中额外判断 &lt;code&gt;p1 &amp;lt; 0&lt;/code&gt; 之外的冗余分支。
这能让内存占用进一步减少，运行速度变得更快。&lt;/p&gt;
&lt;h2&gt;解答&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
  void merge(vector&amp;lt;int&amp;gt; &amp;amp;nums1, int m, vector&amp;lt;int&amp;gt; &amp;amp;nums2, int n) {
    auto p1 = m - 1;
    auto p2 = n - 1;
    auto tail = m + n - 1;

    while (p2 &amp;gt;= 0) {
      if (p1 &amp;gt;= 0 &amp;amp;&amp;amp; nums1[p1] &amp;gt; nums2[p2])
        nums1[tail--] = nums1[p1--];
      else
        nums1[tail--] = nums2[p2--];
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;脚注&lt;/h2&gt;
&lt;p&gt;[^1]: 严谨证明可以看 LeetCode 官方&lt;/p&gt;
</content:encoded></item><item><title>Lorem Ipsum</title><link>https://elysium2020.github.io/blog/init/</link><guid isPermaLink="true">https://elysium2020.github.io/blog/init/</guid><description>This is the first post of my new Astro blog.</description><pubDate>Thu, 01 Jan 1970 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin est nisi, sagittis eget enim a, pulvinar vestibulum ex.
Maecenas nec dui ac arcu placerat aliquet at vitae massa. In auctor quam ac erat ornare, vitae pharetra risus aliquet.
Aliquam quis laoreet erat. Fusce id efficitur est. Nullam non elit finibus, malesuada dui vitae, condimentum mauris.
Praesent euismod turpis vitae neque bibendum molestie. Etiam dignissim vitae metus eu placerat. Ut orci mauris, lacinia
vitae nibh sit amet, imperdiet rhoncus orci. Nullam commodo leo felis, et congue libero tincidunt vitae. Mauris quis
blandit tortor. Aliquam ornare justo lectus, vel tincidunt magna venenatis quis. Integer vitae vehicula diam. Nullam sit
amet quam nec felis pretium auctor eu sed ipsum. Phasellus metus felis, semper ac sodales id, varius nec quam. Ut et
justo ut tellus porta volutpat a quis lorem.&lt;/p&gt;
&lt;p&gt;Vivamus eu elit quis erat pulvinar dictum iaculis a risus. Sed eleifend a magna at pretium. Proin finibus consequat sem,
sed ultrices est vulputate quis. Quisque dapibus nisi est, eu eleifend lectus ultrices ac. Nunc quis tortor molestie,
euismod lacus id, pharetra nunc. Aenean a varius odio, ut accumsan nulla. Ut pharetra mollis placerat. Integer at neque
tempor erat condimentum pellentesque non id nibh. Curabitur quis lacus a nisi eleifend hendrerit. Proin quam sem,
iaculis vitae velit in, finibus lacinia eros. Nulla tellus sapien, rutrum nec malesuada quis, suscipit ultricies ligula.&lt;/p&gt;
&lt;p&gt;Sed tortor nisi, pulvinar quis erat ut, malesuada porta velit. Maecenas nec ligula nec est convallis eleifend eu non
velit. Quisque eget sapien a arcu bibendum ullamcorper. Nulla facilisi. In a consequat dolor. Morbi commodo id neque
porttitor dignissim. Etiam et orci in velit semper dapibus. In a tellus ut ante congue semper ac ut justo. Cras vitae
odio vehicula, dapibus urna vel, tincidunt dui. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
inceptos himenaeos. Suspendisse id leo volutpat, mollis enim ac, sollicitudin leo. Phasellus eu volutpat enim, nec
auctor metus. Fusce ac libero vehicula, faucibus sapien vitae, congue tortor. Suspendisse euismod magna eget ipsum
tristique, sed luctus dui dapibus.&lt;/p&gt;
&lt;p&gt;Donec quis ligula hendrerit, tincidunt dolor nec, sollicitudin quam. Fusce tristique ante ut mi commodo posuere. Fusce
porta interdum erat, id condimentum felis condimentum ut. Aliquam nulla risus, tempor a enim vitae, tincidunt gravida
turpis. Quisque luctus lectus vel mauris sagittis, vel suscipit ligula dignissim. Sed arcu justo, consequat et est eget,
sollicitudin faucibus justo. Sed hendrerit eleifend varius. Interdum et malesuada fames ac ante ipsum primis in
faucibus. Vivamus ullamcorper et lectus ut pellentesque. Sed porttitor nisi vel dolor ullamcorper scelerisque. Aliquam
tempus ut dui at rhoncus.&lt;/p&gt;
&lt;p&gt;Ut lacinia libero vehicula nisl maximus pretium. Aenean posuere in ligula et posuere. Phasellus molestie nisl posuere,
iaculis dolor ac, lacinia orci. Morbi sed sapien tellus. Donec id imperdiet orci, in porta erat. Proin at purus luctus,
suscipit lorem pretium, consectetur arcu. Proin sollicitudin nulla ut massa feugiat finibus. Nam sagittis tincidunt elit
at lobortis. Fusce fermentum erat in luctus consequat. Quisque vehicula dolor vel purus consectetur, nec ultrices nisi
vestibulum. Maecenas a erat orci. Curabitur bibendum nisl in lacus ultricies suscipit.&lt;/p&gt;
</content:encoded></item></channel></rss>