一. 数组子串
560. 和为k的子数组
思路:
-
前缀和: 前缀和是一个常用技巧,用于快速查询任意子数组的和。在这里,我们逐步计算数组的累积和,称为前缀和。
-
哈希表: 我们使用一个哈希表
preSum
来存储每个前缀和及其出现次数。这样,我们可以在常数时间内查找特定前缀和的出现次数。 -
统计和为
k
的子数组: 通过遍历前缀和并查找哈希表中的特定值,我们可以统计和为k
的子数组数量。
代码解释
-
初始化: 我们初始化
count
为 0,用于统计和为k
的子数组的数量。我们还初始化sum
为 0,用于计算前缀和。哈希表preSum
用于存储每个前缀和的出现次数,我们将前缀和0
的初始计数设置为1
,因为前缀和0
至少出现一次。 -
遍历数组: 我们遍历数组
nums
中的每个元素num
,并逐步计算前缀和sum
。-
查询前缀和: 我们查询哈希表中键为
sum - k
的值。如果这个键存在于哈希表中,那么它表示存在一个或多个子数组,其和为k
。我们将这些子数组的数量添加到count
中。 -
更新前缀和的计数: 我们使用
preSum.getOrDefault(sum, 0) + 1
来更新当前前缀和的计数。如果当前前缀和已经存在于哈希表中,我们增加其计数;否则,我们将其计数设置为1
。
-
-
返回结果: 最后,我们返回计数
count
,即和为k
的连续子数组的个数。
示例理解
假设 nums = [1,2,3]
,k = 3
。
- 首先,初始化
count = 0
,sum = 0
,preSum = {0: 1}
。 - 遍历第一个元素
1
,sum = 1
,preSum = {0: 1, 1: 1}
。 - 遍历第二个元素
2
,sum = 3
,查找sum - k = 0
在preSum
中找到 1 个,count = 1
,preSum = {0: 1, 1: 1, 3: 1}
。 - 遍历第三个元素
3
,sum = 6
,查找sum - k = 3
在preSum
中找到 1 个,count = 2
,preSum = {0: 1, 1: 1, 3: 1, 6: 1}
。 - 返回
count = 2
。
此算法的时间复杂度是 O(n),其中 n 是数组的长度,空间复杂度也是 O(n)。
前缀和的概念
前缀和是一种计算数组中前 nnn 个元素和的方式。假设有一个数组 nums
和其前缀和数组 prefixSum
,则:
prefixSum[i]=nums[0]+nums[1]+…+nums[i]
public int subarraySum(int[] nums, int k) {
int count = 0; // 用于计算和为 k 的连续子数组的个数
int sum = 0; // 用于计算前缀和
Map<Integer, Integer> preSum = new HashMap<>(); // 哈希表用于存储前缀和的出现次数
preSum.put(0, 1); // 初始化前缀和为 0 的情况
for (int num : nums) {
sum += num; // 计算当前前缀和
// 如果存在前缀和等于当前前缀和减去 k 的情况,将其次数添加到计数器中
if (preSum.containsKey(sum - k)) {
count += preSum.get(sum - k);
}
// 更新当前前缀和的出现次数,更新也用put注意。
//getOrDefualt:如果查询的键存在,则正常返回,如果不存在就返回0.
preSum.put(sum, preSum.getOrDefault(sum, 0) + 1);
}
return count;
}
二. 数组操作
数组操作一般定义辅助数组,比如current和next。像下面的56题就定义了current[ ]和next[ ]来辅助。 53题定义了currentSum和maxSum来辅助。
56 合并区间
思路:
- 排序: 按区间的开始端点对所有区间进行排序。这样,我们可以确保在遍历过程中,相邻的区间要么重叠要么不重叠。
- 合并: 遍历排序后的区间,并将重叠的区间合并为一个新区间。
- 返回结果: 返回合并后的区间列表。
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals == null || intervals.length <= 1) return intervals;
// 按区间的开始端点排序
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
List<int[]> mergedList = new ArrayList<>();
int[] currentInterval = intervals[0]; // 初始化当前区间
for (int i = 1; i < intervals.length; i++) {
int[] nextInterval = intervals[i];
if (currentInterval[1] >= nextInterval[0]) { // 如果当前区间与下一个区间重叠
// 合并区间
currentInterval[1] = Math.max(currentInterval[1], nextInterval[1]);
} else {
// 将当前区间添加到结果列表,并更新当前区间
mergedList.add(currentInterval);
currentInterval = nextInterval;
}
}
// 添加最后一个区间, 注意!!!!!!!别忘
mergedList.add(currentInterval);
//创建一个新的二维整数数组,其大小与 mergedList 中的元素数量相同,并将 mergedList 中的所有元素(每个元素都是一个整数数组)复制到这个新的二维数组中。
return mergedList.toArray(new int[mergedList.size()][]);
}
}
1. 当你调用 toArray
方法时,必须提供一个数组作为参数来指定返回数组的类型。在这个例子中,我们想要一个 int[][]
类型的数组,所以我们传递了一个新的 int[][]
数组作为参数
2. Integer.compare
是 Java 中的一个静态方法,用于比较两个整数值。
3.
`Comparator` 在 Java 中有许多用法,可以用于定义各种自定义的排序逻辑。以下是一些常见的用法:
### 1. 匿名内部类
你可以使用匿名内部类创建一个 `Comparator` 对象:
```java
Comparator<int[]> comparator = new Comparator<int[]>() {
@Override
public int compare(int[] a, int[] b) {
return Integer.compare(a[0], b[0]);
}
};
Arrays.sort(intervals, comparator);
```
### 2. Lambda 表达式(Java 8+)
如前所述,你可以使用 Lambda 表达式创建一个简洁的 `Comparator`:
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
### 3. 使用 `Comparator` 的静态方法
Java 8 引入了一些 `Comparator` 的静态方法,使得创建 `Comparator` 变得更加容易:
- `comparing`: 根据提取的键进行比较。
```java
Arrays.sort(intervals, Comparator.comparing(a -> a[0]));
```
- `reversed`: 返回一个反向排序的 `Comparator`。
```java
Arrays.sort(intervals, Comparator.comparing(a -> a[0]).reversed());
```
- `thenComparing`: 用于链式比较,当主要排序条件相同时,可以使用另一个条件。
```java
Arrays.sort(intervals, Comparator.comparing(a -> a[0]).thenComparing(a -> a[1]));
```
- `naturalOrder` 和 `reverseOrder`: 用于基于自然排序或反向自然排序的比较。
```java
Arrays.sort(strings, Comparator.naturalOrder());
Arrays.sort(strings, Comparator.reverseOrder());
```
### 4. 使用方法引用
如果你的排序逻辑可以用现有方法表示,你可以使用方法引用:
```java
Arrays.sort(strings, String::compareToIgnoreCase);
```
总的来说,`Comparator` 提供了丰富的方式来定义自定义的排序逻辑。你可以选择最适合你需求和代码风格的方式。
53. 最大子数组和
思路:
- 初始化: 定义两个变量
maxSum
和currentSum
来分别存储最大子数组和和当前子数组和。 - 遍历数组: 遍历数组中的每个元素,并对每个元素执行以下操作: a. 将当前元素与当前子数组和的和与当前元素本身比较,选择较大的一个作为新的当前子数组和。这样可以确保如果当前子数组和变为负数,则可以从当前元素开始新的子数组。 b. 将当前子数组和与最大子数组和比较,选择较大的一个作为新的最大子数组和。
- 返回结果: 返回最大子数组和
maxSum
。
class Solution {
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int maxSum = nums[0]; // 初始化最大子数组和
int currentSum = nums[0]; // 初始化当前子数组和
for (int i = 1; i < nums.length; i++) {
// 比较当前元素与当前子数组和的和与当前元素本身,选择较大的一个作为新的当前子数组和,
如果currentSum是负数时,就舍弃之前的,从当前的数开始新的子数组
currentSum = Math.max(nums[i], currentSum + nums[i]);
// 更新最大子数组和
maxSum = Math.max(maxSum, currentSum);
}
return maxSum;
}
}
189. 轮转数组
思路:
- 整体反转: 将整个数组反转。
- 反转前 k 个元素: 其中 kkk 是旋转的步数。
- 反转剩余元素: 反转剩下的元素。
class Solution {
public void rotate(int[] nums, int k) {
k %= nums.length; // 如果 k 大于数组长度,取余数
reverse(nums, 0, nums.length - 1); // 反转整个数组
reverse(nums, 0, k - 1); // 反转前 k 个元素
reverse(nums, k, nums.length - 1); // 反转剩余元素
}
// 辅助方法:反转数组的一部分
private void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}
238. 除自身以外数组的乘积
思路:
- 计算左侧乘积: 创建一个新数组
left
,其中left[i]
包括nums[i]
左侧所有元素的乘积。 - 计算右侧乘积: 创建一个新数组
right
,其中right[i]
包括nums[i]
右侧所有元素的乘积。 - 计算结果: 结果数组
answer
的每个元素answer[i]
等于left[i] * right[i]
。
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] left = new int[n];
int[] right = new int[n];
int[] answer = new int[n];
left[0] = 1;
for (int i = 1; i < n; i++) {
left[i] = left[i - 1] * nums[i - 1];
}
right[n - 1] = 1;
for (int i = n - 2; i >= 0; i--) {
right[i] = right[i + 1] * nums[i + 1];
}
for (int i = 0; i < n; i++) {
answer[i] = left[i] * right[i];
}
return answer;
}
}