数组
面试题6:排序数组中的两个数字之和
Leetcode: LCR 006. 两数之和 II - 输入有序数组
题目:输入一个递增排序的数组和一个值k,请问如何在数组中找出两个和为k的数字并返回它们的下标?假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。例如,输入数组[1,2,4,6,10],k的值为8,数组中的数字2与6的和为8,它们的下标分别为1与3。
// 1.双指针
function twoSum(numbers: number[], target: number): number[] {
if (!numbers.length) return [];
let left = 0, right = numbers.length - 1;
while(left < right) {
if (numbers[left] + numbers[right] === target) {
return [left, right];
} else if (numbers[left] + numbers[right] > target) {
right--;
} else {
left++;
}
}
return [];
};
// 2. 二分查找(适合已排序数组)
面试题7:数组中和为0的3个数字
Leetcode: 15. 三数之和
题目:输入一个数组,如何找出数组中所有和为0的3个数字的三元组?需要注意的是,返回值中不得包含重复的三元组。例如,在数组[-1,0,1,2,-1,-4]中有两个三元组的和为0,它们分别是[-1,0,1]和[-1,-1,2]。
function threeSum(nums: number[]): number[][] {
if (nums.length < 3) return [];
const res = [];
// 将数组排序后,可以使用方向相反的两个指针求和
nums.sort((a, b) => a - b);
for (let i = 0; i < nums.length - 1; i++) {
// for 循环中固定一个值,然后在排序数组[i+1, nums.length-1]范围内,用双指针寻找三和为0的组合
if (i > 0 && nums[i] === nums[i - 1]) continue; // 若遍历到的固定数字出现过,则跳出当前循环
let left = i + 1, right = nums.length - 1; // 定义两个指针,每次寻找的最大数组范围
while (left < right) {
if (nums[i] + nums[left] + nums[right] === 0) {
res.push([nums[i], nums[left], nums[right]]);
// left指针去重
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
// right指针去重
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
// 找到一个组合后,缩小范围继续寻找是否存在下一个组合
left++;
right--;
} else if (nums[i] + nums[left] + nums[right] > 0) {
// 和比0大,需要从排序数组的右边缩小范围,right指针值会减小,从而和减小
right--;
} else {
// 和比0小
left++;
}
}
}
return res;
};
面试题8:和大于或等于k的最短子数组
Leetcode: LCR 008. 长度最小的子数组
题目:输入一个正整数组成的数组和一个正整数k,请问数组中和大于或等于k的连续子数组的最短长度是多少?如果不存在所有数字之和大于或等于k的子数组,则返回0。例如,输入数组[5,1,4,3],k的值为7,和大于或等于7的最短连续子数组是[4,3],因此输出它的长度2。
function minSubArrayLen(target: number, nums: number[]): number {
// left, right将组成一个滑动窗口,两个指针都往同一个方向移动
// left指针永远不会走到right指针的右边
let left = 0, right = 0; // 两个指针初始化都指向数组的第1个数字
let minLen = Number.MAX_VALUE; // 记录和大于等于target的最小子数组长度
let sum = 0; // 记录窗口子数组和
// right右移会增大子数组长度且增大sum值
while (right < nums.length) {
sum += nums[right];
while (left <= right && sum >= target) {
// 右移left,减短数组长度,若剪短后还能大于目标值,则找出一个更短的满足需求的子数组
// 若小于目标值,则跳出while循环,去外层循环右移right,加长数组,也就增加sum
minLen = Math.min(minLen, right - left + 1);
sum -= nums[left];
left++;
}
right++;
}
return minLen === Number.MAX_VALUE ? 0 : minLen;
};
面试题9:乘积小于k的子数组
Leetcode: LCR 009. 乘积小于 K 的子数组
题目:输入一个由正整数组成的数组和一个正整数k,请问数组中有多少个数字乘积小于k的连续子数组?例如,输入数组[10,5,2,6],k的值为100,有8个子数组的所有数字的乘积小于100,它们分别是[10]、[5]、[2]、[6]、[10,5]、[5,2]、[2,6]和[5,2,6]。
function numSubarrayProductLessThanK(nums: number[], k: number): number {
let res = 0; // 记录满足条件的子数组个数
let prod = 1; // 子数组乘积
let left = 0, right = 0; // 两个指针形成一个滑动窗口
while(right < nums.length) {
// 第一层循环,右指针从0向右移动,遍历所有数组元素,且作为子数组右边界
prod *= nums[right];
while (left <= right && prod >= k) {
// 第二层循环,根据条件移动左指针,缩小子数组范围
prod /= nums[left];
left++;
}
res = res + right - left + 1; //两个指针之间有多少个数字,就找到了多少个数字乘积小于k的子数组。
right++;
}
return res;
};
数组求子数组之和解法总结
- 滑动窗口(双指针):使用双指针解决子数组之和的面试题有一个前提条件—数组中的所有数字都是正数。
- 累加数组:数组中的数字有正数、负数和零都可以用。
面试题10:和为k的子数组
Leetcode: LCR 010. 和为 K 的子数组
题目:输入一个整数数组和一个整数k,请问数组中有多少个数字之和等于k的连续子数组?例如,输入数组[1,1,1],k的值为2,有2个连续子数组之和等于2。
function subarraySum(nums: number[], k: number): number {
const map = new Map(); // 哈希表存放 --> key: 前缀和,value: 前缀和出现次数
map.set(0, 1); // 处理特殊场景,遍历的第一个数字就满足要求。假设 nums = [3, 1, 2, -1, 1],k = 3
let preSum = 0; // 记录前缀和
let res = 0; // 记录满足要求的数组个数
for (let i = 0; i < nums.length; i++) {
preSum += nums[i];
if (map.has(preSum - k)) {
res += map.get(preSum - k);
}
map.set(preSum, (map.get(preSum) || 0) + 1);
}
return res;
};