Posted on

最大子序列和問題

概念解釋

最大子序列和問題(Maximum Slice Problem)是指在一個給定的整數序列中,找到一個連續子序列,使得子序列的和最大。

例如,在序列[-2, 1, -3, 4, -1, 2, 1, -5, 4]中,最大的子序列為[4, -1, 2, 1],其和為6。

這個問題在實際應用中經常出現,比如在股票交易中,尋找一段時間內股票價格的最大漲幅,或者在信號處理中,尋找一段時間內信號的最大能量。

最大子序列和問題可以通過暴力枚舉和動態規劃兩種方法解決。暴力枚舉的時間複雜度為O(n^2),動態規劃的時間複雜度為O(n)。其中,動態規劃是更加高效和常用的方法。

下面介紹一下動態規劃的解法思路:

設f[i]表示以第i個元素結尾的最大子序列和,那麼有:

f[i] = max(f[i-1] + nums[i], nums[i])

其中,nums是原始的整數序列,可以看出,f[i]的值只與f[i-1]和nums[i]有關。

狀態轉移方程表示以第i個元素結尾的最大子序列和要么是以第i-1個元素結尾的最大子序列和加上nums[i]得到,要么是只包含第i個元素。

最終的結果就是max(f[0], f[1], …, f[n-1])。

這個算法只需要遍歷一遍整個序列,時間複雜度為O(n),是一個線性算法。因此,在解決最大子序列和問題時,動態規劃是一個高效的解法。

解題思路

假設我們有一個整數序列 nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4],我們要找到一個連續子序列,使得子序列的和最大。

  1. 定義一個狀態數組 f,其中 f[i] 表示以第 i 個元素結尾的最大子序列和。初始時,f[0] = nums[0]。
  2. 現在開始遍歷整個序列,對於每個元素 nums[i],我們需要計算以它為結尾的最大子序列和 f[i]。
    對於 i = 1,我們有:f[1] = max(f[0] + nums[1], nums[1]) = max(-2 + 1, 1) = 1
    對於 i = 2,我們有:f[2] = max(f[1] + nums[2], nums[2]) = max(1 – 3, -3) = -2
  3. 以此類推,我們繼續遍歷整個序列,計算出所有的 f[i]。最後,最大的子序列和即為 max(f[0], f[1], …, f[n-1])。
function maxSubarray(nums) {
  if (!nums || nums.length === 0) {
    return 0;
  }
  let maxSum = nums[0];
  let curSum = nums[0];
  for (let i = 1; i < nums.length; i++) {
    curSum = Math.max(nums[i], curSum + nums[i]);//要不要包含前面的
    maxSum = Math.max(maxSum, curSum);//要不要包含自己
  }
  return maxSum;
}

解題練習 – MaxProfit

題目連結: MaxProfit

function solution(A) {
    if(A.length <= 1){
        return 0
    }
    var diff = []
    var current_diff = 0
    for (var i = 1; i < A.length; i++) {
        current_diff = A[i] - A[i - 1]
        diff.push(current_diff)
    }
    var max_sum = diff[0]
    var curr_sum = diff[0]
    for (var i = 1; i < diff.length; i++) {
        curr_sum = Math.max(diff[i], curr_sum + diff[i]);
        max_sum = Math.max(curr_sum, max_sum);
    }
    return max_sum < 0 ? 0:max_sum
}

https://app.codility.com/demo/results/training4F3SK7-QSS/

解題練習 – MaxSliceSum

題目連結: MaxSliceSum

function solution(A) {
    if(A.length == 0) return 0
    var cur_sum = A[0]
    var max_sum = A[0]
    for(var i=1;i<A.length;i++){
        cur_sum = Math.max(A[i], cur_sum+A[i])
        max_sum = Math.max(max_sum, cur_sum)
    }
    return max_sum
}

https://app.codility.com/demo/results/trainingX4VPNP-3QU/

Posted on

Leader – Dominator解題紀錄

題目內容

題目頁面: https://app.codility.com/programmers/lessons/8-leader/dominator/

Leader概念教學

在LeetCode中,”Leader”通常指的是一個數組中出現次數超過數組長度一半的元素。因為這種元素在數組中出現的次數比其他元素都多,所以被稱為”Leader”。

LeetCode中的一些問題會要求你尋找數組中的Leader或者判斷數組中是否存在Leader。解決這些問題的一種常見方法是使用摩爾投票算法(Moore Voting Algorithm)。

摩爾投票算法的基本思想是遍歷數組,對於當前遍歷到的元素,如果它和當前候選元素相同,則將計數器加一,否則將計數器減一。當計數器為零時,更換當前的候選元素。最後剩下的候選元素就是可能的Leader,需要再次遍歷數組來驗證它是否真的是Leader。

需要注意的是,摩爾投票算法的前提是數組中一定存在Leader,如果數組中不存在Leader,那麼算法得出的結果可能是錯誤的。

摩爾投票算法

摩爾投票算法是一種快速尋找數組中出現次數超過一半的元素的方法,其基本思想已經在之前的回答中進行了介紹。這裡再介紹一下摩爾投票算法的實現方法。

假設我們要在數組中尋找出現次數超過一半的元素,可以使用一個計數器count和一個候選元素candidate來輔助實現。初始時,將count和candidate分別設為0和null。

遍歷數組,對於每一個元素:

  1. 如果count為0,將candidate設置為當前元素;
  2. 如果當前元素和candidate相同,將count加1;
  3. 如果當前元素和candidate不同,將count減1;
  4. 遍歷結束後,candidate就是可能的超過一半的元素,但需要再次遍歷數組來驗證它是否真的出現次數超過一半。

它的時間複雜度是O(n)

解題紀錄

function solution(A) {
    var candidate = 0
    var count = 0
    for(var i=0;i<A.length;i++){
        if(count == 0){
            candidate = A[i]
            count++
        }else if(candidate == A[i]){
            count++
        }else {
            count--
        }
    }
    var idx = []
    for(var i=0;i<A.length;i++){
        if(A[i] == candidate){
            idx.push(i)
        }
    }
    //console.log(idx)
    return idx.length > A.length/2 ? idx[0]:-1
}

https://app.codility.com/demo/results/trainingNPG8GS-3QA/

EquiLeader題目內容

題目連結: https://app.codility.com/programmers/lessons/8-leader/equi_leader/

解題想法

  1. 找出過半的那個數字
  2. 使用前綴和的方式去計算從0到現在的Leader的加總
  3. 計算在某個點的前面、後面是否有占全部長度的一半

我的解題

function solution(A) {
    var candidate = 0
    var count = 0
    for(var i=0;i<A.length;i++){
        if(count == 0){
            candidate = A[i]
            count++
        }else if(candidate == A[i]){
            count++
        }else {
            count--
        }
    }
    var amount_list = []
    var currentCount = 0
    for(var i=0;i<A.length;i++){
        if(A[i] == candidate){
            currentCount++
        }
        amount_list.push(currentCount)
    }
    var equi_leaders = 0
    for(var i=0;i<amount_list.length;i++){
        if(amount_list[amount_list.length-1]-amount_list[i] > (amount_list.length-i-1)/2 && amount_list[i] > (i+1)/2){
            equi_leaders++
        }
    }
    return equi_leaders
}
Posted on

Stacks and Queues – StoneWall解題紀錄

題目內容

題目頁面: https://app.codility.com/programmers/lessons/7-stacks_and_queues/stone_wall/

題目解釋

在這個例子中,最後要建造的牆的寬度是 N = 9 米,高度在不同的位置上是不同的。牆的左端高度是 H[0] = 8,右端高度是 H[8] = 8。

這道題的目標是計算構建牆所需的最小石塊數,因此我們需要找到一種方法來計算構建牆所需的石塊數。我們可以使用一個堆疊來維護已經堆疊的石塊,然後從左到右遍歷數組,將石塊一層一層地堆疊起來。當當前高度高於前面高度時,應該添加一個新石塊。在堆疊石塊時,我們應該儘可能地重複使用現有的石塊,以減少新石塊的使用量。

你要建造一堵石牆,牆長 N 公尺,而牆的厚度是恆定的,但不同的位置高度不同。牆的高度由一個長度為 N 的正整數數組 H 指定。H[i] 表示牆的左端到右側 i 公尺處的高度。特別地,H[0] 是牆的左端高度,H[N-1] 是牆的右端高度。

石牆應由長方體石塊建造,所有面都是矩形。你的任務是計算構建牆所需的最小石塊數。

比方說,給定一個包含 N = 9 個整數的數組 H:

H[0] = 8 H[1] = 8 H[2] = 5 H[3] = 7 H[4] = 9 H[5] = 8 H[6] = 7 H[7] = 4 H[8] = 8

最終要的形狀會是不規則形,現在主要就是要計算有哪些是可以用更大的長方形去包含(把多個石塊用一個長方形合併起來)

對於給定的例子,以下是從左到右處理數組時的石塊堆疊狀態:

第 1 米處:添加一個高度為 8 的新石塊,堆疊中有 1 個石塊(堆疊中的石塊數為 1)。

第 2 米處:與前面的高度相同,不需要添加新的石塊,堆疊中有 1 個石塊(堆疊中的石塊數為 1)。

第 3 米處:當前高度比前面低,需要添加一個高度為 5 的新石塊,堆疊中有 2 個石塊(堆疊中的石塊數為 2)。

第 4 米處:當前高度比前面高,需要添加一個高度為 2 的新石塊,堆疊中有 3 個石塊(堆疊中的石塊數為 3)。

第 5 米處:當前高度比前面高,需要添加一個高度為 4 的新石塊,堆疊中有 4 個石塊(堆疊中的石塊數為 4)。

第 6 米處:當前高度比前面低,需要添加一個高度為 3 的新石塊,堆疊中有 5 個石塊(堆疊中的石塊數為 5)。

….以此類推

解題紀錄

function solution(H) {
    var stack = []
    var total = 0
    for(var i = 0;i<H.length;i++){
        if(stack.length == 0){
            stack.push(H[i])
            total++
        }else if(i > 0 && H[i-1] > H[i]){
            var height = H[i-1]
            while(stack.length > 0){
                height = height - stack.pop()
                if(height < H[i]) {
                    total++
                    stack.push(H[i]-height)
                    break
                }else if(height == H[i]){
                    break
                }
            }
        }else if(i > 0 && H[i-1] < H[i]){
            stack.push(H[i]-H[i-1])
            total++
        }else if(i ==  0 && H[i-1] < H[i]){
            stack.push(H[i])
            total++
        }
        //console.log(i,stack, total)
    }
    return total
}

https://app.codility.com/demo/results/training5QUJPS-FR6/

Posted on

Stacks and Queues – Fish解題紀錄

題目內容

題目頁面: https://app.codility.com/programmers/lessons/7-stacks_and_queues/fish/

題意分析

又是一個有故事的題目,最討厭了,不要管魚的故事的話,大概就是有一個循序數列會從0~N-1這樣子,例如[1,2,3,4,5],但是不會照著順序排列,而是會打散著的排列,如[4,3,2,1,5],然後有另一個陣列B,會指定該數字是往上還是往下,如[0,1,0,0,0],接著往下的若是遇到往上的,就比大小,刪掉較小的數字。

然後回傳最後剩下的數字的數量

解題紀錄

這是我第一次的解題紀錄

function solution(A, B) {
    var point = 0
    while(point < B.length){
        var currnet_direction = B[point]
        if(currnet_direction == 0){//往上
            while(point > 0 && A[point-1] < A[point]){
                //吃掉目標
                A.splice(point,1)
                B.splice(point,1)
            }
            //換目標
            point++
        }else{//往下
            while(point < B.length-1 && A[point+1] < A[point]){
                //吃掉目標
                A.splice(point+1,1)
                B.splice(point+1,1)
            }
            //換目標
            point++
        }
    }
    return A.length
}

只拿了12分,慘不忍睹,應該有思考邏輯上的錯誤。

仔細想一下這一題,我覺得應該會是2N左右的複雜度,就是往上遍歷一次、往下遍歷一次。往下遍歷時,選擇方向為下的,且之後的數字的方向為上的,把該吃掉的吃掉。接著再從下往上一次,選擇方向為上的,且之前的數字的方向為下的,把該吃掉的吃掉,應該就會試剩下的了。

下面為嘗試的實作程式碼

function solution(A, B) {
    var alive_A = []
    var alive_B = []
    // 往下
    while(A.length > 0){
        var target = A.shift()
        var target_direction = B.shift()
        while(target_direction == 1 && B[0] == 0 && target > A[0]){
            A.shift()
            B.shift()
        }
        alive_A.push(target)
        alive_B.push(target_direction)
    }
    A = alive_A
    B = alive_B
    alive_A = []
    alive_B = []
    //往上
    while(A.length > 0){
        var target = A.pop()
        var target_direction = B.pop()
        while(target_direction == 0 && B[B.length-1] == 1 && target > A[A.length-1]){
            A.pop()
            B.pop()
        }
        alive_A.push(target)
        alive_B.push(target_direction)
    }
    return alive_A.length
}

25%,慘不忍睹+1。

我發現問題是他們往上或往下可能不會只有兩次,而是會不停地變換方向,所以應該會需要把這樣的程式碼抽出來,然後使用迴圈或遞迴的去呼叫。但是比較慘的是,下面的效能測試其實是超時的,若是再透過遞迴或者迴圈,整個效能應該會更差。

觀察之後,因為我是整個陣列去掃,而事實上,他們往上或往下的範圍不會是整個陣列,而會是遇到與自己方向不同的範圍。所以我覺得應該不會是完整的掃過兩次迴圈,而是會有兩層的迴圈,然後根據現在的方向,掃到以自己方向為準最多可吃掉的位置做停止,在構想上應該比較類似我第一次的做法。

function solution(A, B) {
    var point = 0
    var finish = 0
    while(A.length > 0){
        if(point >= A.length ){
            point = A.length-1
        }
        //console.log("main", A, B, point)
        if(point == 0 && B[point] == 0){
            finish ++
            A.shift()
            B.shift()
        }else if(point == A.length-1 && B[A.length-1] == 1){
            finish ++
            A.pop()
            B.pop()
        }else{
            var target_direction = B[point] == 0 ? -1:1
            while(A[point] > A[point+target_direction] && B[point] != B[point+target_direction]){
                A.splice(point+target_direction,1)
                B.splice(point+target_direction,1)
            }
            point = point+target_direction
        }
    }
    return finish
}

邏輯正確了,但是效能不佳…要來優化了!

上面的作法會耗時間是因為下面的原因

  1. 使用了 while 迴圈,導致較長的運行時間。while 迴圈對於每條魚的比較都需要遍歷數組中的某些元素。相比之下,前面的實現使用了 for 迴圈,因此在處理每條魚時,它只需要對數組中的一個元素進行操作。
  2. 這個實現使用了多次數組操作,例如 splice()、shift() 和 pop()。這些操作可能會導致重新分配和複製整個數組,導致更長的運行時間。若只使用了一個堆疊紀錄往上的魚,原本的全部都是往下的,操作次數會少得多。

為了解決這個問題,我們可以使用一個堆疊,它用於記錄下游游動的魚,這些魚還沒有被吃掉。從頭到尾遍歷所有魚,並將所有向上游動的魚推入堆疊。如果遇到向下游動的魚,則將其與堆疊中的魚進行比較。如果堆疊中的魚較小,則將其彈出,並繼續與新的堆疊頂部進行比較,直到它遇到較大的魚,或者堆疊為空。

在這個算法中,堆疊中的魚保證向下游動,因此它們是可以被吃掉的魚。當向上游動的魚遇到堆疊頂部的魚時,它們的大小也被比較。如果它們相等,它們都可以保持存活,因為它們不會相互吃掉。如果向上游動的魚比堆疊頂部的魚大,則堆疊中的魚會被吃掉,否則向上游動的魚會被堆疊頂部的魚吃掉。在此過程中,我們不斷更新堆疊的內容,以便在下一輪比較中使用。

最後,堆疊中剩下的所有魚都可以保持存活,因為它們沒有遇到向下游動的魚,或者在它們前面的向下游動的魚都比它們大。

function solution(A, B) {
    var alive = 0
    var downstream = []
    for (var i = 0; i < A.length; i++) {
        if (B[i] == 1) {
            downstream.push(A[i])
        } else {
            //開始吃掉之前的往下的魚直到自己被吃掉
            while (downstream.length > 0 && downstream[downstream.length - 1] < A[i]) {
                downstream.pop()
            }
            if(downstream.length == 0){
                //過關
                alive++
            }
        }
    }
    return alive + downstream.length
}

https://app.codility.com/demo/results/trainingHMNQVG-XRN/

Posted on

Stacks and Queues – Brackets解題紀錄

題目內容

題目頁面: https://app.codility.com/programmers/lessons/7-stacks_and_queues/brackets/

解題紀錄

感覺很簡單的題目,先隨便寫一個答案

function solution(S) {
    var data = S.split("")
    var pair = {'[':']','{':'}','(':')'}
    var queue = []
    for (var i = 0; i < data.length; i++) {
        var start = data[i].match(/[\{\(\[]/)
        if(start != null){
            queue.push(pair[start.input])
        }else {
            var end = queue.pop()
            if(end != data[i]){
                return 0
            }
        }
    }
    return 1
}

這種簡單的題目真的就是陷阱多,這應該是也要考慮到})]為開頭的狀況,要過濾掉這種CASE

function solution(S) {
    //新增檢查邏輯
    if(S.length % 2 == 1 || S.match(/^[\}\)\]]/) != null){
        return 0
    }
    var data = S.split("")
    var pair = {'[':']','{':'}','(':')'}
    var queue = []
    for (var i = 0; i < data.length; i++) {
        var start = data[i].match(/[\{\(\[]/)
        if(start != null){
            queue.push(pair[start.input])
        }else {
            var end = queue.pop()
            if(end != data[i]){
                return 0
            }
        }
    }
    //一定要全部都有成對
    if(queue.length > 0){
        return 0
    }
    return 1
}

這種考細心度的我最吃虧了= =但是還是過了(https://app.codility.com/c/run/training2C9S74-MHH/)

Posted on

Sorting – NumberOfDiscIntersections解題紀錄

題目內容

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

解題思考

這個是在算N個圓是否有交集,例如以題目來說,A[0] = 1 A[1] = 5 A[2] = 2 A[3] = 1 A[4] = 4 A[5] = 0單純以範圍來看,會是這樣的範圍陣列[ -1, 1 ], [ -4, 6 ], [ 0, 4 ], [ 2, 4 ], [ 0, 8 ], [ 5, 5 ],要尋找在這些範圍內取出兩者是有交集的

先用C6取2算出兩兩相配的最多次數,然後找出沒有交集的圈圈,就會是(0,3),(0,5),(2,5),(3,5)沒有交集,我們使用C6取2減掉沒有交集的4個,就會是有交集的數字11

解題紀錄

先用暴力法把沒有交集的找出來

function solution(A) {
    var count = A.length*(A.length-1)/2;
    for(var i=0;i<A.length;i++){
        for(var j = i+1;j<A.length;j++){
            if(i+A[i] < j-A[j]){
                count--;
            }
        }
    }
    return count
}

好,至少答案是正確的,先來想想優化方式

嘗試換個寫法,儲存所有結束的點(照順序排序),然後後面的用開始的點去比較之前的結束的點,看看會不會比前面結束的點更大,若更大代表沒有交集,這樣子做會可以在計算時透過排序去節省時間。

function solution(A) {
    var range = []
    var no_intersect = 0
    for(var i=0;i<A.length;i++){
        var count = binarySearch(range, i-A[i])
        no_intersect += count
        //console.log(range,count, i-A[i])
        range.splice(binarySearch(range, i+A[i]), 0, i+A[i])
    }
    return A.length*(A.length-1)/2 - no_intersect
}
//在一個已排序的陣列裡,取得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
}

應該有邊界性的邏輯錯誤QQ但效能是有一個有通過了,來找找邊界性錯誤的位置…

哈..發現問題了

The function should return −1 if the number of intersecting pairs exceeds 10,000,000

所以就是用第一次的寫法,然後若數字超過直接就返回錯誤。但是因為這樣要知道現在的次數,所以要改一下加總的方式

function solution(A) {
    var count = 0//A.length*(A.length-1)/2;
    for(var i = A.length-1;i >=0;i--){
        for(var j = i-1;j>=0;j--){
            if(i-A[i]<=j+A[j]){
        //console.log(i,j)
                count++
                if(count > 10000000) return -1
            }
        }
    }
    return count
}

原來即便是跑到10000000就return也是超過標準…

經過思考之後,我覺得還是去尋找我第二個做法邏輯錯誤的部分好了…

我覺得應該是我在實作二分搜尋法上面的問題,因為對這個題目而言,應該要尋找”第一個”<=目標值的index,但是因為現在使用的二分搜尋法的邏輯,很可能所選到的不會是第一個,這樣就會造成些微的錯誤,所以會需要在遇到第一個arr[mid] === target的時候,往前搜尋確認現在的mid是第一個大於等於目標值的index

function solution(A) {
    var range = []
    var total = A.length*(A.length-1)/2
    var no_intersect = 0
    for(var i=0;i<A.length;i++){
        var count = binarySearch(range, i-A[i])
        no_intersect += count
        //console.log(range,count, i-A[i])
        range.splice(binarySearch(range, i+A[i]), 0, i+A[i])
        
    }
    var intersecting = total - no_intersect
    return intersecting > 10000000 ? -1: intersecting
}
//在一個已排序的陣列裡,取得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) {
            //在這邊確認拿到的是"第一個"大於等於目標值的數字
            while(mid > 0 && arr[mid-1] === target){
                mid--
            }
            return mid;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return left
}

100%過關!!(https://app.codility.com/demo/results/training7T4RZ2-QP8/)

Posted on

Sorting – Triangle解題紀錄

題目內容

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

題意解析


除了任兩邊的長度必須大於第三邊之外,組成三角形的線段還有一些其他的限制:

  1. 三條邊的長度必須為正數。
  2. 任兩邊的長度之差必須小於第三邊的長度,也就是說,最長的邊的長度必須小於其他兩邊的長度之和。
  3. 任何一邊的長度都不能超過其他兩邊長度之和的兩倍。

如果以上任何一個條件不滿足,則無法組成三角形。

0 ≤ P < Q < R < N

解題過程

先寫一個一定會被打槍的暴力解,複雜度為O(N^3)

function solution(A) {
    for (let i = 0; i < A.length; i++) {
        if (A[i] > 0) {
            for (let j = i + 1; j < A.length; j++) {
                if (A[j] > 0) {
                    for (let k = j + 1; k < A.length; k++) {
                        if (A[k] > 0) {
                            if (A[i] + A[j] > A[k] &&
                                A[j] + A[k] > A[i] &&
                                A[k] + A[i] > A[j]) {
                                return 1;
                            }
                        }
                    }
                }
            }
        }
        return 0;
    }
}

結果0%,,沒一個過的了,不是答案錯誤就是runtime error

直覺猜測,這一定和三角形的定義有關,查了一下谷歌大神,找到這段

當一個陣列已經排好序時,如果能夠構成三角形,那麼任意三個相鄰的元素都能構成三角形。

假設排序後的陣列為arr,元素個數為n。我們使用一個for循環遍歷arr中的元素arr[i]。由於arr已經排好序,因此arr[i]一定是arr[i]、arr[i+1]和arr[i+2]中最小的一個,arr[i+1]是中間的一個,arr[i+2]是最大的一個。如果這三個元素能夠構成三角形,那麼其他任意三個相鄰的元素也能構成三角形。

如果arr[i]+arr[i+1]>arr[i+2],那麼一定存在一組三個相鄰的元素可以構成三角形。因此,在檢查完arr[i]、arr[i+1]和arr[i+2]之後,我們可以繼續檢查arr[i+1]、arr[i+2]和arr[i+3],arr[i+2]、arr[i+3]和arr[i+4],以此類推,直到遍歷完整個陣列。

總之,當一個陣列已經排好序時,只需要檢查相鄰的三個元素是否能夠構成三角形,即可判斷是否存在三個元素能夠構成三角形。這比遍歷所有可能的三元組要更高效。

所以就是要知道這個定理就可以了

function solution(A) {
    A.sort((a, b) => a - b);

    for (let i = 0; i < A.length - 2; i++) {
        if (A[i] + A[i + 1] > A[i + 2]) {
            return 1;
        }
    }

    return 0;
}
Posted on

Sorting – MaxProductOfThree解題紀錄

題目內容

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

題目理解

總之就是取得陣列A裡面,由N個數字所挑選隨意三個相乘的最大值,這些數字可能是-1000~1000,然後陣列長度最少3個最多10萬個

解題紀錄

首先我想到,形成最大值的三個值,當該值為正數時,只有可能由3個正數或2個負數*1個正數組成。當該值為負數時,則有可能由3個負數或2個正數*1個負數組成。所以,現在所需的就是先遍歷整個陣列,然後確認正數和負數的數量。當有可能有正數乘積時,則以正數乘積為優先,否則則需要考慮負數的乘積。

因此,我決定先遍歷A陣列,找尋最大的前三個正數數字以及最小的前三個負數數字(當正值時使用),以及最小的前三個正數數字以及最大的負數數字(當負值時使用),然後統計正值的數量和負值的數量,以決定最後輸出的會是正值還是負值

function solution(A) {
    // Implement your solution here
    var max_positive = new triplet(true)
    var min_positive = new triplet(false)
    var positive_count = 0
    var max_negative = new triplet(true)
    var min_negative = new triplet(false)
    var negative_count = 0
    var has_zero = false
    for (var i = 0; i < A.length; i++) {
        if (A[i] == 0) has_zero = true
        else if (A[i] > 0) {
            max_positive.compareMax(A[i])
            min_positive.compareMin(A[i])
            positive_count++
        } else {
            max_negative.compareMax(A[i])
            min_negative.compareMin(A[i])
            negative_count++
        }
    }
    if (positive_count >= 3 || (negative_count >= 2 && positive_count > 0)) {
        var pos_3 = 0
        var pos_2_neg_1 = 0
        if (positive_count >= 3) {
            pos_3 = max_positive.max * max_positive.middle * max_positive.min
        }
        if (negative_count >= 2 && positive_count > 0) {
            pos_2_neg_1 = min_negative.middle * min_negative.min * max_positive.max
        }
        return Math.max(pos_3, pos_2_neg_1)
    } else if (has_zero) {
        return 0
    } else {
        //確認該使用2個正數*1個負數還是3個負數
        var max_2_pos_1_neg = -Infinity
        var max_3_neg = -Infinity
        if (negative_count > 0 && positive_count >= 2) {
            max_2_pos_1_neg = min_positive.min * min_positive.middle * max_negative.max
        }
        if (negative_count >= 3) {
            max_3_neg = max_negative.max * max_negative.middle * max_negative.min
        }
        return Math.max(max_2_pos_1_neg, max_3_neg)
    }
}
class triplet {
    constructor(isMaxCompare) {
        var data = isMaxCompare ? -Infinity : Infinity
        this.max = data;
        this.middle = data;
        this.min = data;
    }
    getValue() {
        console.log(this.max, this.middle, this.min)
    }
    compareMax(value) {
        if (value > this.max) {
            this.min = this.middle
            this.middle = this.max
            this.max = value
        } else if (value > this.middle) {
            this.min = this.middle
            this.middle = value
        } else if (value > this.min) {
            this.min = value
        }
    }
    compareMin(value) {
        if (value < this.min) {
            this.max = this.middle
            this.middle = this.min
            this.min = value
        } else if (value < this.middle) {
            this.max = this.middle
            this.middle = value
        } else if (value < this.max) {
            this.max = value
        }
    }
}

100%!!!一次成功~感動(https://app.codility.com/demo/results/trainingR9PVFF-P78/)

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大神!!!