334. 递增的三元子序列
给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。
如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。
示例 1:
输入:nums = [1,2,3,4,5]
输出:true
解释:任何 i < j < k 的三元组都满足题意
示例 2:
输入:nums = [5,4,3,2,1]
输出:false
解释:不存在满足题意的三元组
示例 3:
输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6
提示:
1 <= nums.length <= 10 5
-231 <= nums[i] <= 231 - 1
1.暴力 超时
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
if(nums.size()==0||nums.size()==1||nums.size()==2) return false;//判特
for(int i=0;i<nums.size();i++){
for(int j=i+1;j<nums.size();j++){
if(nums[i]<nums[j]){
for(int k=j+1;k<nums.size();k++){
if(nums[k]>nums[j]) return true;
}
}
}
}
return false;
}
};
改进1:
新建small和mid,分别保存长度为3的递增子序列的最小值和中间值
遍历数组,每遇到一个数字,将它和 small 和 mid 相比,
若<= small ,则替换 small;
否则,若<=mid,则替换 mid;
否则,若> mid,则说明找到了长度为 3 的递增数组
问题:有一个比 small 大比 mid 小的前·最小值出现在 mid 之前
当已经找到了长度为 2 的递增序列,这时又来了一个比 small 还小的数字
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
int len = nums.size();
if (len < 3) return false;
int small = INT_MAX, mid = INT_MAX;
for (auto num : nums) {
if (num <= small) {
small = num;
} else if (num <= mid) {
mid = num;
}
else if (num > mid) {
return true;
}
}
return false;
}
};
改进2:
遍历一遍数组,希望遍历到的这个数three,前面已经有一个比他小的数two,再前面有一个比two小的数one。
我们需要维护两个变量:one和two。代表递增子序列的第一个数和第二个数。
假设我们已经有了这两个数,那么three的大小有以下三种情况:
1.three大于two :即找到了三元组,直接返回true。
2.three介于two和one之间(three<=two && three>one)
应更新two,赋值为这个更小的值。
3.three小于one :应更新one,赋值为这个更小的值。而不需要动two
two附带隐含信息——这之前有个数比two小
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
int one = INT_MAX,two = INT_MAX;
for(int three : nums){ //简化写法
/* for(int i=0;i<nums.size();++i){
int three=nums[i];*/
if(three > two) return true;
else if(three <= one) one = three;
else two = three;
// if(three > one && three < two) two = three;
}
return false;
}
};
2.动态规划 超时
//每次寻找比当前数小的元素个数,dp表示nums的下标对应的最大递增子序列的长度
//当前数分别跟在前面每个数后,如果比那数大,递增序列长度+1。从0到i取最大
//找最大递增序列长度超过3的位置
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
int size = nums.size();
vector<int> dp(size, 1);
for (int i = 0; i < size; ++i) {
for (int j = 0; j < i; ++j) {
if (nums[j] < nums[i]) {
dp[i] = max(dp[i], dp[j] + 1); //动态方程,第i个数大于第j个数
}
if (dp[i] >= 3) return true;
}
}
return false;
}
};
238. 除自身以外数组的乘积
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
原始思路:(没按要求,用了除法)
1.求出所有数字的乘积ans 2.遍历到哪个数,nums[i],就用ans乘以1/nums[i]
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int ans = 1;
int zeroCount = 0;//0的个数
for (int num:nums) {
if(num==0) {
zeroCount++; //0的个数加一
}else{
ans *= num; //求乘积
}
}
if(zeroCount>=2) { //0的个数大于2
for(int i=0;i<nums.size();i++){
nums[i]=0;
}
return nums;
}
if(zeroCount==1) { //0的个数为0
for(int i=0;i<nums.size();i++) {
if(nums[i]==0) {
nums[i] = ans; //等于乘积
}else{
nums[i] = 0;//为0
}
}
return nums;
}
for(int i=0;i<nums.size();i++) {
nums[i] = ans/nums[i];
}
return nums;
}
};//12ms 23.4MB
其他思路:
左右乘积表
利用索引左侧所有数字的乘积和右侧所有数字的乘积(即前缀与后缀)相乘得到答案
1.初始化两个空数组 L 和 R。L[i]代表左侧所有数字的乘积,R[i]代表右侧所有数字的乘积
2.两个循环填充数组L和R的值。
例如L[0]=1,其余:L[i] = L[i-1] * nums[i-1]; R[length - 1] = 1,其余:R[i]=R[i+1]*nums[i+1]
3.结果:L[i] * R[i]
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int length = nums.size();
// L 和 R 分别表示左右两侧的乘积列表
vector<int> L(length, 0), R(length, 0);
vector<int> answer(length);
// L[i] 为索引 i 左侧所有元素的乘积
// 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1
L[0] = 1;
for (int i = 1; i < length; i++) {
L[i] = nums[i - 1] * L[i - 1];
}
// R[i] 为索引 i 右侧所有元素的乘积
// 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1
R[length - 1] = 1;
for (int i = length - 2; i >= 0; i--) {
R[i] = nums[i + 1] * R[i + 1];
}
// 对于索引 i,除 nums[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积
for (int i = 0; i < length; i++) {
answer[i] = L[i] * R[i];
}
return answer;
}
};//16ms 24.3MB
改进:
由于输出数组不算在空间复杂度内,那么我们可以将 L 或 R 数组用输出数组来计算。
把输出数组当作 L 数组来计算,然后再动态构造 R 数组得到结果。
//初始化 answer 数组,对于给定索引 i,answer[i] 代表的是 i 左侧所有数字的乘积(先把 answer 作为方法一的 L 数组)
//唯一变化就是没有构造 R 数组。而是用一个遍历来跟踪右边元素的乘积。并更新数组 answer[i]=answer[i]*Ranswer[i]=answer[i]∗R。然后R更新为 R=R*nums[i]
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int length = nums.size();
vector<int> answer(length);
// answer[i] 表示索引 i 左侧所有元素的乘积
// 因为索引为 '0' 的元素左侧没有元素, 所以 answer[0] = 1
answer[0] = 1;
for (int i = 1; i < length; i++) {
answer[i] = nums[i - 1] * answer[i - 1];
}
// R 为右侧所有元素的乘积
// 刚开始右边没有元素,所以 R = 1
int R = 1;
for (int i = length - 1; i >= 0; i--) {
// 对于索引 i,左边的乘积为 answer[i],右边的乘积为 R
answer[i] = answer[i] * R;
// R 需要包含右边所有的乘积,所以计算下一个结果时需要将当前值乘到 R 上
R *= nums[i];
}
return answer;
}
};
560. 和为K的子数组
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :
数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。
原始思路:超时
//求和为k的子数组的个数(子数组中数字要连续)
//双指针:1.i指向第一个数 2. j指向i后一个数,直到i+j所遍历的数等于K为止 ,j遍历之和记为ans
//若ans<k,判断i+ans是否等于k,其余情况均为不等
//3.ans清零
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int i=0,j,res=0,ans=0;
while(i<nums.size()){
for(j=i+1;j<nums.size();j++){
ans+=nums[j];
if(ans<=k){
if(ans+nums[i]==k) res++;
}
}
ans=0;
}
return res;
}
};
其他思路:
1.暴力枚举 超时
class Solution {
public int subarraySum(vector<int>& nums, int k) {
int len = nums.size();
int sum = 0;
int count = 0;
//双重循环
for (int i = 0; i < len; ++i) {
for (int j = i; j < len; ++j) {
sum += nums[j];
//发现符合条件的区间
if (sum == k) {
count++;
}
}
//记得归零,重新遍历
sum = 0;
}
return count;
}
}
2.前缀思想 超时
通过前缀和数组保存前 n 位的和,presum[1]保存的就是 nums 数组中前 1 位的和,
也就是 presum[1] = nums[0], presum[2] = nums[0] + nums[1] = presum[1] + nums[1].
依次类推,所以我们通过前缀和数组可以轻松得到每个区间的和。
(类似于数列:S5-S2=a3+a4+a5)
class Solution {
public int subarraySum(vector<int>& nums, int k) {
//前缀和数组
int presum[nums.size()+1];
for (int i = 0; i < nums.size(); i++) {
//这里需要注意,我们的前缀和是presum[1]开始填充的
presum[i+1] = nums[i] + presum[i];
}
//统计个数
int count = 0;
for (int i = 0; i < nums.size(); ++i) {
for (int j = i; j < nums.size(); ++j) {
//注意偏移,因为我们的nums[2]到nums[4]等于presum[5]-presum[2]
//所以这样就可以得到nums[i,j]区间内的和
if (presum[j+1] - presum[i] == k) {
count++;
}
}
}
return count;
}
}
3.前缀和 + HashMap
建立哈希表mp,以和为键,出现次数为对应的值,记录pre[i] 出现的次数,从左往右边更新mp 边计算答案
最后的答案即为所有下标结尾的和为k的子数组个数之和
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> mp;
mp[0] = 1;
int count = 0, pre = 0;
for (auto& x:nums) {
pre += x;//前缀和
if (mp.find(pre - k) != mp.end()) {
count += mp[pre - k];
}
mp[pre]++;
}
return count;
}
};