有关数组的算法题
1.找到最大值减去最小值小于等于一个数值的子数组数量
如果L~R范围上达标,那么里面的任何一个子数组都达标
如果L~R范围上不达标,当R向右扩时,必定不达标。
所有我们只需要遍历一次,每次找到以L开头的子数组达标的子数组数量。
使用滑动窗口,这里用到两个滑动窗口。特别简单,就是保持队列里面的大小顺序,当加入的数值破坏了规矩,那么就把队列的队头给弹出。
public class AllLessNumSubArray { public static int getNum(int[] arr ,int num){ if (arr == null || arr.length == 0) { return 0; } LinkedList<Integer> qmin = new LinkedList<Integer>(); LinkedList<Integer> qmax = new LinkedList<Integer>(); int L = 0; int R = 0; int res = 0; while(L < arr.length){ //L确定了之后,R往右扩. while(R < arr.length){ while(!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[R]){ qmin.pollLast(); } qmin.addLast(R); while(!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]){ qmin.pollLast(); } qmax.addLast(R); //如果达标了,就可以得出R-L个子数组 if(arr[qmax.getFirst()] - arr[qmin.getFirst()] > num){ break; } R++; } if (qmin.peekFirst() == L) { qmin.pollFirst(); } if (qmax.peekFirst() == L) { qmax.pollFirst(); } res += R -1; //如果R了不能扩了,就扩L L++; } return res; } }
2.找到累加和为特定值的最长子串(有0有正有负)
输入:[7,3,2,1,1,7,-6,-1,7] aim = 7 输出: 7 解释: [3,2,1,1,7,-6,-1,7]
这种类型的子串问题,是一种算法模型,就是遍历以每个位置结尾的子串。
这道题的解题思路就是,假设当0~x的子串的累加和为1000,那么只要找到从0开始到在x之前的下标最早能够形成200的子串,那么后面那段子串即为形成800累加和的最长子串
所以我们生成一个map,里面存放着每一个累加和最早出现的下标位置,key为累加和,value为出现时下标。
比如这个例子的map就为{(0,-1),(7,0),(10,1),(12,2),(13,3),(14,4),(21,5),(15,6)}这里累加和为21出现了两次,但是我们只需要记录第一次出现的下标和累加和就行。
public static int maxLength(int[] arr, int k) { if (arr == null || arr.length == 0) { return 0; } HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(); map.put(0, -1); // important int len = 0; int sum = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; if (map.containsKey(sum - k)) { len = Math.max(i - map.get(sum - k), len); } if (!map.containsKey(sum)) { map.put(sum, i); } } return len; }
3.找到奇偶个数相等的最长子串
这也是根据上面的算法模型来解决的算法题,奇偶我们可以把它们分别看成1和-1,所以这道题就可以变成找到 sum = 0 的最长子串。
4.求异或和为0的子数组个数
输入:[3,2,1,0,1,2,3,0] 输出:4 解释:[3,2,1]和[0]和[1,2,3]和[0]
public static int mostEOR(int[] arr){ int ans = 0; int xor = 0; int[] dp = new int[arr.length]; HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(); map.put(0, -1);//异或和为0的,下标0之前也能得到,所以是(0,-1) for (int i = 0; i < arr.length; i++) { xor^=arr[i]; //每个位置累加异或和 if (map.containsKey(xor)) {//如果map已经存了这个异或和 int pre = map.get(xor); dp[i] = pre == -1 ? 1 : (dp[pre] + 1); } if (i >0) { dp[i] = Math.max(dp[i - 1],dp[i]); } map.put(xor,i); ans = Math.max(ans, dp[i]); } return ans;//子串数量 }
5.求累加和为特定值的最长子数组(arr[]没有负数)
这道题给的条件比较特殊,也导致这道题比较简单,因为只有正数,所有整个数组都是单调的,只要我们不断地向右累加,那么累加和必定增加,所以我们使用滑动窗口,当sum<=aim时,R向右移动,当sum>aim时,L向右移动,在此过程中,指针永远往右滑,并且永不回退.
public static int getMaxLength(int[] arr, int k) { if (arr == null || arr.length == 0 || k <= 0) { return 0; } int L = 0; int R = 0; int sum = arr[0]; int len = 0; while (R < arr.length) { if (sum == k) { len = Math.max(len, R - L + 1); sum -= arr[L++]; } else if (sum < k) { R++; if (R == arr.length) { break; } sum += arr[R]; } else { sum -= arr[L++]; } } return len; }
6.求累加和<=aim的最长子数组(arr[]有正有负有零)
此时我们需要有两个辅助数组
arr [7 5 5 -3 -1]
index [0 1 2 3 4]
min_sum[7 5 1 -4 -1] 以 i 开始的所有子数组的最小累加和
min_sum_index[0 1 4 4 4] 取得最小累加和的右边界
这里值得强调的是,我们求min_sum数组的时候,我们要从右往左倒过来求值,最右的最小累加和就是自己,然后等到中间的位置,只要我们发现右边的最小累加和<0,那么我们只要把右边的最小累加和+自己的值就能得出自己的最下累加和,下标也可以直接取他右边的右边界,如果发现右边的最小累加和>0,那么我们最小累加和就是自己,下标为当前下标
所以当我们知道了以 i 开始的所有子数组的最小累加和,那么我们去找最长子数组的时候就能跳得比较快,但此时也是需要双指针.
public static int maxLengthAwesome(int[] arr, int aim) { if (arr == null || arr.length == 0) { return 0; } int[] min_sum = new int[arr.length]; int[] min_sum_index = new int[arr.length]; min_sum[arr.length-1] = arr[arr.length-1]; min_sum_index[arr.length-1] = arr.length-1; for (int i = arr.length - 2; i >= 0; i--) { if (min_sum[i + 1] < 0) { min_sum[i] = arr[i] + min_sum[i + 1]; min_sum_index[i] = min_sum_index[i+1]; } else { min_sum[i] = arr[i]; min_sum_index[i] = i; } } int R = 0; int sum = 0; int len = 0; for (int start = 0; start < arr.length; start++) { while (R < arr.length && sum + min_sum[R] <= aim) { sum += min_sum[R]; R = min_sum_index[R]+1; } sum -= R > start ? arr[start] : 0; len = Math.max(len, R - start); R = Math.max(R, start + 1); } return len; }
7.找到累加和最大的子数组(有正有负有零)
其实这道题也挺简单的,创建一个max,然后不断更新
这道题的关键就在于,如果一个子数组的累加和最大,那么该子数组的左右两边数组的累加和必<0.
所以我们遍历的时候不断地去更新最大累加和的值,当sum<0时,清零,重新计算。
即使数组中的数都为负数,一开始遇到负数,max也会更新,sum随后清零。
public int getBiggestSumSubArray(int[] arr){ if (arr == null || arr.length == 0) { return 0; } int maxSum = Integer.MIN_VALUE; int cur = 0; for (int i = 0; i < arr.length; i++) { cur += arr[i]; maxSum = Math.max(cur, maxSum); cur = cur < 0 ? 0 : cur ; } return maxSum; }