This is an interview problem that I am stuck on:
Given a string consisting of a, b and c\'s, we can perform the following operation: Take any two adjacent
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;
}
}
Your use of LinkedList is interesting (and potentially unexpected), but some of the other aspects are a bit strange & distracting ...
My first instinct would have been to repeatedly loop over the String, replacing characters into a StringBuilder
- with a while loop surrounding the foor loop (as suggested by sehe). This may be what the interviewer was expecting, rather than your clever use of LinkedList.
The interviewer may be distracted by these other aspects. e.g.:
LinkedList<Character>
instead of LinkedList<String>
.LinkedList
directly from deconstruct(String)
. No need to wrap it.reconstruct()
method. Just use temp.size()
Regex is a bit of an undesirable way to get the third char. I can't think of a one-liner, but you could use an array, like so:
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);
}
After these edits, the interviewer might be able to focus more clearly on your very interesting solution.
EDIT: perhaps the question is asking you to return the smallest string itself, not its length. I think last line of the interview question ought to read as follows: "For the given string, return the smallest string which can result by applying this operation repeatedly"
HTH
As people have already pointed out the error is that your algorithm makes the substitutions in a predefined order. Your algorithm would make the transformation:
abcc --> ccc
instead of
abcc --> aac --> ab --> c
If you want to use the technique of generating the reduced strings, you need to either:
If all you need is the length of the reduced string, there is however a much simpler implementation which does not require the reduced strings to be generated. This is an extended version of @Matteo's answer, with some more details and a working (very simplistic) algorithm.
I postulate that the following three properties are true about abc-strings under the given set of rules.
If it is impossible to reduce a string further, all the characters in that string must be the same character.
It is impossible that: 2 < answer < string.length
is true
While performing a reduction operation, if the counts of each letter prior to the operation is even, the count of each letter after the operation will be odd. Conversely, if the counts of each letter is odd prior to the operation, the counts will be even after the operation.
Property one is trivial.
Assume: we have a reduced string of length 5 which can be reduced no more.
AAAAA
As this string is the result of a reduction operation, the previous string must've contained one B
and one C
. Following are some examples of possible "parent strings":
BCAAAA
, AABCAA
, AAACBA
For all of the possible parent strings we can easily see that at least one of the C:s and the B:s can be combined with A:s instead of each other. This will result in a string of length 5 which will be further reducible. Hence, we have illustrated that the only reason for which we had an irreducible string of length 5 was that we had made incorrect choice of which characters to combine while performing the reduction operation.
This reasoning applies for all reduced strings of any length k such that 2 < k < string.length
.
If we have for example [numA, numB, numC] = [even, even, even]
and perform a reduction operation in which we substitute AB with a C. The count of A and B will decrease by one, making the counts odd, while the count of C will increase by one, making that count odd as well.
Similarly to this, if two counts are even and one is odd, two counts will be odd and one even after the operation and vice versa.
In other words, if all three counts have the same "evenness", no reduction operation can change that. And, if there are differences in the "evenness" of the counts, no reduction operation can change that.
Consider the two irreducible strings:
A
and AA
For A
notice that [numA, numB, numC] = [odd, even, even]
For AA
notice that [numA, numB, numC] = [even, even, even]
Now forget those two strings and assume we are given an input string of length n.
If all characters in the string are equal, the answer is obviously string.length.
Else, we know from property 2 that it is possible to reduce the string to a length smaller than 3. We also know the effect on evenness of performing reduction operations. If the input string contains even counts of all letters or odd count of all letters, it is impossible to reduce it to a single letter string, since it is impossible to change the evenness structure from [even, even, even]
to [odd, even, even]
by performing reduction operation.
Hence a simpler algorithm would be as follows:
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
This problem also appears in HackerRank as an introduction to Dynamic Programming. Even though there are nice close-form solution as many posters have already suggested, I find it helpful to still work it out using the good-old dynamic programming way. i.e. find a good recurrence relation and cache intermediate results to avoid unnecessary computations.
As some people have already noted, the brute-force method of iterating through consecutive letters of the input string and all the resulting reduced strings will not work when input string is long. Such solution will only pass one test-case on HackerRank. Storing all reduced strings are also not feasible as the number of such string can grow exponentially. I benefit from some people's comments that the order of the letters does not matter, and only the numbers of each letter matter.
Each string can be reduced as long as it has more than one of two distinct letters. Each time a string is reduced, 1 of each of the distinct letters goes away and a letter of the third kind is added to the string. This gives us an recurrence relation. Let f(a,b,c)
be the length of the smallest string given a
of the letter 'a', b
of the letter 'b', and c
of the letter 'c' in the input string, then
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));
since there are three possibilities when we reduce a string. Of course, every recurrence relation is subject to some initial conditions. In this case, we have
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;
here MAX_SIZE
is the maximum number of a given letter in the HackerRank problem. Anytime we run out of a given letter, the maximum size is returned to indicate that this string reduction is invalid. We can then compute the size of the smallest reduced string using these initial conditions and the recurrence relation.
However, this will still not pass the HackerRank test cases. Also, this incurs too many repeated calculations. Therefore, we want to cache the computed result given the tuple (a,b,c)
. The fact that we can cache the result is due to the fact that the order of the letters does not change the answer, as many of the posts above have proved.
My solution is posted below
#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;
}
Pattern matching in Scala makes it easier to pick apart lists of items like this. To reduce the string:
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
}
And then you can just call length on the result.
And yes, I'd give this answer for a Java question. 99 times out of 100 the interviewer couldn't care less what language you use to talk about code, and using things like Scala, Clojure, F# etc are the right way to go. (Or if not, they're the right way to figure out you don't actually want to work there.)
(And also yes, the people who said the answer is a number are correct. However, like everyone else said, this is more about an interview question, and it's probably correct to say that what they asked isn't what they wanted to ask. It's a (small) bad sign for working there if this is a standard question they use more than once. If they're using this question in an automated screen, that's really not a good sign.)
This is one of the sample questions on InteviewStreet. I tried solving it in Java and my code produces the expected results for the three test cases given, but then only passes one of the ten actual test cases. I'm a lot rusty in programming and in Java but this is what I came up with to solve the three given examples...
I can't get the code formatting just right...
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);
}
}
}