两数之和 & 三数之和

144 阅读3分钟

两数之和

暴力解法

时间复杂度:每个元素需要遍历n-1个元素,共有n个元素,复杂度为O(n2)O(n^2)

空间复杂度:O(1)O(1)

function twoSum(nums: number[], target: number): number[] {
  let res: number[] = []
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[i] + nums[j] === target) {
        res.push(i)
        res.push(j)
      } 
    }
  }
  return res
};

优化解法

时间复杂度:对于数组的遍历,O(n)O(n)是必不可少的,但是检查某个元素是否可以相加得到target不一定要花费O(n)O(n),可以存储在map中,O(1)O(1)s时间内判断是否满足条件。总体复杂度O(n)O(n)

空间复杂度:O(n)O(n)

function twoSum(nums: number[], target: number): number[] {
  let res: number[] = [], cache: Map<number, number> = new Map()
  for (let i = 0; i < nums.length; i++) {
    const num1: number = nums[i]
    const num2Index: number | undefined = cache.get(target - num1)
    if (num2Index !== undefined) {
      res.push(i)
      res.push(num2Index)
      break
    } else {
      cache.set(num1, i)
    }
  }
  return res
};

三数之和

暴力解法

时间复杂度:O(n3)O(n^3),超出时间限制了。。。

function compareTwoArray(nums1: number[], nums2: number[]): boolean {
  if (nums1.length !== nums2.length) return false
  for (let i = 0; i < nums1.length; i++) {
    if (nums1[i] !== nums2[i]) return false
  }
  return true
}

function threeSum(nums: number[]): number[][] {
  let res: number[][] = []
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      for (let k = j + 1; k < nums.length; k++) {
        if (nums[i] + nums[j] + nums[k] === 0) {
          const exist: boolean = !!res.find(item => {
            return compareTwoArray([nums[i], nums[j], nums[k]].sort(), item)
          })
          !exist && res.push([nums[i], nums[j], nums[k]].sort())
        }
      }
    }
  }
  return res
}

优化解法

时间复杂度:O(n2)O(n^2) 空间复杂度:O(1)O(1) 在计算两数之和时,本解法采用了双指针方案,但是该方案依赖于数组是有序的。

function threeSum(nums: number[]): number[][] {
  // 先对数组进行排序,便于双指针查找
  nums.sort((a, b) => a - b)
  let res: number[][] = []
  if (nums.length < 3) return []
  for (let i = 0; i < nums.length - 2; i++) {
    // 避免重复
    if (i > 0 && nums[i] === nums[i - 1]) continue
    // 提前退出
    if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break
    if (nums[i] + nums[nums.length - 2] + nums[nums.length - 1] < 0) continue
    let j = i + 1,
      k = nums.length - 1
    while (j < k) {
      const sum = nums[i] + nums[j] + nums[k]
      if (sum === 0) {
        res.push([nums[i], nums[j], nums[k]])
        j++
        while (j < k && nums[j] === nums[j - 1]) j++
        k--
        while (j < k && nums[k] === nums[k + 1]) k--
      } else if (sum > 0) {
        k--
      } else {
        j++
      }
    }
  }
  return res
}

最接近的三数之和

跟三数之和类似,但是需要注意指针状态的转移:

  • 当前sum > target

    • 距离target更近时,需要更新距target最近的值
    • right--
  • 当前sum < target

    • 距离target更近时,需要更新距target最近的值
    • left++
  • 当前sum = target

    • 直接返回结果
function threeSumClosest(nums: number[], target: number): number {
  nums.sort((a, b) => a - b)
  let res: number = Infinity
  for (let i = 0; i < nums.length - 2; i++) {
    let left = i + 1,
      right = nums.length - 1
    while (left < right) {
      const sum = nums[i] + nums[left] + nums[right]
      if (sum > target) {
        if (Math.abs(sum - target) < Math.abs(res - target)) {
          res = sum
        }
        right--
      } else if (sum < target) {
        if (Math.abs(sum - target) < Math.abs(res - target)) {
          res = sum
        }
        left++
      } else {
        return target
      }
    }
  }
  return res
}

三数之和的多种可能性

整体迭代流程与三数之和类似,需要注意状态的转移

  • sum > target时,right--
  • sum < target时,left++
  • sum = target时
    • arr[left] = arr[right], 如 1 1 1 1,此时结果为Cn2C_n^2,n的值为right - left + 1
    • arr[left] !== arr[right],如 1 1 2 2,左边相同数字个数 * 右边相同数字个数
function threeSumMulti(arr: number[], target: number): number {
  arr.sort((a, b) => a - b)
  let res: number = 0
  for (let i = 0; i < arr.length - 2; i++) {
    let left = i + 1,
      right = arr.length - 1
    while (left < right) {
      const sum = arr[i] + arr[left] + arr[right]
      if (sum > target) {
        right--
      } else if (sum < target) {
        left++
      } else {
        if (arr[left] === arr[right]) {
          res += ((right - left) * (right - left + 1)) / 2
          break
        } else {
          let leftNum = 1
          let rightNum = 1
          left++
          while (arr[left] === arr[left - 1]) {
            left++
            leftNum++
          }
          right--
          while (arr[right] === arr[right + 1]) {
            right--
            rightNum++
          }
          res += leftNum * rightNum
        }
      }
    }
  }
  return res % (1000000000 + 7)
}

竟然是runtime和memory双100%!!! image.png

总结

a+b=c

对于数组中两个数等于某个数的场景,正常双向指针即可

a+b>c

数组中两个数之和大于某个数的场景,c从最大值反向枚举(假设c的index为k)

  • left + right < target时,left++
  • left + right > target时,从left到right-1的值和right相加时均大于target,执行相关操作后,right--
  • left + right = target,执行相关操作

a+b<c

  • left + right < target时,从left+1到right的值和left相加均小于target,执行相关操作后,left++
  • left + right > target时,right--
  • left + right = target,执行相关操作