题目
我的思路(错误)
根据提示The first chunk can be found as the smallest k for which A[:k+1] == [0, 1, 2, ...k]; then we repeat this process.
,先找到最小的值,包括该值的之前的那一段为一个block,不断重复上述过程,可以确定把数组分成几个block
class Solution {
public int maxChunksToSorted(int[] arr) {
int cnt = 0;
int minIdx = -1;
do {
cnt++;
minIdx = findMin(arr, minIdx+1, arr.length);
} while (minIdx != arr.length - 1);
return cnt;
}
private int findMin(int[] arr, int start, int end) {
int min = 11;
int idx = -1;
for (int i = start; i < end; i++) {
if (arr[i] < min) {
idx = i;
min = arr[i];
}
}
return idx;
}
}
加上一个判断条件,如果该数组中的最大值就在第一位,那么只能分一个block
class Solution {
public int maxChunksToSorted(int[] arr) {
int maxIdx = findMax(arr, 0, arr.length);
if (maxIdx == 0) {
return 1;
}
int cnt = 0;
int minIdx = -1;
do {
cnt++;
minIdx = findMin(arr, minIdx + 1, arr.length);
} while (minIdx != arr.length - 1);
return cnt;
}
private int findMin(int[] arr, int start, int end) {
int minValue = 11;
int idx = -1;
for (int i = start; i < end; i++) {
if (arr[i] < minValue) {
idx = i;
minValue = arr[i];
}
}
return idx;
}
private int findMax(int[] arr, int start, int end) {
int maxValue = -1;
int idx = -1;
for (int i = start; i < end; i++) {
if (arr[i] > maxValue) {
idx = i;
maxValue = arr[i];
}
}
return idx;
}
}
官方思路 贪心
根据题意我们可以得到以下两个定理:
- 定理一:对于某个块A,它由块B和块C组成,即A=B+C。如果块B和块C分别排序后都与原数组排序后的结果一致,那么块A排序后与原数组排序后的结果一致
证明:因为块B和块C分别排序后都与原数组排序后的结果一致,所以块B的元素和块C的元素的并集为原始数组排序后在对应区间的所有元素。而A=B+C,因此将块A排序后与原数组排序后的结果一致
- 定理二:对于某个块A,它由块B和块C组成,即A=B+C。如果块A和块B分别排序后都与原数组排序后一致,那么块C排序后与原数组排序后的结果一致
如果块C排序后与原数组排序后的结果不一致,那么块B的元素和块C的元素的并集不等同于原数组排序后在对应区间的所有元素。而块A排序后与原数组排序后的结果一致,说明块A的元素等同于原数组排序后在对应区间的所有元素。这与块A元素由块B和块C的元素组成矛盾,因此块C排序后与原数组排序后结果一致
假设数组arr一种分割方案为 [ a 0 , a i 1 ] , [ a i 1 + 1 , a i 2 ] , . . . , [ a i m + 1 , a n − 1 ] [a_0,a_{i1}],[a_{i1+1},a_{i2}],...,[a_{im+1},a_{n-1}] [a0,ai1],[ai1+1,ai2],...,[aim+1,an−1],那么由定理一可知, [ a 0 , a i 1 ] , [ a 0 , a i 2 ] , . . . , [ a 0 , a n − 1 ] [a_0,a_{i1}],[a_0,a_{i2}],...,[a_0,a_{n-1}] [a0,ai1],[a0,ai2],...,[a0,an−1]分别排序后与原数组排序后的结果一致。反之,如果 [ a 0 , a i 1 ] , [ a 0 , a i 2 ] , . . . , [ a 0 , a n − 1 ] [a_0,a_{i1}],[a_0,a_{i2}],...,[a_0,a_{n-1}] [a0,ai1],[a0,ai2],...,[a0,an−1]分别排序后与原数组排序后的结果一致,那么,由定理二可知 [ a 0 , a i 1 ] , [ a i 1 + 1 , a i 2 ] , . . . , [ a i m + 1 , a n − 1 ] [a_0,a_{i1}],[a_{i1+1},a_{i2}],...,[a_{im+1},a_{n-1}] [a0,ai1],[ai1+1,ai2],...,[aim+1,an−1]是数组arr一种分割方案
因此,只要找到 [ a 0 , a i 1 ] , [ a 0 , a i 2 ] , . . . , [ a 0 , a n − 1 ] [a_0,a_{i1}],[a_0,a_{i2}],...,[a_0,a_{n-1}] [a0,ai1],[a0,ai2],...,[a0,an−1]的最大数目,就能找到最大分割块数目。因为数组arr的元素在区间 [ 0 , n − 1 ] [0,n-1] [0,n−1]之间且互不相同,所以,数组排序后有 a r r [ i ] = i arr[i]=i arr[i]=i。如果数组arr的某个长为i+1的前缀块 [ a 0 , a i ] [a_0,a_i] [a0,ai]的最大值等于i,那么说明它排序后与原数组排序后的结果一致。统计这些前缀块的数目,就可以得到最大分割块数目
class Solution {
public int maxChunksToSorted(int[] arr) {
int cnt = 0;
int maxValue = -1;
for (int i = 0; i < arr.length; i++) {
maxValue = Math.max(maxValue, arr[i]);
if (maxValue == i) {
cnt++;
}
}
return cnt;
}
}
其他解法
我们定义每个块所处的区间为 [min,max]
遍历时,认为每个数组都是一个单独的块,区间为[num,num]
然后比较当前栈顶快的区间,判断当前num是否包含在块中
设栈顶元素为curInterval
- 若当前num小于nurInterval[0],则进行块合并,合并为[num,curInterval[1]],循环遍历栈顶元素直到大于curInterval[1]
- Q:为什么进行块合并?
- A:若当前元素小于栈顶块的最小值,意味着这排序后会调整到栈顶块中元素的前面,所以必须合并成一个块才能保证所处的位置是正确的。栈内存储的块是严格递增的
-
若当前num大于curInterval[0],且小于curInterval[1],直接合并到栈顶元素的块
-
若当前num大于curInterval[1],入栈
当可以合并的时候,需要弹出栈中元素
原文中使用的是ArrayDeque,我这里使用Stack
import java.util.Stack;
class Solution {
public int maxChunksToSorted(int[] arr) {
Stack<int[]> stack = new Stack<>();
int maxValue, minValue;
int[] curInterval;
for (int num : arr) {
minValue = num;
maxValue = num;
while (!stack.isEmpty() && (num < stack.peek()[0] || num < stack.peek()[1])) {
curInterval = stack.pop();
minValue = Math.min(minValue, curInterval[0]);
maxValue = Math.max(maxValue, curInterval[1]);
}
stack.add(new int[]{minValue, maxValue});
}
return stack.size();
}
}