<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Oleg</title><generator>teletype.in</generator><description><![CDATA[Oleg]]></description><image><url>https://img2.teletype.in/files/54/7e/547e6649-e372-403e-bd97-bede82dfd310.png</url><title>Oleg</title><link>https://teletype.in/@olegtar</link></image><link>https://teletype.in/@olegtar?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/olegtar?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/olegtar?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Sun, 04 Oct 2026 18:47:24 GMT</pubDate><lastBuildDate>Sun, 04 Oct 2026 18:47:24 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@olegtar/lup5YEudAj9</guid><link>https://teletype.in/@olegtar/lup5YEudAj9?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><comments>https://teletype.in/@olegtar/lup5YEudAj9?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar#comments</comments><dc:creator>olegtar</dc:creator><title>Разбор задачи 646. Maximum Length of Pair Chain с Литкода</title><pubDate>Fri, 02 Oct 2026 21:11:09 GMT</pubDate><description><![CDATA[<img src="https://img3.teletype.in/files/aa/d9/aad9008a-3c70-4258-a8f0-a2f8fcad9c3e.png"></img>646. Maximum Length of Pair Chain]]></description><content:encoded><![CDATA[
  <p id="Bk9a"><a href="https://leetcode.com/problems/maximum-length-of-pair-chain/" target="_blank">646. Maximum Length of Pair Chain</a></p>
  <h3 id="QhXx">Условие</h3>
  <p id="yOmQ">Дан массив из <code>n</code> пар <code>pairs</code> где<code>pairs[i] = [lefti, righti]</code> и <code>lefti &lt; righti</code>.</p>
  <p id="opvO">Пара <code>p2 = [c, d]</code> <strong>следует</strong> за парой <code>p1 = [a, b]</code> если <code>b &lt; c</code>. Таким образом можно сформировать <strong>цепочку</strong> пар.</p>
  <p id="X2mZ">Верни длину самой длинной цепочки, которая может быть сформирована.</p>
  <p id="5nhu">Вам не обязательно использовать все заданные интервалы. Вы можете выбирать пары в любом порядке.</p>
  <p id="H3mc"><strong>Пример 1:</strong></p>
  <pre id="4r5F">Ввод: pairs = [[1,2],[2,3],[3,4]]
Вывод: 2
Объяснение: Самая длинная цепочка [1,2] -&gt; [3,4].</pre>
  <p id="Duzh"><strong>Пример  2:</strong></p>
  <pre id="zsEZ">Ввод: pairs = [[1,2],[7,8],[4,5]]
Вывод: 3
Объяснение: Самая длинная цепочка [1,2] -&gt; [4,5] -&gt; [7,8].</pre>
  <p id="TuEQ"><strong>Ограничения:</strong></p>
  <ul id="AqO5">
    <li id="zPAz"><code>n == pairs.length</code></li>
    <li id="LUuL"><code>1 &lt;= n &lt;= 1000</code></li>
    <li id="WrQt"><code>-1000 &lt;= lefti &lt; righti &lt;= 1000</code></li>
  </ul>
  <h3 id="PK2q">Решение</h3>
  <p id="hVk9">Итак, представим наши пары в виде графа, где вершина - это одна пара, и две вершины соединяются ребром, если две пары могут образовать цепочку. То есть, если есть пары pair1 и pair2 и pair1[1] &lt; pair2[0], то будет ребро от pair1 до pair2.<br />Например, массив [[1,2],[7,8],[4,5]]</p>
  <p id="f3zl"></p>
  <figure id="v0tw" class="m_original">
    <img src="https://img3.teletype.in/files/aa/d9/aad9008a-3c70-4258-a8f0-a2f8fcad9c3e.png" width="299" />
    <figcaption>Граф</figcaption>
  </figure>
  <p id="CxjN">Далее я буду употреблять слова &quot;пара&quot; и &quot;вершина&quot; как синонимы.<br />Если мы будем отмечать у каждой вершины длину максимальной цепочки, которая оканчивается этой вершиной, то следующуя вершина будет иметь длину цепочки + 1:</p>
  <figure id="zO7c" class="m_original">
    <img src="https://img2.teletype.in/files/98/d1/98d15805-a1e9-4bca-ac78-321882bd0613.png" width="230" />
    <figcaption>Длины цепочек</figcaption>
  </figure>
  <p id="5TdU">Если две цепочки с длинами N и M оканчиваются на двух вершинах, и у этих двух вершин один и тот же сосед, то чтобы составить максимальную цепочку, нужно выбрать цепочку с наибольшей длиной:</p>
  <figure id="Z8uu" class="m_original">
    <img src="https://img2.teletype.in/files/1d/1c/1d1c67e7-dfc6-4e78-89b6-9c0a14222051.png" width="262" />
    <figcaption>Выбор максимальной цепочки</figcaption>
  </figure>
  <p id="JiEE">Кажется можно сделать такой  алгоритм:</p>
  <ol id="pzFN">
    <li id="x9or">Пометить все вершины единицами (цепочка из одной вершины всегда есть).</li>
    <li id="LJLF">Пройтись по всем вершинам:</li>
    <ul id="lBuq">
      <li id="O6os">У каждой вершины взять длину цепочки, которая у неё сейчас (длину цепочки, которая оканчивается этой вершиной).</li>
      <li id="LZfP">Пройтись по соседям вершины. Каждому соседу проставить длину цепочки + 1, если у соседа длина его текущей цепочки (цепочки, которая оканчивается этим соседом) меньше чем длина цепочки взятой вершиы + 1</li>
    </ul>
  </ol>
  <p id="2CS2">Но этот алгоритм обламывается на таком случае, когда у нас есть массив<br />[[4,5], [7,8],[1,2]]<br />Мы сначала возьмем вершину [4,5] возьмем её длину (изначально у всех по единице). Пройдем от неё к соседу [7,8], поставим соседу длину + 1, то есть 2.<br />Потом возьмем вершину [7,8] от неё нет никаких вершин дальше, потому что нет пары, у которой первый элемент больше 8. Потом пройдем к паре [1,2] от неё перейдем к соседу [4,5], и проставим соседу длину 2. Получится, что максимальная длина равна якобы 2, хотя она на самом деле 3.<br />[[1,2],[4,5],[7,8]].<br />Порядок обхода имеет значение. Если мы начинаем с вершины A, мы должны быть уверены, что сейчас у вершины максимальная длина цепочки, которая на ней оканчивается. Если длина цепочки у вершины поменялась, то надо перестроить всех соседей, соседей соседей, и т.д. дальше. Если мы посмотрим на ограничения к задаче, то увидим, что у pair[lefti, righti] lefti &lt; righti.<br />Если мы отсортируем массив по первому или последнему элементу пары, то мы гарантировано будем начинать с пары, у которой не будет другой пары, которая могла бы стоять перед ней, и если на очередной итерации мы перешли к новой паре, то в этот момент у неё уже будет максимальная вершина.</p>
  <p id="x9gY">Итак, алгоритм дополняется сортировкой:</p>
  <ol id="4mKW">
    <li id="NV7T">Отсортировать массив пар (по первому или последнему числу в паре).</li>
    <li id="lVAH">Пометить все вершины единицами (цепочка из одной вершины всегда есть).</li>
    <li id="dCN8">Пройтись по всем вершинам:</li>
    <ul id="lBuq">
      <li id="WvyF">У каждой вершины взять длину цепочки, которая у неё сейчас (длину цепочки, которая оканчивается этой вершиной).</li>
      <li id="tfSI">Пройтись по соседям вершины. Каждому соседу проставить длину цепочки + 1, если у соседа длина его текущей цепочки (цепочки, которая оканчивается этим соседом) меньше чем длина цепочки взятой вершиы + 1</li>
    </ul>
  </ol>
  <p id="FoLa">Кстати, наш алгоритм прохода немного похож на Алгоритм Дейкстры, только там вычисляется длина минимальной цепочки, а здесь максимальной.</p>
  <p id="9Rta">Нужно ли строить реальный граф? Заводить класс для вершины, ссылку на соседей? Необязательно.<br />Можно воспользоваться одномерным массивом dp (dynamic programming)<br />dp[i] будет содержать длину цепочки, которая оканчивается парой i (в уже отсортированном массиве).<br />Итак, сортируем массив.<br />Создаем массив dp и заполняем его единицами (цепочки из одной вершины).<br />Проходим по исходному уже отсотированному массиву, идем от каждой пары правее и смотрим соседей. Сосед проверяетя if&#x27;ом pairs[j][0] &gt; pairs[i][1].<br />Если вершина является соседом, то выставляем соседу текущую длину + 1, если текущая длина + 1 больше чем та длина, которая записана у соседа сейчас.<br />Попутно сразу считаем максимальную длину.</p>
  <p id="8kwc"><strong>Полный код такой:</strong></p>
  <pre id="iJav" data-lang="clike">public class Solution {  
    public int FindLongestChain(int[][] pairs) {
        Array.Sort(pairs, (a, b) =&gt; a[0].CompareTo(b[0]));
        var dp = new int[pairs.Length];
        Array.Fill(dp, 1);
        var max = 1;
        for (var i = 0; i &lt; dp.Length; i++)
        {
            for (var j = i + 1; j &lt; dp.Length; j++)
            {
                if (pairs[j][0] &gt; pairs[i][1])
                {
                    dp[j] = Math.Max(dp[j], dp[i] + 1);
                    max = Math.Max(max, dp[j]);
                }
            }
        }
        
        return max;
    }
}</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@olegtar/guk-djs45fS</guid><link>https://teletype.in/@olegtar/guk-djs45fS?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><comments>https://teletype.in/@olegtar/guk-djs45fS?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar#comments</comments><dc:creator>olegtar</dc:creator><title>Разбор задачи 90. Subsets II c Литкода</title><pubDate>Tue, 29 Sep 2026 20:23:24 GMT</pubDate><description><![CDATA[https://leetcode.com/problems/subsets-ii/description/]]></description><content:encoded><![CDATA[
  <p id="Dlax"><a href="https://leetcode.com/problems/subsets-ii/description/" target="_blank">https://leetcode.com/problems/subsets-ii/description/</a></p>
  <h3 id="t0DF">Условие</h3>
  <p id="CaGp">Дан массив чисел <code>nums</code> который может содержать дубликаты, верни все<em> возможные подмножества (the power set)</em>.</p>
  <p id="BQYe">Результат <strong>не должен</strong> содержать повторяющихся подмножеств. Верните результат <strong>в любом порядке</strong>.</p>
  <p id="yDxl"><strong>Пример 1:</strong></p>
  <pre id="JnaA">Ввод: nums = [1,2,2]
Вывод: [[],[1],[1,2],[1,2,2],[2],[2,2]]</pre>
  <p id="69rO"><strong>Пример 2:</strong></p>
  <pre id="6dUv">Ввод: nums = [0]
Вывод: [[],[0]]</pre>
  <p id="lSJJ"><strong>Ограничения:</strong></p>
  <ul id="9irU">
    <li id="CzoV"><code>1 &lt;= nums.length &lt;= 10</code></li>
    <li id="XA2G"><code>-10 &lt;= nums[i] &lt;= 10</code></li>
  </ul>
  <h3 id="Hovt">Решение</h3>
  <p id="t7Rb">Мы видим из примера, что здесь не учитывается порядок, а учитваются сами элементы, если мы добавили [1,2], то мы не добавляем [2,1].<br /><br />Возьмем массив [1, 2, 1].<br />Если мы добавим в результат первый и третий элемент ([1, 2]), а потом добавим второй и третий элемент ([2, 1]), то последний случай будет считаться как дубликат.<br /><br />Чтобы узнать, является ли [2,1] дубликатом для [1,2], мы можем отсортировать, числа в этих массивах по возрастанию: [1,2] [1,2], и тогда нам будет легче сравнивать два массива.<br /><br />Если мы отсортируем весь входной массив изначально, то у нас все подмножества сразу будут содержать числа по возрастанию.<br /><br />Поэтому первым делом отсортируем исходный массив.<br /><code>Array.Sort(nums);</code></p>
  <p id="4dMl">Итак, мы формируем очередное подмножество ([], [1], [1, 2], и т.д.)<br />Проверяем, а было ли добавлено такое же ранее, если да, то не добавляем.<br />Если нет, то добавляем.</p>
  <p id="HsBB">Как нам проверить, что подмножество было добавлено ранее?<br />Один из вариантов пробегать по текущему результату в поиске дубликата. Это сложность O(n).<br />Другой вариант - воспользоваться хэш-сетом.<br />Заведем переменную типа HashSet&lt;string&gt;. Назовем её added (&quot;добавлено&quot;).<br />Текущее подмножество мы переводим в строку, соединяя числа через делиметр, (например, через запятую), чтобы избежать такого случая, когда у нас есть, например, массив [-1, 0, -10], и выборки [-1, 0] и [-10] преобразуются в одну и ту же строку &quot;-10&quot;. Далее мы ищем эту строку в хэш-сете, если строка там есть, значит текущее подмножество не добавляем в результат. Если строки нет, то подмножество добавляем в результат, а её строковое представление добавляем в хэш-сет added. В этом случае будет сложность проверки дубликата за O(1). (если не считать преобразование в строку, но это быстро).<br /><br />Другое решение воспользоваться HashSet&lt;List&lt;int&gt;&gt;, но указать, как вычислять хэш-функцию и как проводить сравнение элементов.<br /></p>
  <pre id="D44b" data-lang="clike">HashSet&lt;List&lt;int&gt;&gt; result = new HashSet&lt;List&lt;int&gt;&gt;(EqualityComparer&lt;List&lt;int&gt;&gt;.Create((a, b) =&gt; a.SequenceEqual(b), (a) =&gt;
{
    var hashCode = 0;
    foreach (var item in a)
    {
        hashCode += item.GetHashCode();
    }
    return hashCode;
}));</pre>
  <p id="tznT">Но этот вариант требует хорошей памяти документации C#, если решение писать не в IDE.<br /><br />Сделаем рекурсивную функцию. <br />Она будет проходить по элементам массива. Как только она дошла до конца массива (это базовый случай рекурсии), у неё должно быть сформировано очередное подмножество ([], [1], [1,2] и т.д.), это подмножество преобразуется в строку путем конкатенации элементов через делиметр (запятую). Полученная строка ищется в хэш-сете, если она там есть, то просто выходим, если есть полученное множество добавляем в результат, а его строковое представление добавляем в хэш-сет.<br />Не в базовом случае функция будет добавлять элемент в подмножество, вызывать себя рекурсивно, потом будет удалять элемент из подмножества и снова вызывать себя рекурсивно. Таким образом, каждый элемент будет в одних множествах добавлен, в других - нет.<br /><br /><strong>Итого, полный код такой:</strong></p>
  <pre id="n1jD" data-lang="clike">public class Solution {
    public IList&lt;IList&lt;int&gt;&gt; SubsetsWithDup(int[] nums)
    {
        HashSet&lt;string&gt; added = new();
        IList&lt;IList&lt;int&gt;&gt; result = new List&lt;IList&lt;int&gt;&gt;();
        Array.Sort(nums);
        Solve(0, nums, [], result, added);
        return result.Select(v =&gt; (IList&lt;int&gt;)v).ToList();
    }

    public void Solve(int i, int[] nums, List&lt;int&gt; subset, IList&lt;IList&lt;int&gt;&gt; result, HashSet&lt;string&gt; added)
    {
        if (i == nums.Length)
        {
            var subsetString = string.Join(&quot;,&quot;, subset);
            if (added.Contains(subsetString))
            {
                return;
            }
            added.Add(subsetString);
            result.Add([.. subset]);
            return;
        }
        subset.Add(nums[i]);
        Solve(i + 1, nums, subset, result, added);
        subset.RemoveAt(subset.Count - 1);
        Solve(i + 1, nums, subset, result, added);
    }
}</pre>
  <p id="gHtQ">Код работает. Решение на Литкоде проходит. Но можно ли ускорить?<br />Да.<br />Давайте посмотрим на такое множество:<br />1,1,1,1,2,2,2,2,3,3,3,3<br />мы увидим, что в ответе у нас должны быть комбинации<br />[], [3], [3,3], [3,3,3], [3,3,3,3], [2,3,3,3,3],[2,2,3,3,3,3], [2,2,2,3,3,3,3], [2,2,2,2,3,3,3,3],<br />[1,2,2,2,2,3,3,3,3]...[1,1,1,1,2,2,2,2,3,3,3,3], [1,3,3,3,3], [1,1,3,3,3,3],[1,1,1,3,3,3,3][1,1,1,13,3,3,3] и другие множества<br />Разобъем числа на группы одинаковых чисел.<br />Возьмем группу чисел 1,1,1,1.<br />Есть множества, где нет единицы.<br />Есть множества, где одна единица<br />Есть множества, где две единицы<br />Есть множества, где три единицы<br />Есть множества, где четыре единицы<br />Тоже самое с другими группами.<br /><br />Поэтому перепишем функцию Solve таким образом.<br />Мы будем искать начало группы одинаковых чисел: [1,1,1,1], [2,2,2,2] и т.д.<br />Добавляем одно число из группы, вызываем функцию Solve рекурсивно.<br />Добавляем другое число из группы, вызываем функцию Solve рекурсивно.<br />и .т.д.<br />Потом удаляем группу и вызываем функцию Solve рекурсивно.<br />Нам уже не нужен HashSet.<br /><br /><strong>Полный код такой:</strong></p>
  <pre id="mHV4" data-lang="clike">public class Solution {
    public IList&lt;IList&lt;int&gt;&gt; SubsetsWithDup(int[] nums) {
        IList&lt;IList&lt;int&gt;&gt; result = new List&lt;IList&lt;int&gt;&gt;();
        Array.Sort(nums);
        Solve(0, nums, [], result);
        return result;
    }
    public void Solve(int indexOfGroupStart, int[] nums, List&lt;int&gt; subset, IList&lt;IList&lt;int&gt;&gt; result)
    {
        if (indexOfGroupStart == nums.Length)
        {
            result.Add([..subset]);
            return;
        }
                
        var groupNumber = nums[indexOfGroupStart];//число группы
        var indexOfNextGroupStart = indexOfGroupStart + 1;//ищем начало следующей группы
        for (; indexOfNextGroupStart &lt; nums.Length; indexOfNextGroupStart++)
        {
            if (nums[indexOfNextGroupStart] != groupNumber)
            {
                break;
            }
        }

        var groupSize = indexOfNextGroupStart - indexOfGroupStart;//количество элементов в группе
        Solve(indexOfNextGroupStart, nums, subset, result);//запускаем Solve с пустой группой.
        for (var i = 0; i &lt; groupSize; i++)
        {
            subset.Add(groupNumber);//добавляем по одному
            Solve(indexOfNextGroupStart, nums, subset, result);//запускаем Solve рекурсивно
        }
        subset.RemoveRange(subset.Count - groupSize, groupSize);//удаляем группу
    }
}</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@olegtar/zo4w9UiNxlo</guid><link>https://teletype.in/@olegtar/zo4w9UiNxlo?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><comments>https://teletype.in/@olegtar/zo4w9UiNxlo?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar#comments</comments><dc:creator>olegtar</dc:creator><title>Разбор задачи 89. Gray Code с Литкода</title><pubDate>Fri, 25 Sep 2026 06:44:56 GMT</pubDate><description><![CDATA[https://leetcode.com/problems/gray-code/description/]]></description><content:encoded><![CDATA[
  <p id="VwNj"><a href="https://leetcode.com/problems/gray-code/description/" target="_blank">https://leetcode.com/problems/gray-code/description/</a></p>
  <h3 id="P0xn">Условие</h3>
  <p id="hVXi"><strong>Последовательность n-битного кода Грея</strong> — это последовательность из 2ⁿ целых чисел, в которой:</p>
  <ul id="zh4y">
    <li id="gH4v">Каждое числое в диапазоне <code>[0, 2n - 1]</code> <strong>включительно </strong></li>
    <li id="AkgL">Первое число <code>0</code>,</li>
    <li id="uxHN">Каждое целое число встречается в последовательности <strong>не более одного раза</strong>,</li>
    <li id="Nub3">Двоичное представление каждой пары <strong>соседних </strong>целых чисел отличается <strong>ровно на один бит</strong>, и</li>
    <li id="a1Rv">Двоичное представление <strong>первого </strong>и <strong>последнего </strong>целых чисел отличается <strong>ровно на один бит</strong>.</li>
  </ul>
  <p id="liO5">Дано число <code>n</code>, верните любуюдопустимую <strong>последовательность n-битного кода Грея</strong>.</p>
  <p id="vrAp"><strong>Пример 1:</strong></p>
  <pre id="HiIx">Ввод: n = 2
Вывод: [0,1,3,2]
Объяснение:
Двоичное представление of [0,1,3,2] is [00,01,11,10].
- 00 and 01 отличается на один бит
- 01 and 11 отличается на один бит
- 11 and 10 отличается на один бит
- 10 and 00 отличается на один бит
[0,2,3,1] также валидная последовательность кода Грея, двоичное представление которой [00,10,11,01].
- 00 and 10 отличается на один бит
- 10 and 11 отличается на один бит
- 11 and 01 отличается на один бит
- 01 and 00 отличается на один бит</pre>
  <p id="hxeA"><strong>Пример 2:</strong></p>
  <pre id="NDgE">Ввод: n = 1
Вывод: [0,1]</pre>
  <p id="OoKM"><strong>Ограничения:</strong></p>
  <ul id="WAyU">
    <li id="rZsM"><code>1 &lt;= n &lt;= 16</code></li>
  </ul>
  <h3 id="MtMB">Решение</h3>
  <p id="6W1c">Если n = 3, то у нас числа от 0 до 111 в двоичном представлении.<br /><br />Подготовим переменную для результата<br /><code>var result = new List&lt;int&gt;();</code></p>
  <p id="CObJ">Подготовим переменную для 2^n:<br /><code>var max = (1 &lt;&lt; n);</code></p>
  <p id="tcRw">Подготовим массив для определения, использовано ли число ранее:<br /><code>var used = new bool[max];</code> </p>
  <p id="uMhn">Индексы этого массива числа от 0 до max - 1, т.е. от 0 до 2^n - 1. Если по индексу, например, 5 стоит false, то это значит число 5 ещё не было в последовательности, если true, то было, и мы должны его пропустить. По умолчанию все значения false.</p>
  <p id="UGr8">Положим в результат число 0, и сделаем отметку, что оно заюзано.</p>
  <pre id="QVd9" data-lang="clike">used[0] = true;
result.Add(0);</pre>
  <p id="Nbsd">Далее мы будем идти по оставшимся числам:<br /><code>for (var j = 1; j &lt; max; j++)</code></p>
  <p id="u7j8">Как нам найти следующее число?<br />Мы должны изменить один бит в последнем числе, после этого мы получим потенциально новое число. Мы должны проверить по массиву used, оно использовано ранее или нет. Если да, то его пропускаем и меняем другой бит.</p>
  <p id="htb0">Положим последнее число в переменную num (сначала это 0).<br /><code>var num = 0;</code></p>
  <p id="4G3B">Проходим по всем битам этого числа<br /><code>for (var i = 0; i &lt; n; i++)</code></p>
  <p id="Wa1e">Делаем маску из одного бита на месте i и всех нулей, т.е. 001b, 010b, 100b и т.д.<br /><code>(1 &lt;&lt; i)</code></p>
  <p id="fnzq">Битово умножаем на эту маску последнее число:<br /><code>var bit = num &amp; (1 &lt;&lt; i);</code></p>
  <p id="2SIw">Если полученный результат больше 0, то значит на i&#x27;м месте у нас 1, и надо поменять его на 0.</p>
  <p id="LZX7">Если результат 0, то значит на i&#x27;м месте у нас 0, и надо поменять его на 1.<br />с помощью xor c 1 мы можем поменять 1 на 0.<br />1 ^ 1 = 0</p>
  <p id="t94q">С помощью битового or, мы меняем 0 на 1:</p>
  <p id="Ja3X">0 | 1 = 1<br /><br />Пока код получается таким:</p>
  <pre id="j3Vl" data-lang="clike">var num = 0;//последнее число
for (var j = 1; j &lt; max; j++)
{
    for (var i = 0; i &lt; n; i++)
    {
    var bit = (num &amp; (1 &lt;&lt; i));
    var nextNumber = num;
    
    if (bit &gt; 0)
    {
        nextNumber ^= 1 &lt;&lt; i; 
    }
    else
    {
        nextNumber |= 1 &lt;&lt; i;
    }
}</pre>
  <p id="S7Dt">Теперь надо проверить заюзано ли число:<br />Если нет, то надо добавить его в результат, а в num положить последнее число.</p>
  <pre id="nlQW" data-lang="clike">if (!used[nextNumber])
{
    result.Add(nextNumber);//добавляем в результат
    used[nextNumber] = true;//число заюзано, больше его не повторять
    num = nextNumber;//новое последнее число
    break;//больше не меняем биты
}</pre>
  <p id="60Nx">Итого, код такой:</p>
  <pre id="wqdV" data-lang="clike">public class Solution {
    public IList&lt;int&gt; GrayCode(int n) {
        var result = new List&lt;int&gt;();
        var max = (1 &lt;&lt; n);
        var used = new bool[max];
        used[0] = true;
        result.Add(0);
        var num = 0;
        for (var j = 1; j &lt; max; j++)
        {
          for (var i = 0; i &lt; n; i++)
          {
            var bit = (num &amp; (1 &lt;&lt; i));
            var nextNumber = num;
            
            if (bit &gt; 0)
            {
                nextNumber ^= 1 &lt;&lt; i; 
            }
            else
            {
                nextNumber |= 1 &lt;&lt; i;
            }
            
            if (!used[nextNumber])
            {
                result.Add(nextNumber);
                used[nextNumber] = true;
                num = nextNumber;
                break;
            }
          }
        }
        return result;
    }
}</pre>
  <p id="0KMz">Да, есть короткое решение, если знаешь особенности кода Грея.</p>
  <pre id="dZi8" data-lang="clike">public class Solution {
    public IList&lt;int&gt; GrayCode(int n) 
    {
        int count = 1 &lt;&lt; n; // 2^n
        var result = new int[count];
        
        for (int i = 0; i &lt; count; i++) 
        {
            result[i] = i ^ (i &gt;&gt; 1);
        }
        
        return result;
    }
}</pre>
  <p id="44SU">Но о нём вряд ли догадаешься сам, если не знаешь. Особенно на собесе.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@olegtar/grE8tqsXMNh</guid><link>https://teletype.in/@olegtar/grE8tqsXMNh?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><comments>https://teletype.in/@olegtar/grE8tqsXMNh?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar#comments</comments><dc:creator>olegtar</dc:creator><title>Разбор задачи 126. Word Ladder II с Литкода. Pretty Hard</title><pubDate>Wed, 23 Sep 2026 20:38:01 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/e2/c8/e2c87d47-81a8-4827-a72d-eeceab94d11b.png"></media:content><description><![CDATA[<img src="https://img4.teletype.in/files/fc/45/fc455ee6-644d-49cf-8d52-174b923099ec.png"></img>https://leetcode.com/problems/word-ladder-ii/]]></description><content:encoded><![CDATA[
  <p id="j8Ox"><a href="https://leetcode.com/problems/word-ladder-ii/" target="_blank">https://leetcode.com/problems/word-ladder-ii/</a></p>
  <h3 id="IEYX">Условие</h3>
  <p id="0NHS">Последовательность трансформации от слова <code>beginWord</code> до слова <code>endWord</code>  с использованием словаря<code>wordList</code> это последовательность слов <code>beginWord -&gt; s1 -&gt; s2 -&gt; ... -&gt; sk</code> такая что:</p>
  <ul id="DNHT">
    <li id="JA7g">Каждая соседняя пара слов отличается одной буквой.</li>
    <li id="N5FQ">Каждый <code>si</code> для <code>1 &lt;= i &lt;= k</code> есть в <code>wordList</code>. Заметье<code>beginWord</code> необязательно будет в<code>wordList</code>.</li>
    <li id="W61s"><code>sk == endWord</code></li>
  </ul>
  <p id="hjw3">Дано два слова: <code>beginWord</code> и<code>endWord</code>, и словарь <code>wordList</code>, верните все<em> <strong>самые</strong> <strong>короткие последовательности трансформации</strong> от</em> <code>beginWord</code> до<code>endWord</code><em>, или пустой список если такая последовательность не существует. Каждая последовательность должна быть возвращена как лист слов </em><code>[beginWord, s1, s2, ..., sk]</code>.</p>
  <p id="38Pz"><strong>Пример 1:</strong></p>
  <pre id="MXdf">Ввод: beginWord = &quot;hit&quot;, endWord = &quot;cog&quot;, wordList = [&quot;hot&quot;,&quot;dot&quot;,&quot;dog&quot;,&quot;lot&quot;,&quot;log&quot;,&quot;cog&quot;]
Вывод: [[&quot;hit&quot;,&quot;hot&quot;,&quot;dot&quot;,&quot;dog&quot;,&quot;cog&quot;],[&quot;hit&quot;,&quot;hot&quot;,&quot;lot&quot;,&quot;log&quot;,&quot;cog&quot;]]
Объяснение: Здесь 2 самых коротких последовательностей трансформации:
&quot;hit&quot; -&gt; &quot;hot&quot; -&gt; &quot;dot&quot; -&gt; &quot;dog&quot; -&gt; &quot;cog&quot;
&quot;hit&quot; -&gt; &quot;hot&quot; -&gt; &quot;lot&quot; -&gt; &quot;log&quot; -&gt; &quot;cog&quot;
</pre>
  <p id="qjeh"><strong>Пример 2:</strong></p>
  <pre id="aR61">Ввод: beginWord = &quot;hit&quot;, endWord = &quot;cog&quot;, wordList = [&quot;hot&quot;,&quot;dot&quot;,&quot;dog&quot;,&quot;lot&quot;,&quot;log&quot;]
Вывод: []
Объяснение: Слово &quot;cog&quot; не в wordList, поэтому здесь нет валидной последовательности трансформации.
</pre>
  <p id="gnl5"><strong>Ограничения:</strong></p>
  <ul id="8Fdd">
    <li id="qii4"><code>1 &lt;= beginWord.length &lt;= 5</code></li>
    <li id="YRfT"><code>endWord.length == beginWord.length</code></li>
    <li id="2x5i"><code>1 &lt;= wordList.length &lt;= 500</code></li>
    <li id="UgOZ"><code>wordList[i].length == beginWord.length</code></li>
    <li id="xlO2"><code>beginWord</code>, <code>endWord</code>, и<code>wordList[i]</code> состоят из маленьких английских букв.</li>
    <li id="KvIv"><code>beginWord != endWord</code></li>
    <li id="MylH">Все слова в <code>wordList</code> <strong>уникальны</strong>.</li>
    <li id="44uC"><strong>Сумма</strong> все самых коротких последовательностей трансформации не превышает <code>10^5</code>.</li>
  </ul>
  <h3 id="qjXP">Решение</h3>
  <p id="2Vdz">Код который, который нужно дополнить выглядит вот так:</p>
  <pre id="O75b" data-lang="clike">public Solution
{
  public IList&lt;IList&lt;string&gt;&gt; FindLadders(string beginWord, string endWord, IList&lt;string&gt; wordList) 
  {
  }
}</pre>
  <p id="JXnb">нам нужно вернуть IList&lt;IList&lt;string&gt;&gt; (лист листов)<br />beginWord может содержаться, а может и нет в wordList, поэтому подготовим список всех уникальных слов из wordList включая слово beginWord.</p>
  <pre id="g9Ee" data-lang="clike">var set = new HashSet&lt;string&gt;(wordList);
set.Add(beginWord);</pre>
  <p id="eQeP">Проверим, что если конечное слово не содержится в общем списке, то мы сразу выходим.</p>
  <pre id="rKgt" data-lang="clike">if (!set.Contains(endWord))
{
	return [];
}</pre>
  <p id="jeFk">Чтобы найти кратчайший путь от слова А, до слова Б, построим граф, где узел будет слово. Нам надо найти кратчайший путь от А до Б, при этом помним, что по условию задачи соседние слова должны отличаться только на один символ.</p>
  <p id="BALV">Заведем узел для графа.</p>
  <pre id="E8Bh" data-lang="clike">public class Node    
{        
   public string Word {get; set;} = null;        
   public List&lt;Node&gt; Neighbors {get; set;} = [];        
}</pre>
  <p id="yxWe">Word - это слово<br />List&lt;Node&gt; Neighbors - это список всех соседей узла. <br />Так как в последовательности трансформации два соседних слова должны отличаться только на один символ, то это значит, что слова соседей одного узла должны отличаться от слова этого узла только одним символом.</p>
  <p id="8bhc">Создадим вспомогательную функцию, которая возвращает true, если слова отличаются только одним символом, иначе - false.</p>
  <pre id="vHkt" data-lang="clike">public bool IsDiffOnlyByOneChar(string word1, string word2)
{ 
    var countDiff = 0;
    for (var i = 0; i &lt; word1.Length; i++)
    {
          if (word1[i] != word2[i])
          {
            countDiff++;
            if (countDiff &gt; 1)
            {
              return false;
            }
       }
    }
    return countDiff == 1;
}</pre>
  <p id="R4O7">Построим граф, где соседними узлами какого-то узла будут узлы со словом, отличающимся только на один символ от слова этого узла.</p>
  <p id="viYh">Но один узел может быть соседом сразу нескольких узлов, поэтому будем запоминать уже созданные узлы в словаре map:<br /><code>var map = new Dictionary&lt;string, Node&gt;();</code></p>
  <p id="tjBr">где ключ слово, а value - узел, его содержащий. И так, построим граф:</p>
  <pre id="ZCay" data-lang="clike">var map = new Dictionary&lt;string, Node&gt;();        
foreach (var word in set)
{
    Node currentNode = null;
    if (!map.TryGetValue(word, out currentNode))
    {
        map[word] = new Node()
        {
            Word = word
        };
        currentNode = map[word];
    }

    foreach (var otherWord in set)
    {
        if (IsDiffOnlyByOneChar(word, otherWord))
        {
            Node neighborNode = null;
            if (!map.TryGetValue(otherWord, out neighborNode))
            {
                map[otherWord] = new Node
                {
                    Word = otherWord
                };
                neighborNode = map[otherWord];
            }
            currentNode.Neighbors.Add(neighborNode);
        }
    }
}</pre>
  <p id="XDsi">Теперь нам надо стартовать от узла со словом beginWord и идти до узла со словом endWord.</p>
  <p id="7vCx">Проход будет таким</p>
  <figure id="rScw" class="m_original">
    <img src="https://img4.teletype.in/files/fc/45/fc455ee6-644d-49cf-8d52-174b923099ec.png" width="1273" />
    <figcaption>Проход по графу</figcaption>
  </figure>
  <p id="fPJY">Каждая итерация отделена пунктирной линией.</p>
  <p id="Uj4s">Примерный код такой (он нерабочий):</p>
  <pre id="E3vf" data-lang="clike">var queue = new Queue&lt;Node&gt;();//очередь в которую будут класться соседи
queue.Enqueue(map[beginWord]);//кладем первый узел
while (queue.Count() &gt; 0)
{
    var cnt = queue.Count();
    for (var i = 0; i &lt; cnt; i++)
    {
        //вытаскиваем первый узел на первой итерации
        //на всех последующих итерациях вытаскиваем соседей
        var node = queue.Dequeue();
        if (node.Word == endWord)
        {         
            //дошли до конечного узла
        }
        
        //проходимся по соседям
        foreach (var neighbor in node.Neighbors)
        {                    
            queue.Enqueue(nextNode);
        }                
    }
}</pre>
  <p id="LUVm">Но нужно решить две проблемы. Первая - надо избежать зацикливания. Ведь если узел Б сосед А, то и наоборот узел А является соседом узла Б. А также зацикливание может быть через несколько узлов.</p>
  <p id="oM22">Введем в узел свойство int Cnt.<br />Для первого узла и первой итерации оно будет равно нулю.<br />Для соседей первого узла (т.е. при второй итерации), оно будет равно 1.<br />Для соседей соседей первого узла (третья итерация), оно будет равно 2.<br />и т.д.<br />Итак, если узел А имеет значение Cnt == N,  то сосед узла Б должен иметь значение Cnt == N + 1. Если так окажется, что у узла Б будет сосед В, который будет иметь соседа А, то при переходе от узла В к соседнему узлу (узлу А), мы должны проверить, а не установлено ли у него Cnt. Если оно установлено и уже меньше или равно чем Cnt текущего узла, то значит, если мы зайдем в этот узел, мы зациклимся.</p>
  <p id="ORho">Итак, класс Node обрастает ещё одним свойством:</p>
  <pre id="pPFU" data-lang="clike">public class Node    
{        
   public string Word {get; set;} = null;        
   public List&lt;Node&gt; Neighbors {get; set;} = [];   
   public int Cnt {get;set;} = int.MaxValue;
}</pre>
  <p id="9p2B">По умолчанию Cnt = int.MaxValue, чтобы можно было таким if&#x27;ом проверить, стоить ли заходить в узел или нет:<br /><code>if (neighbor.Cnt &lt;= node.Cnt)</code></p>
  <p id="4ML0">Если условие выполняется, то значит мы уже были в neighbor, и поэтому пропускаем этот узел. По условию задачи у нас не более 501 узла (500 слов wordList и одно слово beginWord), так что Cnt не может достигнуть int.MaxValue в процессе обхода.<br />Итого, код обхода получается таким:</p>
  <pre id="7Mdp" data-lang="clike">var queue = new Queue&lt;Node&gt;();//очередь в которую будут класться соседи
map[beginWord].Cnt = 0;
queue.Enqueue(map[beginWord]);//кладем первый узел
while (queue.Count() &gt; 0)
{
    var cnt = queue.Count();
    for (var i = 0; i &lt; cnt; i++)
    {
        //вытаскиваем первый узел на первой итерации
        //на всех последующих итерациях вытаскиваем соседей
        var node = queue.Dequeue();
        if (node.Word == endWord)
        {         
            //дошли до конечного узла
        }
        
        //проходимся по соседям
        foreach (var neighbor in node.Neighbors)
        { 
            if (neighbor.Cnt &lt;= node.Cnt)
            {
                continue;//пропускаем узел
            }
            neighbor.Cnt = node.Cnt + 1;                  
            queue.Enqueue(nextNode);
        }                
    }
}</pre>
  <p id="Vi0L">Но есть вторая проблема, два разных узла могут ссылаться на один и тот же узел. Если ничего не сделать, то на следующей итерации мы можем достать этот узел дважды, и дважды добавить в очередь его соседей. Что в итоге может привести к багам, когда будем строить цепочки трансформации, они будут дублироваться (и приводит, я проверил :-)). Поэтому при обработке узлов в итерации всех соседей будем добавлять не сразу в очередь, а в HashSet, а потом из HashSet в очередь:</p>
  <pre id="k5Ms" data-lang="clike">var nextNodes = new HashSet&lt;Node&gt;();
var queue = new Queue&lt;Node&gt;();//очередь в которую будут класться соседи
map[beginWord].Cnt = 0;
queue.Enqueue(map[beginWord]);//кладем первый узел
while (queue.Count() &gt; 0)
{
    var cnt = queue.Count();
    nextNodes.Clear();//подготавливаем set для очередных соседей
    for (var i = 0; i &lt; cnt; i++)
    {
        //вытаскиваем первый узел на первой итерации
        //на всех последующих итерациях вытаскиваем соседей
        var node = queue.Dequeue();
        if (node.Word == endWord)
        {         
            //дошли до конечного узла
        }
        
        //проходимся по соседям
        foreach (var neighbor in node.Neighbors)
        { 
            if (neighbor.Cnt &lt;= node.Cnt)
            {
                continue;//пропускаем узел
            }
            neighbor.Cnt = node.Cnt + 1;                  
            nextNodes.Add(neighbor);
        }                
    }
    
    //кладем очередних соседей, исключая дубликаты
    foreach (var nextNode in nextNodes)
    {    
         queue.Enqueue(nextNode);
    }
}</pre>
  <p id="0C3A">Окей, зацикливание исключили, проход в один и тот же узел исключили. Дошли до конечного узла, а дальше-то что? Нам надо теперь раскрутить путь от endWord до beginWord обратно. Путей может быть несколько.<br />Как узнать путь обратно?<br />Введем новое поле List&lt;Node&gt; Back {get; set;}, которое будет указывать на узлы из которого можно прийти в этот узел.</p>
  <pre id="J2RX" data-lang="clike">public class Node
{
    public string Word {get; set;} = null;
    public List&lt;Node&gt; Back = [];
    public List&lt;Node&gt; Neighbors {get; set;} = [];
    public int Cnt {get; set;} = int.MaxValue;
}</pre>
  <p id="BtrL">Разница между Back и Neighbors в том, что Neighbors - это соседи вообще, а Back содержит только те узлы, которые попадут в трансформацию: beginWord -&gt; s1 -&gt; s2 -&gt; ... -&gt; endWord</p>
  <p id="GvyE">Итак, теперь обход такой:</p>
  <pre id="8DOP" data-lang="clike">var nextNodes = new HashSet&lt;Node&gt;();
var queue = new Queue&lt;Node&gt;();
map[beginWord].Cnt = 0;
queue.Enqueue(map[beginWord]);
while (queue.Count() &gt; 0)
{
    var cnt = queue.Count();
    nextNodes.Clear();
    for (var i = 0; i &lt; cnt; i++)
    {
        var node = queue.Dequeue();
        if (node.Word == endWord)
        {         
            //дошли до конца
        }
        
        foreach (var neighbor in node.Neighbors)
        {                    
            if (neighbor.Cnt &lt;= node.Cnt)
            {
                continue;
            }
            neighbor.Back.Add(node);//записываем путь назад
            neighbor.Cnt = node.Cnt + 1;
            nextNodes.Add(neighbor);
        }                
    }
    foreach (var nextNode in nextNodes)
    {
        queue.Enqueue(nextNode);
    }
}</pre>
  <p id="PtxI">Итак, мы подходим к концовке.<br />У нас есть узел node c Word == endWord, у этого узла есть Back со списком узлов, которые отличаются на одну букву и которые участвуют в трансформации, и у этих узлов тоже есть свои свойства Back.<br /><br />Нужно идти перебором по всем узлам списка Back, для каждого узла из этого списка идти перебором по всем узлам его списка Back, и т.д.</p>
  <p id="HA0x">Заведем переменную для результата:</p>
  <p id="lYDn"><code>var result = new List&lt;IList&lt;string&gt;&gt;();</code></p>
  <p id="pKFg">Заведем вспомогательную функцию Solve, которая собирается все пути от endWord до beginWord, переворачивает их и добавляет в результат:</p>
  <pre id="pdLm" data-lang="clike">//Node текущий узел (endWord -&gt; sk -&gt; s2 -&gt; s1 -&gt; beginWord)
//result результат
//текущий путь в словах List&lt;string&gt; path = endWord, sk, s2, s1, beginWord
public void Solve(Node node, IList&lt;IList&lt;string&gt;&gt; result, List&lt;string&gt; path, 
string beginWord)
{
    //дошли до конечного узла, рекурсия здесь останавливается
    if (node.Word == beginWord)
    {
        //добавили слово
        path.Add(beginWord);
        //сделаю копию. Копия нужна, так как path может быть задействован
        //где-то ещё.
        List&lt;string&gt; copyPath = [..path];
        //перевернули путь
        copyPath.Reverse();
        //добавили в результат
        result.Add(copyPath);
        //удалили последнее слово из результата
        //нужно для возврата назад на несколько слов
        //некоторые пути могут частично совпадать
        //endWord -&gt; a -&gt; b -&gt; d
        //endword -&gt; c -&gt; b -&gt; d
        path.RemoveAt(path.Count - 1);
        return;
    }
    
    //добавили очередное слово в путь
    path.Add(node.Word);
    //перебор всех последующих слов в nodeBak
    foreach (var backNode in node.Back)
    {
        Solve(backNode, result, path, beginWord);
    }
    //сделали возврат.
    path.RemoveAt(path.Count - 1);
}</pre>
  <p id="r3th">Это &quot;перебор с возвратами&quot;, по-английски - &quot;BackTracking&quot;.</p>
  <p id="cqqV">Так вот, полное решение такое:</p>
  <pre id="6KI4" data-lang="clike">public class Solution 
{
    public class Node
    {
        public string Word {get; set;} = null;
        public List&lt;Node&gt; Back = [];
        public List&lt;Node&gt; Neighbors {get; set;} = [];
        public int Cnt {get; set;} = int.MaxValue;
    }

    public IList&lt;IList&lt;string&gt;&gt; FindLadders(string beginWord, string endWord, IList&lt;string&gt; wordList) {
        var set = new HashSet&lt;string&gt;(wordList);
        set.Add(beginWord);
        
        if (!set.Contains(endWord))
        {
            return [];
        }

        //строим граф
        var map = new Dictionary&lt;string, Node&gt;();        
        foreach (var word in set)
        {
            Node currentNode = null;
            if (!map.TryGetValue(word, out currentNode))
            {
                map[word] = new Node()
                {
                    Word = word
                };
                currentNode = map[word];
            }

            foreach (var otherWord in set)
            {
                //сосед? добавляем
                if (IsDiffOnlyByOneChar(word, otherWord))
                {
                    Node neighborNode = null;
                    if (!map.TryGetValue(otherWord, out neighborNode))
                    {
                        map[otherWord] = new Node
                        {
                            Word = otherWord
                        };
                        neighborNode = map[otherWord];
                    }
                    currentNode.Neighbors.Add(neighborNode);
                }
            }
        }
        
        //обход графа в ширину
        //чтобы не обходить соседа дважды, если он сосед двух узлво
        var nextNodes = new HashSet&lt;Node&gt;();
        var queue = new Queue&lt;Node&gt;();
        //у первого узла Cnt = 0
        map[beginWord].Cnt = 0;
        queue.Enqueue(map[beginWord]);
        var result = new List&lt;IList&lt;string&gt;&gt;();
        while (queue.Count() &gt; 0)
        {
            //подготавливаем соседей текущих узлов для следующей итерации
            nextNodes.Clear();
            while (queue.Count() &gt; 0)
            {
                var node = queue.Dequeue();
                //дошли до конечного узла?
                if (node.Word == endWord)
                {
                    //раскручиваем пути назад         
                    Solve(node, result, [], beginWord);
                    return result;
                }
                
                foreach (var neighbor in node.Neighbors)
                {              
                    //уже проходили этот узел?      
                    if (neighbor.Cnt &lt;= node.Cnt)
                    {
                        continue;
                    }
                    //Путь назад
                    neighbor.Back.Add(node);
                    //Метка, чтобы не зациклиться
                    neighbor.Cnt = node.Cnt + 1;
                    nextNodes.Add(neighbor);
                }                
            }
            //подготавливаем новые узлы для следующей итерации
            foreach (var nextNode in nextNodes)
            {
                queue.Enqueue(nextNode);
            }
        }
        return [];
    }
    
    //Перебор с возвратами
    public void Solve(Node node, IList&lt;IList&lt;string&gt;&gt; result, List&lt;string&gt; path, string beginWord)
    {
        if (node.Word == beginWord)
        {
            path.Add(beginWord);
            List&lt;string&gt; copyPath = [..path];
            copyPath.Reverse();
            result.Add(copyPath);
            path.RemoveAt(path.Count - 1);
            return;
        }

        path.Add(node.Word);
        foreach (var backNode in node.Back)
        {
            Solve(backNode, result, path, beginWord);
        }
        path.RemoveAt(path.Count - 1);
    }

    public bool IsDiffOnlyByOneChar(string word1, string word2)
    {
        var countDiff = 0;
        for (var i = 0; i &lt; word1.Length; i++)
        {
            if (word1[i] != word2[i])
            {
                countDiff++;
                if (countDiff &gt; 1)
                {
                    return false;
                }
            }
        }
        return countDiff == 1;
    }
}</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@olegtar/MBWBwxlUtcm</guid><link>https://teletype.in/@olegtar/MBWBwxlUtcm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><comments>https://teletype.in/@olegtar/MBWBwxlUtcm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar#comments</comments><dc:creator>olegtar</dc:creator><title>Разбор задачи 326. Power of Three с Литкода</title><pubDate>Tue, 22 Sep 2026 20:27:47 GMT</pubDate><description><![CDATA[https://leetcode.com/problems/power-of-three/]]></description><content:encoded><![CDATA[
  <p id="DVrG"><a href="https://leetcode.com/problems/power-of-three/" target="_blank">https://leetcode.com/problems/power-of-three/</a><br /></p>
  <h3 id="PxBW">Условие</h3>
  <p id="tZjO">Дано число <code>n</code>, верните<em><code>true</code> Если оно степень тройки. Иначе, верните <code>false</code></em>.</p>
  <p id="OkO4">Число <code>n</code> степень тройки, если есть число <code>x</code> такое, что <code>n == 3^x</code>.</p>
  <p id="KMIb"><strong>Пример 1:</strong></p>
  <pre id="GL3m">Ввод: n = 27
Вывод: true
Explanation: 27 = 3^3</pre>
  <p id="iNGj"><strong>Пример  2:</strong></p>
  <pre id="3z7P">Ввод: n = 0
Вывод: false
Объяснение: Нет x, чтобы 3^x = 0.</pre>
  <p id="axsl"><strong>Пример 3:</strong></p>
  <pre id="hylF">Ввод: n = -1
Вывод: false
Объяснение: Нет x, чтобы 3^x = -1.</pre>
  <p id="pZpG"><strong>Ограничения:</strong></p>
  <ul id="HST3">
    <li id="2N1P"><code>-231 &lt;= n &lt;= 231 - 1</code></li>
  </ul>
  <p id="TSbs"><strong>Дополнительно:</strong> Можешь ли ты это решить без циклов/рекурсии?</p>
  <h3 id="6CVd">Решение</h3>
  <p id="6Ld6">Обычное решение такое:</p>
  <pre id="9wJC" data-lang="clike">public class Solution {
    public bool IsPowerOfThree(int n) {
        while (n &gt; 1)
        {
            if (n % 3 != 0)
            {
                return false;
            }
            n /= 3;
        }
        return n == 1;
    }
}</pre>
  <p id="Exzv">А есть ли необычное решение? Да, есть.</p>
  <p id="RBdk">3 - это простое число (делится только на себя и на единицу).</p>
  <p id="r9m4">Если представим, некую степень тройки, как:</p>
  <p id="NHrp">3 * 3 * 3 * 3 * 3 * 3 * 3...</p>
  <p id="TPjA">то увидим, что оно тоже делится только на степень 3. На 3, или на 3 * 3 (9), или на 3 * 3 * 3 (27), и т.д.</p>
  <p id="TPNG">Диапазон значений у нас от int.MinValue до int.MaxValue. Понятно, что отрицательные числа и 0 нам не нужны. Итого, остается диапазон от единицы до int.MaxValue. Если мы найдем такую максимальную степень тройки (пусть будет называться она max), что входит в этот дипазон, то мы можем определить, является ли n степеню 3-ки так:</p>
  <pre id="Ja3f">max % n == 0</pre>
  <p id="Is9m">Если n не будет степень 3, то max не будет делиться на неё.</p>
  <p id="y06X">Как найти этот max?</p>
  <p id="nXqU">Узнаем степень 3, чтобы получить int.MaxValue:</p>
  <p id="E1e8"><code>var power = Math.Log(int.MaxValue, 3);</code></p>
  <p id="gLNc">Мы узнали, такое x, что 3^x = int.MaxValue<br />Понятно, что это какое-то дробное число.<br />Отбросим дробную часть:</p>
  <p id="pPqS"><code>var power  = Math.Truncate(Math.Log(int.MaxValue, 3))</code></p>
  <p id="fcIW">Теперь возведем 3 в эту степень.</p>
  <pre id="1vLh">var max = Math.Pow(3, power);</pre>
  <p id="OXtg">Одно из решение такое:</p>
  <pre id="9xi9" data-lang="clike">public class Solution {
    public bool IsPowerOfThree(int n) {
        if (n &lt;= 0)
        {
            return false;
        }
        var power = Math.Truncate(Math.Log(int.MaxValue, 3));
        var max = Math.Pow(3, power);
        return max % n == 0;
    }
}</pre>
  <p id="73ga">Если кто не знал, то на Литкоде можно выводить в консоль. выведем max:</p>
  <p id="vfIP"><code>Console.WriteLine(max);</code></p>
  <p id="mgtN">Получим 1162261467, если выведем power, то получим 19. Итак, максимальная степень тройки в диапазоне от 1 до int.MaxValue - это 3^19.</p>
  <p id="56Ep">Итого, решение может быть в одну строчку.</p>
  <pre id="6eOC" data-lang="clike">public class Solution {
    private const int MaxPowerOf3 = 1162261467;
    public bool IsPowerOfThree(int n) {
        return n &gt; 0 &amp;&amp; MaxPowerOf3 % n == 0;
    }
}</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@olegtar/gK4aMS3YsRG</guid><link>https://teletype.in/@olegtar/gK4aMS3YsRG?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><comments>https://teletype.in/@olegtar/gK4aMS3YsRG?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar#comments</comments><dc:creator>olegtar</dc:creator><title>Разбор задачи 231. Power of Two с Литкода</title><pubDate>Mon, 21 Sep 2026 16:11:38 GMT</pubDate><description><![CDATA[https://leetcode.com/problems/power-of-two/]]></description><content:encoded><![CDATA[
  <p id="FVel"><a href="https://leetcode.com/problems/power-of-two/" target="_blank">https://leetcode.com/problems/power-of-two/</a></p>
  <h3 id="oeFN">Условие</h3>
  <p id="DfU8">Дано число <code>n</code>, верни <em><code>true</code>, если оно степень двойки, Иначе - <code>false</code></em>.</p>
  <p id="5LHw">Число <code>n</code> степень двойки, если есть число <code>x</code> такое, что <code>n == 2^x</code>.</p>
  <p id="LdKg"><strong>Пример 1:</strong></p>
  <pre id="OWru">Ввод: n = 1
Вывод: true
Объяснение: 2^0 = 1</pre>
  <p id="IkWn"><strong>Пример 2:</strong></p>
  <pre id="KTPY">Ввод: n = 16
Вывод: true
Объяснение: 2^4 = 16</pre>
  <p id="gdSy"><strong>Пример 3:</strong></p>
  <pre id="lrwq">Ввод: n = 3
Вывод: false</pre>
  <p id="vTlL"><strong>Ограничения:</strong></p>
  <ul id="nsge">
    <li id="7ZgX"><code>-2^31 &lt;= n &lt;= 2^31 - 1</code></li>
  </ul>
  <p id="1ube"><strong>Дополнительно:</strong> Можете ли вы решить это без циклов и рекурсии?</p>
  <h3 id="6Wyh">Решение</h3>
  <p id="PeNK">Можно решить просто делением на 2, и не забывая проверять остаток отделения на 2. </p>
  <pre id="gdzr" data-lang="clike">public class Solution {
    public bool IsPowerOfTwo(int n) {
        while (n &gt; 1)
        {
            if (n % 2 != 0)
            {
                return false;
            }
            n /= 2;
        }
        return n == 1;
    }
}</pre>
  <p id="NehS">Сложность O(logN). Если точнее, то логарифм от N по основанию 2. Мы можем опускать основание логарифма, если оно константа.</p>
  <p id="3QLA">Можно ли это решить за O(1)? Да, можно.<br />Рассмотрим n в двоичном виде.<br />Если n - степень двойки, то в двоичном виде это будет 1 и какое-то количество нулей. Например, 8 = 1000b, 16 = 10000b, 64 = 1000000b и т.д.<br />Как узнать, что единица у нас только одна?<br />Рассмотрим число n - 1.<br />Если n = 8, то n - 1 = 7 = 111b, если n = 16, то n - 1 = 15 = 1111b.<br />Итак, получается, что если n - степень двойки, то n - 1, это такое число, где в двоичном виде стоит 0, где в n стоит единица, и где стоят единицы, где в n были нули. n - 1 получается инверсией числа n (если n - степень двойки).</p>
  <p id="QSJM">Таким образом, для определения является ли число степенью двойки можно было бы использовать n &amp; (n - 1) == 0<br />Однако, есть такое число:<br />1000 0000 0000 0000 0000 0000 0000 0000 <br />Это int, и это -2147483648 или по-другому int.MinValue<br />И его не надо учитывать.<br />Если у нас знаковый int, то старший бит используется для знака, поэтому в итоге будет решение такое:</p>
  <pre id="NTYQ" data-lang="clike">public class Solution {
    public bool IsPowerOfTwo(int n) {
        return n &gt; 0 &amp;&amp; (n &amp; (n - 1)) == 0;
    }
}</pre>
  <p id="eOGR">Ещё одно решение такое:</p>
  <pre id="YXdE" data-lang="clike">public class Solution {
    public bool IsPowerOfTwo(int n) {
       return n &gt; 0 &amp;&amp; int.PopCount(n) == 1;
    }
}</pre>
  <p id="9sbW">PopCount - это метод для подсчета числа единиц в битовом представлении.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@olegtar/PRS1_Nn5RYG</guid><link>https://teletype.in/@olegtar/PRS1_Nn5RYG?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><comments>https://teletype.in/@olegtar/PRS1_Nn5RYG?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar#comments</comments><dc:creator>olegtar</dc:creator><title>Почему быстрая сортировка никогда не деградирует до O(n^2)</title><pubDate>Mon, 21 Sep 2026 15:53:56 GMT</pubDate><description><![CDATA[Быстрая сортировка может иметь сложность в худшем случае O(n2)?
На практике при определенной реализации этой сортировки этого никогда не произойдет.]]></description><content:encoded><![CDATA[
  <p id="9Rmc">Быстрая сортировка может иметь сложность в худшем случае O(n2)?<br />На практике при определенной реализации этой сортировки этого никогда не произойдет.</p>
  <p id="u3Jx">Давайте разберемся почему.</p>
  <p id="V6K6">Сначала вспомним, как происходит алгоритм быстрой сортировки.</p>
  <p id="9iXZ">Он разделяется на три этапа:</p>
  <ol id="I83J">
    <li id="UKa1">1.поиск некоторого элемента называемого опорным.</li>
    <li id="s4EQ">2.перекидываем все элементы меньше опорного в левую часть от опорного, перекидываем все элементы большего опорного в правую часть от опорного.</li>
    <li id="fYI8">3.рекурсивно вызываем быструю сортировку для левых и правых частей (не включай опорный), пока не дойдем до подмассивов из нуля или одного элементов (такие массивы считаются уже отсортированными).</li>
  </ol>
  <p id="yQIK">Например, алгоритм быстрой сортировки может быть реализован так (так делать не надо, ниже будет понятно почему):</p>
  <pre id="VkG1" data-lang="clike">class QuickSort
{
    static void Main()
    {
        int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        Console.WriteLine(&quot;Исходный массив: &quot; + string.Join(&quot;, &quot;, arr));
        QuickSortAlgorithm(arr, 0, arr.Length - 1);
        Console.WriteLine(&quot;Отсортированный массив: &quot; + string.Join(&quot;, &quot;, arr));
    }

    static void QuickSortAlgorithm(int[] arr, int low, int high)
    {
        if (low &lt; high)
        {
            // Находим правильную позицию опорного элемента
            int pi = Partition(arr, low, high);

            // Рекурсивно сортируем элементы до и после опорного
            QuickSortAlgorithm(arr, low, pi - 1);
            QuickSortAlgorithm(arr, pi + 1, high);
        }
    }

    static int Partition(int[] arr, int low, int high)
    {
        // Выбираем последний элемент как опорный
        int pivot = arr[high];
        int i = low - 1; // Индекс для меньших элементов

        for (int j = low; j &lt; high; j++)
        {
            // Если текущий элемент меньше или равен опорному
            if (arr[j] &lt;= pivot)
            {
                i++;
                // Меняем местами arr[i] и arr[j]
                if (i != j)
                {
                    (arr[i], arr[j]) = (arr[j], arr[i]);
                }
            }
        }

        // Ставим опорный элемент на его законное место
        (arr[i + 1], arr[high]) = (arr[high], arr[i + 1]);
        return i + 1;//возвращаем новый индекс опорного элемента.
    }
}
</pre>
  <p id="bF8L">Код может быть непонятен с первого раза. Опорный элемент здесь выбирается в конце массива, но мы меняем местами i-ый и j-ый элементы. Здесь смысл в том, что есть два указателя i и j. j проходит по всем элементам массива кроме последнего, а i + 1 будет указывать на индекс куда сместиться опорный элемент после перестановок. Если все элементы больше опорного, то опорный элемент станет на первом месте. Если встретится элемент меньше опорного, то индекс опорного элемента станет правее (i++) (и arr[i] с arr[j] поменяются местами). Такой алгоритм позволяет избежать сдвигания элементов массива.</p>
  <p id="SwWA">Алгоритм выше легко довести до O(n^2), если на вход дать массив уже отсортированный, при том неважно в каком порядке (по возрастанию или убыванию).</p>
  <p id="22pd">Здесь в качестве опорного элемента выбирается последний.</p>
  <p id="Nmh8">Допустим мы подали полностью отсортированный (по возрастанию) массив от 1 до 10 включительно.<br />Метод Partition поделит массив на 2 части вернет индекс 9 и поделит массив на две части: [1, 2, 3, 4, 5, 6, 7, 8, 9] и пустой массив []<br />Потом метод Partition для первой части вернет индекс 8 и поделит первый массив, на две части [1, 2, 3, 4, 5, 6, 7, 8] и пустой массив.<br />Каждый раз он алгоритм будет проходить по n, потом по n-1, потом по n - 2 элементам и т.д.</p>
  <p id="hwW7">Общее количество операций:<br />n + (n-1) + (n-2) + ... + 2 + 1 = n(n+1)/2 ≈ n²/2 = O(n²)</p>
  <p id="2Rg4">Но есть несколько способов, чтобы не допустить O(n^2).<br />Один из них – это выбрать опорный элемент случайным образом.</p>
  <p id="J06E">Код теперь выглядит так:</p>
  <pre id="EUR4" data-lang="clike">static void QuickSort(int[] arr, int low, int high)
{
     if (low &lt; high)
     {
         // ГЛАВНОЕ ОТЛИЧИЕ: случайный выбор опорного элемента
         int pivotIndex = RandomPartition(arr, low, high);

         // Рекурсивно сортируем левую и правую части
         QuickSort(arr, low, pivotIndex - 1);
         QuickSort(arr, pivotIndex + 1, high);
     }
}

static int RandomPartition(int[] arr, int low, int high)
{
     // Выбираем случайный индекс в диапазоне [low, high]
     int randomIndex = rand.Next(low, high + 1);
     // Меняем случайный элемент с последним
     (arr[randomIndex], arr[high]) = (arr[high], arr[randomIndex]);
     // Теперь используем стандартную partition с последним элементом
     return Partition(arr, low, high);
}

static int Partition(int[] arr, int low, int high)
{
     int pivot = arr[high]; // опорный элемент (теперь случайный)
     int i = low - 1;
     for (int j = low; j &lt; high; j++)
     {
         if (arr[j] &lt;= pivot)
         {
             i++;
             (arr[i], arr[j]) = (arr[j], arr[i]);
         }
     }
     (arr[i + 1], arr[high]) = (arr[high], arr[i + 1]);
     return i + 1;
}  
</pre>
  <p id="FRYy">а есть и другой способ – выбрать медианный элемент из первого, среднего и последнего элементов.</p>
  <pre id="404u" data-lang="clike">static void QuickSort(int[] arr, int low, int high) 
{ 
    if (low &lt; high) 
    { 
        int pi = MedianOfThreePartition(arr, low, high); 
        QuickSort(arr, low, pi - 1); 
        QuickSort(arr, pi + 1, high); 
    } 
} 
 
static int MedianOfThreePartition(int[] arr, int low, int high) 
{ 
    int mid = low + (high - low) / 2; 
 
    // Сортируем три элемента: arr[low], arr[mid], arr[high] 
    // чтобы arr[low] &lt;= arr[mid] &lt;= arr[high] 
    if (arr[low] &gt; arr[mid]) (arr[low], arr[mid]) = (arr[mid], arr[low]); 
    if (arr[low] &gt; arr[high]) (arr[low], arr[high]) = (arr[high], arr[low]); 
    if (arr[mid] &gt; arr[high]) (arr[high], arr[mid]) = (arr[mid], arr[high]); 
 
    // Теперь arr[mid] — медиана из трёх 
    (arr[mid], arr[high]) = (arr[high], arr[mid]); 
 
    return Partition(arr, low, high); 
} 
 
static int Partition(int[] arr, int low, int high) 
{ 
    int pivot = arr[high]; 
    int i = low - 1; 
 
    for (int j = low; j &lt;= high; j++) 
    { 
        if (arr[j] &lt;= pivot) 
        { 
            i++; 
            (arr[i], arr[j]) = (arr[j], arr[i]); 
        } 
    } 
    return i; 
}
</pre>
  <p id="v1M4">На примере, почему меньше операций.</p>
  <p id="2ZQq">Массив из 16 элементов, pivot = медиана</p>
  <blockquote id="qgfm"><em>Уровень 1: [16 элементов] → разделение на [8] и [8]Работа: 16 операций (проход по всем элементам)</em></blockquote>
  <blockquote id="g0U2"><em>Уровень 2: [8] [8] → разделение на [4] [4] [4] [4]Работа: 8 + 8 = 16 операций</em></blockquote>
  <blockquote id="U28u"><em>Уровень 3: [4] [4] [4] [4] → разделение на [2] × 8Работа: 4×4 = 16 операций</em></blockquote>
  <blockquote id="GMjQ"><em>Уровень 4: [2] × 8 → разделение на [1] × 16Работа: 2×8 = 16 операций</em></blockquote>
  <p id="yiWj">и т.д.</p>
  <p id="Kr5f">Итого: 4 уровня × 16 операций = 64 операции</p>
  <blockquote id="MCas"><em>Массив из 16 элементов, pivot = максимум</em></blockquote>
  <blockquote id="uNWu"><em>Уровень 1: [16 элементов] → разделение на [15] и []Работа: 16 операций</em></blockquote>
  <blockquote id="LORZ"><em>Уровень 2: [15 элементов] → разделение на [14] и []Работа: 15 операций</em></blockquote>
  <blockquote id="jQfA"><em>Уровень 3: [14 элементов] → разделение на [13] и []Работа: 14 операций</em></blockquote>
  <blockquote id="4m04"><em>Уровень 4: [13 элементов] → разделение на [12] и []Работа: 13 операций</em></blockquote>
  <p id="X3uZ">и т.д.</p>
  <p id="ZPWd">Итого: 16 + 15 + 14 + ... + 2 + 1 = 136 операций</p>
  <p id="EsV4">Теперь давайте затестим время трех сортировок.<br />с сортировкой, где опорным выбирается последний элемент.<br />с сортировкой, где опорным выбирается случайный элемент.<br />с сортировкой, где опорным выбирается медианный элемент.</p>
  <p id="cKEM">На моем компьютере получились такие данные:</p>
  <p id="U912">Отсортированный массив (время в мс).</p>
  <p id="6vqu"><code>Количество элементов 1000 5000 10000 15000 Опорный элемент последний 1,3481 49,9192 126,8646 282,8915 Опорный элемент случайный 0,0633 0,4826 0,8018 1,2721 Опорный элемент медианный 0,0430 0,2728 0,5773 0,8535</code></p>
  <p id="5wEv">Отсортированный массив в порядке убывания (время в мс).</p>
  <p id="tOyH"><code>Количество элементов 1000 5000 10000 15000 Опорный элемент последний 1,1011 30,0971 111,3659 248,0829 Опорный элемент случайный 0,0698 0,3894 0,8156 1,2952 Опорный элемент медианный 0,0705 0,4464 0,9652 1,5092</code></p>
  <p id="jiXN">Перемешанный случайно массив (время в мс).</p>
  <p id="LifL"><code>Количество элементов 1000 5000 10000 15000 Опорный элемент последний 0,0911 0,5372 1,2565 1,8101 Опорный элемент случайный 0,0943 0,5634 1,2180 1,8907 Опорный элемент медианный 0,0890 0,5598 1,1798 1,8461</code></p>
  <p id="Atit">Почти отсортированный массив (перемешено 5% массива) (время в мс).</p>
  <p id="HycR"><code>Количество элементов 1000 5000 10000 15000 Опорный элемент последний 0,1012 0,8644 1,5544 2,8059 Опорный элемент случайный 0,0704 0,4432 0,9017 1,3402 Опорный элемент медианный 0,0707 0,4196 1,0123 1,4061</code></p>
  <p id="6LZ6">Как видим, на практике легко избежать O(n^2).</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@olegtar/ooDuvpHXUti</guid><link>https://teletype.in/@olegtar/ooDuvpHXUti?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><comments>https://teletype.in/@olegtar/ooDuvpHXUti?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar#comments</comments><dc:creator>olegtar</dc:creator><title>Хитрый LIS. Разбор задачи 300. Longest Increasing Subsequence</title><pubDate>Mon, 21 Sep 2026 15:51:30 GMT</pubDate><description><![CDATA[https://leetcode.com/problems/longest-increasing-subsequence/]]></description><content:encoded><![CDATA[
  <p id="hBD6"><a href="https://leetcode.com/problems/longest-increasing-subsequence/" target="_blank">https://leetcode.com/problems/longest-increasing-subsequence/</a></p>
  <p id="TonJ">LIS - Longest Increasing Subsequence. Наибольшая возрастающая последовательность.</p>
  <p id="PdNv">Дан массив чисел nums. Верните длину наиболее длинной строго возрастающей подпоследовательности.</p>
  <blockquote id="FmAF"><em>Подпоследовательность - это последовательность элементов полученная из другой последовательности путем удаления нескольких (возможно нуля) элементов с сохранением порядка.например, последовательность [1, 2, 3, 4, 5]:подпоследовательности: 1, 2, 3, 4, 5 (удалили 0 элементов)1,3,5 - (удалили 2 и 4)</em></blockquote>
  <p id="Rz5u"><strong>Пример 1:</strong></p>
  <pre id="VH7F">Ввод: nums = [10,9,2,5,3,7,101,18]
Вывод: 4
Объяснение: Наибольшая строговозрастающая последовательность [2,3,7,101], 
таким образом длина 4.</pre>
  <p id="oZUN"><strong>Пример 2:</strong></p>
  <pre id="cyY6">Ввод: nums = [0,1,0,3,2,3]
Вывод: 4</pre>
  <p id="SPYY"><strong>Пример 3:</strong></p>
  <pre id="pJnv">Ввод: nums = [7,7,7,7,7,7,7]
Вывод: 1</pre>
  <p id="TYh6">Решение</p>
  <p id="10af">Заведем массив dp (dynamic programming).<br />Определим, что мы будем в нём хранить.<br />Договоримся, что мы будем хранить в dp[i] длину максимальной строговозрастающей подпоследовательности, если бы у нас было только i элементов, тогда ответ будет dp[dp.Length - 1] (или более кратко dp[^1]).<br />Например:<br />nums = [5, 6, 7, 1, 2]<br />dp = [1, 2, 3, 3, 3]</p>
  <p id="obq7">nums = [5, 6, 1, 2, 3]<br />dp = [1, 2, 2, 2, 3]</p>
  <pre id="lO1F">Мы могли бы договориться, хранить в dp[i] длину максимальной 
строговозрастающей подпоследовательности, которая оканчивается элементом 
nums[i] и тогда было бы так
nums = [5, 6, 7, 1, 2]
dp = [1, 2, 3, 1, 2]

nums = [5, 6, 1, 2, 3]
dp = [1, 2, 1, 2, 3]
Тогда ответ был бы в dp.Max();</pre>
  <p id="prdS">Определим базовый случай:<br />dp[0] = 1 - если бы у нас был только один элемент он бы и составлял максимальную подпоследовательность размера 1.</p>
  <p id="KvQK">Теперь определимся, как вычислять dp[i].<br />В нашем случае может показаться, что если nums[i] &gt; nums[i - 1],<br />то dp[i] = dp[i - 1] + 1, но это не так.</p>
  <p id="UYDv">возьмем последовательность 5, 6, 7, 1, 2<br />здесь 2 больше 1, а dp[3] == 3 (у нас в dp[3] должна храниться максимальная длина, как если бы у нас было бы только числа [5, 6, 7, 1], т.е. 3).</p>
  <p id="DfBk">то есть, чтобы вычислить dp[i] нам нужно найти длины всех последовательностей, оканчивающейся элементом меньше чем nums[i], и из них выбрать максимальную.</p>
  <p id="IOyp">Заведем второй массив dp2. В dp2[i] будем хранить максимальную длину строговозрастающей последовательности, которая оканчивается элементом nums[i].</p>
  <p id="NhYW">Пока код такой:</p>
  <pre id="KZgf" data-lang="clike">public class Solution {
    public int LengthOfLIS(int[] nums) {
        //dp[i] - длина максимальной строговозрастающей последовательности,
        //если бы у нас было i элементов.
        var dp = new int[nums.Length];
        //dp2[i] - длина максимальной строговозрастающей последовательности,
        //оканчивающийся элементом nums[i].
        var dp2 = new int[nums.Length];
        dp[0] = 1;
        dp2[0] = 1;
        for (var i = 1; i &lt; dp.Length; i++)
        {
            var maxSeq = 1;
            //идём назад и ищем элемент меньше текущего.
            for (var j = i - 1; j &gt;= 0; j--)
            {
                if (nums[j] &lt; nums[i])
                {
                    //нашли. тогда длина строговозврастающей
                    //последовательности - это длина которая оканчивается
                    //элементом nums[j] (то есть значение dp2[j]) плюс 1.
                    maxSeq = Math.Max(maxSeq, dp2[j] + 1);
                }
            }
            dp2[i] = maxSeq;
            dp[i] = Math.Max(dp[i - 1], maxSeq);//максимальная длина
            //оканчивающиейся элементом nums[i], необязательно максимальная
            //длина вообще
            //nums =[5,6,7, 1, 2]
            //dp2 =[1, 2, 3, 1, 2]
            //dp = [1, 2, 3, 3, 3]
        }
        return dp[^1];
    }
}</pre>
  <p id="P3Cl">Если мы посмотрим, то увидим, что из массива dp в итерациях нужен только последний элемент (dp[i] и предыдущий dp[i - 1]).<br />мы можем заменить его на одну переменную. Назовем её макс, а массив dp2<br />переименовать в массив dp.</p>
  <pre id="7kcl" data-lang="clike">public class Solution {
    public int LengthOfLIS(int[] nums) {
        var max = 1;//вместо массива dp
        var dp = new int[nums.Length];//бывший массив dp2
        dp[0] = 1;
        for (var i = 1; i &lt; dp.Length; i++)
        {
            var maxSeq = 1;
            for (var j = i - 1; j &gt;= 0; j--)
            {
                if (nums[j] &lt; nums[i])
                {
                    maxSeq = Math.Max(maxSeq, dp[j] + 1);
                }
            }
            dp[i] = maxSeq;
            max = Math.Max(max, maxSeq);
        }
        return max;
    }
}</pre>
  <p id="9w8R">Если бы мы сразу договорились хранить в dp[i] длину максимальной строговозрастающей последовательности, оканчивающейся элементом nums[i],<br />мы бы могли сразу перейти к такому решению.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@olegtar/gx-e4zehAV5</guid><link>https://teletype.in/@olegtar/gx-e4zehAV5?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar</link><comments>https://teletype.in/@olegtar/gx-e4zehAV5?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=olegtar#comments</comments><dc:creator>olegtar</dc:creator><title>Шаблонный метод</title><pubDate>Mon, 21 Sep 2026 15:49:20 GMT</pubDate><description><![CDATA[Поведенченский паттерн.]]></description><content:encoded><![CDATA[
  <p id="nZrk">Поведенченский паттерн.</p>
  <p id="A5Jz">Определяет скелет алгоритма, позволяющий изменять определенные шаги.</p>
  <p id="NvZA">Определяется абстрактный класс, в нём определяется метод, который вызывает последовательность других виртуальны или абстрактных методов методов.</p>
  <p id="Xhvg">Определяется класс наследник (concrete) от абстрактного, который переопределяет некоторые шаги.</p>
  <p id="wuEQ">Например, классы, который выкачивает данные, обрабатывает и сохраняет.</p>
  <pre id="1yNR" data-lang="clike">public abstract class DataProcessor
{
     public void Process()
     {
         DownloadDAta();
         ParseData();
         SaveData();
         
         if (ShouldSendNotification())
         {
            SendNotification();
         }
     }
     
     //обязательные шаги
     protected abstract void DownloadData();
     protected abstract void ParseData();
     
     //шаг с дефолтной реализацией
     protected virtual void SaveData()
     {
     }
     
     //Хук
     protected virtual bool ShouldSendNotification() =&gt; true;
     
     //неопределяемый
     private void SendNotification()
     {
     }
}</pre>
  <p id="8pk3">Классы-реализации:</p>
  <pre id="1qNk" data-lang="clike">public class JsonDataProcessor : DataProcessor
{
      protected override void DownloadData()
      {
          //загрузка из json
      }
      
      protected override void ParseData()
      {
          //парсинг json
      }
}</pre>
  <p id="J89K">Использование</p>
  <pre id="QQsO" data-lang="clike">DataProcessor processor = new JsonDataProcessor()
processor.Process();</pre>
  <p id="3wP1">Паттерн используют для создания библиотек, позволяющей пользователю определять какие-то шаги.</p>
  <p id="cgPZ">Примеры из стандартных библиотек .NET:</p>
  <p id="qoOC"><strong>BackgroundService </strong>- класс для создания долгосрочных задач.<br />Определяет метод StartAsync с дефолтной реализацией, также определяет чистый виртуальный метод ExecuteAsync, который надо переопределить.</p>
  <p id="GMQh"><strong>IO.Stream</strong> - базовый класс для всех потоков. В нём методы WriteAsync, ReadAsync и т.д. содержат базовую логику проверки параметров, создания асинхронных контекстов и вызова внутренних абстрактных виртуальных методов, которые должны перопределиться в конкретных реализациях FileStream, NetworkStream, MemoryStream и т.д.</p>

]]></content:encoded></item></channel></rss>