Три цвета треугольников - PullRequest
       38

Три цвета треугольников

0 голосов
/ 03 декабря 2018

Я пытаюсь создать код для этой проблемы:

(Источник: https://www.codewars.com/kata/insane-coloured-triangles/train/c)

Цветной треугольник создается из ряда цветов, каждый из которых имеет видкрасный, зеленый или синий. Последовательные строки, каждая из которых содержит на один цвет меньше, чем предыдущая, создаются с учетом двух соприкасающихся цветов в предыдущей строке. Если эти цвета идентичны, в новой строке используется один и тот же цвет.иначе, в новой строке используется отсутствующий цвет, который продолжается до тех пор, пока не будет сгенерирована последняя строка, содержащая только один цвет.

Например, возможны следующие варианты:

Colour here:            G G        B G        R G        B R 
Becomes colour here:     G          R          B          G 

With a bigger example:
   R R G B R G B B  
    R B R G B R B
     G G B R G G
      G R G B G
       B B R R
        B G R
         R B
          G

Вам будет дан первый ряд треугольника в виде строки, и ваша задача - вернуть последний цвет, который будет отображаться в нижнем ряду в виде строки. В случае с приведенным выше примером вам будет дано 'RRGBRGBB',и вы должны вернуть 'G'.

Ограничения:

1 <= length(row) <= 10 ** 5

Входная строка будет содержать только заглавные буквы 'B', 'G' or 'R'.

Точное числоКоличество тестов будет следующим:

100 tests of 100 <= length(row) <= 1000 
100 tests of 1000 <= length(row) <= 10000 
100 tests of 10000 <= length(row) <= 100000

Примеры:

triangle('B') == 'B'
triangle('GB') == 'R'
triangle('RRR') == 'R'
triangle('RGBG') == 'B'
triangle('RBRGBRB') == 'G'
triangle('RBRGBRBGGRRRBGBBBGG') == 'G'

Итак, я создал этот код, и он работает на любой вкус, но когда он приходитдля length of row > 25 это не удается из-за моей факториальной функции, а длина в некоторых случаях составляет до 100,000, поэтому любое предложение по решению этой проблемы, по крайней мере, любая математическая формула, которая может решить проблему, или небольшой намек.

кстати, я сделал этот код после того, как нашел математический способ решения проблемы на этом сайте:

https://math.stackexchange.com/questions/2257158/three-color-triangle-challenge

long long factorial(long long num)     
{
    long long fact = num;

    if (fact == 0)
        fact = 1;

    while (num > 1)
    {
        num--;
        fact *= num;
    }
    return (fact);
}

long long combination(long long n, long long k, long long fact_n)
{
    long long fact_k = factorial(k);
    long long comb = ( fact_n / (fact_k * factorial(n - k)) );
    return (comb);
}

char triangle(const char *row)
{
    long long len = strlen(row);
    long long n = len - 1;
    long long k = 0;
    int sign = pow(-1,n);
    long long sum = 0;
    char *result = "RGB";
    int a;
    long long fact_n = factorial(n);

    while (k < len)                 //This while calculate Segma (∑). 
    {
        if (row[k] == 'R')
            a = 0;
        else if (row[k] == 'G')
            a = 1;
        else if (row[k] == 'B')
            a = 2;

        if (a != 0)
            sum = sum + (combination(n,k,fact_n) * a); 
        k++;
    }

    sum = sign * (sum % 3);

    if (sum == -1 || sum == -2)
        sum += 3;

  return (result[sum]);
}

Ответы [ 2 ]

0 голосов
/ 03 декабря 2018

Я предполагаю, что формула в указанной вами ссылке верна:

enter image description here


Чтобы избежать целочисленного переполнения, мы будемнеобходимо применять эти по модулю арифметических правил :

(a * b) mod c = ((a mod c) * (b mod c)) mod c

(a ± b) mod c = ((a mod c) ± (b mod c)) mod c

Применяя их к формуле:

enter image description here


Но как рассчитать

enter image description here

... без непосредственного расчета самого коэффициента (который может вызвать переполнение)?

Поскольку 3 - простое число, это можно сделать с помощью Теорема Лукаса :

enter image description here

... где n_i, m_i - это i -ые цифры n, m в base-3 .


Преобразование в base-3 легко с целочисленным делением:

// convert a number to base 3
// and returns the number of digits
unsigned conv_base_3(unsigned n, unsigned max, unsigned* out)
{
    unsigned i = 0;
    while (i < max && n > 0)
    {
        out[i] = n % 3;
        n /= 3;
        i++;
    }
    return i;
}

Обратите внимание, что, поскольку n_i, m_i всегда находятся в диапазоне [0, 2] (потому что они являются base-3)цифры), C(n_i, m_i) очень легко вычислить:

// calculate the binomial coefficient for n < 3
unsigned binom_max_2(unsigned n, unsigned k)
{
    if (n < k)
        return 0;
    switch (n)
    {
        case 0:
        case 1:
            return 1;
        case 2:
            return 1 + (k == 1);

        // shouldn't happen
        default:
            return 0;
    }
}

А теперь сама теорема:

// Lucas's theorem for p = 3
unsigned lucas_3(
    unsigned len_n, const unsigned * dig_n,
    unsigned len_k, const unsigned * dig_k
)
{
    // use modulo product rule:
    // prod[i] % 3 = ((prod[i - 1] % 3) * value[i])      
    unsigned prod = 1;
    for (unsigned i = 0; i < len_n; i++) {
        unsigned n_i = dig_n[i];
        unsigned k_i = (i < len_k) ? dig_k[i] : 0;
        prod = (prod * binom_max_2(n_i, k_i)) % 3;
    }
    return prod % 3;
}

Преобразование символов:

// convert from 012 to RGB
char int_2_char(int i)
{
    switch (i) {
        case 0: return 'R';
        case 1: return 'G';
        case 2: return 'B';

        // shouldn't happen
        default:
            return '\0';
    }
}

// convert from RGB to 012
unsigned char_2_int(char c)
{
    switch (c) {
        case 'R': return 0;
        case 'G': return 1;
        case 'B': return 2;

        // shouldn't happen
        default:
            return 3;
    }
}

Наконец,алгоритм треугольника:

// the problem constraints state that n <= 10 ** 5
// max number of base-3 digits
#define MAX_N_LOG_3 11

// main algorithm function
char triangle(const char * input)
{
    unsigned sum = 0;
    const int n = strlen(input);

    // calculate digits of n - 1
    unsigned dig_n[MAX_N_LOG_3];
    unsigned len_n = conv_base_3(n - 1, MAX_N_LOG_3, dig_n);

    for (unsigned km1 = 0; km1 < n; km1++)
    {
        // calculate digits of k - 1
        unsigned dig_k[MAX_N_LOG_3];
        unsigned len_k = conv_base_3(km1, MAX_N_LOG_3, dig_k);

        // calculate C(n - 1, k - 1) mod 3
        unsigned Cnk_mod3 = lucas_3(len_n, dig_n, len_k, dig_k);

        // add using the modulo rule
        sum = (sum + Cnk_mod3 * char_2_int(input[km1])) % 3;
    }

    // value of (-1) ** (n - 1)
    // (no need for pow; just need to know if n is odd or even)
    int sign = (n % 2) * 2 - 1;

    // for negative numbers, must resolve the difference
    // between C's % operator and mathematical mod
    int sum_mod3 = (3 + (sign * (int)(sum % 3)) % 3;
    return int_2_char(sum_mod3);
}

Приведенный выше код проходит все тесты;обратите внимание, что он был написан в пользу ясности, а не производительности.


Так почему же этот код смог пройти все тесты в назначенное время, тогда как простой табличный подход не был?Из-за сложности времени :

  • Подход на основе таблиц обрабатывает все уровни треугольника, который равен O(n^2) (см. Треугольные числа ).

  • Конечно, используя алгоритм Лукаса, нужно обрабатывать только верхний уровень;однако сам алгоритм равен O(log n), поскольку он проходит через каждую цифру n (независимо от базы).Общая сложность составляет O(n log n), что все еще представляет собой значительное улучшение.

0 голосов
/ 03 декабря 2018

Рекурсия здесь бесполезна (a) , поскольку вам никогда не придется возвращаться в предыдущее состояние.Итерация является лучшим решением и может быть выполнена довольно просто.

Сначала получите исходную строку цветов и создайте еще одну с тем же размером.

Затем для каждого набора из двух символов (перекрытия) в оригинале установите эквивалентный символ в новой строке в соответствии с вашими правилами.Наконец, после того, как новая строка полностью сформирована (на один символ короче оригинала), скопируйте ее обратно в оригинал и, если длина не превышает одного символа, продолжайте (вернитесь к началу этого абзаца).

Вот пример кода, который показывает это в действии:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Helper function to output nicely spaced string.

static void output(int gap, char *str) {
    // Spaces at line start.

    for (int i = 0; i < gap; ++i)
        putchar(' ');

    // Actual string with spaces between letters, end line following.

    for (int i = 0, len = strlen(str); i < len; ++i)
        printf(" %c", str[i]);
    putchar('\n');
}

// The meat of the solution.

int main(int argc, char *argv[]) {
    // Check parameter count and content.

    if (argc != 2) {
        fprintf(stderr, "*** Needs one argument\n");
        return 1;
    }

    for (int i = 0, len = strlen(argv[1]); i < len; ++i) {
        if (strchr("RGB", argv[1][i]) == NULL) {
            fprintf(stderr, "*** Bad character '%c' found\n", argv[1][i]);
            return 1;
        }
    }

    // Allocate strings with check, prepare first.

    char *first = malloc(strlen(argv[1]) + 1);
    char *second = malloc(strlen(argv[1]) + 1);
    if (first == NULL || second == NULL) {
        fprintf(stderr, "*** Memory allocation error\n");
        free (first);
        free (second);
        return 1;
    }
    strcpy(first, argv[1]);

    // Continue until you have a short enough string.

    int spaces = 0;
    while (strlen(first) > 1) {
        // Output current string, update spaces for next.

        output(spaces++, first);

        // Process each overlapping pair in string.

        for (int i = 0, len = strlen(first); i < len - 1; ++i) {
            // Select what goes in new string, based on pair.

            char newCh = '?';

            if (first[i] == 'R' && first[i+1] == 'R') newCh = 'R';
            if (first[i] == 'G' && first[i+1] == 'G') newCh = 'G';
            if (first[i] == 'B' && first[i+1] == 'B') newCh = 'B';

            if (first[i] == 'G' && first[i+1] == 'R') newCh = 'B';
            if (first[i] == 'R' && first[i+1] == 'G') newCh = 'B';

            if (first[i] == 'B' && first[i+1] == 'G') newCh = 'R';
            if (first[i] == 'G' && first[i+1] == 'B') newCh = 'R';

            if (first[i] == 'B' && first[i+1] == 'R') newCh = 'G';
            if (first[i] == 'R' && first[i+1] == 'B') newCh = 'G';

            // Sanity check, should never be true.

            if (newCh == '?') {
                fprintf(stderr, "*** Bad data %c/%c\n", first[i], first[i+1]);
                free (first);
                free (second);
                return 1;
            }

            // Place in new string and add terminator.

            second[i] = newCh;
            second[i+1] = '\0';
        }

        // Transfer second back to first for next cycle.

        strcpy(first, second);
    }

    // Output final one-character string, clean up.

    output(spaces, first);

    free (first);
    free (second);
    return 0;
}

Вывод этого кода при запуске с аргументом RRGBRGBB, как и ожидалось:

 R R G B R G B B
  R B R G B R B
   G G B R G G
    G R G B G
     B B R R
      B G R
       R B
        G

Это также работаетс:

RRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBB

, что на существенно длиннее 25 символов, но я не буду отображать вывод, поскольку он ужасно широк: -)


(a) Честно говоря (и я не пытаюсь здесь излишне критиковать), все использование математики / факториалов также сомнительно.Я не уверен, почему вы подумали, что это необходимо для решения проблемы обработки текста.

...