1. 长度最小的子数组 题目描述 给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0 。
示例 1:
1 2 3 输入:target = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
1 2 输入:target = 4, nums = [1,4,4] 输出:1
示例 3:
1 2 输入:target = 11, nums = [1,1,1,1,1,1,1,1] 输出:0
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int minSubArrayLen (int target, int [] nums) { int n = nums.length; int ans = n + 1 ; int sum = 0 ; int left = 0 ; for (int right = 0 ; right < n; right++) { sum += nums[right]; while (sum - nums[left] >= target) { sum -= nums[left]; left++; } if (sum >= target) { ans = Math.min(ans, right - left + 1 ); } } return ans <= n ? ans : 0 ; } }
优化
只要达到target ,就更新最小数组长度,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int minSubArrayLen (int target, int [] nums) { int left = 0 ; int right = 0 ; int res = Integer.MAX_VALUE; int sum = 0 ; while (right < nums.length) { sum += nums[right]; right++; while (sum >= target) { res = Math.min(res, right - left); sum -= nums[left]; left++; } } return res == Integer.MAX_VALUE ? 0 : res; } }
题目描述 给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
示例 1:
1 2 3 输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。
示例 2:
1 2 3 输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
1 2 3 4 输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
0 <= s.length <= 105
s 由英文字母、数字、符号和空格组成
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int lengthOfLongestSubstring (String s) { char [] S = s.toCharArray(); int n = S.length; int [] cnt = new int [128 ]; int ans = 0 ; int left = 0 ; for (int right = 0 ; right < n; right++) { char c = S[right]; cnt[c]++; while (cnt[c] > 1 ) { cnt[S[left]]--; left++; } ans = Math.max(ans, right - left + 1 ); } return ans; } }
思路 1 用每个出现过的字符作为集合的索引,索引>1时,移除左边元素直到索引减为1,记录最长长度
题目描述 给定一个整数数组 nums 和一个整数 k。
请你返回 子数组 内所有元素的 乘积严格小于 k 的连续子数组的数目。
示例 1:
1 2 3 4 输入:nums = [10,5,2,6], k = 100 输出:8 解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2]、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。 需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。
示例 2:
1 2 输入:nums = [1,2,3], k = 0 输出:0
提示:
1 <= nums.length <= 3 * 104
1 <= nums[i] <= 1000
0 <= k <= 106
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int numSubarrayProductLessThanK (int [] nums, int k) { if (k <= 1 ) { return 0 ; } int n = nums.length; int left = 0 ; int ji = 1 ; int ans = 0 ; for (int right = 0 ; right < n; right++) { ji = ji * nums[right]; while (ji >= k) { ji = ji / nums[left]; left++; } ans += right - left + 1 ; } return ans; } }
思路 这样遍历可以得到满足要求的以 left 开头的大数组,每个大数组可以拆分成 right - left + 1 个小数组
right - left + 1 为大数组长度,长度为4的大数组可以被分为4个以left开头的小数组,不会重复
题目描述 给你一个字符串 s ,请找出满足每个字符最多出现两次的最长子字符串,并返回该子字符串的 最大 长度。
示例 1:
输入: s = “bcbbbcba”
输出: 4
解释:
以下子字符串长度为 4,并且每个字符最多出现两次:"bcbbbcba"。
示例 2:
输入: s = “aaaa”
输出: 2
解释:
以下子字符串长度为 2,并且每个字符最多出现两次:"aaaa"。
提示:
2 <= s.length <= 100
s 仅由小写英文字母组成。
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int maximumLengthSubstring (String s) { char [] S = s.toCharArray(); int n = S.length; int [] cnt = new int [128 ]; int ans = 0 ; int left = 0 ; for (int right = 0 ; right < n; right++) { char c = S[right]; cnt[c]++; while (cnt[c] > 2 ) { cnt[S[left]]--; left++; } ans = Math.max(ans, right - left + 1 ); } return ans; } }
思路 同3
题目描述 给你一个整数数组 nums 和一个整数 k 。
一个元素 x 在数组中的 频率 指的是它在数组中的出现次数。
如果一个数组中所有元素的频率都 小于等于 k ,那么我们称这个数组是 好 数组。
请你返回 nums 中 最长好 子数组的长度。
子数组 指的是一个数组中一段连续非空的元素序列。
示例 1:
1 2 3 4 输入:nums = [1,2,3,1,2,3,1,2], k = 2 输出:6 解释:最长好子数组是 [1,2,3,1,2,3] ,值 1 ,2 和 3 在子数组中的频率都没有超过 k = 2 。[2,3,1,2,3,1] 和 [3,1,2,3,1,2] 也是好子数组。 最长好子数组的长度为 6 。
示例 2:
1 2 3 4 输入:nums = [1,2,1,2,1,2,1,2], k = 1 输出:2 解释:最长好子数组是 [1,2] ,值 1 和 2 在子数组中的频率都没有超过 k = 1 。[2,1] 也是好子数组。 最长好子数组的长度为 2 。
示例 3:
1 2 3 4 输入:nums = [5,5,5,5,5,5,5], k = 4 输出:4 解释:最长好子数组是 [5,5,5,5] ,值 5 在子数组中的频率没有超过 k = 4 。 最长好子数组的长度为 4 。
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 109
1 <= k <= nums.length
错误 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int maxSubarrayLength (int [] nums, int k) { if (k < 1 ) { return 0 ; } long [] cnt = new long [1000000000 ]; int left = 0 ; int n = nums.length; int ans = 0 ; for (int right = 0 ; right < n; right++) { cnt[nums[right]]++; while (cnt[nums[right]] > k) { cnt[nums[left]]--; left++; } ans = Math.max(ans, right - left + 1 ); } return ans; } }
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int maxSubarrayLength (int [] nums, int k) { if (k < 1 ) { return 0 ; } HashMap<Integer, Integer> cnt = new HashMap <>(); int left = 0 ; int n = nums.length; int ans = 0 ; for (int right = 0 ; right < n; right++) { int rVal = nums[right]; cnt.put(rVal, cnt.getOrDefault(rVal, 0 ) + 1 ); while (cnt.get(rVal) > k) { int lVal = nums[left]; cnt.put(lVal, cnt.get(lVal) - 1 ); left++; } ans = Math.max(ans, right - left + 1 ); } return ans; } }
优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int maxSubarrayLength (int [] nums, int k) { int ans = 0 ; int left = 0 ; Map<Integer, Integer> cnt = new HashMap <>(); for (int right = 0 ; right < nums.length; right++) { cnt.merge(nums[right], 1 , Integer::sum); while (cnt.get(nums[right]) > k) { cnt.merge(nums[left], -1 , Integer::sum); left++; } ans = Math.max(ans, right - left + 1 ); } return ans; } }
merge() 方法:key 不存在存默认值;key 存在,执行合并函数算出新值写入;合并函数返回 null,则移除 key。
题目描述 给你一个下标从 0 开始的字符串 s ,这个字符串只包含 0 到 9 的数字字符。
如果一个字符串 t 中至多有一对相邻字符是相等的,那么称这个字符串 t 是 半重复的 。例如,"0010" 、"002020" 、"0123" 、"2002" 和 "54944" 是半重复字符串,而 "00101022" (相邻的相同数字对是 00 和 22)和 "1101234883" (相邻的相同数字对是 11 和 88)不是半重复字符串。
请你返回 s 中最长 半重复 子字符串 的长度。
示例 1:
**输入:**s = “52233”
**输出:**4
解释:
最长的半重复子字符串是 “5223”。整个字符串 “52233” 有两个相邻的相同数字对 22 和 33,但最多只能选取一个。
示例 2:
**输入:**s = “5494”
**输出:**4
解释:
s 是一个半重复字符串。
示例 3:
**输入:**s = “1111111”
**输出:**2
解释:
最长的半重复子字符串是 “11”。子字符串 “111” 有两个相邻的相同数字对,但最多允许选取一个。
提示:
1 <= s.length <= 50
'0' <= s[i] <= '9'
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int longestSemiRepetitiveSubstring (String s) { char [] c = s.toCharArray(); int left = 0 ; int ans = 1 ; int cnt = 0 ; for (int right = 1 ; right < c.length; right++) { if (c[right] == c[right - 1 ]) { cnt++; } while (cnt > 1 ) { if (c[left] == c[left + 1 ]) { cnt--; } left++; } ans = Math.max(ans, right - left + 1 ); } return ans; } }
题目描述 给定一个二进制数组 nums 和一个整数 k,假设最多可以翻转 k 个 0 ,则返回执行操作后 数组中连续 1 的最大个数 。
示例 1:
1 2 3 4 输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2 输出:6 解释:[1,1,1,0,0,1,1,1,1,1,1] 粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
1 2 3 4 输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3 输出:10 解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1] 粗体数字从 0 翻转到 1,最长的子数组长度为 10。
提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1
0 <= k <= nums.length
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int longestOnes (int [] nums, int k) { int ans = 0 ; int l = 0 ; int n = nums.length; int cnt = 0 ; for (int r = 0 ; r < n; r++) { if (nums[r] == 0 ) { cnt++; } while (cnt > k) { if (nums[l] == 0 ) { cnt--; } l++; } ans = Math.max(ans, r - l + 1 ); } return ans; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int longestOnes (int [] nums, int k) { int ans = 0 ; int l = 0 ; int n = nums.length; for (int r = 0 ; r < n; r++){ if (nums[r] == 0 ){ if (k <= 0 ) { ans = Math.max(ans, r - l); while (k <= 0 ){ if (nums[l] == 0 ){ k++; } l++; } } k--; } } ans = Math.max(ans, n - l); return ans; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int longestOnes (int [] nums, int k) { int ans = 0 ; int cnt0 = 0 ; int left = 0 ; for (int right = 0 ; right < nums.length; right++) { cnt0 += 1 - nums[right]; while (cnt0 > k) { cnt0 -= 1 - nums[left]; left++; } ans = Math.max(ans, right - left + 1 ); } return ans; } }
亮点 在遇见统计出现次数为二进制数 时,可以用 1 - num 来统计0出现的次数
题目描述 给你一个整数数组 nums 和一个 正整数 k 。
请你统计有多少满足 「 nums 中的 最大 元素」至少出现 k 次的子数组,并返回满足这一条件的子数组的数目。
子数组是数组中的一个连续元素序列。
示例 1:
1 2 3 输入:nums = [1,3,2,3,3], k = 2 输出:6 解释:包含元素 3 至少 2 次的子数组为:[1,3,2,3]、[1,3,2,3,3]、[3,2,3]、[3,2,3,3]、[2,3,3] 和 [3,3] 。
示例 2:
1 2 3 输入:nums = [1,4,2,1], k = 3 输出:0 解释:没有子数组包含元素 4 至少 3 次。
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 106
1 <= k <= 105
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public long countSubarrays (int [] nums, int k) { int mx = Arrays.stream(nums).max().getAsInt(); long ans = 0 ; int cnt = 0 ; int left = 0 ; for (int right = 0 ; right < nums.length; right++){ if (nums[right] == mx) { cnt++; } while (cnt == k) { if (nums[left] == mx) { cnt--; } left++; } ans += left; } return ans; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public long countSubarrays (int [] nums, int k) { int mx = 0 ; for (int x : nums) { mx = Math.max(mx, x); } long ans = 0 ; int cntMx = 0 , left = 0 ; for (int x : nums) { if (x == mx) { cntMx++; } while (cntMx == k) { if (nums[left] == mx) { cntMx--; } left++; } ans += left; } return ans; } }
思路 只需要保持 区间内 只有 k 个最大元素,此时左指针即为当前区间负责的所有子区间数(0~left)
即只要该区间有k 个最大元素,则该区间前0到left的所有区间 + left~right 的区间最大元素数必 > k
总结 获取数组中最大值的方法:
1 2 3 4 5 6 int mx = Arrays.stream(nums).max().getAsInt();int mx = 0 ;for (int x : nums) { mx = Math.max(mx, x); }
题目描述 一个数组的 分数 定义为数组之和 乘以 数组的长度。
比方说,[1, 2, 3, 4, 5] 的分数为 (1 + 2 + 3 + 4 + 5) * 5 = 75 。
给你一个正整数数组 nums 和一个整数 k ,请你返回 nums 中分数 严格小于 k 的 非空整数子数组数目 。
子数组 是数组中的一个连续元素序列。
示例 1:
1 2 3 4 5 6 7 8 9 10 11 输入:nums = [2,1,4,3,5], k = 10 输出:6 解释: 有 6 个子数组的分数小于 10 : - [2] 分数为 2 * 1 = 2 。 - [1] 分数为 1 * 1 = 1 。 - [4] 分数为 4 * 1 = 4 。 - [3] 分数为 3 * 1 = 3 。 - [5] 分数为 5 * 1 = 5 。 - [2,1] 分数为 (2 + 1) * 2 = 6 。 注意,子数组 [1,4] 和 [4,3,5] 不符合要求,因为它们的分数分别为 10 和 36,但我们要求子数组的分数严格小于 10 。
示例 2:
1 2 3 4 5 6 输入:nums = [1,1,1], k = 5 输出:5 解释: 除了 [1,1,1] 以外每个子数组分数都小于 5 。 [1,1,1] 分数为 (1 + 1 + 1) * 3 = 9 ,大于 5 。 所以总共有 5 个子数组得分小于 5 。
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 105
1 <= k <= 1015
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public long countSubarrays (int [] nums, long k) { int left = 0 ; long ans = 0 ; long sum = 0 ; for (int right = 0 ; right < nums.length; right++) { sum += nums[right]; while (sum * (right - left + 1 ) >= k) { sum -= nums[left]; left++; } ans += right - left + 1 ; } return ans; } }
题目描述 给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。
如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。
示例 1:
1 2 3 输入:nums = [1,1,4,2,3], x = 5 输出:2 解释:最佳解决方案是移除后两个元素,将 x 减到 0 。
示例 2:
1 2 输入:nums = [5,6,7,8,9], x = 4 输出:-1
示例 3:
1 2 3 输入:nums = [3,2,20,1,1,3], x = 10 输出:5 解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 104
1 <= x <= 109
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public int minOperations (int [] nums, int x) { int target = -x; for (int num : nums) { target += num; } if (target < 0 ) { return -1 ; } int n = nums.length; int ans = -1 ; int sum = 0 ; int left = 0 ; for (int right = 0 ; right < n; right++) { sum += nums[right]; while (sum > target) { sum -= nums[left]; left++; } if (sum == target) { ans = Math.max(ans, right - left + 1 ); } } return ans < 0 ? -1 : n - ans; } }
思路 逆向思维,只需先找到和为 总和 - x 的最小长度 ans 数组,再用总长度减去 ans 即为所求次数