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 栈和排序的主要内容,如果未能解决你的问题,请参考以下文章