分糖果系列問題
作者: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);
}