Подтвердить что ты не робот

В тупике на Java-интервью, нужны некоторые подсказки

Это проблема интервью, на которую я застрял:

Для строки, состоящей из a, b и c, мы можем выполнить следующую операцию: Возьмите любые два смежных отдельных символа и замените их третьим символом. Например, если "a" и "c" смежны, их можно заменить на "b". Какая наименьшая строка может возникнуть при повторном применении этой операции?

Мое решение:

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;

public class Solution {
    public static void main(String[] args) {
        try {
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    System.in));

            System.out.println(solve(in.readLine()));

            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static int solve(String testCase) {
        LinkedList<String> temp = new LinkedList<String>(deconstruct(testCase));

        for (int i = 0; i < (temp.size() - 1); i++) {
            if (!temp.get(i).equals(temp.get(i + 1))) {
                temp.add(i, getThirdChar(temp.remove(), temp.remove()));
                i = -1;
            }
        }

        return reconstruct(temp).length();
    }

    private static List<String> deconstruct(String testCase) {
        List<String> temp = new LinkedList<String>();

        for (int i = 0; i < testCase.length(); i++) {
            temp.add(testCase.charAt(i) + "");
        }

        return temp;
    }

    private static String reconstruct(List<String> temp) {
        String testCase = "";

        for (int i = 0; i < temp.size(); i++) {
            testCase += temp.get(i);
        }

        return testCase;
    }

    private static String getThirdChar(String firstChar, String secondChar) {
        return "abc".replaceAll("[" + firstChar + secondChar + "]+", "");
    }
}

Код, похоже, отлично работает на тестовых входах "cab" (печатает "2" ), "bcab" (печатает "1" ) и "ccccc" (печатает "5" ). Но мне все время говорят, что мой код ошибочен. Может ли кто-нибудь помочь мне выяснить, где ошибка?

4b9b3361

Ответ 1

Как уже отмечалось, ошибка заключается в том, что ваш алгоритм делает замены в предопределенном порядке. Ваш алгоритм сделает преобразование:

abcc --> ccc вместо abcc --> aac --> ab --> c

Если вы хотите использовать технику генерации сокращенных строк, вам необходимо либо:

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

Если вам нужна только длина уменьшенной строки, тем не менее, гораздо проще которая не требует создания сокращенных строк. Это расширенная версия ответа @Matteo, с некоторыми подробностями и работающим (очень упрощенным) алгоритмом.

Простые свойства

Я утверждаю, что следующие три свойства верны в отношении abc-строк при заданном наборе правил.

  • Если дальнейшее сокращение строки невозможно, все символы в этой строке должны быть одного и того же символа.

  • Невозможно, чтобы: 2 < answer < string.length истинно

  • При выполнении операции уменьшения, если количество каждой буквы до операции равно, количество каждой буквы после операции будет нечетным. И наоборот, если подсчет каждой буквы нечетный до операции, подсчеты будут даже после операции.

Свойство 1

Свойство тривиально.

Свойство 2 (пример для иллюстрации)

Предположим: мы имеем приведенную строку длины 5, которую можно уменьшить не более.

AAAAA

Поскольку эта строка является результатом операции сокращения, предыдущая строка должна содержать один B и один C. Ниже приведены некоторые примеры возможных "родительских строк":

BCAAAA, AABCAA, AAACBA

Для всех возможных родительских строк мы можем легко увидеть, что по крайней мере один из C: s и B: s можно комбинировать с A: s вместо друг друга. Это приведет к тому, что строка длиной 5 будет уменьшаться. Следовательно, мы проиллюстрировали, что единственной причиной, по которой у нас была неприводимая строка длины 5, было то, что мы сделали неправильный выбор того, какие символы объединяться при выполнении операции сокращения.

Это рассуждение применимо для всех приведенных строк любой длины k таких, что 2 < k < string.length.

Свойство 3 (пример для иллюстрации)

Если мы имеем, например, [numA, numB, numC] = [even, even, even] и выполняем операцию сокращения, в которой мы заменяем AB на C. Счетчик A и B будет уменьшаться на единицу, делая подсчеты нечетными, а счетчик C увеличится на единицу, что делает этот подсчет нечетным.

Аналогично этому, если два отсчета четные, а один - нечетный, два счета будут нечетными и одно даже после операции и наоборот.

Другими словами, если все три счета имеют одинаковую "равномерность", никакая операция сокращения не может изменить это. И, если есть различия в "четности" счетчиков, никакая операция сокращения не может изменить это.

Выполняя выводы, вытекающие из свойств

Рассмотрим две неприводимые строки:

A и AA

Для A обратите внимание, что [numA, numB, numC] = [odd, even, even] Для AA обратите внимание, что [numA, numB, numC] = [even, even, even]

Теперь забудьте эти две строки и предположим, что нам дана входная строка длины n.

Если все символы в строке равны, ответ, очевидно, string.length.

В противном случае мы знаем из свойства 2, что можно уменьшить строку до длины меньше 3. Мы также знаем влияние на равномерность выполнения операций сокращения. Если входная строка содержит четные числа всех букв или нечетное количество всех букв, невозможно свести ее к одной буквенной строке, так как невозможно изменить структуру четности от [even, even, even] до [odd, even, even], выполнив операцию сокращения.

Следовательно, более простой алгоритм был бы следующим:

Алгоритм

Count the number of occurences of each letter in the input string [numA, numB, numC]

If two of these counts are 0, then return string.length

Else if (all counts are even) or (all counts are odd), then return 2

Else, then return 1

Ответ 2

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

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

Каждая строка может быть уменьшена до тех пор, пока она содержит более двух отдельных букв. Каждый раз, когда строка уменьшается, 1 из каждого из отдельных букв уходит, а буква третьего типа добавляется к строке. Это дает нам рекуррентное соотношение. Пусть f(a,b,c) - длина наименьшей строки, заданной a буквы "a", b буквы "b" и c буквы "c" во входной строке, тогда

f(a,b,c) = min(f(a-1,b-1,c+1), f(a-1,b+1,c-1), f(a+1,b-1,c-1));

поскольку есть три возможности, когда мы уменьшаем строку. Конечно, каждое рекуррентное отношение подчиняется некоторым начальным условиям. В этом случае имеем

if(a < 0 || b < 0 || c < 0)
    return MAX_SIZE+1;
if(a == 0 && b == 0 && c == 0)
    return 0;
if(a != 0 && b == 0 && c == 0)
    return a;
if(a == 0 && b != 0 && c == 0)
    return b;
if(a == 0 && b == 0 && c != 0)
    return c;

здесь MAX_SIZE - максимальное количество заданной буквы в задаче HackerRank. В любое время, когда мы закончили заданную букву, максимальный размер возвращается, чтобы указать, что это сокращение строки недействительно. Затем мы можем вычислить размер наименьшей приведенной строки, используя эти начальные условия и рекуррентное соотношение.

Однако это все равно не пройдет тестовые примеры HackerRank. Кроме того, это вызывает слишком много повторных вычислений. Поэтому мы хотим кэшировать вычисленный результат, учитывая кортеж (a,b,c). Тот факт, что мы можем кэшировать результат, связан с тем, что порядок букв не меняет ответа, так как многие из вышеперечисленных сообщений доказали.

Мое решение опубликовано ниже

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

#define MAX_SIZE 101

int cache[MAX_SIZE][MAX_SIZE][MAX_SIZE];

void init_cache() {
    for(int i = 0 ; i < MAX_SIZE; i++) {
        for (int j = 0; j < MAX_SIZE; j++) {
            for(int k = 0; k < MAX_SIZE; k++)
                cache[i][j][k] = -1;
        }
    }
}

void count(char* array, int* a, int* b, int* c) {
    int len = strlen(array);

    for(int i = 0; i < len; i++) {
        if(array[i] == 'a')
            (*a)++;
        else if(array[i] == 'b')
            (*b)++;
        else
            (*c)++;
    }
}

int solve(int a, int b, int c) {
    if(a < 0 || b < 0 || c < 0)
        return MAX_SIZE+1;
    if(a == 0 && b == 0 && c == 0)
        return 0;
    if(a != 0 && b == 0 && c == 0)
        return a;
    if(a == 0 && b != 0 && c == 0)
        return b;
    if(a == 0 && b == 0 && c != 0)
        return c;
    if(cache[a][b][c] != -1) {
        return cache[a][b][c];
    }
    int ci = solve(a-1, b-1, c+1);
    int bi = solve(a-1, b+1, c-1);
    int ai = solve(a+1, b-1, c-1);
    if(a > 0 && b > 0)
        cache[a-1][b-1][c+1] = ci;
    if(a > 0 && c > 0)
        cache[a-1][b+1][c-1] = bi;
    if(b > 0 && c > 0)
        cache[a+1][b-1][c-1] = ai;
    return ci < bi ? (ci < ai ? ci : ai) : (ai < bi ? ai : bi);
}

int main() {
    int res, T, i;
    scanf("%d", &T);
    assert(T<=100);

    char arr[100001];

    init_cache();

    for(i = 0; i < T; i++) {
        scanf("%s",arr);

        int a = 0;
        int b = 0;
        int c = 0;
        count(arr, &a, &b, &c);

        int len = solve(a, b, c);
        printf("%d\n", len);  
    }

    return 0;
}

Ответ 3

На мой взгляд, ответом является число (или программа, которая генерирует число), а не программа, которая применяет описанное преобразование.

По этой причине (если строка не пуста) ответ будет 1 с несколькими входами.

Однако, если вход состоит из одного символа, повторяемого несколько раз, строка не может быть разработана, поэтому выходная строка будет такой же, как и входная строка (т.е. такая же длина).

Обратите внимание, что входная строка должна состоять из одного символа; если он имеет два символа, выход будет равен 1: baaaa → caaa → baa → ca → b

Обратите внимание, что последовательность замещений не указана (если доступно более 1 замены). Следовательно, мы не можем сказать намного больше, но мы можем заметить, что некоторые строки, не состоящие из отдельных символов, не могут быть сведены к строке длины 1. Это так, когда все три буквы появляются последовательно (например, abc). Когда эта строка обрабатывается, вывод будет состоять из двух одинаковых символов (например, cc или aa), которые не могут быть уменьшены еще больше.

Ответ 4

Изменить. Для удовольствия я сделал свою собственную версию, которая работает с char[] на месте:

public class Main {

    public static void main(String[] args) {
        System.out.println(solve("abbccaacba"));
    }

    private static int solve(String testCase) {
        if (!testCase.matches("^[abc]*$"))
            throw new IllegalArgumentException("invalid input");

        char[] chars = new char[testCase.length()];
        testCase.getChars(0, testCase.length(), chars, 0);

        int remaining = chars.length;

        for (int i=0; (i<chars.length) && (remaining>1);)
        {
            int next = i+1;
            while (next < chars.length && (' '==chars[next]))
                ++next;

            if (next >= chars.length)
                break;

            if (chars[i]!=chars[next])
            {
                switch (chars[i])
                {
                    case 'a': chars[next] = ('b'==chars[next])? 'c':'b'; break;
                    case 'b': chars[next] = ('a'==chars[next])? 'c':'a'; break;
                    case 'c': chars[next] = ('b'==chars[next])? 'a':'b'; break;
                }
                chars[i] = ' '; // mark as removed
                remaining--;

                while (i>0 && (' '!=chars[i-1]))
                    --i;
                if (' '==chars[i])
                    i = next;
            }
            else
                ++i;
        }

        return remaining;
    }
}

Посмотрите на live http://ideone.com/yhK9t, с отладочным результатом:

a<bbccaacba
 c<bccaacba
  a<ccaacba
   b<caacba
    a<aacba
    aa<acba
    aaa<cba
    a<a bba
    aa< bba
    a<  cba
       b<ba
       bb<a
       b< c
         a<
         a Done.
1

Все-таки обратите внимание на предостережения, о которых я упомянул в своих комментариях: РЕДАКТИРОВАТЬ. Да, я как-то сокрушил комментарий, говорящий, что ответы будут меняться в зависимости от порядка подстановок

  • слева направо или справа налево (моя версия использует слева направо)
  • глубину-сначала (или ширину) (моя версия сначала использует глубину)

Ответ 5

Ваше использование LinkedList является интересным (и потенциально неожиданным), но некоторые из других аспектов немного странны и отвлекают...

Мой первый инстинкт состоял бы в том, чтобы многократно перебирать String, заменяя символы на StringBuilder - с циклом while, окружающим петлю foor (как было предложено sehe). Это может быть то, что ожидал интервьюер, а не ваше умное использование LinkedList.

Интервьюер может быть отвлечен этими другими аспектами. например:.

  • почему бы не использовать LinkedList<Character> вместо LinkedList<String>.
  • почему бы не вернуть a LinkedList непосредственно из deconstruct(String). Не нужно обертывать его.
  • Вам не нужен метод reconstruct(). Просто используйте temp.size()
  • Regex - это немного нежелательный способ получить третий char. Я не могу придумать один-лайнер, но вы можете использовать массив, например:

    private static Character getThirdChar(Character firstChar, Character secondChar) {
        List<Character> li= new ArrayList<Character>(Arrays.asList('a', 'b', 'c'));
        li.remove(firstChar);
        li.remove(secondChar); 
        return li.get(0);
    }
    

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

РЕДАКТИРОВАТЬ: возможно, вопрос заключается в том, чтобы попросить вернуть самую маленькую строку, а не ее длину. Я думаю, что последняя строка вопроса интервью должна выглядеть следующим образом: "Для данной строки верните наименьшую строку, которая может быть получена путем повторного применения этой операции"

НТН

Ответ 6

Это один из примеров вопросов InteveStreet. Я попытался решить это на Java, и мой код дает ожидаемые результаты для трех приведенных тестовых случаев, но затем проходит только один из десяти реальных тестовых случаев. Я очень ржав в программировании и на Java, но это то, что я придумал, чтобы решить три приведенных примера...

Я не могу правильно форматировать код...

import java.io.*;
import java.util.Scanner;
public class Solution
{
    public String transform(String input)
    {
        String output = new String(input);
        // System.out.println(input);
        if(output.length() > 1)
        {
            int i = 0;
            while (i < (output.length() - 1))
            {
                if(output.charAt(i) != output.charAt(i+1))
                {
                    StringBuffer sb = new StringBuffer();
                    if (output.charAt(i) == 'a')
                    {
                        // next character is b or c
                        if (output.charAt(i+1) == 'b')
                        {
                            sb.append('c');
                        }
                        else
                        {
                            sb.append('b');
                        }
                    }
                    else if (output.charAt(i) == 'b')
                    {
                        // next character is a or c
                        if (output.charAt(i+1) == 'a')
                        {
                            sb.append('c');
                        }
                        else
                        {
                            sb.append('a');
                        }
                    }
                    else
                    {
                        // next character is a or b
                        if (output.charAt(i+1) == 'a')
                        {
                            sb.append('b');
                        }
                        else
                        {
                            sb.append('a');
                        }
                    }
                    String remainder = output.substring(i+2);
                    if (remainder.length() > 0)
                    {
                        sb.append(remainder);
                    }
                    Solution anotherAnswer = new Solution();
                    output = anotherAnswer.transform(sb.toString());

                } 
                i++;
            }
        }

        return output;
    }


    /**
     * @param args
     * @throws Exception 
     */
    public static void main(String[] args) throws Exception
    {
        // TODO Auto-generated method stub
        Scanner sc;
        try
        {
            sc = new Scanner(new BufferedReader(new FileReader("input.txt")));
            // sc = new Scanner(System.in);
            int numberOfTests = sc.nextInt();
            for (int i = 1; i <= numberOfTests; i++)
            {
                String testData = sc.next();
                Solution answer = new Solution();
                String transformedData = answer.transform(testData);
                System.out.println(transformedData.length());
            }
        }
        catch (Exception e) 
        {
            throw new Exception(e);
        }
    }

}

Ответ 7

Сравнение шаблонов в Scala упрощает выбор списков таких элементов. Чтобы уменьшить строку:

  def reduce[T](xs: List[T]): List[T] = {
    def reduceImpl(xs1: List[T], accumulator: List[T] = List.empty): List[T] = {
      xs1 match {
        case w :: x :: y :: tail if (w != x) =>
          if (w != x) reduceImpl(y :: tail, accumulator)
          else reduceImpl(x :: y :: tail, w :: accumulator)
        case remainder =>
          remainder.reverse ::: accumulator
      }
    }
    reduceImpl(xs).reverse
  }

И тогда вы можете просто называть длину результата.

И да, я бы дал этот ответ на вопрос Java. В 99 случаях из 100 интервьюеру было все равно, какой язык вы используете для разговора о коде, и использование таких вещей, как Scala, Clojure, F # и т.д. - это правильный путь. (Или, если нет, это правильный способ выяснить, что вы на самом деле не хотите там работать.)

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

Ответ 8

import java.io.BufferedReader; 
import java.io.InputStreamReader;

class Solution {

    public static void main(String args[]) throws Exception {   

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        int T = Integer.parseInt(in.readLine());

        for (int i = 0; i < T; i++) {
            String line = in.readLine();
            System.out.println(solve(line));
        }
    }

    public static int[] countOccurrences(String input) {

        int r[] = new int[3];
        char inputArr[] = input.toCharArray();

        for (int i = 0; i < inputArr.length; i++) {

            if (inputArr[i] == 'a') {
                r[0]++;
            } 
            else if (inputArr[i] == 'b') {
                r[1]++;
            }
            else if(inputArr[i] == 'c') {
                r[2]++;
            }
        }
        return r;
    }

    private static int solve(String input) {
        int num[] = countOccurrences(input);

        if ((num[0]==0 && num[1]==0 )||(num[0]==0 && num[2]==0 )||(num[1]==0 && num[2]==0 )) {
            return input.length();
        }
        if((num[0]%2==0 && num[1]%2==0 && num[2]%2==0)||(num[0]%2==1 && num[1]%2==1 && num[2]%2==1)) {
            return 2;
        }

        return 1;
    }

}