剑指offer---Typescript版本

6 阅读5分钟

数组

面试题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;
};

数组求子数组之和解法总结

  1. 滑动窗口(双指针):使用双指针解决子数组之和的面试题有一个前提条件—数组中的所有数字都是正数。
  2. 累加数组:数组中的数字有正数、负数和零都可以用。

面试题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;
};