给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
看到这道题首先想到的是滑动窗口(双指针)感觉似曾相识,然后失败了。因为会出现负数的情况。
下面和可以用之前双指针思路的题目对比一下
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
划重点:递增排序
思路是双指针 i , j 分别指向数组 nums的左右两端,然后往里面移动
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
if(nums == null){
return new int[0];
}
int i = 0;
int j = nums.length-1;
while(i < j){
int sum = nums[i] + nums[j];
if(sum == target){
return new int[]{
nums[i], nums[j]};
}else if(sum < target){
i++;
}else{
j--;
}
}
return new int[0];
}
}
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
划重点:正整数序列 从小到大排列
思路是用i和j两个指针,然后分别往右移动,如果和小于target,则j++,如果和大于target,则i++
代码
class Solution {
public int[][] findContinuousSequence(int target) {
int i = 1;
int j = 2;
int sum = 3;
List<int[]> res = new ArrayList<>();
while(i <= target / 2){
if(sum == target){
int[] ans = new int[j-i+1];
int k = i;
for(int m = 0; m < j-i+1; m++){
ans[m] = k;
k++;
}
res.add(ans);
sum -= i;
i++;
j++;
sum += j;
}else if(sum < target){
j++;
sum += j;
}else{
sum -= i;
i++;
}
}
return res.toArray(new int[res.size()][]);
}
}
然后回到560. 和为K的子数组这道题,没有说从小到大,也不是正整数。不能用滑动窗口的思路,需要用到前缀和的思想。
找前缀和数组中两个数的差==k的情况
。代码
class Solution {
public int subarraySum(int[] nums, int k) {
// key:前缀和,value:key 对应的前缀和的个数
Map<Integer, Integer> preSumFreq = new HashMap<>();
int count = 0;
int preSum = 0; //前缀和
preSumFreq.put(0, 1);
for(int num : nums){
preSum += num;
// 先获得前缀和为 preSum - k 的个数,加到计数变量里
if(preSumFreq.containsKey(preSum - k)){
count += preSumFreq.get(preSum - k);
}
// 维护 preSumFreq
preSumFreq.put(preSum, preSumFreq.getOrDefault(preSum, 0) + 1);
}
return count;
}
}
参考题解
当前前缀和 - k
,说明存在 【之前的某个前缀和】,满足 【当前前缀和】 - 【之前的某个前缀和】 === k,把对应的次数累加到count中。看【之前的某个前缀和】出现几次,count就加几次。给你一个整数数组 nums 和一个整数 k。
如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中「优美子数组」的数目。
和前面NO.560的思路相同,不同的是这里的前缀和不再是真正的前i个数字的和,而是要把前缀和想象成当前的奇数个数。
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
Map<Integer, Integer> preFixCnt = new HashMap<>();
//key是前缀和(当前奇数的个数),value是前缀和的个数
preFixCnt.put(0, 1);
int count = 0;
int sum = 0;
for(int num : nums){
sum += num & 1;//当前奇数的个数
preFixCnt.put(sum, preFixCnt.getOrDefault(sum, 0) + 1);
if(preFixCnt.containsKey(sum - k)){
count += preFixCnt.get(sum - k);
}
}
return count;
}
}
参考题解
用滑动窗口,先找第k个奇数(右边界),然后看到下一个奇数之间的偶数个数,这些偶数都可以作为终点。再找第一个奇数前面的偶数个数,这些偶数都可以作为起点,这样从起点到终点计算子数组个数。然后left++,再往后更新一个奇数,继续计算。
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int left = 0;
int right = 0;
int oddCnt = 0;
int res = 0;
while(right < nums.length){
//右指针先往右走,直到奇数个数为k
if((nums[right++] & 1) == 1){
oddCnt++;
}
if(oddCnt == k){
//找第k+1个奇数
// tmp是第k个奇数右边的偶数,right指向第k+1个奇数
int tmp = right;
while(right < nums.length && (nums[right] & 1) == 0){
right++;
}
int rightCount = right - tmp;//第k个奇数到k+1个奇数中间的偶数个数,都可以作为终点
//找第一个奇数前面偶数的个数,都可以作为起点
//left指向第一个奇数
int leftCount = 0;
while((nums[left] & 1) == 0){
leftCount++;
left++;
}
res += (leftCount + 1) * (rightCount + 1);
left++;
oddCnt--;
}
}
return res;
}
}
给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。
前缀和+哈希表+状态压缩
首先根据题目中“恰好出现偶数次”,想到可以用异或来表示。
状态压缩
:每一位分别表示每一个元音字母出现的次数,1表示出现次数为奇数,0表示出现次数为偶数。例如:假如到第 i 个位置,u o i e a 出现的奇偶性分别为 1 1 0 0 1,那么我们就可以将其压成一个二进制数 (11001)_2=(25) 作为它的状态,则可以用2^5=32位的数组来表示32位的数组中存的是什么
:存的是首次出现这个status的位置前缀和的理解
:当32位数组中下一次再出现这个status时,二者的位置差的这部分子串就是要求的子字符串(偶数个元音异或后那一位还是0)map[0] = 0
:如果第一个是辅音,如果map[0]=-1的话就没办法更新ans了map[status] = i+1
和i+1-map[status]
为什么是i+1而不是i:class Solution {
public int findTheLongestSubstring(String s) {
int[] map = new int[32];
Arrays.fill(map, -1);
int ans = 0;
int status = 0;
map[0] = 0;
for(int i = 0; i < s.length(); i++){
if(s.charAt(i) == 'a'){
status ^= 1<<0;
}
if(s.charAt(i) == 'e'){
status ^= 1<<1;
}
if(s.charAt(i) == 'i'){
status ^= 1<<2;
}
if(s.charAt(i) == 'o'){
status ^= 1<<3;
}
if(s.charAt(i) == 'u'){
status ^= 1<<4;
}
if(map[status] < 0){
//没有出现过,更新map
map[status] = i+1;
}else{
//求最大区间
ans = Math.max(ans, i+1-map[status]);
}
}
return ans;
}
}
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
哈希表
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap<>();
int[] res = new int[2];
for(int i = 0; i < nums.length; i++){
int dif = target - nums[i];
if(map.get(dif) != null){
res[0] = map.get(dif);
res[1] = i;
}
map.put(nums[i],i);
}
return res;
}
}
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
题目直达链接
为两数之和的扩展,分组。AB为一组,CD为一组。
用哈希表存放AB的和。key为AB的和,value为和出现的次数。
然后算CD的和,如果map中存在key为CD和的相反数,则count计数
代码
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
int count = 0;
Map<Integer, Integer> map = new HashMap<>();
//计算AB数组中的和
for(int a : A){
for(int b : B){
int sumAB = a + b;
map.put(sumAB, map.getOrDefault(sumAB, 0) + 1);
}
}
//算CD数组的和,去map里找,找到是CD和的相反数,就是要找的数
for(int c : C){
for(int d : D){
int sumCD = c + d;
if(map.containsKey(-sumCD)){
count += map.get(-sumCD);
}
}
}
return count;
}
}
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
双指针
首先对数组从小到大进行排序,排序后固定一个数 nums[i],然后用i和j两个指针,一个从nums[i+1]往后移动,一个从末尾往前移动。
如果和刚好为0,则添加答案
如果和<0,i++
如果和>0,,j–
注意去重
代码
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList();
int len = nums.length;
if(nums == null || len < 3){
return ans;
}
Arrays.sort(nums);
for(int i = 0 ; i < len-2 ; i++){
if(nums[i] > 0){
break;
}
if(i > 0 && nums[i] == nums[i-1]){
//去重
continue;
}
int L = i+1;
int R = len-1;
while(L < R){
int sum = nums[i] + nums[L] + nums[R];
if(sum == 0){
ans.add(Arrays.asList(nums[i], nums[L], nums[R]));
while(L < R && nums[L] == nums[L+1]){
L++;
}
while(L < R && nums[R] == nums[R-1]){
R--;
}
L++;
R--;
}else if(sum < 0){
L++;
}else{
R--;
}
}
}
return ans;
}
}
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
在三数之和外面多嵌套一层循环,就变成了四数字之和。
同样用双指针
代码
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> ans = new ArrayList<>();
int len = nums.length;
if(nums == null || len < 4){
return ans;
}
Arrays.sort(nums);
for(int i = 0; i < len-3; i++){
if(i > 0 && nums[i] == nums[i-1]) continue;//去重
for(int j = i+1; j < len-2; j++){
if(j > i+1 && nums[j] == nums[j-1]) continue;//去重
int L = j + 1;
int R = len - 1;
while(L < R){
int sum = nums[i] + nums[j] + nums[L] + nums[R];
if(sum == target){
ans.add(Arrays.asList(nums[i], nums[j], nums[L], nums[R]));
while(L < R && nums[L] == nums[L+1]) L++;//去重
while(L < R && nums[R] == nums[R-1]) R--;//去重
L++;
R--;
}else if(sum < target){
L++;
}else if(sum > target){
R--;
}
}
}
}
return ans;
}
}
这样写时间效果不是很好
可以加上加上「值域判断」,进行最大最小值剪枝:
耗时从22ms降到了3ms
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> ans = new ArrayList<>();
int len = nums.length;
if(nums == null || len < 4){
return ans;
}
Arrays.sort(nums);
for(int i = 0; i < len-3; i++){
if(i > 0 && nums[i] == nums[i-1]) continue;//去重
//获取当前最小值,如果最小值比target大,说明后面越来越大的也不可能,直接退出
int min1 = nums[i] + nums[i+1] + nums[i+2] + nums[i+3];
if(min1 > target) break;
//获取当前最小值,如果最大值比target小,说明后面越来越小的也不可能,直接进行下一个i
int max1 = nums[i] + nums[len-1] + nums[len-2] + nums[len-3];
if(max1 < target) continue;
for(int j = i+1; j < len-2; j++){
if(j > i+1 && nums[j] == nums[j-1]) continue;//去重
int L = j + 1;
int R = len - 1;
//当前最小值,如果最小值比target大,忽略
int min2 = nums[i] + nums[j] + nums[L] + nums[L+1];
if(min2 > target) continue;
//当前最大值,如果最大值比target小,忽略
int max2 = nums[i] + nums[j] + nums[R] + nums[R-1];
if(max2 < target) continue;
while(L < R){
int sum = nums[i] + nums[j] + nums[L] + nums[R];
if(sum == target){
ans.add(Arrays.asList(nums[i], nums[j], nums[L], nums[R]));
while(L < R && nums[L] == nums[L+1]) L++;//去重
while(L < R && nums[R] == nums[R-1]) R--;//去重
L++;
R--;
}else if(sum < target){
L++;
}else if(sum > target){
R--;
}
}
}
}
return ans;
}
}
20200628
难度:中等
题目描述
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0。
示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
进阶:
如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-size-subarray-sum
Solution
class Solution {
public int minSubArrayLen(int s, int[] nums) {
if(nums == null || nums.length == 0) return 0;
int i = 0;
int j = 0;
int sum = nums[0];
int ans = Integer.MAX_VALUE;
while(i <= j && j < nums.length){
if(sum < s){
j++;
if(j < nums.length) sum += nums[j];
}else{
ans = Math.min(ans, j-i+1);
sum -= nums[i];
i++;
}
}
if(i == 0 && sum < s) return 0;
return ans;
}
}
复杂度分析
时间复杂度:O(n)。
空间复杂度:O(1)。
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int n = nums.length;
if (n == 0) {
return 0;
}
if(n == 1 && nums[0] >= s) return 1;
int ans = Integer.MAX_VALUE;
int[] sums = new int[n + 1];//存前缀和
for(int i = 1; i < n+1; i++){
sums[i] = sums[i-1] + nums[i-1];
}
//找sums[bound]- sums[i-1] >= s
for(int i = 1; i <= n; i++){
int target = s + sums[i-1];
//二分在sums中找第一个>=target的元素
int l = i;
int r = n;
while(l < r){
int mid = l + (r - l) / 2;
if(sums[mid] < target){
l = mid + 1;
}else{
r = mid;
}
}
//判断是否找到了
if(sums[l] >= target){
ans = Math.min(ans, l-i+1);
}
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}
复杂度分析
Tips:
Java中有现成的库和函数来实现这里二分查找:大于等于target的第一个位置。
int bound = Arrays.binarySearch(sums, target);