有关数组的算法题

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;
        
    }

 

posted @ 2020-10-04 19:22  拿着放大镜看世界  阅读(372)  评论(0编辑  收藏  举报