用ts体操的方式打开力扣第一题两数之和

129 阅读2分钟

题目

最近刷type-challenges的时候碰到一个两数之和的题目

/*
  8804 - 两数之和
  -------
  by PsiloLau (@Psilocine) #困难 #array #math

  ### 题目

  给定一个整数数组 nums 和一个目标整数 target, 如果 nums 数组中存在两个元素的和等于 target 返回 true, 否则返回 false

  > 在 Github 上查看:https://tsch.js.org/8804/zh-CN
*/

/* _____________ 你的代码 _____________ */


type TwoSum<T extends number[], U extends number> = any

/* _____________ 测试用例 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<TwoSum<[3, 3], 6>, true>>,
  Expect<Equal<TwoSum<[3, 2, 4], 6>, true>>,
  Expect<Equal<TwoSum<[2, 7, 11, 15], 15>, false>>,
  Expect<Equal<TwoSum<[2, 7, 11, 15], 9>, true>>,
  Expect<Equal<TwoSum<[1, 2, 3], 0>, false>>,
  Expect<Equal<TwoSum<[1, 2, 3], 1>, false>>,
  Expect<Equal<TwoSum<[1, 2, 3], 2>, false>>,
  Expect<Equal<TwoSum<[1, 2, 3], 3>, true>>,
  Expect<Equal<TwoSum<[1, 2, 3], 4>, true>>,
  Expect<Equal<TwoSum<[1, 2, 3], 5>, true>>,
  Expect<Equal<TwoSum<[1, 2, 3], 6>, false>>,
  Expect<Equal<TwoSum<[3, 2, 0], 2>, true>>,
]

第一次看到这个题目的时候,我有点懵算法题还可以用体操的方式打开吗?

思路

解题方案

ok,放下体操先让我们看一下这个题如果用正常的方式如何解决,力扣经典的两数之和问题,可以用双指针的方式解决,我们先设计两个索引index1,index2,其中index1在数组开头的位置,index2则遍历从index1位置到数组结尾的元素,当数组元素index1+index2等于我们的目标值,则返回true,先用逻辑代码写一个版本。

const twoNum = (arr:number[],num:number):boolean => {
    for (let index1 = 0; index1 < arr.length - 1; index1++) {
       for(let index2 = index1+1;index2 < arr.length ;index2 ++){
        if(arr[index1] + arr[index2] == num) return true
       }
    }
    return false;
}

类型操作改造

在书写ts类型中我们都很清楚的知道,ts类型操作是无法使用数字运算的,例如++、arr[index1]+arr[index2]等操作,那我们如何模拟这类操作呢。

//在ts中我们可以通过模拟数组的长度来实现加法操作比如我要运算1+1
//在这个案例中我们有两个长度为1的数组进行结构拼接取出他的长度为2
[...[0],...[0]]['length']
//为了可以让数组arr中的元素可以进行加法运算,所以我们应该先吧[3,4,2]转化成
type Test1 = [[0,0,0],[0,0,0,0],[0,0]]
//这样我们就可以计算第一元素加第二个元素是多少了
type Test2 = [...Test1[0],...Test1[1]]['length'] //7
//所以我们对 if(arr[index1] + arr[index2] == num) return true的操作用类型可以这样写
 [...arr[index1['length']],...arr[index2['length']]]['length'] extends num

我们理清了思路第一步把泛型T[]的数组进行可以数字运算操作的转化,第二步创建两个指针遍历T中的元素.

题解

第一步

先写一个工具类型用于将[3,4,2]转化成[[0,0,0],[0,0,0,0],[0,0]],这个工具类型有三个参数,第一个参数是T用于接受[3,4,2],第二个参数是arr用于存储[[0,0,0],[0,0,0,0],[0,0]],第三个参数是item用于存储每一项的值

//首先是有两层遍历,第一次我们判断arr的长度是否等于T如果等于说明遍历完了最后一个元素可以直接return出去了,第二层是判断T当中索引的元素值是否等于item的长度如果等于说明遍历完成了这个值
type NumArrayToArray<T extends number[],arr extends unknown[][] = [], index extends unknown[] = [],item extends unknown[] = []> =  
    T['length'] extends arr['length'] ? arr : T[arr['length']] extends item['length'] 
    ? NumArrayToArray<T,[...arr,item],[...index,unknown]> : NumArrayToArray<T,arr,index,[...item,unknown]>
type Test = NumArrayToArray<[3,4,2]> // [[0,0,0],[0,0,0,0],[0,0]]

第二步

我们要创建两索引遍历这个数组,按着上面的思路做

//首先我们先判断两个索引所在位置的和是否等于U如果等于返回true如果不等于判断一下index1是否走到倒数第二个位置了如果走到了就说明没有等于的返回false
//如果都没有就让索引往下走
type TwoSum<T extends number[], U extends number , arr extends unknown[][] = NumArrayToArray<T>,index1 extends unknown[] = [],index2 extends unknown[] = [...index1,[]]> = 
    [...arr[index1['length']],...arr[index2['length']]]['length'] extends U ? true : index2['length'] extends arr['length'] ? [...index1,unknown]['length'] extends 
    arr['length'] ? false : TwoSum<T,U,arr,[...index1,unknown]> : TwoSum<T,U,arr,index1,[...index2,unknown]>

测试用例

试一下测试用例

type cases = [
  Expect<Equal<TwoSum<[3, 3], 6>, true>>,
  Expect<Equal<TwoSum<[3, 2, 4], 6>, true>>,
  Expect<Equal<TwoSum<[2, 7, 11, 15], 15>, false>>,
  Expect<Equal<TwoSum<[2, 7, 11, 15], 9>, true>>,
  Expect<Equal<TwoSum<[1, 2, 3], 0>, false>>,
  Expect<Equal<TwoSum<[1, 2, 3], 1>, false>>,
  Expect<Equal<TwoSum<[1, 2, 3], 2>, false>>,
  Expect<Equal<TwoSum<[1, 2, 3], 3>, true>>,
  Expect<Equal<TwoSum<[1, 2, 3], 4>, true>>,
  Expect<Equal<TwoSum<[1, 2, 3], 5>, true>>,
  Expect<Equal<TwoSum<[1, 2, 3], 6>, false>>,
  Expect<Equal<TwoSum<[3, 2, 0], 2>, true>>,
]

全部通过,完工下机!