花式实现两数之和

649 阅读2分钟

普通版两数之和

1. 两数之和

image.png

最简单的办法,双重循环,如果 i + j === target 就直接返回

暴力版:

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

优化版:

暴力版我们使用了两层循环,来处理,这样的时间复杂度为 O(n2),并且因为数组是无序的,我们没办法使用双指针来进行处理,所以这里其实可以使用一个 Map 来进行存储,Mapkey 就是 当前的值,value 是当前的下标,然后我们判断 如果 target - nums[i] === target 的话,就是我们需要找的结果,这样最终的时间和空间的复杂度 都是O(n)

function twoSum (nums, target) {
  const map = new Map()

  for (let i = 0; i < nums.length; i++) {
    if (map.has(target - nums[i])) {
        return [map.get(target - nums[i]), i]
    }
    map.set(nums[i], i)
  }
};

有序数组的两数之和

167. 两数之和 II - 输入有序数组

上面我我们实现了无序数组的两数之和,现在我们在看下有序版的两数之和,有序版的话,我们可以通过双指针来实现,其实就2个变量,一个指向开始,另外一个指向结束的,每次循环的时候,l + r 去判断 是否大于 target, 大于的话,就 r--,如果小于的话,r++ 相等的话,直接返回就行。

function twoSum (numbers: number[], target: number): number[] {
  let l = 0
  let r = numbers.length - 1

  while (l <= r) {
    if (numbers[l] + numbers[r] === target) {
      return [l + 1, r + 1]
    }
    if (numbers[l] + numbers[r] < target) {
      l++
    }
    if (numbers[l] + numbers[r] > target) {
      r--
    }
  }
}

通过类型体操来实现两数之和

上面我们大概过了下 leetcode 的两数之和,现在如何用类型体操来实现两数之和呢?

typescript 强大的类型能力,不仅可以实现类型推断,我们还可以用类型来实现函数的功能,大部分应该都可以实现,从而来实现类型推断,虽然写上去会复杂一丢丢,但是对于开发体验会友好点。

大概的说下思路,这里采用双指针的思路,去进行实现,由于 ts 类型并没一些 好用的 api,所以这些都要自己实现,这边的主要思路是如果不匹配的话,就删除第一个或者最后一个,直到等于 target,抽空去看下如何用类型实现 index 获取数组对应的值,这样的话,就不需要删除数组的第一个和最后一个了。

type shift<T extends unknown[]> = T extends [] ? [] : T extends [unknown, ...infer Rest] ? Rest : never
type pop<T extends unknown[]> = T extends [] ? [] : T extends [...infer  Rest, unknown] ? Rest : never

// 判断 2 个数 是否相等
type If<number1 extends unknown, number2 extends unknown> = number1 extends number2 ? true : false

// 获取数组第一个值
type getFirst<T extends unknown[]> = T extends [infer F, ...unknown[]] ? F : never

// 获取数组最后一个值
type getEnd<T extends unknown[]> = T extends [...unknown[], infer End] ? End : never

// 填充 n 个值
type fill<Len extends number, V extends any, R extends unknown[] = []> = R['length'] extends Len ? R : fill<Len, V, [...R, V]>

// 实现加法
type add<number1 extends number, number2 extends number> = [...fill<number1, unknown>, ...fill<number2, unknown>]['length']

// 大于
type greaterThan<number1 extends number, number2 extends number, Len extends unknown[] = []> = 
  number1 extends number2 ? false :
  Len['length'] extends number1 ? false : 
  Len['length'] extends number2 ? true : greaterThan<number1, number2, [...Len, unknown]>


/**
 * 通过类型体操,来实现两数之和
 */
type TwoSum<T extends number[], target extends number> = 
  // 如果 数组 长度小于2  直接返回 never
  greaterThan<T['length'], 1> extends false ? never 
  // 如果两个值相等,则返回当前数组符合条件的结果
  : add<getFirst<T>, getEnd<T>> extends target ? [getFirst<T>, getEnd<T>] 
  // 如果左边 + 右边的 比 target 大,删除数组最后一个,在递归调用计算
  : greaterThan<add<getFirst<T>, getEnd<T>>, target> extends true ? TwoSum<pop<T>, target>  
  // 如果比 target 小 ,则相反 依然递归计算
  : greaterThan<add<getFirst<T>, getEnd<T>>, target> extends false ? TwoSum<shift<T>, target>
  : never


type result11 = TwoSum<[1, 2, 3, 4, 5, 6], 9> // [2, 7]
type result12 = TwoSum<[1, 2, 3, 4, 100, 104], 109> // never

差不多就是这样了。