Разбор задачи 89. Gray Code с Литкода
https://leetcode.com/problems/gray-code/description/
Условие
Последовательность n-битного кода Грея — это последовательность из 2ⁿ целых чисел, в которой:
- Каждое числое в диапазоне
[0, 2n - 1]включительно - Первое число
0, - Каждое целое число встречается в последовательности не более одного раза,
- Двоичное представление каждой пары соседних целых чисел отличается ровно на один бит, и
- Двоичное представление первого и последнего целых чисел отличается ровно на один бит.
Дано число n, верните любую допустимую последовательность n-битного кода Грея.
Ввод: 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 отличается на один бит
Ввод: n = 1 Вывод: [0,1]
Решение
Если n = 3, то у нас числа от 0 до 111 в двоичном представлении.
Подготовим переменную для результатаvar result = new List<int>();
Подготовим переменную для 2^n:var max = (1 << n);
Подготовим массив для определения, использовано ли число ранее:var used = new bool[max];
Индексы этого массива числа от 0 до max - 1, т.е. от 0 до 2^n - 1. Если по индексу, например, 5 стоит false, то это значит число 5 ещё не было в последовательности, если true, то было, и мы должны его пропустить. По умолчанию все значения false.
Положим в результат число 0, и сделаем отметку, что оно заюзано.
used[0] = true; result.Add(0);
Далее мы будем идти по оставшимся числам:for (var j = 1; j < max; j++)
Как нам найти следующее число?
Мы должны изменить один бит в последнем числе, после этого мы получим потенциально новое число. Мы должны проверить по массиву used, оно использовано ранее или нет. Если да, то его пропускаем и меняем другой бит.
Положим последнее число в переменную num (сначала это 0).var num = 0;
Проходим по всем битам этого числаfor (var i = 0; i < n; i++)
Делаем маску из одного бита на месте i и всех нулей, т.е. 001b, 010b, 100b и т.д.(1 << i)
Битово умножаем на эту маску последнее число:var bit = num & (1 << i);
Если полученный результат больше 0, то значит на i'м месте у нас 1, и надо поменять его на 0.
Если результат 0, то значит на i'м месте у нас 0, и надо поменять его на 1.
с помощью xor c 1 мы можем поменять 1 на 0.
1 ^ 1 = 0
С помощью битового or, мы меняем 0 на 1:
0 | 1 = 1
Пока код получается таким:
var num = 0;//последнее число
for (var j = 1; j < max; j++)
{
for (var i = 0; i < n; i++)
{
var bit = (num & (1 << i));
var nextNumber = num;
if (bit > 0)
{
nextNumber ^= 1 << i;
}
else
{
nextNumber |= 1 << i;
}
}Теперь надо проверить заюзано ли число:
Если нет, то надо добавить его в результат, а в num положить последнее число.
if (!used[nextNumber])
{
result.Add(nextNumber);//добавляем в результат
used[nextNumber] = true;//число заюзано, больше его не повторять
num = nextNumber;//новое последнее число
break;//больше не меняем биты
}public class Solution {
public IList<int> GrayCode(int n) {
var result = new List<int>();
var max = (1 << n);
var used = new bool[max];
used[0] = true;
result.Add(0);
var num = 0;
for (var j = 1; j < max; j++)
{
for (var i = 0; i < n; i++)
{
var bit = (num & (1 << i));
var nextNumber = num;
if (bit > 0)
{
nextNumber ^= 1 << i;
}
else
{
nextNumber |= 1 << i;
}
if (!used[nextNumber])
{
result.Add(nextNumber);
used[nextNumber] = true;
num = nextNumber;
break;
}
}
}
return result;
}
}Да, есть короткое решение, если знаешь особенности кода Грея.
public class Solution {
public IList<int> GrayCode(int n)
{
int count = 1 << n; // 2^n
var result = new int[count];
for (int i = 0; i < count; i++)
{
result[i] = i ^ (i >> 1);
}
return result;
}
}Но о нём вряд ли догадаешься сам, если не знаешь. Особенно на собесе.