程序员代码面试指南第二版 10.最大值减去最小值小于或等于num的子数组数量

welcome to my blog

程序员代码面试指南第二版 10.最大值减去最小值小于或等于num的子数组数量

题目描述

题目描述
给定数组 arr 和整数 num,共返回有多少个子数组满足如下情况:
max(arr[i...j] - min(arr[i...j]) <= num
max(arr[i...j])表示子数组arr[i...j]中的最大值,min[arr[i...j])表示子数组arr[i...j]中的最小值。

输入描述:
第一行输入两个数 n 和 num,其中 n 表示数组 arr 的长度
第二行输入n个整数,表示数组arr中的每个元素

输出描述:
输出给定数组中满足条件的子数组个数

示例1

输入
5 2 
1 2 3 4 5

输出
12
第一次做; 核心: 寻找子数组的方式, 两个约束条件, 维护最大值最小值索引队列; 所有元素索引都会进队一次, 出队一次; 在内循环外面更新res
/*
核心: 正常来说要考察每个子数组是否满足条件, 如何找到所有的子数组? 常规的想法是按照子数组的长度进行寻找,
长为1的子数组, 长为2的子数组, ... ,长为x的子数组; 按长度遍历子数组的话, 需要每次都没从头遍历, 所以这种方法不行
需要找到只需要遍历一次的方式? 
可以按照子数组的起点寻找子数组, 以arr[0]开头的子数组, 以arr[1]开头的子数组, 以arr[2]开头的子数组,...,以arr[x]开头的子数组
要习惯这种找子数组的方式, 结合下面两个约束,因为可以带来极大的便利!
对于子数组arr[i,..,j]来说, 
1)如果arr[i,..,j]中的max - min <= num, 那么子数组arr[i,...,j-1], 子数组arr[i,...,j-2],...,
子数组arr[i,i+1], 子数组arr[i]都满足条件(更新右边界)
2)如果arr[i,...,j]中的max - min > num, 那么包含arr[i,...,j]的子数组都不满足条件, 此时只能以arr[i+1]开头寻找新的子数组(更新左边界)
*/
import java.util.Scanner;
import java.util.LinkedList;

public class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        String curr = sc.nextLine();
        int  n = Integer.parseInt(curr.split(" ")[0]);
        int num = Integer.parseInt(curr.split(" ")[1]);
        String[] str = sc.nextLine().split(" ");
        int[] arr = new int[n];
        for(int i=0; i<arr.length; i++)
            arr[i] = Integer.parseInt(str[i]);
        //
        int res = 0;
        //使用双端队列维护最小值的索引, 队首是当前子数组的最小值索引; 所有元素进队一次, 出队一次
        LinkedList<Integer> qmin = new LinkedList<>();
        //使用双端队列维护最大值的索引, 队首是当前子数组的最大值索引; 所有元素进队一次, 出队一次
        LinkedList<Integer> qmax = new LinkedList<>();
        //initialize
        int left=0, right=0, max, min;
        //每个right走到头或者当前子数组不满足题目不等式时, 还下一个left进行遍历
        while(left < n){
            while(right < n){
                //先往队列中加入元素再进行下一步处理
                //队列为空时
                if(qmin.isEmpty()){
                    qmin.add(right);
                    qmax.add(right);
                    right++;
                }
                //队列不为空时
                else{
                    //维护qmax
                    while(!qmax.isEmpty() && arr[right] > arr[qmax.peekLast()]){
                        qmax.pollLast();
                    }
                    //防止重复加入
                    if(qmax.isEmpty() || qmax.peekLast()!=right)
                        qmax.add(right);
                    //维护qmin
                    while(!qmin.isEmpty() && arr[right] < arr[qmin.peekLast()]){
                        qmin.pollLast();
                    }
                    if(qmin.isEmpty() || qmin.peekLast() != right)
                        qmin.add(right);
                    //进行下一步处理
                    //队首元素不在子数组范围中,就要删掉
                    if(qmax.peek()<left)
                        qmax.poll();
                    if(qmin.peek()<left)
                        qmin.poll();
                    max = arr[qmax.peek()];
                    min = arr[qmin.peek()];
                    if(max - min > num)
                        break;
                    right++;
                }
            }
            //不在内循环中更新res, 因为如果在内循环中更新res的话, 没法考虑到right==n的情况
            res += right - left;
            left++;
        }
        System.out.println(res);
    }
}
左神的高效简洁代码; 和我的逻辑是一样的, 但是进行了综合
import java.util.Scanner;
import java.util.LinkedList;

public class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        String curr = sc.nextLine();
        int  n = Integer.parseInt(curr.split(" ")[0]);
        int num = Integer.parseInt(curr.split(" ")[1]);
        String[] str = sc.nextLine().split(" ");
        int[] arr = new int[n];
        for(int i=0; i<arr.length; i++)
            arr[i] = Integer.parseInt(str[i]);
        //
        int res = 0;
        //使用双端队列维护最小值的索引, 队首是当前子数组的最小值索引; 所有元素进队一次, 出队一次
        LinkedList<Integer> qmin = new LinkedList<>();
        //使用双端队列维护最大值的索引, 队首是当前子数组的最大值索引; 所有元素进队一次, 出队一次
        LinkedList<Integer> qmax = new LinkedList<>();
        //initialize
        int i=0, j=0, max, min;
        //每个right走到头或者当前子数组不满足题目不等式时, 还下一个left进行遍历
        while(i<n){
            while(j<n){
                //维护队列
                if(qmin.isEmpty() || qmin.peekLast() != j){
                    while(!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[j])
                        qmin.pollLast();
                    qmin.addLast(j);
                    while(!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[j])
                        qmax.pollLast();
                    qmax.addLast(j);
                }
                //
                if(arr[qmax.getFirst()] - arr[qmin.getFirst()] > num)
                    break;
                j++;
            }
            res += j - i;
            //本轮外循环最后要进行i++操作, 如果现在的队列首元素==i, 说明队首元素不在下一轮循环的考虑范围(不在新的子数组范围中了)中了, 需要出队
            if(qmin.peekFirst() == i)
                qmin.pollFirst();
            if(qmax.peekFirst() == i)
                qmax.pollFirst();
            i++;
        }
        System.out.println(res);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值