每日算法-250605
每日算法 - 20240605
525. 连续数组
题目描述
给定一个二进制数组 nums
, 找到含有相同数量的 0
和 1
的最长连续子数组,并返回该子数组的长度。
思路
前缀和 + 哈希表
解题过程
核心思想是将问题巧妙地转换为寻找和为特定值的子数组问题。
-
转换问题:我们将数组中的
0
视为-1
,1
视为1
。这样,如果一个子数组中0
和1
的数量相等,那么这个子数组(转换后)的和就为0
。问题就变成了“找到和为0的最长连续子数组的长度”。 -
前缀和:我们定义
prefix[i]
为原数组从0
到i-1
经过转换后的元素之和。- 如果子数组
nums[j...k]
(转换后) 的和为0
,那么prefix[k+1] - prefix[j] = 0
,即prefix[k+1] == prefix[j]
。
- 如果子数组
-
哈希表优化:
- 我们使用一个哈希表
map
来存储特定前缀和首次出现的索引。map
的键是前缀和的值,值是该前缀和第一次出现时的索引i
。 - 初始化时,我们放入
map.put(0, -1)
。这表示和为0
的前缀“出现”在索引-1
处,这样做是为了方便计算当一个从索引0
开始的子数组本身和为0
时的长度(即i - (-1) = i + 1
)。 - 遍历数组,计算当前的前缀和
current_prefix
。- 如果在
map
中已经存在current_prefix
,假设它之前出现的索引是prev_index = map.get(current_prefix)
,那么从prev_index + 1
到当前索引i
的子数组(转换后)的和就是0
。其长度为i - prev_index
。我们用这个长度更新最大长度max
。 - 如果
map
中不存在current_prefix
,则将其存入map
:map.put(current_prefix, i)
。我们只记录第一次出现的位置,因为这样能保证在后续找到相同前缀和时,计算出的子数组长度是最大的。
- 如果在
- 我们使用一个哈希表
复杂度
- 时间复杂度: O ( N ) O(N) O(N),其中 N 是数组的长度。我们只遍历数组一次。
- 空间复杂度: O ( N ) O(N) O(N),在最坏的情况下,哈希表可能存储 N 个不同的前缀和。
Code
class Solution {public int findMaxLength(int[] nums) {int max = 0, n = nums.length;Map<Integer, Integer> map = new HashMap<>(n);map.put(0, -1);int prefix = 0;for (int i = 0; i < n; i++) {prefix += (nums[i] == 0 ? -1 : 1);if (map.containsKey(prefix)) {max = Math.max(max, (i - map.get(prefix)));} else {map.put(prefix, i);}}return max;}
}
面试题 17.05. 字母与数字
题目描述
给定一个放有字母和数字的数组,找到最长的子数组,且包含的字母和数字的个数相同。返回该子数组。若存在多个最长子数组,返回最靠左的。
思路
前缀和 + 哈希表
这道题与上一题的思路非常相似。
解题过程
-
转换问题:我们将数组中的字母视为
1
(或-1
),数字视为-1
(或1
,只要两者相反即可)。例如,字母计为+1
,数字计为-1
。如果一个子数组中字母和数字的数量相同,那么这个子数组(转换后)的和就为0
。问题就变成了“找到和为0的最长连续子数组”。 -
前缀和与哈希表:
- 同样使用
prefixSum
记录从数组开头到当前位置i-1
的转换后元素之和。 - 使用哈希表
map
存储<前缀和, 首次出现的索引>
。 - 初始化
map.put(0, -1)
。 - 遍历数组,计算当前的前缀和
current_prefixSum
。- 如果
map
中已存在current_prefixSum
,设其之前出现的索引为prev_index = map.get(current_prefixSum)
。这意味着从prev_index + 1
到当前索引i
的子数组(转换后)的和为0
。其长度为current_length = i - prev_index
。 - 如果
current_length
大于已记录的最大长度maxLen
,则更新maxLen
和最长子数组的起始索引startIndex = prev_index + 1
。 - 如果
map
中不存在current_prefixSum
,则将其存入map
:map.put(current_prefixSum, i)
。
- 如果
- 同样使用
-
返回结果:根据记录的
startIndex
和maxLen
,从原数组中截取并返回结果。由于题目要求“若存在多个最长子数组,返回最靠左的”,我们只在第一次遇到前缀和时记录索引,并且在current_length > maxLen
时才更新,这样自然保证了最靠左的特性。
复杂度
- 时间复杂度: O ( N ) O(N) O(N),其中 N 是数组的长度。
- 空间复杂度: O ( N ) O(N) O(N),哈希表可能存储 N 个不同的前缀和。
Code
class Solution {public String[] findLongestSubarray(String[] array) {int maxLen = 0, n = array.length;Map<Integer, Integer> map = new HashMap<>(n);map.put(0, -1);int prefix = 0, index = 0;for (int i = 0; i < n; i++) {prefix += (isNum(array[i]) ? -1 : 1);if (map.containsKey(prefix)) {int preIndex = map.get(prefix);if (i - preIndex > maxLen) {index = preIndex + 1;maxLen = i - preIndex;}} else {map.put(prefix, i);}}String[] s = new String[maxLen];System.arraycopy(array, index, s, 0, maxLen);return s;}private boolean isNum(String s) {return (s.charAt(0) >= '0' && s.charAt(0) <= '9');}
}