分糖果系列問題

分糖果系列問題

作者:Grey

原文地址: 分糖果系列問題

LeetCode 135. Candy

主要思路

本題有一個貪心點,即:針對局部最小值位置,只需要分一顆糖果即可。

什麼是局部最小值?

如果i位置是局部最小值,則有arr[i] < arr[i+1]arr[i] < arr[i-1]。如果是第一個位置,則只需要滿足arr[i] < arr[i+1],如果是最後一個位置,則只需要滿足arr[i] < arr[i-1]

如果某個位置j不是局部最小值所在位置,則有如下兩種情況

第一種情況,如果arr[j] > arr[j-1],則j位置分的糖果數量的一種可能性是是j-1位置分得糖果的數量加1,

第二種情況,如果arr[j] < arr[j+1],則j位置分的糖果數量的另外一個可能性是j+1位置分得糖果的數量加1,

上述兩種情況取相對大的一個,即爲arr[j]上需要分的糖果數量。

如何優雅表示兩種情況呢?我們可以設置兩個數組,長度和原始數組一樣,其中

        int[] left = new int[n]; 
        left[0] = 1;
        for (int i = 1; i < n; i++) {
            left[i] = arr[i] > arr[i - 1] ? left[i - 1] + 1 : 1;
        }

表示arr[j]只考慮和前一個數arr[j-1]得到的結果數組。

        int[] right = new int[n];
        right[n - 1] = 1;
        for (int i = n - 2; i >= 0; i--) {
            right[i] = arr[i] > arr[i + 1] ? right[i + 1] + 1 : 1;
        }

表示arr[j]只考慮和後一個數arr[j+1]得到的結果數組。

最後,每個位置取最大值累加即可

        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += Math.max(left[i], right[i]);
        }

完整代碼如下

    public static int candy(int[] arr) {
        if (null == arr || arr.length == 0) {
            return 0;
        }
        int n = arr.length;
        int[] left = new int[n];
        int[] right = new int[n];
        left[0] = 1;
        for (int i = 1; i < n; i++) {
            left[i] = arr[i] > arr[i - 1] ? left[i - 1] + 1 : 1;
        }
        right[n - 1] = 1;
        for (int i = n - 2; i >= 0; i--) {
            right[i] = arr[i] > arr[i + 1] ? right[i + 1] + 1 : 1;
        }
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += Math.max(left[i], right[i]);
        }
        return sum;
    }

牛客:分糖果問題進階問題

本題和上一問唯一的區別就是,針對相等分數的同學,糖果必須一致,我們可以根據上述代碼稍作改動即可, 見如下注釋部分。

        int[] left = new int[n];
        int[] right = new int[n];
        left[0] = 1;
        for (int i = 1; i < n; i++) {
            if (arr[i] > arr[i - 1]) {
                left[i] = left[i - 1] + 1;
            } else if (arr[i] == arr[i - 1]) {
                // 在相等的情況下,保留前一個位置的值
                left[i] = left[i - 1];
            } else {
                left[i] = 1;
            }
        }
        right[n - 1] = 1;
        for (int i = n - 2; i >= 0; i--) {
            if (arr[i] > arr[i + 1]) {
                right[i] = right[i + 1] + 1;
            } else if (arr[i] == arr[i + 1]) {
                // 在相等的情況下,保留後一個位置的值
                right[i] = right[i + 1];
            } else {
                right[i] = 1;
            }
        }

完整代碼如下

import java.util.Scanner;

// 鏈接:https://www.nowcoder.com/questionTerminal/de342201576e44548685b6c10734716e
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = in.nextInt();
        }
        System.out.println(candy(arr));
        in.close();
    }

    // 進階:相等分數糖果一定要相等
    public static int candy(int[] arr) {
        int n = arr.length;
        int[] left = new int[n];
        int[] right = new int[n];
        left[0] = 1;
        for (int i = 1; i < n; i++) {
            if (arr[i] > arr[i - 1]) {
                left[i] = left[i - 1] + 1;
            } else if (arr[i] == arr[i - 1]) {
                left[i] = left[i - 1];
            } else {
                left[i] = 1;
            }
        }
        right[n - 1] = 1;
        for (int i = n - 2; i >= 0; i--) {
            if (arr[i] > arr[i + 1]) {
                right[i] = right[i + 1] + 1;
            } else if (arr[i] == arr[i + 1]) {
                right[i] = right[i + 1];
            } else {
                right[i] = 1;
            }
        }
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += Math.max(left[i], right[i]);
        }
        return sum;
    }
}

環形分糖果問題

給定一個正數數組arr,表示每個小朋友的得分,任何兩個相鄰的小朋友,如果得分一樣,怎麼分糖果無所謂,但如果得分不一樣,分數大的一定要比分數少的多拿一些糖果,假設所有的小朋友坐成一個環形,返回在不破壞上一條規則的情況下,需要的最少糖果數。

本題的關鍵是如何將環狀數組轉換成非環狀數組。由於是環轉數組,所以,任何位置作爲第一個值都可以。比如:原始數組是: [3, 4, 2, 3, 2]。其答案和如下數組一定是一樣的

[2, 3, 4, 2, 3]
[3, 2, 3, 4, 2]
[2, 3, 2, 3, 4]
[4, 2, 3, 2, 3]

爲了方便,我們可以選取局部最小值位置作爲開頭位置,並在數組末尾增加一個相同的局部最小值,這樣,例如,原始數組中,2位置的2是局部最小值,那麼我們可以選取如下數組

[2, 3, 2, 3, 4]

然後在這個數組後面增加一個同樣的局部最小值2,得到新的數組

[2, 3, 2, 3, 4, 2]

假設這個新數組的的長度是n+1,那麼前n個數用前面的分糖果問題解決得到的答案,就是環形數組需要的答案。之所以在末尾添加一個局部最小值,就是排除掉環形的影響,轉換爲前面分糖果的算法模型。

完整代碼如下

    public static int minCandy(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        if (arr.length == 1) {
            return 1;
        }
        int n = arr.length;
        int minIndex = 0;
        for (int i = 0; i < n; i++) {
            // 尋找一個局部最小值
            if (arr[i] <= arr[last(i, n)] && arr[i] <= arr[next(i, n)]) {
                minIndex = i;
                break;
            }
        }
        // 原始數組爲[3,4,2,3,2]
        // nums數組爲[2,3,2,3,4,2]
        int[] nums = new int[n + 1];
        for (int i = 0; i <= n; i++, minIndex = next(minIndex, n)) {
            nums[i] = arr[minIndex];
        }
        int[] left = new int[n + 1];
        left[0] = 1;
        for (int i = 1; i <= n; i++) {
            left[i] = nums[i] > nums[i - 1] ? (left[i - 1] + 1) : 1;
        }
        int[] right = new int[n + 1];
        right[n] = 1;
        for (int i = n - 1; i >= 0; i--) {
            right[i] = nums[i] > nums[i + 1] ? (right[i + 1] + 1) : 1;
        }
        int sum = 0;
        // 這裏不是到n而是到n-1,因爲n是佔位的,不需要統計進去
        for (int i = 0; i < n; i++) {
            sum += Math.max(left[i], right[i]);
        }
        return sum;
    }

    // 環形數組的下一個位置
    private static int next(int i, int n) {
        return i == n - 1 ? 0 : (i + 1);
    }

    // 環形數組的上一個位置
    private static int last(int i, int n) {
        return i == 0 ? (n - 1) : (i - 1);
    }

更多

算法和數據結構筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章