930. 和相同的二元子数组/238. 除自身以外数组的乘积/1262. 可被三整除的最大和/NC90 设计getMin功能的栈/NC67连续子数组的最大和/NC115 栈和排序

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了930. 和相同的二元子数组/238. 除自身以外数组的乘积/1262. 可被三整除的最大和/NC90 设计getMin功能的栈/NC67连续子数组的最大和/NC115 栈和排序相关的知识,希望对你有一定的参考价值。

930. 和相同的二元子数组

2021.7.8每日一题

题目描述

给你一个二元数组 nums ,和一个整数 goal ,请你统计并返回有多少个和为 goal 的 非空 子数组。

子数组 是数组的一段连续部分。


示例 1:

输入:nums = [1,0,1,0,1], goal = 2
输出:4
解释:
如下面黑体所示,有 4 个满足题目要求的子数组:
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
示例 2:

输入:nums = [0,0,0,0,0], goal = 0
输出:15

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-subarrays-with-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

前几天刚做过,前缀和加哈希表是一种方法,需要预先在哈希表中添加(0,1)键值对

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        int l = nums.length;
        int[] pre = new int[l + 1];
        int res = 0;
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        for(int i = 0; i < l; i++){
            pre[i + 1] = pre[i] + nums[i];
            if(map.containsKey(pre[i + 1] - goal))
                res += map.get(pre[i + 1] - goal);
            map.put(pre[i + 1], map.getOrDefault(pre[i + 1], 0) + 1);
        }
        return res;
    }
}

看示例很容易想到滑动窗口
这里和一般的滑动窗口还是有点区别的,区别在于left指针是一个范围,我想到的计算范围的方法是每次都计算一遍当前符合要求的范围,繁琐了,超过10%

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        //双指针
        int l = nums.length;
        int left = 0;
        int right = 0;
        int sum = 0;
        int res = 0;
        while(right < l){
            sum += nums[right];
            while(left < right && sum > goal){
                sum -= nums[left];
                left++;
            }
            int index = left;
            int tempsum = sum;
            while(tempsum == goal && index <= right){
                res++;
                tempsum -= nums[index++];
            }
            right++;
        }
        return res;
    }
}

看了题解,这个范围可以用两个left指针来确定。
其中两次滑动,第一次确定left的左范围,第二次确定left的右范围
关于右范围的确定,比左范围就加了个等于号
为什么这样是可行的,这是因为给出的数组是一个二元组,所以得到left右范围以后,可以保证left右范围左边到left左范围之间内的下标和right组成的子数组都是满足条件的
这个用法针对这个题,很巧妙,学习了,这样一改超过99%

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        int l = nums.length;
        int left1 = 0, left2 = 0, right = 0;
        int sum1 = 0, sum2 = 0;
        int res = 0;
        while (right < l) {
            sum1 += nums[right];
            //第一个常规的循环,判断left的左范围
            while (left1 <= right && sum1 > goal) {
                sum1 -= nums[left1];
                left1++;
            }
            //第二个循环,判断left的右边范围,这里的操作就是等于也向右边移动
            sum2 += nums[right];
            while (left2 <= right && sum2 >= goal) {
                sum2 -= nums[left2];
                left2++;
            }
            //为什么可以直接加两个左范围之差呢,因为题目保证了这个数组是一个二元组,只有0和1,
            //所以这里保证了第二次循环得到的left2肯定在符合条件最右左端点的右边
            //这个用法针对于这个题,很巧
            res += left2 - left1;
            right++;
        }
        return res;
    }
}

238. 除自身以外数组的乘积

朋友写了两个题解,做一下去给点个赞

题目描述

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。


示例:

输入: [1,2,3,4]
输出: [24,12,8,6]
 
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。

说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。

进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/product-of-array-except-self
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

前后缀,O(n)空间复杂度的

class Solution {
    public int[] productExceptSelf(int[] nums) {
        //前后缀,好像是剑指offer里面的一道题
        int l = nums.length;
        int[] pre = new int[l + 1];
        pre[0] = 1;
        int[] post = new int[l + 1];
        post[l] = 1;
        int[] res = new int[l];
        for(int i = 0; i < l; i++){
            pre[i + 1] = pre[i] * nums[i];
            post[l - 1 - i] = post[l - i] * nums[l - 1 - i]; 
        }
        for(int i = 0; i < l; i++){
            res[i] = pre[i] * post[i + 1];
        }
        return res;
    }
}

O(1)空间复杂度的

class Solution {
    public int[] productExceptSelf(int[] nums) {
        //前后缀,好像是剑指offer里面的一道题
        //O(1)空间复杂度,就是将输出数组变成前缀
        int l = nums.length;
        int[] res = new int[l];
        res[0] = 1;
        //先处理前缀
        for(int i = 1; i < l; i++){
            res[i] = res[i - 1] * nums[i - 1];
        }
        int post = 1;
        for(int i = l - 2; i >= 0; i--){
            post = post * nums[i + 1];
            res[i] = post * res[i];
        }
        return res;
    }
}

1262. 可被三整除的最大和

题目描述

给你一个整数数组 nums,请你找出并返回能被三整除的元素最大和。
	
示例 1:

输入:nums = [3,6,5,1,8]
输出:18
解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。
示例 2:

输入:nums = [4]
输出:0
解释:4 不能被 3 整除,所以无法选出数字,返回 0。
示例 3:

输入:nums = [1,2,3,4,4]
输出:12
解释:选出数字 1, 3, 4 以及 4,它们的和是 12(可被 3 整除的最大和)。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/greatest-sum-divisible-by-three
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

有点特别的动规,因为数据范围的缘故,不能直接用背包问题求解。所以想到第二维将目标值变成了余数,分别对应0 1 2
状态转移呢,就是根据余数转移的,刚开始写了个二维的,但是运行的时候发现后面会将前面变更的结果覆盖,导致最后输出有问题,所以直接去掉写了个一维的

class Solution {
    public int maxSumDivThree(int[] nums) {
        //动规
        //定义dp[i][j]为到下标i为止,除以3余数为0 1 2 的最大和
        //当前数除以3余数为t
        //dp[i][0] = dp[i - 1][0], 

        int l = nums.length;
        
        int[] dp = new int[3];
        for(int i = 0; i < l; i++){
            //加上当前数
            int temp0 = dp[0] + nums[i];
            int temp1 = dp[1] + nums[i];
            int temp2 = dp[2] + nums[i];
            //状态转移,取余的结果等于,之前这个dp[i][yushu]和当前结果的较大者
            dp[temp0 % 3] = Math.max(dp[temp0 % 3], temp0);
            dp[temp1 % 3] = Math.max(dp[temp1 % 3], temp1);
            dp[temp2 % 3] = Math.max(dp[temp2 % 3], temp2);
        }
        return dp[0];
    }
}

弄不出二维的不甘心,又去改了一下,主要问题是在某一次遍历中,有的余数可能会取不到,所以导致没办法更新,所以加一个覆盖的代码就行了

class Solution {
    public int maxSumDivThree(int[] nums) {
        //动规
        //定义dp[i][j]为到下标i为止,除以3余数为0 1 2 的最大和
        //当前数除以3余数为t
        //dp[i][0] = dp[i - 1][0],

        int l = nums.length;

        int[][] dp = new int[l + 1][3];
        for(int i = 0; i < l; i++){
            for(int j = 0; j < 3; j++) {
                //加上当前数
                int temp = dp[i][j] + nums[i];
                //状态转移,取余的结果等于,之前这个dp[i][yushu]和当前结果的较大者
                //因为可能会重复计算当前余数的情况,所以要再取一次最大值
                int max = Math.max(dp[i][temp % 3], temp);
                dp[i + 1][temp % 3] = Math.max(dp[i + 1][temp % 3], max);
                //因为在上述过程中,有的值更新不到,所以这里用上面的值覆盖一下
                dp[i + 1][j] = Math.max(dp[i + 1][j], dp[i][j]);
            }
        }

        return dp[l][0];
    }
}

NC90 设计getMin功能的栈

题目描述

实现一个特殊功能的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
示例1
输入:
[[1,3],[1,2],[1,1],[3],[2],[3]]
返回值:
[1,2]
备注:
有三种操作种类,op1表示push,op2表示pop,op3表示getMin。你需要返回和op3出现次数一样多的数组,表示每次getMin的答案

1<=操作总数<=1000000
-1000000<=每个操作数<=1000000
数据保证没有不合法的操作

思路

牛客这题出的,真的是迷一样…都不知道该写点啥,示例也说的不明白

import java.util.*;


public class Solution {
    /**
     * return a array which include all ans for op3
     * @param op int整型二维数组 operator
     * @return int整型一维数组
     */
    //单调栈,试着猜测一下怎么写吧.....
    Stack<Integer> stack1 = new Stack<>();
    Stack<Integer> stack2 = new Stack<>();    //单调栈
    public int[] getMinStack (int[][] op) {
        // write code here
        int l = op.length;
        List<Integer> list = new ArrayList<>();
        for(int i = 0; i < l; i++){
            if(op[i][0] == 1){
                push(op[i][1]);
            }else if(op[i][0] == 2){
                pop();
            }else{
                list.add(getMin());
            }
        }
        int[] res = new int[list.size()];
        for(int i = 0; i < list.size(); i++){
            res[i] = list.get(i);
        }
        return res;
    }
    
    public void push(int t){
        stack1.push(t);
        if(stack2.isEmpty() || stack2.peek() > t){
            stack2.push(t);
        }
    }
    
    public void pop(){
        int t = stack1.pop();
        if(stack2.peek() == t)
            stack2.pop();
    }
    
    public int getMin(){
        return stack2.peek();
    }
}

NC67连续子数组的最大和

题目描述

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n).
示例1
输入:
[1,-2,3,10,-4,7,2,-5]
返回值:
18
说明:
输入的数组为{1,-2,3,10,—4,7,2,一5},和最大的子数组为{3,10,一4,7,2},因此输出为该子数组的和 18。

思路

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int l = array.length;
        int[] dp = new int[l];
        dp[0] = array[0];
        int max = dp[0];
        for(int i = 1; i < l; i++){
            dp[i] = Math.max(dp[i - 1] + array[i], array[i]);
            max = Math.max(max, dp[i]);
        }
        return max;
    }
}

NC115 栈和排序

题目描述

给你一个1->n的排列和一个栈,入栈顺序给定
你要在不打乱入栈顺序的情况下,对数组进行从大到小排序
当无法完全排序时,请输出字典序最大的出栈序列
示例1
输入:
[2,1,5,3,4]

返回值:
[5,4,3,1,2]

说明:
2入栈;1入栈;5入栈;5出栈;3入栈;4入栈;4出栈;3出栈;1出栈;2出栈
备注:
n<=1e6

思路

这个题花了很长时间,实在不应该
思路就是找到当前最大的数,然后开始拼接,但是第二大的数可能已经在之前出现过了,所以要用一个哈希表来记录出现过的数,每次都得到当前应该取的“最大数”。如果栈顶的数比这个数大,那么说明拼接到结果上结果是合理的;如果栈顶的数比这个数小,那么这个数在后面,拼接后面的这个数比当前栈顶的数得到的结果要大,所以继续向后遍历

import java.util.*;


public class Solution {
    /**
     * 栈排序
     * @param a int整型一维数组 描述入栈顺序
     * @return int整型一维数组
     */
    public int[] solve (int[] a) {
        // write code here
        //感觉也是做过的一道题,但怎么描述的就这么拗口了....
        //先出最大数,然后往后依次出就行了
        int l = a.length;
        int cur = l;
        int[] res = new int[l];
        int index = 0;
        Stack<Integer> stack = new Stack<>();
        Set<Integer> set = new HashSet<>();
        for(int t : a){
            stack.push(t);
            set.add(t);
            //如果有当前数,就一直减,直到再小的数没有出现
            while(set.contains(cur))
                cur--;
            //如果此时栈顶的数大于这个数,那么拼接到后面是大的,所以就弹出
            //如果小于这个数,那么说明这个数在后面,拼接后面的这个数得到的序列会更大
            while(!stack.isEmpty() && cur < stack.peek()){
                res[index++] = stack.pop();
            }
        }
        return res;        
    }
}

以上是关于930. 和相同的二元子数组/238. 除自身以外数组的乘积/1262. 可被三整除的最大和/NC90 设计getMin功能的栈/NC67连续子数组的最大和/NC115 栈和排序的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode238. 除自身以外数组的乘积

力扣238(java)-除自身以外数组的乘积(中等)

238. 除自身以外数组的乘积

数组238. 除自身以外数组的乘积

238. 除自身以外数组的乘积

238. 除自身以外数组的乘积