2014年7月24日木曜日

Lesson 3: CountDiv (Count Div)

https://codility.com/programmers/lessons/3
Lesson 3: CountDivの解答がこちらになります。

これも算数の問題ですね。


















上の図のようになりますので

A % K == 0 なら (B - A) / K + 1
A % K != 0 なら (B - (A - A % K)) / K

が答えです。









----------------------------------------
SOLUTION
----------------------------------------
int solution(int A, int B, int K) 
{
    if (A % K == 0){
        return (B - A) / K + 1;
    }

    return (B - (A - A % K)) / K;
}

2014年7月23日水曜日

Lesson 3: MinAvgTwoSlice (Min Avg Two Slice)

https://codility.com/programmers/lessons/3
Lesson 3: MinAvgTwoSliceの解答がこちらになります。

基本、数学の問題だそうです。

Codilityの例題PDFにかかれたprefix sumのアルゴリズムの工夫だけでとけるのかな?と思って、ずっと考えてたのですがどうやら違いそうだという事でググってしまいました。

とりあえず下に色々みてわかった簡単な説明を。

まず、一番小さい平均を出す連続した配列のスロットは2つか3つのスロットを見ればわかるということです。

例えば、4つの大きさの連続した配列のスロットが一番小さい平均を出すと仮定してみましょう。これは2+2の二つに分割できますよね。そうすると、前者か後者の2の平均がもし、全体の4つの平均より小さかった場合、仮定に反しますね。もし、大きかったらどうなるでしょう?もう一方のペアのほうの平均がもとの4つの平均より小さくなりますね。だから、2+2に分割しても同じなのです。これは例えば5つのスロットを2+3に分解しても同じ事です。

もっと一般化できますよね。適当なサイズの連続した配列のスロット中の値の平均が一番小さいと仮定した場合、先頭部分から2つか3つのペアをとってそれより小さいものが出来ると、最初の仮定に違反します。どちらにしろ、同じ平均を出せる部分が切り出せるということになりますから、2つか3つの平均だけを先頭から見て言って、一番小さいもので最初に現れた部分のインデックスを返せば良いという事です

これで100%とれました。









int solution(int A[], int N) 
{
    int i = 0;
    
    if (N == 2){
        return 0;
    }

    //initialize the current minimum average of the first two slots.
    double minavg = (A[0] + A[1]) / 2.0;
    int idx = 0;
    

    for (i = 2; i < N; i++){
        double tmp1 = (A[i - 1] + A[i]) / 2.0;
        double tmp2 = (A[i - 2] + A[i - 1] + A[i]) / 3.0;
        
        if (tmp1 < minavg){
            idx = i - 1;
            minavg = tmp1;
        }
        if (tmp2 < minavg){
            idx = i - 2;
            minavg = tmp2;
        }
    }
    return idx;
}


2014年7月22日火曜日

Lesson 3: GenomicRangeQuery (Genomic Range Query)

https://codility.com/programmers/lessons/3
Lesson 3:  GenomicRangeQueryの解答がこちらになります。

また、別の「running total」の問題です。

やり方としては、一度文字列Sをスキャンしますが、その間に、ある位置でそれまでに出現したACGTのそれぞれの数を数えておきます。

そうした場合、ある位置pでACGTのそれぞれの今までのトータルが[4,2,3,1]であり、ある位置qでは[4,5,5,1](p <= qとします)、AとTのカウントは増えてないので、pとqの間には存在しないと言えます。逆にCとGのカウントは増えていますから、存在したと言えます。

下のサンプルのように、このやり方で100%のスコアがでますね。









--------------------------------
SOLUTION
--------------------------------
struct Results solution(char *S, int P[], int Q[], int M) 
{
    typedef struct ACGT {
        int a;
        int c;
        int g;
        int t;
    } ACGT;
    
    int N = strlen(S);
    ACGT* acgt = calloc(sizeof(ACGT), (N + 1));
    
    int i;
    int a = 0;
    int c = 0;
    int g = 0;
    int t = 0;

    //count the numbers of ACGT. 
    for (i = 0; i < N; i++){
        switch(S[i]){
            case 'A':
                a++;
                break;
            case 'C':
                c++;
                break;
            case 'G':
                g++;
                break;
            case 'T':
                t++;
                break;
            default:
                exit(-1);
        }
        //we leave acgt[0] untouched (filled with zero)
        //so that we can simplify the next step.
        acgt[i + 1].a = a;
        acgt[i + 1].c = c;
        acgt[i + 1].g = g;
        acgt[i + 1].t = t;
    }
    
    
    struct Results result;
    
    result.A = calloc(sizeof(int), M);
    result.M = M;
    
    for (i = 0; i < M; i++){
        ACGT s = acgt[P[i]];
        ACGT e = acgt[Q[i] + 1];
        if (e.a - s.a > 0){
            result.A[i] = 1;
        }
        else if (e.c - s.c > 0){
            result.A[i] = 2;
        }
        else if (e.g - s.g > 0){
            result.A[i] = 3;
        }
        else {
            result.A[i] = 4;
        }
    }
    
    
    free(acgt);
    
    return result;
}

2014年7月20日日曜日

Lesson 3: PassingCars (Passing Cars)

https://codility.com/programmers/lessons/3
Lesson 3: Passing Carsの答えがこちらになります。

西に進んでいる車は、反対側からくる東にすすんでいる車の全てにあうことになります。

ですから、東に進んでいる車の数を数えつつ、配列を調べていき、東に進んでいる来るまであれば、「東に進んでいる車のカウンタ」を一つ増やします。

西に進んでいる車があったら、「すれ違う車のカウンタ」に、「東に進んでいる車のカウンタ」を足せばよいのです。

で、忘れ易いのが1,000,000,000以上カウントが増えたら-1を返せという仕様ですね。
codilityの環境はintの範囲内の値なので、カウンタその他はint型でいいですね。

この方針で100%がでます。







int solution(int A[], int N) 
{
    int cnt = 0;
    int cntCarsTravelingEast = 0;
    
    int i;
    for (i = 0; i < N; i++){
        if (A[i] == 0){
            cntCarsTravelingEast++;
        }
        else {
            cnt += cntCarsTravelingEast;             
            if (cnt > 1000000000){
                return -1;
            }
        }
    }
    
    return cnt;
}

2014年7月19日土曜日

Lesson 2: MissingIntegers (Missing Integers)

https://codility.com/programmers/lessons/2
Lesson 2: Missing Integersの答えがこちらになります。

やり方として最初に浮かぶのは、ある値が見つかったかどうかわかるようにフラグたてておいて、後でチェックすることですね。 問題もO(N)のメモリ使用量で良いと言ってるので、簡単にNの大きさの配列を作ってしまいましょう。

このやり方でも、ちゃんとやろうとすると、一つの値につき、見つかった・見つかっていないの1ビットですむのですが、文句を言われなければそうはせず、単純にintとかで富豪的に試してしまいましょう。

最も小さい正の整数といわれているので、0以下は無視して良く、またNより大きい値があった場合は、必ずN以下にかけている数字が入ってきますからNより大きい値は無視していいですね。

1-Nまで全部見つかった場合は、当然N+1がないという答えでよいので、これで取りあえず実装してみましょう。

とりあえず100%のスコアがでたので、これでよいということにしましょう。








--------------------------------
       the solution
--------------------------------

int solution(int A[], int N) 
{
    //prepare the flags
    int* flag = (int*)calloc(sizeof(int), N);
    
    //iterate the given array A.
    int i;
    for (i = 0; i < N; i++){
        //we can neglect the value below 1 or larger than N.
        if (A[i] <= 0 || A[i] > N){
            continue;
        }
        //turn on the flag. this is the zero-indexed array.
        //so give -1 as the offset.
        flag[A[i] - 1] = 1;
    }
    
    //if there is no positive integer lacking between 1 - N,
    //the answer should be N + 1.
    int ans = N + 1;
    //found the minimum integer that is not found in the array A.
    for (i = 0; i < N; i++){
        if (flag[i] == 0){
            //the answer is (the index + 1). (we have -1 offset).
            ans = i + 1;
            break;
        }
    }
    
    //free the allocated memory space.
    free(flag);
    
    return ans;


}

Lesson 2: MaxCounters (Max Counters)

https://codility.com/programmers/lessons/2
Lesson 2:MaxCountersの解答がこちらになります。

これは結構面白い課題で、普通に問題の言う仕様でそのまま作ってしまうと、パフォーマンスが悪くて点数がもらえません。

たとえば、下のそういったかんじの実装ですと77%しかもらえないのです。

 


--------------------------------------------
A Simple Solution
--------------------------------------------
struct Results solution(int N, int A[], int M) 

{
    struct Results result;

    result.C = calloc(sizeof(int), N);
    result.L = N;

    int max = 0;
    
    int i, j;
    for (i = 0; i < M; i++){
        int op = A[i];
        //if the op is max counter.
        if (op == N + 1){
            for (j = 0; j < N; j++){
                result.C[j] = max;
            }
        }
        //if the op is increase(x)
        else {
            //op is beweetn 1 to N, but the index for the array C 
            //is between 0 and (N-1). Decrease op to adjust it.
            op--; 
            result.C[op]++;
            if (result.C[op] > max){
                max = result.C[op];
            }
        }
    }

    return result;
}


おそらく、これはいちいちmax counter命令が発効された時に、配列を全部更新しているからで、それをやめれば良さそうです。

いちいち更新するかわりにフラグをおさめる配列を用意して、max counterの時はそれを一気に0クリアするだけ、そして、ある位置がincrease命令を発効されたら、その位置と対応するフラグをみて、初めてそこで更新という戦略にしてみました。

スコアは88%まであがりましたが、まだまだ先がありそうです。


--------------------------------------------
A Better Solution (1)
--------------------------------------------
struct Results solution(int N, int A[], int M) 
{
    struct Results result;
    result.C = calloc(sizeof(int), N);
    result.L = N;

    int* flg = alloca(sizeof(int) * N);
    memset(flg, 0x00, sizeof(int) * N);
    
    int max = 0;
    int maxAtTheLastMaxCntOp = 0;
    
    int i;
    for (i = 0; i < M; i++){
        int op = A[i];
        //if the op is max counter.
        if (op == N + 1){
            maxAtTheLastMaxCntOp = max;
            memset(flg, 0x00, sizeof(int) * N);    
        }
        //if the op is increase(x)
        else {
            //op is beweetn 1 to N, but the index for the array C 
            //is between 0 and (N-1). Decrease op to adjust it.
            op--; 
            if (flg[op] == 1){
                result.C[op]++;
            }
            else {
                result.C[op] = maxAtTheLastMaxCntOp + 1;
                flg[op] = 1;                
            }
            
            if (result.C[op] > max){
                max = result.C[op];
            }
        }
    }
    
    //apply the 'max counter' operation
    //to the slot(s) where it should be applied. 
    int j;  
    for (j = 0; j < N; j++){
        if (flg[j] == 0){
            result.C[j] = maxAtTheLastMaxCntOp;
        }
    }
    return result;
}


結局、フラグを使うのではダメだという事がわかりました。ここで気づくのは、もし、更新しようとしている位置が、過去のmax counterが発行された時の値より小さいかどうかだけ調べればフラグっていらないんですよね。そうするとフラグのクリアなどをする必要がなくなります。

この方針だと100%になりますね。ちょっとした発想の転換を試される問題でした。


--------------------------------------------
A Better Solution (2)
--------------------------------------------
struct Results solution(int N, int A[], int M) 
{
    struct Results result;

    result.C = calloc(sizeof(int), N);
    result.L = N;
    
    int max = 0;
    int maxAtTheLastMaxCntOp = 0;
    
    int i;
    for (i = 0; i < M; i++){
        int op = A[i];
        //if the op is max counter.
        if (op == N + 1){
            maxAtTheLastMaxCntOp = max;
        }
        //if the op is increase(x)
        else {
            //op is beweetn 1 to N, but the index for the array C 
            //is between 0 and (N-1). Decrease op to adjust it.
            op--; 
            if (result.C[op] < maxAtTheLastMaxCntOp){
                result.C[op] = maxAtTheLastMaxCntOp + 1;
            }
            else {
                result.C[op]++;
            }     
            //update the max value if necessary.
            if (max < result.C[op]){
                max = result.C[op];
            }
        }
    }
    
    //apply the 'max counter' operation
    //to the slot(s) where it should be applied. 
    int j;  
    for (j = 0; j < N; j++){
        if (result.C[j] < maxAtTheLastMaxCntOp){
            result.C[j] = maxAtTheLastMaxCntOp;
        }
    }
    return result;
}

2014年7月16日水曜日

Lesson 2: FrogRiverOne (Frog River One)

https://codility.com/programmers/lessons/2
Lesson 2: FrogRiverOneの答えがこちらになります。

position 1 - Xまでの間が全て葉っぱで埋まったかどうかという事をチェックするだけなので、
別の配列B、サイズはXを用意して、そこに葉っぱが既に落ちたかどうかのフラグに利用します。
既に落ちてればカウントはせず、初めて落ちた時にカウントして、カウント数がXになれば、そこまでのみちが完成した事になります。








int solution(int X, int A[], int N) 
{
    //the array to store flags
    int B[X];

    //clear the flags.
    memset(B, 0x00, sizeof(B[0]) * X);
    
    //let leaves fall!
    int t;
    int cnt = 0;
    int ans = -1;
    for (t = 0; t < N; t++){
        //get the position where a leave fall at time T. 
        int p = A[t] - 1; //(the array B is from 0 to X-1)
        //if it is between 0 to X-1 and 
        //no leave has fallen onto the position before, count it.
        if (p < X && B[p] == 0){
            B[p] = 1;
            cnt++;
            //is the route filled with leaves?
            if (cnt == X){
                ans = t;
                break;
            }
        }
    }

    return ans;
}