#697 Degree of an Array
我承认慢慢有了思路的前提是你要见过那些解法,否则怎么想也想不到。多做题目,就像是多看书一样重要。
问题:一个数组的度=这个数组中出现次数最多元素的出现次数。要找的是最短的子数组,而这个数组的度=原数组的度。
思路一:我肯定需要一次循环,找到数组的度;接着计算每个子数组,计算它们的度,找到和原数组的度相同的最短的子数组。每个子数组就是从下标0开始的子数组,从下标1开始的子数组…。所以有了如下代码。代码时间复杂度O(n^2),发生TLE。
public int findShortestSubArray(int[] nums) {
// 数组的度
Map<Integer, Integer> countMap = new HashMap<Integer, Integer>();
int degree = 0;
for (int num : nums) {
if (countMap.get(num) == null) {
countMap.put(num, 1);
} else {
countMap.put(num, 1 + countMap.get(num));
}
degree = Math.max(degree, countMap.get(num));
}
// 找子数组
int minlength = nums.length;
for (int start = 0; start < nums.length; start++) {
Map<Integer, Integer> subCoutMap = new HashMap<Integer, Integer>();
int subDegree = 0;
for (int end = start; end < nums.length; end++) {
int num = nums[end];
if (subCoutMap.get(num) == null) {
subCoutMap.put(num, 1);
} else {
subCoutMap.put(num, 1 + subCoutMap.get(num));
}
subDegree = Math.max(subDegree, subCoutMap.get(num));
if(subDegree == degree){
minlength = Math.min(end-start+1, minlength) ;
break;
}
}
}
return minlength;
}
思路二:需要把两层循环改为1层。观察例子中给出的子数组:[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2],符合条件的是[2,2]。既然数组的度是由出现次数最多的元素的频次贡献的,那子数组中肯定包含这个元素。要求最短,那子数组的起始元素和结束元素肯定都是这个元素。所以思路改为:
1 需要一次循环,找到数组的度;
2接着再循环找到这个度是由哪个元素贡献的。例如数组 [1, 2, 2, 3, 1]的度是2,是由元素2贡献的。找到2这个元素;
3最后要循环找到这个元素的起止位置,计算子数组的长度。
所以有了如下代码。注意的是:出现次数最多的元素可能不止一个。
public int findShortestSubArrayV2(int[] nums) {
// 数组的度
Map<Integer, Integer> countMap = new HashMap<Integer, Integer>();
int degree = 0;
for (int num : nums) {
if (countMap.get(num) == null) {
countMap.put(num, 1);
} else {
countMap.put(num, 1 + countMap.get(num));
}
degree = Math.max(degree, countMap.get(num));
}
List<Integer> elementList = new ArrayList<Integer>();
for(Integer num : countMap.keySet()){
if(countMap.get(num)==degree){
elementList.add(num);
}
}
int minLength = nums.length;
for(int element : elementList){
int subDegree = 0;
int start = -1;
for (int i = 0; i < nums.length; i++) {
if(nums[i] == element){
if(start == -1){
start = i;
}
subDegree++;
if(subDegree == degree){
minLength = Math.min(minLength, i-start+1);
break;
}
}
}
}
return minLength;
}
思路三:上面的三步有没有可以合并的呢?是不是可以在计算数组的度的时候,顺便记录下每个元素的起止位置呢?当然可以。第二步寻找出现次数等于数组度的元素,和计算子数组长度放在一起。于是有了以下代码。
public int findShortestSubArrayV3(int[] nums) {
Map<Integer, Integer> countMap = new HashMap<Integer, Integer>();
Map<Integer, Integer[]> numIndexMap = new HashMap<Integer, Integer[]>();
int degree = 0;
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
if (countMap.get(num) == null) {
countMap.put(num, 1);
} else {
countMap.put(num, 1 + countMap.get(num));
}
degree = Math.max(degree, countMap.get(num));
if(numIndexMap.get(num)==null){
numIndexMap.put(num, new Integer[]{i,i});
}else{
numIndexMap.get(num)[1] = i;
}
}
int minLength = nums.length;
for(int num : countMap.keySet()){
if(countMap.get(num) == degree){
minLength = Math.min(minLength, numIndexMap.get(num)[1] - numIndexMap.get(num)[0]+1);
}
}
return minLength;
}
一步一步改进自己的思路。从最直觉入手。改进的依据是观察标准答案的特征;缩短使用时间。
思路四:看了discuss。两个map合并为一个map,先准备基础数据再计算。不得不说,作者真是牛。作者代码更注重的细节是: M a p < I n t e g e r , i n t [ ] > n u m M a p Map<Integer, int[]> numMap Map<Integer,int[]>numMap 而不是$ Map<Integer, Integer[]> numMap $,我试过了,速度更快。map的get方法尽量调用一次(看我上面代码就知道,我不是这样做的)。作者在最后遍历的是numMap.values(),速度更快。
public int findShortestSubArrayV4(int[] nums) {
if (nums.length == 0 || nums == null) return 0;
Map<Integer, int[]> numMap = new HashMap<Integer, int[]>();
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
if (numMap.get(num) == null) {
numMap.put(num, new int[]{1,i,i});
} else {
int[] temp = numMap.get(num);
temp[0]++;
temp[2]=i;
}
}
int degree = 0;
int minLength = nums.length;
for(int[] values : numMap.values()){
if(degree < values[0]){
degree = values[0];
minLength = values[2]-values[1] +1;
}else if(degree == values[0]){
minLength = Math.min(minLength, values[2]-values[1] +1);
}
}
return minLength;
}
思路5:第二遍刷题。观察到了需要找到出现最多次数元素最左边、最右边的位置。
public int findShortestSubArray(int[] nums) {
Map<Integer,Integer> left = new HashMap<Integer,Integer>();
Map<Integer,Integer> right = new HashMap<Integer,Integer>();
Map<Integer,Integer> count = new HashMap<Integer,Integer>();
int degree = 0;
for(int i=0;i<nums.length;i++){
if(left.get(nums[i])==null) left.put(nums[i],i);
right.put(nums[i],i);
if(count.get(nums[i])==null)
count.put(nums[i],1);
else
count.put(nums[i],count.get(nums[i])+1);
degree = Math.max(degree,count.get(nums[i]));
}
int answer = nums.length;
for(Integer num : left.keySet()){
if(count.get(num)==degree){
answer = Math.min(answer,right.get(num)-left.get(num)+1);
}
}
return answer;
}