#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,先准备基础数据再计算。不得不说,作者真是牛。作者代码更注重的细节是:Map<Integer,int[]>numMapMap<Integer, int[]> numMapMap<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);elsecount.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;}