数组
1 基础知识
空间效率不一定高:
即使只存储一个元素,也需要按照定义位所有数据预先分配内存。
于是出现了动态数组ArrayList
2 双指针
两端向中间走:
求排序数组中的两个数字之和。
同向双指针:
求正数数组中子数组的和或乘积。
面试题6:排序数组中的两个数字之和
/**给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。
假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。
示例 1:
输入:numbers = [1,2,4,6,10], target = 8
输出:[1,3]
解释:2 与 6 之和等于目标数 8 。因此 index1 = 1, index2 = 3 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[0,2]
示例 3:
输入:numbers = [-1,0], target = -1
输出:[0,1]
提示:
2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers 按 非递减顺序 排列
-1000 <= target <= 1000
仅存在一个有效答案
*/
class Solution {
public int[] twoSum(int[] numbers, int target) {
int i = 0;
int j = numbers.length - 1;
while (i < j && numbers[i] + numbers[j] != target) {
if (numbers[i] + numbers[j] < target) {
i++;
} else {
j--;
}
}
return new int[]{i, j};
}
}
面试题7:数组中和为0的3个数字
/**给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
*/
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> results = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if (i != 0 && nums[i] == nums[i - 1]) continue;
int target = -nums[i];
int start = i + 1;
int end = nums.length - 1;
while (start < end) {
if (nums[start] + nums[end] == target) {
var result = new ArrayList<Integer>();
result.add(-target);
result.add(nums[start]);
result.add(nums[end]);
results.add(result);
start++;
end--;
while (start < nums.length && nums[start] == nums[start - 1]) start++;
while (end > 0 && nums[end] == nums[end + 1]) end--;
} else if (nums[start] + nums[end] < target) {
start++;
while (start < nums.length && nums[start] == nums[start - 1]) start++;
} else {
end--;
while (end > 0 && nums[end] == nums[end + 1]) end--;
}
}
}
return results;
}
}
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
int j = i + 1;
int k = nums.length - 1;
while (j < k) {
if (nums[j] + nums[k] == -nums[i]) {
ArrayList<Integer> list = new ArrayList<>();
list.addAll(List.of(nums[i], nums[j], nums[k]));
result.add(list);
j++;
while (j < nums.length - 1 && nums[j] == nums[j - 1]) {
j++;
}
} else if (nums[j] + nums[k] < -nums[i]) {
j++;
} else {
k--;
}
}
}
return result;
}
}
面试题8:和大于或等于k的最短子数组
/**给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
*/
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left = 0;
int sum = 0;
int minLength = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (left <= right && sum >= target) {
minLength = Math.min(minLength, right - left + 1);
sum -= nums[left++];
}
}
return minLength == Integer.MAX_VALUE ? 0 : minLength;
}
}
面试题9:乘积小于k的子数组
/**给定一个正整数数组 nums和整数 k ,请找出该数组内乘积小于 k 的连续的子数组的个数。
示例 1:
输入: 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:
输入: nums = [1,2,3], k = 0
输出: 0
提示:
1 <= nums.length <= 3 * 104
1 <= nums[i] <= 1000
0 <= k <= 106
*/
class Solution {
public int numSubarrayProductLessThanK(int[] nums, int k) {
int left = 0;
int count = 0;
long product = 1;
for (int right = 0; right < nums.length; right++) {
product *= nums[right];
while (left <= right && product >= k) {
product /= nums[left++];
}
count += right >= left ? right -left + 1 : 0;
}
return count;
}
}
3 累加数组数字求子数组之和
双指针解决子数组之和前提:数组中的所有数字都是正数。
因为如果有负数,增加一个数字不一定能增加子数组之和,删除一个数字也不一定能减少子数组之和。
累加数组数字求子数组之和:
在数组[1,2,3,4,5,6]中,从下标0开始到下标2结束的子数组之和是6,从下标0到下标4的子数组这和为15,则从下标3到下标4的子数组之和为15-9= 6;
面试题10:和为k的子数组
给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。
示例 1:
输入:nums = [1,1,1], k = 2
输出: 2
解释: 此题 [1,1] 与 [1,1] 为两种不同的情况
示例 2:
输入:nums = [1,2,3], k = 3
输出: 2
提示:
1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107
如果给定数组都为正数:
class Solution {
public int subarraySum(int[] nums, int k) {
int left = 0;
int sum = 0;
int count = 0;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (left <= right && sum > k) {
sum -= nums[left++];
}
if (left <= right && sum == k) count++;
}
return count;
}
public static void main(String[] args) {
Solution solution = new Solution();
var test = new int[]{1,2,3,4,5};
System.out.println(solution.subarraySum(test, 9));
}
}
如果不确定正负:
class Solution {
public int subarraySum(int[] nums, int k) {
// 前i个数字之和,以及这个和的出现次数
var sumToCount = new HashMap<Integer, Integer>();
sumToCount.put(0, 1);
int sum = 0;
int count = 0;
for (int num : nums) {
sum += num;
count += sumToCount.getOrDefault(sum - k, 0);
sumToCount.merge(sum, 1, Integer::sum);
}
return count;
}
}
思路:
从头到尾扫描数组中的数字时求出前i个数字之和,并将和保存下来(sum)。哈希表的键是前i个数字之和,值为这个和在截止到目前扫描为止出现的次数。
面试题11:0和1个数相同的子数组
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
示例 1:
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。
示例 2:
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。
提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1
class Solution {
public int findMaxLength(int[] nums) {
var sumToIndex = new HashMap<Integer, Integer>();
sumToIndex.put(0, -1);
int sum = 0;
int maxLength = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i] == 0 ? -1 : 1;
if (sumToIndex.containsKey(sum)) {
maxLength = Math.max(maxLength, i - sumToIndex.get(sum));
} else {
// 第一次出现的数字和的下标--最长长度。
sumToIndex.put(sum, i);
}
}
return maxLength;
}
}
思路:
将0改为-1,类似于面试题10。
和为k的子数组个数。
和为k的最长子数组。
面试题12:左右两边子数组的和相等
给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
示例 1:
输入:nums = [1,7,3,6,5,6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。
示例 2:
输入:nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心下标。
示例 3:
输入:nums = [2, 1, -1]
输出:0
解释:
中心下标是 0 。
左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),
右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。
提示:
1 <= nums.length <= 104
-1000 <= nums[i] <= 1000
class Solution {
public int pivotIndex(int[] nums) {
int leftSum = 0;
int rightSum = 0;
for (int num : nums) {
rightSum += num;
}
for (int i = 0; i < nums.length; i++) {
rightSum -= nums[i];
if (i != 0) {
leftSum += nums[i - 1];
}
if (leftSum == rightSum) {
return i;
}
}
return -1;
}
}
class Solution {
public int pivotIndex(int[] nums) {
int total = 0;
for (int num : nums) {
total += num;
}
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (sum - nums[i] == total - sum) {
return i;
}
}
return -1;
}
}
面试题13:二维子矩阵的数字之和
给定一个二维矩阵 matrix,以下类型的多个请求:
计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。
实现 NumMatrix 类:
NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回左上角 (row1, col1) 、右下角 (row2, col2) 的子矩阵的元素总和。
示例 1:
输入:
[“NumMatrix”,“sumRegion”,“sumRegion”,“sumRegion”]
[[[[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出:
[null, 8, 11, 12]
解释:
NumMatrix numMatrix = new NumMatrix([[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]]);
numMatrix.sumRegion(2, 1, 4, 3); // return 8 (红色矩形框的元素总和)
numMatrix.sumRegion(1, 1, 2, 2); // return 11 (绿色矩形框的元素总和)
numMatrix.sumRegion(1, 2, 2, 4); // return 12 (蓝色矩形框的元素总和)
class NumMatrix {
private int[][] sums;
public NumMatrix(int[][] matrix) {
if (matrix.length == 0 || matrix[0].length == 0) {
return;
}
sums = new int[matrix.length + 1][matrix[0].length + 1];
for (int i = 0; i < matrix.length; i++) {
int rowSum = 0;
for (int j = 0; j < matrix[0].length; j++) {
rowSum += matrix[i][j];
sums[i + 1][j + 1] = sums[i][j + 1] + rowSum;
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
return sums[row2 + 1][col2 + 1] - sums[row1][col2 + 1]
- sums[row2 + 1][col1] + sums[row1][col1];
}
}