Posted on

Sorting – Distinct解題紀錄

題目內容

題目頁面: https://app.codility.com/programmers/lessons/6-sorting/distinct/

題目解析

這一題之前其實出現過,但是因為這一題A的範圍是負一百萬到正一百萬,實在太大了,沒辦法直接用查表的方式儲存,所以會需要使用到二分搜尋法去建立這個不重複陣列

我的解題記錄

第一次的解法

function solution(A) {
    var M = []
    for (var i = 0; i < A.length; i++) {
        var idx = binarySearch(M, A[i]);
        if(M[idx] != A[i]){
            M.splice(idx, 0, A[i])
        }
    }
    //console.log(M)
    return M.length
}

//在一個已排序的陣列裡,取得newValue應插入的index
function binarySearch(arr, target) {
    let left = 0;
    let right = arr.length - 1;
    while (left <= right) {
        let mid = Math.floor((left + right) / 2);
        if (arr[mid] === target) {
            return mid;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return left
}

對於這結果,我真是滿腹委屈,明明二分搜尋法就是O(logN)阿!到底是誰把他變成O(N^2)的…我覺得應該是原本遍歷陣列為N,又乘上搜尋的LogN,因此變得很耗時

經過了一陣思考後,我覺得我根本被這一個章節的篇名所騙了,我幹嘛要建立有序陣列呢?用物件不就好了嗎?甚麼二分搜尋法根本自討苦吃!!

function solution(A) {
    var M = {}
    for (var i = 0; i < A.length; i++) {
        M[A[i]] = true;
    }
    //console.log(Object.keys(M))
    return Object.keys(M).length
}

結果根本沒在鳥甚麼Sorting不Sorting的反而過關,100%PASS!(https://app.codility.com/demo/results/trainingTR8QQS-TDU/)

Posted on

Prefix Sums – MinAvgTwoSlice解題紀錄

題目內容

題目頁面: https://app.codility.com/programmers/lessons/5-prefix_sums/min_avg_two_slice/

題目理解

尋找 (P, Q),其中0 ≤ P < Q < N,為陣列A的切片,N介於2~100000之間,並尋找最小平均切片的起始位置,要注意的是,陣列的內容為-10000~10000

感覺起來就是要先儲存從0~N的加總,然後再來尋找最小平均切片,但是思考起來這樣子複雜度應該也會有O(N**2),但是先這樣試看看

解題思路

第一次的嘗試

function solution(A) {
    var sum = []
    var current_sum = 0
    sum.push(0)
    for(var i=0;i<A.length;i++){
        current_sum += A[i];
        sum.push(current_sum)
    }

    var min = Infinity;
    var min_start = -1
    for(var i = 0;i<A.length;i++){
        for(var j = i+1;j<A.length;j++){
            var avg = (sum[j+1]-sum[i])/(j+1-i);
            if(min > avg) {
                min = avg;
                min_start = i
            }
        }
    }
    return min_start
}

我就知道….60%,timeout errors。複雜度還是太高,現在的解法是很直接的思考平均算法,先嘗試思考看看”平均”有沒有其他可能的算法

本來想嘗試必定包含最小值得所有可能,但是發現有邏輯錯誤,因為一個最小值不代表平均起來就一定最小,因為若該最小值的左右兩邊比較大,旁邊有兩個比較小的值,平均值會比包含最小值的還要小。所以即便是最小的值也不一定在最小平均之內。

//這會有錯誤的結果
function solution(A) {
    var sum = []
    var current_sum = 0
    sum.push(0)
    var mustIncludeIndex = -1
    var mustIncludeValue = Infinity
    for(var i=0;i<A.length;i++){
        current_sum += A[i];
        sum.push(current_sum)
        if(mustIncludeValue > A[i]) {
            mustIncludeValue = A[i];
            mustIncludeIndex = i
        }
    }
    var min = Infinity;
    var min_start = -1
    for(var i = 0;i<mustIncludeIndex;i++){
        for(var j = i+1;j<A.length;j++){
            var avg = (sum[j+1]-sum[i])/(j+1-i);
            if(min > avg) {
                min = avg;
                min_start = i
            }
        }
    }
    return min_start
}

萬般無助之下,詢問了ChatGPT,沒想到他都比我聰明(哭),好吧這就來改寫!!

以下為改寫後的程式碼

function solution(A) {
    var sum = []
    var current_sum = 0
    sum.push(0)
    for(var i=0;i<A.length;i++){
        current_sum += A[i];
        sum.push(current_sum)
    }

    var min = Infinity;
    var min_start = -1
    for(var i = 0;i<A.length;i++){
        var max_possible = Math.min(A.length, i+3)
        for(var j = i+1;j<max_possible;j++){
            var avg = (sum[j+1]-sum[i])/(j+1-i);
            if(min > avg) {
                min = avg;
                min_start = i
            }
        }
    }
    return min_start
}

好,100%!謝謝ChatGPT大神!!!

Posted on

Prefix Sums – GenomicRangeQuery解題紀錄

題目內容

題目頁面: https://app.codility.com/programmers/lessons/5-prefix_sums/genomic_range_query/

題目理解

天啊~我最討厭有故事性的題目了如汽車或DNA,好長阿~~~讓我認真看他在說啥!

DNA和factor可表示為: {‘A’:1,’C’:2,’G’:3,’T’:4},S = CAGCCTA,P=[2,5,0],Q=[4,5,6]

然後要找第 K 個查詢(0 ≤ K < M),找到 P[K] 和 Q[K](含)之間的 DNA 序列中包含的核苷酸的最小影響因子。也就是要計算出一個長度為M(M為P、Q的長度)的陣列,然後返回最小影響因子

例如M[0] = Math.min(S[2],S[3]) = Math.min(‘G’,’C’) = Math.min(2,3) =2

陷阱在這: each element of arrays P and Q is an integer within the range [0..N – 1]; => 所以不一定一樣長

解題思路

先用暴力法寫出一個可行解

// you can write to stdout for debugging purposes, e.g.
// console.log('this is a debug message');

function solution(S, P, Q) {
    // Implement your solution here
    factor = {'A':1,'C':2,'G':3,'T':4}
    var length_M = Math.min(P.length, Q.length)
    var M = []
    for( var i = 0 ; i < length_M; i++){
        var inclusive_S = S.slice(P[i], Q[i]+1).split("")
        var minimum = Infinity
        for(var j =0;j<inclusive_S.length;j++){
            //console.log(minimum,  factor[inclusive_S[j]])
            if(minimum > factor[inclusive_S[j]]){
                minimum = factor[inclusive_S[j]]
            }
        }
        M.push(minimum)
    }
    return M
}

因為邏輯有一點複雜,先把暴力解送出驗證一下正確性,之後再來優化

好的!!62%!!至少邏輯正確,那我們在想想要如何優化。讓我先觀察一下是死在那些效能測試上。分別是GGGGGG..??..GGGGGG..??..GGGGGG、large random string, length、all max ranges,首先先看到almost_all_same_letters,所以先把CAGC對應數字的部分做掉,如: S = CAGCCTA = [2,1,3,2,2,4,1]

下面為改善後的程式碼,可以減少重複將字串轉換為數字的部分。雖然沒有很樂觀,因為還是有先暫存中間值的可能性,先送看看有沒有改善,並評估一下效能瓶頸是否有改變。

function solution(S, P, Q) {
    // Implement your solution here
    factor = {'A':1,'C':2,'G':3,'T':4}
    var length_M = Math.min(P.length, Q.length)
    var S = S.split("").map(x => factor[x])
    var M = []
    for( var i = 0 ; i < length_M; i++){
        M.push(Math.min(...S.slice(P[i], Q[i]+1)))
    }
    return M
}

結果並沒有改善

— M = 0
minimum: Infinity
minimum: 3
minimum: 2
— M = 1
minimum: Infinity
— M = 2
minimum: Infinity
minimum: 2
minimum: 1
minimum: 1
minimum: 1
minimum: 1
minimum: 1

觀察一下現在的迴圈狀況,來找尋可改善的重複步驟,首先最明顯的,就是若遇到1應該可直接break,但我猜這影響不大。另外我發現,因為factor = {‘A’:1,’C’:2,’G’:3,’T’:4}是固定的,且最多就是4種,所以與其在那邊一個個轉換並且比較大小,還不如用4個if…else來從小尋找到最大的值。

function solution(S, P, Q) {
    // Implement your solution here
    //factor = {'A':1,'C':2,'G':3,'T':4}
    var length_M = Math.min(P.length, Q.length)
    var M = []
    for( var i = 0 ; i < length_M; i++){
        var inclusive_S = S.slice(P[i], Q[i]+1).split("")
        //console.log(inclusive_S)
        if(inclusive_S.indexOf('A') >= 0) M.push(1)
        else if(inclusive_S.indexOf('C') >= 0) M.push(2)
        else if(inclusive_S.indexOf('G') >= 0) M.push(3)
        else M.push(4)
    }
    return M
}

仍然沒有好轉

事已至此,感覺卡在一個切入觀點了,所以只好上網搜尋一下: http://myjavawar.blogspot.com/2018/10/genomicrangequery.html

原來要改變一下思考方向,要把原本的思維從”在一個已被切好的陣列中,搜尋某字元有沒有出現過”的思維,改成”計算從0到M以來某字元出現的次數”。接著若是陣列要從N開始,就直接把M-N,就會是從N~M之間某字元出現的次數”了

function solution(S, P, Q) {
    // Implement your solution here
    //factor = {'A':1,'C':2,'G':3,'T':4}
    var length_M = Math.min(P.length, Q.length)
    var M = []
    var showTimes = []
    var currentShowTime =  {'A':0,'C':0,'G':0,'T':0}
    for( var i = 0 ; i < S.length; i++){
        currentShowTime[S[i]]++
        showTimes.push({'A':currentShowTime['A'],'C':currentShowTime['C'],'G':currentShowTime['G'],'T':currentShowTime['T']})
    }
    for( var i = 0 ; i < length_M; i++){
        //var inclusive_S = S.slice(P[i], Q[i]+1).split("")
        //console.log(inclusive_S)
        if(showTimes[Q[i]]['A'] - showTimes[P[i]]['A'] > 0) M.push(1)
        else if(showTimes[Q[i]]['C'] - showTimes[P[i]]['C'] > 0) M.push(2)
        else if(showTimes[Q[i]]['G'] - showTimes[P[i]]['G'] > 0) M.push(3)
        else M.push(4)
    }
    return M
}

結果雖然效率過了,但是會有錯誤的結果,檢查了一下,這是因為當P[i]與Q[i]一樣大小時,就會出現錯誤,所以要加一個if用別的方法找答案

function solution(S, P, Q) {
    // Implement your solution here
    //factor = {'A':1,'C':2,'G':3,'T':4}
    var length_M = Math.min(P.length, Q.length)
    var M = []
    var showTimes = []
    var currentShowTime =  {'A':0,'C':0,'G':0,'T':0}
    for( var i = 0 ; i < S.length; i++){
        currentShowTime[S[i]]++
        showTimes.push({'A':currentShowTime['A'],'C':currentShowTime['C'],'G':currentShowTime['G'],'T':currentShowTime['T']})
    }
    //console.log(showTimes)
    for( var i = 0 ; i < length_M; i++){
        //var inclusive_S = S.slice(P[i], Q[i]+1).split("")
        //console.log(inclusive_S)
        if(P[i] == Q[i]){
            var factor = {'A':1,'C':2,'G':3,'T':4}
            M.push(factor[S[P[i]]])
        }else{
            if(showTimes[Q[i]]['A'] - showTimes[P[i]]['A'] > 0) M.push(1)
            else if(showTimes[Q[i]]['C'] - showTimes[P[i]]['C'] > 0) M.push(2)
            else if(showTimes[Q[i]]['G'] - showTimes[P[i]]['G'] > 0) M.push(3)
            else if(showTimes[Q[i]]['T'] - showTimes[P[i]]['T'] > 0) M.push(4)
        }
        
    }
    return M
}

結果還是有錯

仔細檢查了一下,錯誤的情況是solution(‘AC’, [0, 0, 1], [0, 1, 1]),發現P[i] 應該要減1,但這樣子P[i]==0時會出現問題,所以要給個預設值

function solution(S, P, Q) {
    // Implement your solution here
    //factor = {'A':1,'C':2,'G':3,'T':4}
    var length_M = Math.min(P.length, Q.length)
    var M = []
    var showTimes = []
    var currentShowTime =  {'A':0,'C':0,'G':0,'T':0}
    for( var i = 0 ; i < S.length; i++){
        currentShowTime[S[i]]++
        showTimes.push({'A':currentShowTime['A'],'C':currentShowTime['C'],'G':currentShowTime['G'],'T':currentShowTime['T']})
    }
    //console.log(showTimes)
    for( var i = 0 ; i < length_M; i++){
        var inclusive_S = S.slice(P[i], Q[i]+1).split("")
        //console.log(inclusive_S)
        if(P[i] == Q[i]){
            var factor = {'A':1,'C':2,'G':3,'T':4}
            M.push(factor[S[P[i]]])
        }else{
            var showTimesMax = showTimes[Q[i]]
            var showTimesMin = P[i] == 0 ? {'A':0,'C':0,'G':0,'T':0}:showTimes[P[i]-1]
            //console.log(showTimesMax, showTimesMin)
            if(showTimesMax['A'] - showTimesMin['A'] > 0) M.push(1)
            else if(showTimesMax['C'] - showTimesMin['C'] > 0) M.push(2)
            else if(showTimesMax['G'] - showTimesMin['G'] > 0) M.push(3)
            else M.push(4)
        }
    }
    return M
}

好了~終於100%了,真是個身心俱疲的一題阿! (https://app.codility.com/demo/results/trainingYXRWFN-H4J/)

Posted on

Prefix Sums – CountDiv解題紀錄

題目內容

題目頁面: https://app.codility.com/programmers/lessons/5-prefix_sums/count_div/

解題思路

給定三個整數 A、B 和 K,返回 [A..B] 範圍內可被 K 整除的整數個數,且A 和 B 是 [ 0 .. 2,000,000,000 ]範圍內的整數; K 是 [ 1 .. 2,000,000,000 ]範圍內的整數。

嗯…看起來這是一題用效能卡死人的題目,因為最大值可到2,000,000,000,但是為了求正確性,我們還是先從簡單的方法寫起,再來慢慢改善效能,不要一下想太難的方法。先把暴力破解法寫出來

function solution(A, B, K) {
    // Implement your solution here
    var counter = 0;
    for(var i = A;i<=B;i++){
        if(i%K == 0) counter++
    }
    return counter
}

但是有了上次的經驗,我根本不想送出這個答案被打槍。所以先來想改善方法,首先,我想找到比A大,最小能夠被整除的數字,和比B小最大能夠被整除的數字然後相減後直接除以K

function solution(A, B, K) {
    var minimum_divisible = K*Math.ceil(A/K)
    var maximum_divisible = K*Math.floor(B/K)
    
    return (maximum_divisible - minimum_divisible)/K + 1
}

結果!!甚麼!!!中等程度這麼快解決!!太棒了!!!
https://app.codility.com/demo/results/trainingCYGC6K-3H8/

Posted on

Prefix Sums(前綴和)概念

甚麼是Prefix Sums(前綴和)

當需要快速查詢數組中某一區間的元素和時,Prefix Sums可以幫助我們在O(1)的時間複雜度內進行查詢。

具體做法是先對數組進行預處理,計算出從第一個元素到當前位置的所有元素的和,然後通過兩個元素的前綴和之差來計算出任意區間的元素和。例如,要查詢數組中第i到第j個元素的和,只需要計算sums[j+1] – sums[i]即可。

Prefix Sums通常用於需要頻繁查詢數組中某一區間的元素和的情況,例如在數學、統計學、計算機科學和機器學習等領域中。Prefix Sums的計算可以在預處理的階段中完成,因此可以在之後的查詢中以O(1)的時間複雜度快速地回答各種區間求和問題。

例如,在計算機科學中,Prefix Sums通常用於解決數組中的區間查詢問題,例如求解區間最大子段和、區間平均值、區間中位數等。Prefix Sums還可以用於在數據壓縮、圖像處理和信號處理等領域中,進行離散化、濾波和卷積等操作。

計算出從第一個元素到當前位置的所有元素的和,是因為這樣可以在之後的區間求和操作中,通過兩個元素的前綴和之差來快速計算任意區間的元素和。例如,要查詢數組中第i到第j個元素的和,只需要計算sums[j+1] – sums[i]即可。這樣可以大大降低區間求和操作的時間複雜度,提高算法效率。

Prefix Sums使用時機


在許多需要進行區間求和操作的問題中,Prefix Sums都是一種常用的解決方案。因此,在識別哪些問題需要使用Prefix Sums時,可以考慮以下幾點:

  1. 問題是否需要進行區間求和操作:如果問題需要計算數組中某一區間的元素和,那麼Prefix Sums可能是一個合適的解決方案。
  2. 數據集的大小:Prefix Sums通常適用於數據集較小的情況下,因為在預處理階段中需要計算每個元素的前綴和,這可能需要額外的空間和時間。
  3. 數據是否可以修改:如果數據集可以修改,那麼在每次修改後,需要重新計算前綴和,這可能會影響算法的效率。因此,在這種情況下,需要仔細考慮使用Prefix Sums的適當性。
  4. 計算區間求和的頻率:如果需要頻繁地進行區間求和操作,那麼使用Prefix Sums可能比其他方法更有效。

綜合以上幾點,可以初步判斷哪些問題可能需要使用Prefix Sums。在實際應用中,還需要進一步評估和優化算法,以確保其效率和準確性。

使用Binary Indexed Tree來計算前綴和

BIT的基本思想是將原始數組轉換為一個二進制表示的數組,並在其中儲存一些特定位置的前綴和。BIT的建立和查詢操作都可以在O(n log n)的時間複雜度內完成,其中n是數組的長度。BIT的更新操作可以在O(log n)的時間複雜度內完成。

更多詳細介紹: https://yuihuang.com/binary-indexed-tree/

範例題目 – PassingCars

題目連結

https://app.codility.com/programmers/lessons/5-prefix_sums/passing_cars/

我的第一次解法(javascript)

function solution(A) {
    // Implement your solution here
    var current_sum = 0
    for(var i=0;i<A.length;i++){
        if(A[i] == 0){
            for(var j = i;j<A.length;j++){
                if(A[j] == 1){
                    current_sum++
                }
            }
        }
    }
    return current_sum
}

成績如下,雖然答案是正確的,但是效率不夠,為O(N**2),只拿到70%的分數

改善方向

我發現if(A[j] == 1)和if(A[i] == 0)這段其實重複跑了,其實可能在之前的檢查中早就會知道有那些是1哪些是0,重複遍歷是不必要的,因此我決定嘗試用另外的陣列來儲存index為1的index值(value_1_index)和index為0的index(value_0_index)

接下來因為 0 ≤ P < Q < N,所以我們會需要找value_1_index裡面的key值大於value_0_index的,所以會使用var j = value_1_index.findIndex(x=>x>value_0_index[i])來找尋。

function solution(A) {
    // Implement your solution here
    var current_sum = 0
    var value_1_index = [], value_0_index = []
    for(var i=0;i<A.length;i++){
        if(A[i] == 0) value_0_index.push(i)
        else value_1_index.push(i)
    }
    for(var i=0;i<value_0_index.length;i++){
        var j = value_1_index.findIndex(x=>x>value_0_index[i])
        if(j >= 0){
            while(j < value_1_index.length){
                j++
                current_sum++
            }
        }
    }
    return current_sum
}

結果也太哭了,居然沒省多少XD只多了一個勾勾

Detected time complexity:O(N ** 2)

接著我發現這句話!!

The function should return −1 if the number of pairs of passing cars exceeds 1,000,000,000.

因此我決定加上if(current_sum > 1000000000){ return -1 }的判斷在current_sum ++的後面,但結果仍然一樣,但是可以發現至少程式有跑完(不是顯示Killed),只是速度達不到標準,尤其是large_random和large_alternate差異很大,我的程式跑了5.336秒,要求卻是0.304秒,差了10倍以上(汗顏)。仔細觀察了一下,我個人覺得在large_alternate的題目代表是0和1頻繁交替的狀況下,我的演算法的效能會很差。

因此我猜測這個效能瓶頸和var j = value_1_index.findIndex(x=>x>value_0_index[i])這個搜尋的動作有關,我決定改成當i和j用完就把用完的部分砍掉,以減少搜尋量。為此,我必須把for迴圈改成while,並且讓value_0_index與value_1_index都能隨著時間變小。

function solution(A) {
    // Implement your solution here
    var current_sum = 0
    var value_1_index = [], value_0_index = []
    for(var i=0;i<A.length;i++){
        if(A[i] == 0) value_0_index.push(i)
        else value_1_index.push(i)
    }
    while(value_0_index.length > 0){
        var j = value_1_index.findIndex(x=>x>value_0_index[0])
        if(j >= 0){
            value_1_index = value_1_index.slice(j,value_1_index.length)
            var pointer = 0
            while(pointer < value_1_index.length){
                pointer++
                current_sum++
                if(current_sum > 1000000000){
                    return -1
                }
            }
        }
        value_0_index.shift();
    }
    return current_sum
}

所以問題是出在value_1_index = value_1_index.slice(j,value_1_index.length)這邊太耗時,尤其是最後一項,可以看出是在當某個數字多的時候會更耗時。在這邊我嘗試把slice改成splice,並且在j>0時才去做

function solution(A) {
    // Implement your solution here
    var current_sum = 0
    var value_1_index = [], value_0_index = []
    for(var i=0;i<A.length;i++){
        if(A[i] == 0) value_0_index.push(i)
        else value_1_index.push(i)
    }
    while(value_0_index.length > 0){
        var j = value_1_index.findIndex(x=>x>value_0_index[0])
        if(j >= 0){
            if(j > 0){
                value_1_index.splice(0,j)
            }
            var pointer = 0
            while(pointer < value_1_index.length){
                pointer++
                current_sum++
                if(current_sum > 1000000000){
                    return -1
                }
            }
        }
        value_0_index.shift();
    }
    return current_sum
}

結果有改善,但仍需努力

接著我發現,等等…value_1_index好像不用刪啊!!先決定不要先搜尋好了,把條件設定成從最後一個value_1_index去找起,找到value_1_index裡的值小於value_0_index[0]時停止

function solution(A) {
    // Implement your solution here
    var current_sum = 0
    var value_1_index = [], value_0_index = []
    for(var i=0;i<A.length;i++){
        if(A[i] == 0) value_0_index.push(i)
        else value_1_index.push(i)
    }
    while(value_0_index.length > 0){
        var point = value_1_index.length-1
        while(value_1_index[point] > value_0_index[0]){
            current_sum++
            point--
        }
        value_0_index.shift();
    }
    return (current_sum > 1000000000) ? -1 : current_sum 
}

結果仍然超時,我發現似乎value_0_index也可以不用刪,改回使用for迴圈看看

function solution(A) {
    // Implement your solution here
    var current_sum = 0
    var value_1_index = [], value_0_index = []
    for(var i=0;i<A.length;i++){
        if(A[i] == 0) value_0_index.push(i)
        else value_1_index.push(i)
    }
    for(var i=0;i<value_0_index.length;i++){
        var pointer = value_1_index.length-1
        while(value_1_index[pointer] > value_0_index[i]){
            current_sum++
            pointer  --
        }
    }
    return (current_sum > 1000000000) ? -1 : current_sum 
}

繞了這麼久,我覺得最大的問題應該是在 (0, 1), (0, 3), (0, 4), (2, 3), (2, 4)中,  (2, 3), (2, 4)中的3,4其實是(0, 1), (0, 3), (0, 4)中的1,3,4的子集合,所以應該是要做暫存,把第一次的集合儲存起來,之後直接減掉A[0] = 0 A[1] = 1 A[2] = 0之中兩個0中間的1這個值然後加上去就可以了

function solution(A) {
    // Implement your solution here
    var current_sum = 0
    var value_1_index = [], value_0_index = []
    //紀錄兩個0中間經過的1
    var value_0_interval = []
    for(var i=0;i<A.length;i++){
        if(A[i] == 0) {
            value_0_index.push(i)
            value_0_interval[value_0_index.length-1] = 0
        }else {
            value_1_index.push(i)
            value_0_interval[value_0_index.length-1]++
        }
    }
    var current_value_1 = value_1_index.length
    for(var i=0;i<value_0_index.length;i++){
        current_sum = current_sum + current_value_1
        current_value_1 = current_value_1 - value_0_interval[i]
    }
    return (current_sum > 1000000000) ? -1 : current_sum 
}

好…90%,快過了,一開始就要除去第一個value_0_index之前的value_1_index

function solution(A) {
    // Implement your solution here
    var current_sum = 0
    var value_1_index = [], value_0_index = []
    //紀錄兩個0中間經過的1
    var value_0_interval = []
    for(var i=A.indexOf(0);i<A.length;i++){
        if(A[i] == 0) {
            value_0_index.push(i)
            value_0_interval[value_0_index.length-1] = 0
        }else {
            value_1_index.push(i)
            value_0_interval[value_0_index.length-1]++
        }
    }
    var current_value_1 = value_1_index.length
    for(var i=0;i<value_0_index.length;i++){
        current_sum = current_sum + current_value_1
        current_value_1 = current_value_1 - value_0_interval[i]
    }
    return (current_sum > 1000000000) ? -1 : current_sum 
}

終於100%了….https://app.codility.com/demo/results/trainingWK5QAP-PP8/

結論,一個easy程度的我玩這麼多次,真的很哭…但滿好玩的(有嗎?)

Posted on

Counting Elements

概念介紹

Counting Elements是一種計算數組中元素出現次數的概念。它通常用於解決一些與計數有關的問題,例如找出數組中出現次數超過一定閾值的元素,計算數組中不同元素的個數等。

在Counting Elements中,我們可以使用一個額外的數組counts,將數組中每個元素出現的次數記錄下來。counts[i]表示數組中元素i出現的次數,如果數組中不存在元素i,則counts[i]=0。

具體來說,Counting Elements算法的步驟如下:

  1. 創建一個長度為k的counts數組,其中k是數組中最大元素的值加1。
  2. 遍歷原始數組A,對於每個A[i],增加counts[A[i]]的值。
  3. 計算任意元素的出現次數,只需要查詢counts數組中對應元素的值即可。

Counting Elements算法的時間複雜度為O(n),其中n是數組的長度。它可以在線性時間內計算數組中元素的出現次數,因此非常適用於需要頻繁計算元素次數的問題。另外,Counting Elements算法還可以與其他算法結合使用,例如快速排序和二分查找等,以進一步優化計算效率。

使用時機


在許多與計數有關的問題中,Counting Elements是一種常見的解決方案。因此,在考慮使用Counting Elements時,可以考慮以下幾點:

  1. 問題是否需要計算元素出現的次數:如果問題需要計算數組中每個元素出現的次數,那麼Counting Elements可能是一個合適的解決方案。
  2. 數據集的大小:Counting Elements通常適用於數據集較小的情況下,因為需要額外的空間來存儲counts數組,並且在預處理階段中需要遍歷數組,這可能需要額外的時間和空間。
  3. 需要找出出現次數超過一定閾值的元素:如果需要找出數組中出現次數超過一定閾值的元素,例如Majority Element,那麼Counting Elements可能是一個合適的解決方案。
  4. 需要計算數組中不同元素的個數:如果需要計算數組中不同元素的個數,那麼Counting Elements也可能是一個合適的解決方案。

綜合以上幾點,可以初步判斷哪些問題可能需要使用Counting Elements。在實際應用中,還需要進一步評估和優化算法,以確保其效率和準確性。

練習題目1

題目連結: FrogRiverOne – VIEW

我的解法

function solution(X, A) {
    // Implement your solution here
    var filled = new Array(X).fill(false);
    var current_filled = 0;
    for (let index = 0; index < A.length; index++) {
        //console.log(filled)
        if(!filled[A[index]-1]){
            filled[A[index]-1] = true
            current_filled ++;
            if(current_filled == X){
                return index
            }
        }
    }
    
    return -1
}

測試通過,為100%!

練習題目2

題目連結: PermCheckVIEW

我的作答

function solution(A) {
    // Implement your solution here
    var arr = new Array(A.length).fill(0)
    for(var i=0;i<A.length;i++){
        arr[A[i]-1] = 1
    }
    return (arr.indexOf(0) == -1) ? 1 :0
}

練習題目3- MaxCounters

題目連結: MaxCountersVIEW

我的解法

function solution(N, A) {
    // Implement your solution here
    var arr = new Array(N).fill(0)
    var max_value = 0
    for(var i=0;i<A.length;i++){
        if(A[i]<=N){
            arr[A[i]-1]++
            if(max_value < arr[A[i]-1]){
                max_value = arr[A[i]-1]
            }
        }else{
            arr.fill(max_value)
        }
    }
    return arr
}

結果正確,但時間超時,只得到66的分數,代表需要再修改做法

我檢查了一下寫法,發現arr.fill(max_value)這行雖然只有一行,但是會是O(N)的複雜度,會增加許多的時間複雜度,因此,我嘗試最後再一次性的更新這個值,並且在每一步做加總時,檢查這個值是否需要被更新,就可以避免每次當A[i]>N時都得增加一次O(N)的時間複雜度。

https://app.codility.com/demo/results/training556V6C-HBV/

function solution(N, A) {
    // Implement your solution here
    var arr = new Array(N).fill(0)
    var max_value = 0
    var minimum_value = 0
    for(var i=0;i<A.length;i++){
        if(A[i]<=N){
            if(arr[A[i]-1] < minimum_value) arr[A[i]-1] = minimum_value
            arr[A[i]-1]++
            if(max_value < arr[A[i]-1]){
                max_value = arr[A[i]-1]
            }
        }else{
            //arr.fill(max_value)
            minimum_value = max_value
        }
    }
    
    for(var i=0;i<arr.length;i++){
        arr[i] = Math.max(arr[i], minimum_value)
    }
    return arr
}

練習題目4- MissingInteger

題目連結: MissingInteger: VIEW

這題的意思是說要找到A這個陣列裡面缺少的最小的正整數,所以我的解題思路就是先尋找陣列裡面的最大值,接下來設定一個陣列裡面預設值全部設為0,然後遍歷整個陣列把有出現過的index設定為1,接著再回傳該陣列最小值為1的index

function solution(A) {
    var max = Math.max(...A)
    if (max < 0){
        return 1
    }
    var sort = new Array(max).fill(0)
    // Implement your solution here
    for(var i=0;i<A.length;i++){
        if(sort[A[i]-1] == 0 && A[i] > 0){
            sort[A[i]-1] = 1
        }
    }
    for(var i=0;i<max;i++){
        if(sort[i-1] == 0){
            return i
        }
    }
    return max+1
}

結果如下,正確率為100%,複雜度為O(N)或O(N*log(N))

Posted on

時間複雜度BigO介紹

什麼是BigO

時間複雜度是指算法所需的運行時間與輸入數據規模之間的關係。在計算機科學中,我們使用 BigO 表示法來表示算法的時間複雜度。

BigO 表示法中的 O 代表 “on the order of”,即算法的運行時間隨著輸入規模的增長,按照某種數量級的速度增長。常見的時間複雜度從低到高分別是 O(1)、O(log n)、O(n)、O(n log n)、O(n^2)、O(2^n) 和 O(n!)。

在實際應用中,我們通常希望選擇時間複雜度盡可能低的算法。然而,不同的算法在不同的應用場景下可能表現不同,因此選擇合適的算法需要根據具體問題進行綜合考慮。

另外需要注意的是,時間複雜度只是對算法的一個粗略估計,實際運行時間還受到很多其他因素的影響,如輸入數據的特性、計算機硬件性能等。因此,在實際應用中需要進行充分的測試和優化,以獲得更準確的運行時間估計。

降低常數

在 BigO 表示法中,常數因子通常被省略,因為它們對算法的增長速度影響相對較小。在特定輸入下O(N)可能會比O(1)更小,一般來說,BIG O只是在描述增加的速率。

降低非優勢條件

在算法的時間複雜度分析中,我們通常會關注最壞情況下的時間複雜度,即算法在處理最壞情況下的輸入時所需的時間複雜度。但是,在實際應用中,算法所處理的輸入可能並不總是最壞情況,因此可能存在一些非優勢條件,即輸入規模較小、輸入具有特殊性質等情況。

刷題範例

題目連結: https://app.codility.com/programmers/lessons/3-time_complexity/tape_equilibrium/

我的解答 – https://app.codility.com/demo/results/training5HCTQQ-UW6/

function solution(A) {
    // Implement your solution here
    var total_a = 0;
    var total_b = A.reduce((partialSum, a) => partialSum + a, 0);   
    var min = Infinity;
    for(var i=0;i<A.length-1;i++){
        total_a = total_a+A[i];
        total_b = total_b-A[i];
        //console.log(total_a, total_b,Math.abs(total_a-total_b), i)
        if(Math.abs(total_a-total_b) < min){
            min = Math.abs(total_a-total_b)
        }
    }
    return min
}

結果如下

Posted on

Chunked Encoding介紹

甚麼是Chunked Encoding

Chunked encoding(分塊編碼)是一種HTTP/1.1協議中的傳輸編碼方式,用於將HTTP消息主體分成多個塊(chunks),以便在網絡上進行有效傳輸。分塊編碼主要用於動態生成的內容,以及在事先不知道內容大小的情況下傳輸數據。

分塊編碼的工作原理如下:

  1. 服務器將HTTP消息主體分成多個大小可變的塊。每個塊由兩部分組成:塊大小(十六進制表示)和實際數據。
  2. 每個塊都以塊大小開頭,然後是一個回車換行符(CRLF),接著是實際數據。在每個塊的數據之後,還有另一個回車換行符(CRLF)。
  3. 數據傳輸完成後,服務器會發送一個大小為0的塊,表示數據已經全部傳輸完畢。接著,服務器可以選擇性地傳輸附加的HTTP頭部,以提供更多關於已傳輸數據的信息。
  4. 客戶端接收到分塊編碼的數據後,將各個塊重新組合成完整的HTTP消息主體。

要使用分塊編碼,服務器需要在HTTP響應頭中設置Transfer-Encoding字段為chunked。這告訴客戶端,接收到的數據將使用分塊編碼格式。

分塊編碼的主要優點是允許服務器在不知道最終內容大小的情況下開始傳輸數據。這對於動態生成的內容、實時數據流和大文件傳輸非常有用。此外,分塊編碼還可以實現數據的即時壓縮和傳輸,從而提高傳輸效率。

設定nginx以支持Chunked Encoding

以下是一個簡單的nginx.conf範例,用於支持在http://127.0.0.1/live下的文件開啟chunked encoding。此配置檔案會將請求代理到後端應用伺服器(例如:Node.js、Python或其他後端應用)進行處理。請注意,這裡假設後端應用伺服器已經正確配置並支持分塊編碼。

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    gzip  on;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/xml+rss;

    server {
        listen       80;
        server_name  127.0.0.1;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        location /live {
            proxy_pass http://backend:3000; # 請將 "backend" 替換為您的後端應用伺服器地址(IP 或 域名),並將 "3000" 替換為您的後端應用伺服器的端口

            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            proxy_http_version 1.1;
            proxy_set_header Connection "";
            chunked_transfer_encoding on;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }
}
events {
    worker_connections  1024;
}

這個配置檔案將Nginx設置為代理位於http://127.0.0.1/live的請求,並將請求轉發到後端應用伺服器。在location /live部分,使用chunked_transfer_encoding on;指令開啟分塊編碼。

觀察你的檔案是否有啟用Chunked Encoding

我們可以從伺服器的回應看到這個伺服器的檔案傳輸是否有支持Chunked Encoding,如下圖

Posted on

CMAF – 媒體封裝格式介紹

CMAF介紹(Common Media Application Format)

CMAF(Common Media Application Format,通用媒體應用格式)是一種專為網絡媒體傳輸設計的標準。CMAF旨在簡化不同裝置和網絡環境之間的媒體流適配和交付,從而提高流媒體的性能和覆蓋範圍。CMAF是一種媒體封裝格式。CMAF被設計為簡化在不同裝置和網絡環境之間的媒體流適配和交付,從而提高流媒體的性能和覆蓋範圍。CMAF檔案通常具有.cmf.mp4擴展名。

CMAF(Common Media Application Format,通用媒體應用格式)是一種媒體封裝格式,類似於FLV(Flash Video)和MP4(MPEG-4 Part 14)。CMAF旨在簡化在不同裝置和網絡環境之間的媒體流適配和交付,以提高流媒體的性能和覆蓋範圍。

與FLV和MP4等其他封裝格式相比,CMAF的一個主要優勢在於它的兼容性和靈活性。CMAF可以應對各種不同的網絡環境和裝置能力,並且可以與多種流媒體協議(如HLS和MPEG-DASH)無縫地配合使用。

CMAF的特點

CMAF的主要特點包括:

  1. 單一檔案格式:CMAF允許使用單一檔案格式來適配多種媒體流協議,例如HLS(HTTP Live Streaming)和MPEG-DASH(Dynamic Adaptive Streaming over HTTP)。這使得內容提供商能夠更容易地在各種裝置和網絡上傳輸和管理媒體內容。
  2. 切片:CMAF將媒體流切成較小的片段(通常稱為chunks),這些片段可以在不同品質層次之間進行無縫切換,以適應不同的網絡條件和裝置能力。這有助於實現更低的延遲和更高的播放質量。
  3. 編碼效率:CMAF支持各種編解碼器,例如H.264(AVC)和H.265(HEVC),以實現高效的媒體編碼。此外,CMAF還支持多種音頻編解碼器,例如AAC和Opus。
  4. 整合DRM(數字版權管理):CMAF允許整合各種DRM技術,如Widevine、PlayReady和FairPlay,以保護內容的版權。

總之,CMAF是一種簡化網絡媒體傳輸的標準,它有助於提高流媒體的性能、覆蓋範圍和兼容性。

CMAF的延遲

CMAF(Common Media Application Format,通用媒體應用格式)主要用於適配和交付HLS(HTTP Live Streaming)和MPEG-DASH(Dynamic Adaptive Streaming over HTTP)等流媒體協議。CMAF的目標是在不同裝置和網絡環境之間提供高效的媒體流適配和交付,而非專注於低延遲。

由於CMAF依賴於HTTP,其延遲通常會比使用基於UDP的協議(如SRT或RTP)高。HTTP協議需要較長的時間來建立連接、請求和接收數據,這導致了較大的延遲。此外,CMAF通常用於分段媒體流,每個分段的持續時間也會增加延遲。

然而,CMAF的延遲性能可以通過使用CMAF的低延遲模式(CMAF-LLC)來改善。CMAF-LLC使用chunked encoding傳輸技術來實現低延遲,這允許客戶端在接收到完整的媒體分段之前開始解碼和播放。這樣可以將延遲降低到可接受的範圍,但仍然可能高於使用基於UDP的協議,如SRT。

總之,儘管使用CMAF的延遲可能較長,但通過使用CMAF-LLC可以改善延遲性能。然而,對於實時應用或低延遲要求非常嚴格的場景,使用基於UDP的協議,如SRT或RTP,可能是更合適的選擇。

Posted on

使用Helm來部署K8S

Helm介紹

Helm 是一個 Kubernetes 包管理器,它可以幫助您在 Kubernetes 上部署和管理應用程序。Helm 允許您定義、安裝和升級 Kubernetes 應用程序,並且可以管理它們的依賴關係。

Helm 由兩部分組成:Helm CLI 和 Helm Charts。Helm CLI 是一個命令行界面,用於管理 Helm Charts。Helm Charts 是一個包含 Kubernetes 资源描述文件的打包文件,例如 Deployment、Service、Ingress、ConfigMap 等等。這些文件被打包到壹個壓縮文件中,通常是 tar.gz 或 zip 格式。

使用 Helm,您可以通過創建自己的 Charts 或者使用社區提供的 Charts 快速部署應用程序。您可以使用 Helm Charts 定義 Kubernetes 资源,然後通過 Helm CLI 安裝 Charts 來創建和管理 Kubernetes 资源。

Helm 還允許您管理 Charts 的版本控制,從而使您可以輕鬆地升級或回滾到先前的版本。此外,Helm 還支持模板化和參數化 Charts,從而使您可以通過使用不同的參數集在不同的環境中部署同一個 Chart。

Helm的安裝

  1. 下載 Helm: 下載頁面有各個作業系統的下載檔案,這邊是官方的安裝指南(Installing Helm)
  2. 解壓 Helm
  3. 將 Helm 的執行文件複製到可執行路徑中,若為linux可能為/usr/local/bin/,若為Window則為C:\Users\my_name
  4. 驗證 Helm 是否正確安裝。運行以下命令應該會顯示 Helm 的版本信息: helm version

Helm的使用指令

這邊是使用的指令碼的介紹: https://helm.sh/docs/intro/using_helm/

創建新的專案可用下面指令

helm install happy-panda bitnami/wordpress

會有類似這樣的資料夾結構

其中Chart.yaml會在創建時設定好,values.yaml可以設定在templates要使用的變數,而templates則是要放我們要部署的YAML設定檔。

接著用下面的指令就可以部署到K8S了!

helm upgrade --install -n stu-srs --set APP_ENV=QAT srs-core1 . --values ./values-core1.yaml --version v1.0.0