java codility training Genomic-range-query
Solution 1:
Here is the solution that got 100 out of 100 in codility.com. Please read about prefix sums to understand the solution:
public static int[] solveGenomicRange(String S, int[] P, int[] Q) {
//used jagged array to hold the prefix sums of each A, C and G genoms
//we don't need to get prefix sums of T, you will see why.
int[][] genoms = new int[3][S.length()+1];
//if the char is found in the index i, then we set it to be 1 else they are 0
//3 short values are needed for this reason
short a, c, g;
for (int i=0; i<S.length(); i++) {
a = 0; c = 0; g = 0;
if ('A' == (S.charAt(i))) {
a=1;
}
if ('C' == (S.charAt(i))) {
c=1;
}
if ('G' == (S.charAt(i))) {
g=1;
}
//here we calculate prefix sums. To learn what's prefix sums look at here https://codility.com/media/train/3-PrefixSums.pdf
genoms[0][i+1] = genoms[0][i] + a;
genoms[1][i+1] = genoms[1][i] + c;
genoms[2][i+1] = genoms[2][i] + g;
}
int[] result = new int[P.length];
//here we go through the provided P[] and Q[] arrays as intervals
for (int i=0; i<P.length; i++) {
int fromIndex = P[i];
//we need to add 1 to Q[i],
//because our genoms[0][0], genoms[1][0] and genoms[2][0]
//have 0 values by default, look above genoms[0][i+1] = genoms[0][i] + a;
int toIndex = Q[i]+1;
if (genoms[0][toIndex] - genoms[0][fromIndex] > 0) {
result[i] = 1;
} else if (genoms[1][toIndex] - genoms[1][fromIndex] > 0) {
result[i] = 2;
} else if (genoms[2][toIndex] - genoms[2][fromIndex] > 0) {
result[i] = 3;
} else {
result[i] = 4;
}
}
return result;
}
Solution 2:
Simple, elegant, domain specific, 100/100 solution in JS with comments!
function solution(S, P, Q) {
var N = S.length, M = P.length;
// dictionary to map nucleotide to impact factor
var impact = {A : 1, C : 2, G : 3, T : 4};
// nucleotide total count in DNA
var currCounter = {A : 0, C : 0, G : 0, T : 0};
// how many times nucleotide repeats at the moment we reach S[i]
var counters = [];
// result
var minImpact = [];
var i;
// count nucleotides
for(i = 0; i <= N; i++) {
counters.push({A: currCounter.A, C: currCounter.C, G: currCounter.G});
currCounter[S[i]]++;
}
// for every query
for(i = 0; i < M; i++) {
var from = P[i], to = Q[i] + 1;
// compare count of A at the start of query with count at the end of equry
// if counter was changed then query contains A
if(counters[to].A - counters[from].A > 0) {
minImpact.push(impact.A);
}
// same things for C and others nucleotides with higher impact factor
else if(counters[to].C - counters[from].C > 0) {
minImpact.push(impact.C);
}
else if(counters[to].G - counters[from].G > 0) {
minImpact.push(impact.G);
}
else { // one of the counters MUST be changed, so its T
minImpact.push(impact.T);
}
}
return minImpact;
}
Solution 3:
Java, 100/100, but with no cumulative/prefix sums! I stashed the last occurrence index of lower 3 nucelotides in a array "map". Later I check if the last index is between P-Q. If so it returns the nuclotide, if not found, it's the top one (T):
class Solution {
int[][] lastOccurrencesMap;
public int[] solution(String S, int[] P, int[] Q) {
int N = S.length();
int M = P.length;
int[] result = new int[M];
lastOccurrencesMap = new int[3][N];
int lastA = -1;
int lastC = -1;
int lastG = -1;
for (int i = 0; i < N; i++) {
char c = S.charAt(i);
if (c == 'A') {
lastA = i;
} else if (c == 'C') {
lastC = i;
} else if (c == 'G') {
lastG = i;
}
lastOccurrencesMap[0][i] = lastA;
lastOccurrencesMap[1][i] = lastC;
lastOccurrencesMap[2][i] = lastG;
}
for (int i = 0; i < M; i++) {
int startIndex = P[i];
int endIndex = Q[i];
int minimum = 4;
for (int n = 0; n < 3; n++) {
int lastOccurence = getLastNucleotideOccurrence(startIndex, endIndex, n);
if (lastOccurence != 0) {
minimum = n + 1;
break;
}
}
result[i] = minimum;
}
return result;
}
int getLastNucleotideOccurrence(int startIndex, int endIndex, int nucleotideIndex) {
int[] lastOccurrences = lastOccurrencesMap[nucleotideIndex];
int endValueLastOccurenceIndex = lastOccurrences[endIndex];
if (endValueLastOccurenceIndex >= startIndex) {
return nucleotideIndex + 1;
} else {
return 0;
}
}
}