使用Ts按照不同类型刷LeetCode

88 阅读13分钟

数组、链表

对于链表的题目,在笔试之前一定需要多练练,链表的解题思路是直接的,就看代码实现能力

  1. 206. 反转链表
function reverseList(head: ListNode | null): ListNode | null {
    if (!head) return null;
    let pre = null;
    let cur = head;
    while (cur) {
      let next = cur.next;
      cur.next = pre;
      pre = cur;
      cur = next;
    }
    return pre;
};

2. 24. 两两交换链表中的节点

function swapPairs(head: ListNode | null): ListNode | null {
  let self = new ListNode(-1);
  let pre = self;
  pre.next = head;
  while (pre.next && pre.next.next) {
    let a = pre.next;
    let b = pre.next.next;
    let tem = b.next;
    // 将当前链表的前两个节点翻转
    pre.next = b;
    b.next = a;
    a.next = tem;
    // 准备下一个两节点翻转
    pre = a;
  }
  return self.next
};

3. 25. K 个一组翻转链表

function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
  if (!head) return null;
  let self = new ListNode(-1);
  self.next = head
  let pre = self;
  while (head) {
    let tail = pre;
    for (let i = 0; i < k; i++) {
      tail = tail.next
      if(!tail) {
        return self.next
      }
    }
    let next = tail.next;
    [head,tail] = myRevers(head, tail);
    
    pre.next = head;
    tail.next = next;

    pre = tail;
    head = next;
  }
  return self.next;
};
function myRevers(head:ListNode, tail:ListNode): ListNode[] {
  let pre = tail.next;
  let cur = head;
  while (pre !== tail) {
    let next = cur.next;
    cur.next = pre;
    pre = cur;
    cur = next;
  }
  return [tail, head]
}

4. 141. 环形链表

function hasCycle(head: ListNode | null): boolean {
  // if (!head) return false;
  // let fast = head;
  // let slow = head;
  // while (1) {
  //   if (!fast.next || !fast.next.next) {
  //     return false
  //   }
  //   fast = fast.next.next;
  //   slow = slow.next;
  //   if (fast === slow) {
  //     return true
  //   }
  // }
  if (!head || !head.next) return false;
  let slow = head;
  let fast = head.next.next;
  while (slow && fast) {
    if (slow === fast) {
      return true;
    }
    slow = slow.next;
    if (fast.next) {
      fast = fast.next.next;
    } else {
      return false
    }
  }
  return false
};

5. 142. 环形链表 II

// 快指针所走长度:a+n(b+c)+b=a+(n+1)b+nc。
// 慢指针所走长度:a+b
// 关系为:a+(n+1)b+nc = 2(a+b) => a = n(b+c) + c
function detectCycle(head: ListNode | null): ListNode | null {
  if (!head) return null;
  let fast = head;
  let slow = head;
  while (1) {
    if (!fast.next || !fast.next.next) return null;
    fast = fast.next.next;
    slow = slow.next;
    if (fast === slow) {
      fast = head;
      break;
    }
  }
  while (fast !== slow) {
    fast = fast.next;
    slow = slow.next
  }
  return fast
};

补充: 19. 删除链表的倒数第 N 个结点 参考官方题解:双指针

function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
  let first = head
  let second = head
  while(n--) {
    second = second.next
  }
  if (!second) return first.next
  while (second.next) {
    first = first.next
    second = second.next
  }
  if (first.next) {
    first.next = first.next.next
  }
  return head
};

参考官方题解:使用栈

function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
  let stack:ListNode[] = []
  let cur = head
  while(cur) {
    stack.push(cur)
    cur = cur.next
  }
  let deNode = null
  while(n) {
    deNode = stack.pop()
    n -= 1
  }
  if (stack.length) {
    let curNode = stack[stack.length - 1]
    curNode.next = deNode.next
    return stack[0]
  } else {
    return deNode.next
  }
};

21. 合并两个有序链表

function mergeTwoLists(list1: ListNode | null, list2: ListNode | null): ListNode | null {
  if (!list1 || !list2) return list1 ? list1 : list2
  let pre = new ListNode(-1)
  let cur = pre
  while (list1 && list2) {
    if (list1.val > list2.val) {
      cur.next = list2
      list2 = list2.next
    } else {
      cur.next = list1
      list1 = list1.next
    }
    cur = cur.next
  }
  cur.next = list1 ? list1 : list2
  return pre.next
};

23. 合并 K 个升序链表 两两合并

function mergeKLists(lists: Array<ListNode | null>): ListNode | null {
  let len = lists.length
  if (!len) return null
  function mergeTLists(a: ListNode | null, b: ListNode | null): ListNode | null {
    if (!a || !b) {
      return !a ? b : a
    }
    let head = new ListNode(-1)
    let tail = head
    let aPre = a
    let bPre = b
    while(aPre && bPre) {
      if (aPre.val < bPre.val) {
        tail.next = aPre
        aPre = aPre.next
      } else {
        tail.next = bPre
        bPre = bPre.next
      }
      tail = tail.next
    }
    tail.next = !aPre ? bPre : aPre
    return head.next
  }
  let res = null
  for (let item of lists) {
    res = mergeTLists(res, item)
  }
  return res
};

使用队列罗列所有元素,排序后新建链表

function mergeKLists(lists: Array<ListNode | null>): ListNode | null {
  let len = lists.length
  if (!len) return null
  let arr = []
  for (let i = 0; i < len; i++) {
    let item = lists[i]
    while (item) {
      arr.push(item.val)
      item = item.next
    }
  }

  arr.sort((a, b) => {
    return a - b
  })
  let res = new ListNode(-1)
  let now = res
  for (let j = 0; j < arr.length; j++) {
    now.next = new ListNode(arr[j])
    now = now.next
  }
  return res.next
};

148. 排序链表 使用数组排序的方式实现

function sortList(head: ListNode | null): ListNode | null {
  if (!head) return null
  let valArr = []
  let cur = head
  while (cur) {
    valArr.push(cur.val)
    cur = cur.next
  }
  valArr.sort((a, b) => a - b)
  let newHead = new ListNode(valArr[0])
  cur = newHead
  for(let i = 1; i < valArr.length; i++) {
    cur.next = new ListNode(valArr[i])
    cur = cur.next
  }
  return newHead
};

将链表分为两半,分别对两半链表使用sortList获得排序后的结果,随后使用合并排序链表的方式合并得到最终结果

function sortList(head: ListNode | null): ListNode | null {
  if (!head || !head.next) return head
  let fast = head.next
  let slow = head
  while (fast && fast.next) {
    fast = fast.next.next
    slow = slow.next
  }
  let mid = slow.next
  slow.next = null

  let left = sortList(head)
  let right = sortList(mid)
  let newHead = new ListNode(0)
  let cur = newHead
  while (left && right) {
    if (left.val < right.val) {
      cur.next = left
      left = left.next
    } else {
      cur.next = right
      right = right.next
    }
    cur = cur.next
  }
  cur.next = left ? left : right
  return newHead.next
};

160. 相交链表 双指针遍历两个链表

function getIntersectionNode(headA: ListNode | null, headB: ListNode | null): ListNode | null {
  let A = headA, B = headB
  while (A !== B) {
    A = !A ? headB : A.next
    B = !B ? headA : B.next
  }
  return A
};

234. 回文链表

function isPalindrome(head: ListNode | null): boolean {
  if (!head || !head.next) return true
  let slow = head
  let fast = head
  let pre = head
  let cur = null

  while (fast && fast.next) {
    pre = slow
    slow = slow.next
    fast = fast.next.next
    pre.next = cur
    cur = pre
  }
  
  if (fast) {
    slow = slow.next
  }
  while (pre && slow) {
    if (pre.val !== slow.val) return false
    pre = pre.next
    slow = slow.next
  }
  return true
};

栈和队列

  1. 20. 有效的括号 方法一:使用栈
function isValid(s: string): boolean {
  if (!s) return false;
  let obj = {')': '(', ']': '[', '}': '{'};
  let stack:string[] = [];
  for (let i = 0; i < s.length; i++) {
    if (!obj[s[i]] || !stack.length) {
      stack.push(s[i])
    }else if (stack[stack.length - 1] === obj[s[i]]) {
      stack.pop()
    } else if (stack[stack.length - 1] !== obj[s[i]]) {
      return false
    }
  }
  return stack.length === 0
};

方法二:使用替换法,执行的时间会更长一点

function isValid(s: string): boolean {
  if(s.length % 2 !== 0) {
    return false
  }
  let halfLen = s.length / 2
  for(let i = 0; i < halfLen; i++) {
    s = s.replace('()', ''); 
    s = s.replace('[]', ''); 
    s = s.replace('{}', ''); 
  }
  return s === ''
};

7. 232. 用栈实现队列 主要思路:输入的内容首先进入第一个栈,然后进入第二个栈,顺序就可变为先入先出

// 只能使用 push 和 shift
class MyQueue {
    private in = []
    private out = []
    constructor() {
      
    }

    push(x: number): void {
      if(this.out.length) {
        this.in.push(x)
      } else {
        this.in.push(x)
        while(this.in.length) {
          this.out.push(this.in.pop())
        }
      }
    }

    pop(): number {
      if(this.out.length) {
        return this.out.pop()
      } else {
        while(this.in.length) {
          this.out.push(this.in.pop())
        }
        return this.out.pop()
      }
    }

    peek(): number {
      if(this.out.length) {
        return this.out[this.out.length - 1]
      } else {
        while(this.in.length) {
          this.out.push(this.in.pop())
        }
        return this.out[this.out.length - 1]
      }
    }

    empty(): boolean {
      return this.in.length === 0 && this.out.length === 0
    }
}

8. 225. 用队列实现栈 主要思路:输入的内容首先进入操作队列,然后除了进入操作队列最后一个数之外的其他数,依次存入存储队列,取数时就取操作队列中的唯一一个数,一旦操作队列清空了,存储队列中的数依次添加到操作队列,随后操作队列中保留最后一个数,其余的存入存储队列中。

// 只能使用 push 和 shift
class MyStack {
    private store = []
    private operat = []
    constructor() {

    }

    push(x: number): void {
      while(this.operat.length) {
        this.store.push(this.operat.shift())
      }
      this.operat.push(x)
    }

    pop(): number {
      if(this.operat.length) {
        return this.operat.shift()
      } else {
        while(this.store.length) {
          this.operat.push(this.store.shift())
        }
        while(this.operat.length > 1) {
          this.store.push(this.operat.shift())
        }
        return this.operat.shift()
      }
    }

    top(): number {
      if(this.operat.length) {
        return this.operat[0]
      } else {
        while(this.store.length) {
          this.operat.push(this.store.shift())
        }
        while(this.operat.length > 1) {
          this.store.push(this.operat.shift())
        }
        return this.operat[0]
      }
    }

    empty(): boolean {
      return this.store.length === 0 && this.operat.length === 0
    }
}

补充: 32. 最长有效括号

function longestValidParentheses(s: string): number {
  let len = s.length
  if (len < 2) return 0
  let ret = 0 
  let stack = [-1]
  for (let i = 0; i < len; i++) {
    let item = s[i]
    if (item === '(') {
      stack.push(i)
    } else {
      stack.pop()
      if (!stack.length) {
        stack.push(i)
      } else {
        ret = Math.max(ret, i - stack[stack.length - 1])
      }
    }
  }
  return ret
};

42. 接雨水 使用的是双指针移动

function trap(height: number[]): number {
  let len = height.length
  if (len < 3) return 0
  let lefIndex = 0
  let rightIndex = len - 1
  let lefMax = 0
  let rightMax = 0
  let ret = 0
  while (lefIndex < rightIndex) {
    if (height[lefIndex] < height[rightIndex]) {
      if (lefMax < height[lefIndex]) {
        lefMax = height[lefIndex]
      } else {
        ret += lefMax - height[lefIndex]
      }
      lefIndex += 1
    } else {
      if (rightMax < height[rightIndex]) {
        rightMax = height[rightIndex]
      } else {
        ret += rightMax - height[rightIndex]
      }
      rightIndex -= 1
    }
  }
  return ret
};

84. 柱状图中最大的矩形 使用栈和哨兵

function largestRectangleArea(heights: number[]): number {
  let stack = []
  let ret = 0
  heights = [0, ...heights, 0]
  for (let i = 0; i < heights.length; i++) {
    while (heights[i] < heights[stack[stack.length - 1]]) {
      let lastIndex = stack.pop()
      ret = Math.max(ret, heights[lastIndex] * (i - stack[stack.length - 1] - 1))
    }
    stack.push(i)
  }
  return ret
};

85. 最大矩形

function maximalRectangle(matrix: string[][]): number {
  // 借用84题的解法
  let m = matrix.length
  let n = matrix[0].length
  let res = 0
  if (m === 0 || n === 0) return 0
  let heights = Array.from({length: n}, () => 0)
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (matrix[i][j] === '1') {
        heights[j] += 1
      } else {
        heights[j] = 0
      }
    }
    res = Math.max(res, getAre(heights))
  }  
  return res
  function getAre(heights:number[]): number{
    let are = 0
    heights = [0, ...heights, 0]
    let stack = []
    for (let i = 0; i < heights.length; i++) {
      while (heights[i] < heights[stack[stack.length - 1]]) {
        let topIndex = stack.pop()
        are = Math.max(are, heights[topIndex] * (i - stack[stack.length - 1] - 1))
      }
      stack.push(i)
    }
    return are
  }
};

114. 二叉树展开为链表

function flatten(root: TreeNode | null): void {
  let stack = [root]
  let pre = new TreeNode()
  while (root && stack.length) {
    let curNode = stack.pop()

    curNode.right && stack.push(curNode.right)
    curNode.left && stack.push(curNode.left)

    curNode.left = null
    
    pre.right = curNode
    pre = curNode
  }
};

155. 最小栈

class MinStack {
    private stack = []
    private minStack = []
    constructor() {

    }

    push(val: number): void {
      this.stack.push(val)
      let minLen = this.minStack.length
      if (minLen && this.minStack[minLen - 1] < val) {
        this.minStack.push(this.minStack[minLen - 1])
      } else {
        this.minStack.push(val)
      }
    }

    pop(): void {
      this.stack.pop()
      this.minStack.pop()
    }

    top(): number {
      return this.stack[this.stack.length - 1]
    }

    getMin(): number {
      return this.minStack[this.minStack.length - 1]
    }
}

394. 字符串解码

function decodeString(s: string): string {
  // 使用栈解答
  let stack = []
  let res = ''
  let num = 0
  for (let i = 0; i < s.length; i++) {
    if (s[i] === '[') {
      stack.push([res, num])
      res = ''
      num = 0
    } else if (s[i] === ']') {
      let item = stack.pop()
      res = item[0] + res.repeat(item[1])
    } else if (+s[i] > -1) {
      num = num * 10 + (+s[i])
    } else {
      res += s[i]
    }
  }
  return res
};

581. 最短无序连续子数组 双指针

function findUnsortedSubarray(nums: number[]): number {
  // 参考https://leetcode.cn/problems/shortest-unsorted-continuous-subarray/solution/si-lu-qing-xi-ming-liao-kan-bu-dong-bu-cun-zai-de-/
  let len = nums.length
  let start = 0
  let end = -1
  let max = nums[0]
  let min = nums[len - 1]
  for (let i = 0; i < len; i++) {
    if (nums[i] < max) {
      end = i
    } else {
      max = nums[i]
    }

    if (nums[len-i-1] > min) {
      start = len-i-1
    } else {
      min = nums[len-i-1]
    }
  }
  console.log(end, start)
  return end - start + 1
};

739. 每日温度 使用单调递减栈

function dailyTemperatures(temperatures: number[]): number[] {
  let stack = []
  let ret = Array.from({length: temperatures.length}, () => 0)
  for (let i = 0; i < temperatures.length; i++) {
    while (stack.length && temperatures[stack[stack.length - 1]] < temperatures[i]) {
      let lastTem = stack.pop()
      ret[lastTem] = i - lastTem
    }
    stack.push(i)
  }
  return ret
};

优先队列

703. 数据流中的第 K 大元素

class KthLargest {
    k:number;
    nums:number[];
    kNums:number[];
    constructor(k: number, nums: number[]) {
        this.k = k;
        this.nums = nums;
        this.kNums = []
        for (let j = 0; j < nums.length; j++) {
            this.bouble(nums[j])
        }
    }

    add (val: number): number {
        this.bouble(val);
        return this.kNums[this.k-1]
    }

    bouble (item:number) {
        // 维护最小队列
        // 如果队列为空就直接将数据存入
        if (!this.kNums.length) {
            this.kNums.push(item)
        } else if (this.kNums.length <= this.k) {
            // 新加入的值与队列中的值从大到小的进行比较,如果找到比其中的一个大,就插入到该值的位置上,结束寻找
            for (let i = 0; i < this.kNums.length; i++) {
                if (this.kNums[i] < item) {
                    this.kNums.splice(i,0,item);
                    // 如果存入数之后队列长度大于指定长度就pop最小的值
                    if (this.kNums.length > this.k) {
                        this.kNums.pop()
                    }
                    // 找到之后直接返回
                    return;
                }
            }
            // 如果没找到对应的位置说明新加入的值比队列中的值都小,此时队列的长度没达到指定长度,将该值添加到队列中
            if (this.kNums.length < this.k) {
                this.kNums.push(item)
            }
        }
    }
}

239. 滑动窗口最大值

function maxSlidingWindow(nums: number[], k: number): number[] {
  // 双端队列
  if (!k || !nums.length) return [];
  let res:number[] = [];
  let window:number[] = [];
  for (let i = 0; i < nums.length; i++) {
    // 向前去掉老旧的值
    if (i >= k && i >= window[0] + k) {
      window.shift();
    }
    // 向后去掉比当前值小的值
    while (nums[i] > nums[window[window.length - 1]] && window.length) {
      window.pop()
    }
    // 将当前值放入移动窗口内
    window.push(i);
    // 当移动的值大于窗口长度时开始存值
    if (i >= k - 1) {
      res.push(nums[window[0]])
    }
  }
  return res;
};

还可以使用动态规划

哈希表

242. 有效的字母异位词 利用数组进行记录

function isAnagram(s: string, t: string): boolean {
  let sLen = s.length, tLen = t.length;
  if (sLen !== tLen) return false
  let staticArr:number[] = Array.from({length: 26}, ()=>0);
  for (let i = 0; i < sLen; i++) {
    let index = s[i].charCodeAt(0) - 'a'.charCodeAt(0)
    staticArr[index] += 1;
  }
  for (let j = 0; j < sLen; j++) {
    let jndex = t[j].charCodeAt(0) - 'a'.charCodeAt(0)
    staticArr[jndex] -= 1;
  }
  for (let item of staticArr) {
    if (item) {
      return false
    }
  }
  return true
};

1. 两数之和 利用obj当做Map记录每个数与target的差值的index

function twoSum(nums: number[], target: number): number[] {
  if (!nums.length) return [];
  let subObj = {};
  for (let i = 0; i < nums.length; i++) {
    let sub = target - nums[i];
    if (subObj[nums[i]] > -1) {
      return [subObj[nums[i]], i]
    }
    subObj[sub] = i;
  }
};

15. 三数之和 出现频率是比较高的

function threeSum(nums: number[]): number[][] {
  let len = nums.length;
  if (len < 3) return [];
  let res:number[][] = [];
  // 首先对原数组进行排序
  nums.sort((a,b)=>{
    return a - b;
  })
  for (let i = 0; i < len - 2; i++) {
    // 去重,如果这次获取的数值与上一次遍历的数值相同就直接跳过
    if (i > 0 && nums[i] === nums[i-1]) {
      continue
    }
    // 剪枝操作
    // 当最小的三个数都大于0,后面的数就不需要遍历了
    if (nums[i] + nums[i+1] + nums[i+2] > 0) {
      break
    }
    // 当前值加上最大的两个数的和都小于0,表明后面的数也会小于0,直接进入i+1遍历
    if (nums[i] + nums[len-1] + nums[len-2] < 0) {
      continue
    }
    // 获取每一个数后的剩余数组序列
    let start = i + 1, end = len - 1;
    // 遍历剩余序列,当开始序列数小于结束序列数时保持遍历
    while (start < end) {
      // 获取三数之和
      let sum = nums[start] + nums[end] + nums[i];
      if (sum < 0 || (start > i + 1 && nums[start] === nums[start - 1])) {
        // 当三数之和小于0时,表示当前值小于目标值(不达标),或者开始序列值与上一次的开始序列值相同(去重),开始序列+1操作
        start += 1
      } else if (sum > 0 || (end < len - 1 && nums[end] === nums[end + 1])) {
        // 当三数之和大于0时,表示当前值大于目标值(不达标),或者结束序列值与上一次的结束序列值相同(去重),结束序列-1操作
        end -= 1
      } else {
        // 当三数之和等于0时,表示目标值被找到了,此时存入一个正确结果,为了找到其他结果开始序列和结束序列同时向中间靠拢一个数
        res.push([nums[i], nums[start], nums[end]]);
        start += 1;
        end -= 1;
      }
    }
  }
  return res;
};

18. 四数之和 解题思路同三数之和相同,使用排序+双指针法,此时需要运用剪枝

function fourSum(nums: number[], target: number): number[][] {
  let len = nums.length;
  if (len < 4) return [];
  let res:number[][] = [];
  nums.sort((a,b)=> a - b);
  // 使用排序+双指针的方式求解,先遍历两次
  // 剪枝操作:
  // nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target 表示最小的4个数的和大于target,直接退出
  // nums[i] + nums[len - 1] + nums[len - 2] + nums[len - 3] < target 表示最大的4个数的和小于target,直接进入到i+1的循环中
  for (let i = 0; i < len - 3; i++) {
    if (i > 0 && nums[i] === nums[i-1]) {
      continue
    }
    if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
      break
    }
    if (nums[i] + nums[len - 1] + nums[len - 2] + nums[len - 3] < target) {
      continue
    }
    for (let j = i + 1; j < len -2; j++) {
      if (j > i + 1 && nums[j] === nums[j-1]) {
        continue
      }
      if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
        break
      }
      if (nums[i] + nums[j] + nums[len - 1] + nums[len - 2] < target) {
        continue
      }
      let start = j + 1, end = len - 1;
      while(start < end) {
        let sum = nums[i] + nums[j] + nums[start] + nums[end] - target;
        if (sum < 0 || (start > j + 1 && nums[start] === nums[start - 1])) {
          start += 1
        } else if (sum > 0 || (end < len - 1 && nums[end] === nums[end + 1])) {
          end -= 1
        } else {
          res.push([nums[i], nums[j], nums[start], nums[end]])  
          start += 1;
          end -= 1;
        }
      }
    }
  }
  return res;
};

49. 字母异位词分组 主要思路使用数据结构hash表,使用质数的乘积作为key 任何一个大于1的自然数N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积 算术基本定理保证不同质数 或 相同质数不同个数,乘积唯一

function groupAnagrams(strs: string[]): string[][] {
  if(!strs.length) return []
  let map = new Map()
  let prime = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101]
  for(let i = 0; i < strs.length; i++) {
    let item = strs[i]
    let key = 1
    if(item) {
      for(let j = 0; j < item.length; j++) {
        let index = item[j].charCodeAt(0) - 'a'.charCodeAt(0)
        key *= prime[index]
      }
    }
    if(map.has(key)) {
      map.set(key, [...map.get(key), item])
    } else {
      map.set(key, [item])
    }
  }
  return [...map.values()]
};
function groupAnagrams(strs: string[]): string[][] {
  if(!strs.length) return []
  let map = new Map()
  for(let i = 0; i < strs.length; i++) {
    let item = strs[i]
    let key = '#'
    if(item) {
      let arr = Array.from({length: 26}, () => 0)
      for(let j = 0; j < item.length; j++) {
        arr[item[j].charCodeAt(0) - 'a'.charCodeAt(0)] += 1
      }
      key = arr.join('#')
    }
    if(map.has(key)) {
      map.set(key, [...map.get(key), item])
    } else {
      map.set(key, [item])
    }
  }
  return [...map.values()]
};

补充: 3. 无重复字符的最长子串 17. 电话号码的字母组合 76. 最小覆盖子串 105. 从前序与中序遍历序列构造二叉树 (递归) 128. 最长连续序列 139. 单词拆分(动态规划) 146. LRU 缓存

难度中等1600 收藏 分享 切换为英文 接收动态 反馈

树、二叉树、二叉搜索树

前序(Pre-order):根-左-右 中序(In-order):左-根-右 后序(Post-order):左-右-根

98. 验证二叉搜索树

function isValidBST(root: TreeNode | null): boolean {
  // 中序遍历
  // let order = inorder(root);
  // for (let i = 0; i < order.length; i++) {
  //   if (i > 0 && order[i] <= order[i-1]) {
  //     return false
  //   }
  // }
  // return true;
  // function inorder(root: TreeNode | null):number[] {
  //   if (!root) return []
  //   return inorder(root.left).concat([root.val]).concat(inorder(root.right))
  // }
  // 中序遍历
  let pre:number = -Infinity;
  return inorder(root);
  function inorder(root: TreeNode | null): boolean {
    if (!root) return true;
    if (!inorder(root.left)) return false;
    if (pre >= root.val) return false;
    pre = root.val;
    return inorder(root.right)
  }
};

边界值法

function isValidBST(root: TreeNode | null, min:number = -Infinity, max:number = Infinity): boolean {    
  if (!root) return true;
  if (root.val < min) return false;
  if (root.val > max) return false;
  return isValidBST(root.left, min, root.val) && isValidBST(root.right, root.val, max)   
};

235. 二叉搜索树的最近公共祖先

function lowestCommonAncestor(root: TreeNode | null, p: TreeNode | null, q: TreeNode | null): TreeNode | null {
  // 不使用递归的写法
	// while (root) {
  //       if (root.val > p.val && root.val > q.val) {
  //           root = root.left
  //       } else if (root.val < p.val && root.val < q.val) {
  //           root = root.right
  //       } else {
  //           return root
  //       }
  //   }
  // 递归写法
  if (root === null || root === p || root === q) return root;
  if (p && q && p.val > root.val && q.val > root.val) {
    return lowestCommonAncestor(root.right, p, q)
  }
  if (p && q && p.val < root.val && q.val < root.val) {
    return lowestCommonAncestor(root.left, p, q)
  }
  return root;
};

236. 二叉树的最近公共祖先

function lowestCommonAncestor(root: TreeNode | null, p: TreeNode | null, q: TreeNode | null): TreeNode | null {
	if (root === null || root === p || root === q) return root;
  let left = lowestCommonAncestor(root.left, p, q);
  let right = lowestCommonAncestor(root.right, p, q);
  return !left ? right : !right ? left : root;
};

递归、分治法

50. Pow(x, n)

function myPow(x: number, n: number): number {
  // 递归调用
  // if (!n) return 1;
  // if (n < 0) return 1 / myPow(x, -n);
  // // n为奇数时的情况
  // if (n & 1) return x * myPow(x, n-1);
  // // n为偶数时的情况
  // return myPow(x*x, n / 2)
  
  // while循环
  if (!n) return 1;
  // n小于0时
  if (n < 0) {
    n = -n;
    x = 1 / x;
  }
  let res = 1;
  // 对n进行循环,n为奇数和n为偶数
  while(n) {
    if (n & 1) {
      res *= x;
      n -= 1;
    } else {
      x *= x;
      n = n / 2
    }
  }
  return res;
};

169. 多数元素 最优解法投票法,时间复杂度O(n)

function majorityElement(nums: number[]): number {
    // 投票法
    let count = 0;
    let candidate = 0;
    for (let i = 0; i < nums.length; i++) {
        if (count === 0) {
            candidate = nums[i]
        }
        count += candidate === nums[i] ? 1 : -1
    }
    return candidate;
};
function majorityElement(nums: number[]): number {
    // 分治法 适用于存在众数的情况
    function computTotal(num:number, left:number, right:number):number {
        let count = 0;
        for (let i = left; i <= right; i++) {
            if (nums[i] === num) {
                count += 1
            }
        }
        return count
    }

    function getRes(left:number, right:number): number{
        if (left === right) {
            return nums[left]
        }
        let mid = left + Math.floor((right - left ) / 2);
        let leftRes = getRes(left, mid);
        let rightRes = getRes(mid + 1, right);

        if (leftRes === rightRes) {
            return leftRes
        }

        let leftResCount = computTotal(leftRes, left, mid)
        let rightResCount = computTotal(rightRes, mid+1, right)
        return leftResCount > rightResCount ? leftRes : rightRes
    }
    return getRes(0, nums.length - 1)
};

哈希map方法

function majorityElement(nums: number[]): number {
    let map = new Map()
    for(let item of nums){
        if(map.get(item)){
            map.set(item, map.get(item) + 1)
        }else{
            map.set(item, 1)
        }
    }
    for(let [key,value] of map.entries()){
        if(value >= Math.ceil(nums.length / 2)){
            return key
        }
    }
};

贪心算法

适⽤ Greedy 的场景 简单地说,问题能够分解成⼦问题来解决,⼦问题的最优解能递推到最终问题的最优解。这种⼦问题最优解成为最优⼦结构。 122. 买卖股票的最佳时机 II

function maxProfit(prices: number[]): number {
    let res:number = 0
    for (let i = 0; i < prices.length - 1; i++) {
        if (prices[i] < prices[i + 1]) {
            res += prices[i + 1] - prices[i]
        }
    }
    return res
};

⼴度优先搜索(BFS)和 深度优先搜索(DFS)

操作示意图

BFS采用非递归写法

visited = set()
def BFS(graph, start, end):
  queue = []
  queue.append([start])
  visited.add(start)

  while queue:
    node = queue.popleft()
    visited.add(node)

    process(node)
    nodes = generate_related_nodes(node)
    queue.append(nodes)
  # other processing work
  ...

DFS采用递归写法

visited = set()
def DFS(node, visited):
  visited.add(node)
  # process current node here.
  ...
  for next_node in node.children():
    if not next_node in visited:
      DFS(next_node, visited)

前,中,后序遍历都是深度优先搜索(DFS)。 前、中、后代码模板 image.png

102. 二叉树的层序遍历

BFS 解法

function levelOrder(root: TreeNode | null): number[][] {
    if (!root) return [];
    let res:number[][] = [];
    let queue:TreeNode[] = [];
    let currentNodes:number[] = [];
    queue.push(root);
    
    while (queue.length) {
        let levelSize:number = queue.length;
        currentNodes = []
        for (let i = 0; i < levelSize; i++) {
            let node = queue.shift();
            currentNodes.push(node.val)
            if (node.left) {
                queue.push(node.left)
            }
            if (node.right) {
                queue.push(node.right)
            }
        }
        res.push(currentNodes)
    }
    return res;
};

DFS解法

function levelOrder(root: TreeNode | null): number[][] {
    if (!root) return [];
    let res:number[][] = [];
    dfs(root,0)
    return res
    function dfs(node:TreeNode | null, level:number) {
        if (!node) return;
        if (res.length < level + 1) {
            res.push([])
        }
        res[level].push(node.val)
        dfs(node.left, level + 1)
        dfs(node.right, level + 1)
    }
};

104. 二叉树的最大深度

function maxDepth(root: TreeNode | null): number {
    if(!root) return 0;
    return 1 + Math.max(maxDepth(root.left),maxDepth(root.right))
};

111. 二叉树的最小深度

function minDepth(root: TreeNode | null): number {
    if(!root){
        return 0
    }
    let left = minDepth(root.left)
    let right = minDepth(root.right)
    return (!left || !right) ? left + right + 1 : 1 + Math.min(left,right)
};
function minDepth(root: TreeNode | null): number {
  // 使用BFS
  if(!root) return 0
  let queue: TreeNode[] = []
  let currentNode: TreeNode = root
  let res = 0
  queue.push(root)
  while(queue.length) {
    res += 1
    let layerSize = queue.length
    for(let i = 0; i < layerSize; i++) {
      currentNode = queue.shift()
      if(currentNode.left) {
        queue.push(currentNode.left)
      }
      if(currentNode.right) {
        queue.push(currentNode.right)
      }
      if(!currentNode.left && !currentNode.right) {
        return res
      }
    }
  }
};

22. 括号生成

function generateParenthesis(n: number): string[] {
  if (!n) return [];
  let res:string[] = []
  function _gen(lef:number, rig:number, result:string){
    if (lef === n && rig === n) {
      res.push(result)
    }
    if (lef < n) {
      _gen(lef + 1, rig, result + '(')
    }
    if (rig < lef) {
      _gen(lef, rig + 1, result + ')')
    }
  }
  _gen(0, 0, '');
  return res;
};

BFC 遍历去重

function generateParenthesis(n: number): string[] {
  if(!n) return []
  let res = ['()']
  n = n - 1
  let set:Set<string> = new Set()

  while(n > 0) {
    set.clear()
    for (let str of res) {
      for(let i = 0; i < str.length; i++) {
        set.add(str.slice(0,i) + '()' + str.slice(i))
      }
    }
    res = [...set]
    n -= 1
  }
  return res
};

51. N 皇后

function solveNQueens(n: number): string[][] {

  function DFS(row:number, current:number[]) {
    if(row === n) {
      res.push(current)
      return
    }
    for (let i = 0; i < n; i++) {
      if (col.has(i) || pie.has(row + i) || na.has(row - i)) {
        continue
      }
      
      col.add(i)
      pie.add(row + i)
      na.add(row - i)

      DFS(row + 1, current.concat(i))

      col.delete(i)
      pie.delete(row + i)
      na.delete(row - i)
    }
  }
  let res = []
  let col: Set<number> = new Set()
  let pie: Set<number> = new Set()
  let na: Set<number> = new Set()

  DFS(0, [])
  let str = new Array(n).join('.')
  let sul = []
  res.forEach(itemArr => {
    let tem = []
    itemArr.forEach(item => {
      tem.push(str.slice(0,item) + 'Q' + str.slice(item))
    })
    sul.push(tem)
  })
  return sul
};

二分法

69. x 的平方根

function mySqrt(x: number): number {
  // 精度为0.01
  // if (x === 0 || x === 1) return x;
  // let min:number = 0, max:number = x;
  // let res:number = 0;
  // while (min <= max) {
  //   let mid = min + Math.floor((max - min) / 2);
  //   let sub = mid - x / mid;
  //   if (sub < 0.01 / mid && sub >= 0) {
  //       return mid
  //   } else if (sub < 0 / mid) {
  //       min = mid + 0.01
  //   } else if (sub > 0.001 / mid) {
  //       max = mid - 0.01
  //   }
  //   res = mid;
  // }
  // return res
  
  // 当前题目解法
  if(x === 0 || x === 1) return x;
    let l:number = 0, r:number = x, res:number;
    while(l <= r){
      let mid:number = Math.floor((l + r) / 2);
      if(mid === x / mid){
        return mid
      } else if (mid > x / mid) {
        r = mid - 1
      } else {
        l = mid + 1
        res = Math.floor(mid);
      }
    }
  return res
};

牛顿迭代法 image.png 确定一个切线方程在于它的斜率和对于x轴的位移, 可以这样写每个点的切线方程 y=(2x)(x-b)。 (2x)表示斜率,b代表对于x轴向正方向的位移。 这个切线方程和f(x)的交点这样就可以求了: (2x)(x-b)=x^2-a, 可得b=(x+a/x)/2。 这个b就是切线方程和x轴的交点的x坐标, 由图像可以直观看出交点坐标比x更接近平方根,不断重新赋值,逐渐趋近。

位运算

&: 与 
|: 或
^: 异或
~: 取反
<<: 左移
>>: 右移
X  ^ 0 = X
X ^ 1s = -X // 1s = -0
X ^ (-X) = 1s
X ^ X = 0
a ^ b = c => a ^ c = b, b ^ c = a // swap
a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c  // 满足结合律

常用位运算

X & 1 === 1 OR === 0  // 判断一个数的奇偶性,为1是奇数,为0是偶数
X = X & (X - 1) => 清除最低位的1
X & -X => 得到最低位的1 (-X:取反加1

191. 位1的个数

function hammingWeight(n: number): number {
  // X = X & (X - 1) => 清除最低位的1
  // if (!n) return 0
  // let res:number =  0;
  // while (n) {
  //   res += 1;
  //   n &= (n - 1)
  // }
  // return res;
  
  if (!n) return 0;
  let res:number = 0;
  let mask = 1;
  let len = n.toString(2).length
  for (let i = 0; i < len; i++) {
    if (n & mask) {
      res += 1
    }
    mask <<= 1
  }
  return res
};

231. 2 的幂

function isPowerOfTwo(n: number): boolean {
  // return (n > 0) && (n & (-n)) === n
  return (n > 0) && (n & (n -1)) === 0
};

338. 比特位计数

function countBits(n: number): number[] {
  let res:number[] = new Array(n+1).fill(0);
  for (let i = 1; i <= n; i++) {
    res[i] = res[i & (i - 1)] + 1
  }
  return res
};

52. N皇后 II

function totalNQueens(n: number): number {
    // 使用位运算解题
    if ( n < 1 ) return 0;
    let res: number = 0;
    DFS(0, 0, 0, 0)
    return res;
    function DFS (row:number, col:number, pie:number, na:number): void {
        if(row >= n) {
            res += 1;
            return;
        }
        let bit:number = (~(col | pie | na)) & ((1 << n) - 1) // 得到当前所有空位
        while (bit) {
            let p = bit & -bit  // 取到最低位的1
            DFS(row + 1, col | p, (pie | p) << 1, (na | p) >> 1)
            bit &= bit - 1  // 去掉最低位的1
        }
    }
};

动态规划

  1. 递归+记忆化 --> 递推
  2. 定义状态: opt[n], dp[n], fib[n]
  3. 状态转移方程:opt[n] = best_of(opt[n-1],opt[n-2],...)
  4. 最优子结构 70. 爬楼梯 动态方程为1维
// 递归+记忆化
function climbStairs(n: number, memer?:number[]): number {
  if (n === 0 || n === 1) return 1;
  if (!memer) {
    memer= new Array(n + 1).fill(0);
  }
  if (!memer[n]) {
    memer[n] = climbStairs(n-1, memer) + climbStairs(n-2, memer);
  }
  return memer[n];
};

动态规划(使用数组储存变量的变化)

function climbStairs(n: number): number {
    let tem:number[] = new Array(n + 1).fill(1);
    for (let i = 2; i <= n; i++) {
        tem[i] = tem[i-1] + tem[i-2]
    }
    return tem[n]
};

动态规划(使用3个变量记录中间变量的变化)

function climbStairs(n: number): number {
    if (n === 0 || n === 1) return 1;
    let one:number = 1, tow:number = 1, three:number = 1;
    for (let i = 2; i <= n; i++) {
        three = one + tow;
        one = tow;
        tow = three;
    }
    return three
};

公式法 斐波那契数列的公式: image.png

function climbStairs(n: number): number {
  let sq5 = Math.sqrt(5)
  let fib = Math.pow((1 + sq5) / 2, n + 1) - Math.pow((1-sq5)/2, n + 1)
  return Math.round(fib / sq5)
};

// 不能连续跳两步 DP[i][0] 上一步到这一步只走了一步 DP[i][1] 上一步到这一步走了两步 DP[i][0] = DP[i-1][0] + DP[i-1][1] DP[i][1] = DP[i-1][0] return DP[i][0] + DP[i][1] 不能连续走两步转换为只能走1步和走3步 DP[0] = 1 DP[1] = 1 DP[2] = 2 DP[3] = 3 DP[4] = DP[3] + DP[1] = 3 + 1 = 4

62. 不同路径

DP[i][j] = DP[i+1][j] + DP[i][j+1]

function uniquePaths(m: number, n: number): number {
  // 需要初始化 DP 数组,DP数组的最后一列和最后一行的值均为1
  let mem:number[][] = Array.from({length:m}, (i, indexI) => Array.from({length:n}, (j,indexJ) => {
    if (indexI === m - 1 || indexJ === n - 1) {
      return 1
    } else {
      return 0
    }
  }))
  for (let i = m - 2; i >= 0; i--) {
    for (let j = n - 2; j >= 0; j--) {
      mem[i][j] = mem[i+1][j] + mem[i][j+1]
    }
  }
  return mem[0][0]
};

63. 不同路径 II

动态转换方程同上,转换过程中遇到障碍就跳过障碍点,为方便计算,初始化 DP 数组时要多增加一行和一列,终点的值为1,其余为0,遍历过程跳过终点。

function uniquePathsWithObstacles(obstacleGrid: number[][]): number {
  let m = obstacleGrid.length
  let n = obstacleGrid[0].length
  if (obstacleGrid[m-1][n-1] || obstacleGrid[0][0]) return 0
  if (m === 1 && n === 1) return 1
  let mem:number[][] = Array.from({length:m+1}, (i, indexI) => Array.from({length:n+1}, (j,indexJ) => {
    if (indexI === m - 1 && indexJ === n - 1) {
      return 1
    } else {
      return 0
    }
  }))
  for (let i = m - 1; i >= 0; i--) {
    for (let j = n - 1; j >= 0; j--) {
      if(i === m - 1 && j === n - 1) {
        continue
      }
      if (!obstacleGrid[i][j] ) {
        mem[i][j] = mem[i+1][j] + mem[i][j+1]
      }
    }
  }
  return mem[0][0]
};

53. 最大子数组和 动态方程为1维 DP方程:DP[i] = max(DP[i-1] + nums[i], nums[i]) 以npms[i] 作为结尾的连续子数组,nums[i] 一定被选取。 image.png image.png

function maxSubArray(nums: number[]): number {
    if (!nums) return 0;
    let dp:number[] = new Array(nums.length).fill(nums[0]);
    let res:number = nums[0]
    for (let i = 1; i < nums.length; i++) {
        dp[i] = Math.max(nums[i], dp[i-1] + nums[i])
        res = Math.max(res, dp[i])
    }
    return res;
};

120. 三角形最小路径和 动态方程为2维的 DP方程: 从下往上:dp[i][j] = min(dp[i+1][j], dp[i+1][j+1]) + triangle[i][j]

function minimumTotal(triangle: number[][]): number {
  if (!triangle.length) return 0;
  let min:number[] = triangle[triangle.length - 1];
  let len = 0
  for (let i = triangle.length - 2; i >= 0; i--) {
    len = triangle[i].length;
    for (let j = 0; j < len; j++) {
      min[j] = Math.min(min[j], min[j+1]) + triangle[i][j]
    }
  }
  return min[0]
};

152. 乘积最大子数组

动态方程提升为2维

DP方程:需要用两维的状态方程 DP[x][0] 在x处的最大值,DP[x][1] 在x处的最小值 DP[x][0] = if( nums[x] >= 0 ) { DP[x-1][0]*nums[x]) } else { DP[x-1][1] * nums[x]} DP[x][1] = if( nums[x] >= 0 ) { DP[x-1][1]*nums[x]) } else { DP[x-1][0] * nums[x]}

function maxProduct(nums: number[]): number {
    // if (!nums) return 0;
    // let dp:number[][] = Array.from({length: nums.length}, ()=> {return Array.from({length: 2}, ()=> nums[0])});
    // let res:number = dp[0][0]
    // for (let x = 1; x < nums.length; x++) {
    //   dp[x][0] = Math.max(dp[x-1][0] * nums[x], dp[x-1][1] * nums[x], nums[x])
    //   dp[x][1] = Math.min(dp[x-1][1] * nums[x], dp[x-1][0] * nums[x], nums[x])
    //   res = Math.max(res, dp[x][0])
    // }
    // return res
    
    // 内存优化
    if (!nums) return 0;
    let dp:number[][] = Array.from({length: 2}, ()=> {return Array.from({length: 2}, ()=> nums[0])});
    let res:number = dp[0][0]
    for (let x = 1; x < nums.length; x++) {
        let i = x % 2, j = (x - 1) % 2;
        dp[i][0] = Math.max(dp[j][0] * nums[x], dp[j][1] * nums[x], nums[x])
        dp[i][1] = Math.min(dp[j][1] * nums[x], dp[j][0] * nums[x], nums[x])
        res = Math.max(res, dp[i][0])
    }
    return res
};

股票系列问题: 买卖股票

    定义状态转移方程
    MP表示获取的最大利润
    MP[i][k][j]: 第i天交易了k次手里持有j只股票

    如果只能持有1只股票,那么j取值为0,1
    
    MP[i][k][0] = max(MP[i-1][k][0], MP[i-1][k-1][1] + a[i]) 
    // 第i天交易了k次手里不持有股票的最大利润   
    // 第一项表示保持前一天一样不操作,第二项表示在前一天持有的情况下卖掉
    MP[i][k][1] = max(MP[i-1][k][1], MP[i-1][k][0] - a[i])
    // 第i天交易了k次手里持有股票的最大利润
    // 第一项表示保持前一天一样不操作,第二项表示在前一天不持有的情况下买入(成功买卖一次才能算成功交易一次,所以此处只能为k次)

    max (MP[n-1][0,...k],[0])   // 到最后一天手里不持有股票时,在k次交易中能获取到的最大利润值

    如果可以持有X只股票

    MP[i][k][j] = max(MP[i-1][k][j], MP[i-1][k-1][j+1] + a[i], MP[i-1][k][j-1] - a[i])
    // 第i天交易了k次手里持有股票j只股票的最大利润   
    // 第一项表示保持前一天一样不操作,第二项表示在前一天的完成k-1次交易并且持有j+1只股票的基础上卖掉一只,
    // 第三项表示在前一天完成k次交易并持有j-1只股票的基础上买入一只(只有买卖完成才能算完成一次交易,所以此处只能是k次)

121. 买卖股票的最佳时机

function maxProfit(prices: number[]): number {
  // 递归求解

  // let minP:number = prices[0];
  // let res:number = 0;
  // for (let i = 1; i < prices.length; i++) {
  //   minP = Math.min(minP, prices[i]);
  //   res = Math.max(res, prices[i] - minP)
  // }
  // return res;

  // 动态规划求解

  // if (!prices.length) return 0;
  // let MP:number[][][] = Array.from({length: prices.length}, ()=>{return Array.from({length:2}, ()=>{return Array.from({length:2}, ()=>0)})})
  // MP[0][0][1] = -prices[0];
  // let res:number = 0;
  // // MP[i][0][1]: 第i天,是否完成1次交易(0:交易0次,1: 交易1次),是否持有股票(0:不持有,1:持有1只股票)
  // for (let i = 1; i < prices.length; i++) {
  //   MP[i][0][1] = Math.max(MP[i-1][0][1], MP[i-1][0][0] - prices[i]);
  //   MP[i][1][0] = Math.max(MP[i-1][1][0], MP[i-1][0][1] + prices[i]);
  //   res = Math.max(res, MP[i][1][0]);
  // }
  // return res;

  // 动态规划求解(内存优化)

  if (!prices.length) return 0;
  let MP:number[][] = Array.from({length: prices.length}, ()=>{return Array.from({length:3}, ()=>0)})
  MP[0][0] = 0; // 没有买入股票
  MP[0][1] = -prices[0];  // 买入还没卖出
  MP[0][2] = 0; // 买入股票之后卖出
  for (let i = 1; i < prices.length; i++) {
    MP[i][1] = Math.max(MP[i-1][1], MP[i-1][0] - prices[i]);
    MP[i][2] = Math.max(MP[i-1][2], MP[i-1][1] + prices[i]);
  }
  return MP[prices.length - 1][2];
};

122. 买卖股票的最佳时机 II

function maxProfit(prices: number[]): number {
  // 贪心算法
  // let res:number = 0;
  // for (let i = 1; i < prices.length; i++) {
  //   if (prices[i] > prices[i-1]) {
  //     res += prices[i] - prices[i-1]
  //   }
  // }
  // return res;
  
  // 动态规划
  let len:number = prices.length
  if (!len) return 0;
  let MP:number[][] = Array.from({length: len}, ()=>{return Array.from({length: 2}, ()=>0)});
  MP[0][1] = -prices[0];
  // MP[i][0]:第i天不持有股票的收益,MP[i][1]: 第i天持有股票的收益
  for (let i = 1; i < len; i++) {
    MP[i][0] = Math.max(MP[i-1][0], MP[i-1][1] + prices[i]);
    MP[i][1] = Math.max(MP[i-1][1], MP[i-1][0] - prices[i]);
  }
  return MP[len-1][0]
};

123. 买卖股票的最佳时机 III

function maxProfit(prices: number[]): number {
  let len = prices.length
  if (!len) return 0;
  let MP:number[][][] = Array.from({length:len}, ()=>{return Array.from({length: 3}, ()=>{return Array.from({length:2}, ()=>-Infinity)})})
  // MP[i][j][0] 第i天,完成交易j次,是否持有股票
  MP[0][0][0] = 0
  MP[0][0][1] = -prices[0]
  for (let i = 1; i < len; i++) {
    for (let j = 0; j < 3; j++) {
      if (j < 1) {
        MP[i][j][0] = MP[i-1][j][0];
        MP[i][j][1] = Math.max(MP[i-1][j][1], MP[i-1][j][0] - prices[i])
      } else if (j >= 1 && j < 2) {
        MP[i][j][0] = Math.max(MP[i-1][j][0], MP[i-1][j-1][1] + prices[i])
        MP[i][j][1] = Math.max(MP[i-1][j][1], MP[i-1][j][0] - prices[i])
      } else {
        MP[i][j][0] = Math.max(MP[i-1][j][0], MP[i-1][j-1][1] + prices[i])
      }
    }
  }
  return Math.max(MP[len-1][0][0], MP[len-1][1][0], MP[len-1][2][0])
};

188. 买卖股票的最佳时机 IV

function maxProfit(k: number, prices: number[]): number {
  let len = prices.length;
  if (!prices.length || !k) return 0;
  let res:number = 0
  let MP:number[][][] = Array.from({ length: len }, ()=>{ 
    return Array.from({ length: k + 1 }, ()=>{ 
      return Array.from({ length: 2 }, ()=>-Infinity)
    })
  });
  MP[0][0][0] = 0;
  MP[0][0][1] = -prices[0];  
  // MP[i][j][0]: 第i天,第j笔交易,不持有股票
  for (let i = 1; i < len; i++) {
    for (let j = 0; j < k + 1; j++) {
      if (j < 1) {
        MP[i][j][0] = MP[i-1][j][0];
        MP[i][j][1] = Math.max(MP[i-1][j][1], MP[i-1][j][0] - prices[i]);
      } else if (j > 0 && j < k) {
        MP[i][j][0] = Math.max(MP[i-1][j][0], MP[i-1][j-1][1] + prices[i]);
        MP[i][j][1] = Math.max(MP[i-1][j][1], MP[i-1][j][0] - prices[i]);
      } else {
        MP[i][j][0] = Math.max(MP[i-1][j][0], MP[i-1][j-1][1] + prices[i]);
      }
      res = Math.max(res, MP[i][j][0])
    }
  }
  return res
};

309. 最佳买卖股票时机含冷冻期


function maxProfit(prices: number[]): number {
    
    // let len = prices.length;
    // if (!prices.length) return 0;
    // let res:number = 0
    // let MP:number[][][] = Array.from({ length: len }, ()=>{ 
    //   return Array.from({ length: 2 }, ()=>{ 
    //     return Array.from({ length: 2 }, ()=>-Infinity)
    //   })
    // });
    // MP[0][0][0] = 0;
    // MP[0][0][1] = -prices[0];
    // // MP[i][j][0]: 第i天,是否处于冷静期,0:否,1:是,不持有股票,0:不持有,1:持有
    // for (let i = 1; i < len; i++) {
    //     // 不存在在冷冻期并持有股票,第i-1天不是冷冻期持有股票第i天卖掉(那么将第i天视为冷冻期)
    //     // 第i天不是冷冻期不持有股票 => 第i-1天也不是冷冻期不持有股票,第i-1天在冷冻期不持有股票
    //     MP[i][0][0] = Math.max(MP[i-1][0][0], MP[i-1][1][0]);
    //     // 第i天是冷冻期不持有股票 => 第i-1天是非冷冻期持有股票
    //     MP[i][1][0] = MP[i-1][0][1] + prices[i];
    //     // 第i天不是冷冻期持有股票 => 第i-1天不是冷冻期持有股票,第i-1天不是冷冻期不持有股票第i天买入
    //     MP[i][0][1] = Math.max(MP[i-1][0][1], MP[i-1][0][0] - prices[i]);
    //     res = Math.max(res, MP[i][0][0], MP[i][1][0])
    // }
    // return res
    

    if(prices.length === 0 || prices === null){
        return 0;
    }
    let n = prices.length
    let dp:number[][] = Array.from({length:n},()=>{ return Array.from({length:3},()=>0)});
    
    // dp[i][0]为第i天不持有,当天没有卖出
    // dp[i][1]为第i天持有
    // dp[i][2]为第i天不持有,当天卖出
    
    dp[0][1] = -prices[0];
    for(let i = 1; i < n;i++){
        dp[i][0] = Math.max(dp[i-1][0],dp[i-1][2]);
        dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]);
        dp[i][2] = dp[i-1][1] + prices[i]
    }
    return Math.max(dp[n-1][0],dp[n-1][2]);
};

714. 买卖股票的最佳时机含手续费 解题思路同122类似

function maxProfit(prices: number[], fee: number): number {
  // 动态规划
  // let len = prices.length
  // if (!len) return 0;
  // let MP:number[][] = Array.from({length:len}, () => { return Array.from({length: 2}, ()=>-Infinity)});
  // let res:number = 0;
  // MP[0][1] = -prices[0];
  // MP[0][0] = 0;
  // for (let i = 1; i < len; i++) {
  //   MP[i][0] = Math.max(MP[i-1][0], MP[i-1][1] + prices[i] - fee);
  //   MP[i][1] = Math.max(MP[i-1][1], MP[i-1][0] - prices[i]);
  //   res = Math.max(res, MP[i][0])
  // }
  // return res;
  
  // 贪心算法
  // 将手续费放在买入时进行计算,buy初始值为prices[0]加上手续费fee,
  // 当prices[i] + fee < buy,buy = prices[i] + fee;
  // 当prices[i] > buy,记录收益prices[i] - buy,为了防止第二天继续上涨,buy更新为prices[i], 
  // 如果第i+1天价格上涨,那么继续记录收益prices[i+1] - prices[i],连续两天上涨的收益为 prices[i+1] - prices[i] + prices[i] - buy = prices[i+1] - buy
  // 如果第i+1天价格下跌,第i+2天的价格上涨到大于第i天,那么收益为prices[]i+2] - prices[i+1] - fee + prices[i] - fee
  let len = prices.length;
  if (!len) return 0;
  let res:number = 0;
  let buy:number = prices[0] + fee;
  for (let i = 1; i < len; i++) {
    if (prices[i] + fee < buy){
      buy = prices[i] + fee
    } else if (prices[i] > buy) {
      res += prices[i] - buy;
      buy = prices[i]
    }
  }
  return res;
};

300. 最长递增子序列

function lengthOfLIS(nums: number[]): number {
  // 解法一:动态规划,动态转移方程:DP[i] = Math.max(DP[i], DP[j] + 1)
  // let len:number = nums.length;
  // if (!len) return 0;
  // let DP:number[] = Array.from({length: len}, ()=>1)
  // let res:number = 1;
  // for (let i = 1; i < len; i++) {
  //   for (let j = 0; j < i; j++) {
  //     if (nums[j] < nums[i]) {
  //       DP[i] = Math.max(DP[i], DP[j] + 1);
  //     }
  //   }
  //   res = Math.max(res, DP[i])
  // }
  // return res;
  // 使用二分查找法,维护一个数组,这个数组的长度就是最长子序列的长度,时间复杂度为nlog(n)
  let len = nums.length
  if ( !len ) return 0;
  let res: number[] = [];
  for (let n of nums) {
    if (!res.length || n > res[res.length - 1]) {
      res.push(n)
    } else {
      // 二分插入的过程,在数组中找到第一个比当前值大的数的位置,并替换该数
      let s = 0;
      let e = res.length - 1;
      let loc = e;
      while (s <= e) {
        let mid = Math.floor((s + e) / 2)
        if (res[mid] >= n) {
          loc = mid;
          e = mid - 1
        } else {
          s = mid + 1
        }
      }
      res[loc] = n
    }
  }
  return res.length
};

322. 零钱兑换 解题思路同斐波拉契数列类似

function coinChange(coins: number[], amount: number): number {
    // 动态规划求解
    let max = amount + 1
    let DP:number[] = Array.from({length: max}, ()=> max)
    DP[0] = 0
    for (let i = 1; i <= amount; i++) {
        for (let j = 0; j < coins.length; j++) {
            if (i >= coins[j]) {
                DP[i] = Math.min(DP[i - coins[j]] + 1, DP[i])
            }
        }
    }
    return DP[amount] > amount ? -1 : DP[amount]
};

72. 编辑距离

非常经典的一道题目

function minDistance(word1: string, word2: string): number {
  // 动态转移方程:DP[i][j] word1的前i个字符与word2的前j个字符最少变化次数
  let len1 = word1.length
  let len2 = word2.length
  if (!len1 || !len2) return Math.max(len1, len2)
  let DP = Array.from({length: len1 + 1}, (w1, index1) => Array.from({length: len2 + 1}, (w2, index2) => {
    if (index1 === 0) return index2
    if (index2 === 0) return index1
    return 0
  }))

  for (let i = 1; i < len1 + 1; i++) {
    for (let j = 1; j < len2 + 1; j++) {
      if (word1[i-1] === word2[j-1]) {
        DP[i][j] = DP[i-1][j-1]
      } else {
        DP[i][j] = Math.min(DP[i-1][j], DP[i][j-1], DP[i-1][j-1]) + 1
      }
    }
  }
  return DP[len1][len2]
};

并查集

200. 岛屿数量 此处还可以使用并查集进行求解

function numIslands(grid: string[][]): number {
  let m:number = grid.length, n:number = grid[0].length;
  if (!m || !n) return 0;
  let res:number = 0;
  let dircts = [[-1,0],[1,0],[0,-1],[0,1]];
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (grid[i][j] === '1') {
        DFS(i,j);
        res += 1;
      }
    }
  }
  function DFS (r:number, c:number):void {
    if (r < 0 || c < 0 || r >= m || c >= n || grid[r][c] === '0') {
      return
    }
    grid[r][c] = '0';
    for (let i = 0; i < dircts.length; i++) {
      DFS(r + dircts[i][0], c + dircts[i][1])
    }
  }

  return res;
};

547. 省份数量 并查集方法

function findCircleNum(isConnected: number[][]): number {
    // 并查集求解
    let n = isConnected.length;
    let res:number = n; // 最多有n个
    // 建立并查集
    class UnionFind {        
        union: Array<number>;   // 也可以是对象
        constructor(){
            this.union = Array.from({length:n}, (val:number,index:number)=>index)
        }
        // 路径压缩
        find (x:number) {
            let root = x;
            while (root !== this.union[root]) {
                root = this.union[root]
            }

            while (x !== this.union[x]) {
                let tem = this.union[x]
                this.union[x] = root;
                x = tem;
            }

            return root;
        }
        // 合并路径,合并时数量减一
        merge(x:number,y:number){
            let x_root = this.find(x);
            let y_root = this.find(y);

            if (x_root !== y_root) {
                this.union[x_root] = y_root
                res -= 1
            }
        }
    }
    
    let union = new UnionFind()
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < i; j++) {
            if (isConnected[i][j]) {
                union.merge(i,j)
            }
        }
    }
    return res;
};

DFS求解法

function findCircleNum(isConnected: number[][]): number {
  let len = isConnected.length;
  if (!len) return 0;
  let visited:Map<number, boolean> = new Map();
  let res = 0
  // 如果这座城市没有被访问过,就遍历这座城市
  for (let i = 0; i < len; i++) {
    if (!visited.has(i)) {
      DFS(visited, i);
      res += 1
    }
  }
  return res;
  function DFS(visited:Map<number, boolean>, i:number){
    visited.set(i, true);
    // 标记该城市被访问过了
    // 遍历剩下的城市,如果这个城市没有被访问过并且与该城市相连就沿着这个城市进行访问,并进行访问标记
    for (let j = 0; j < len; j++) {
      if (!visited.has(j) && isConnected[i][j]) {
        DFS(visited, j)
      }
    }
  }
};

146. LRU 缓存

class LRUCache {
    // 使用map数据结构实现
    capSize: number;
    constructor(capacity: number) {
        this.capSize = capacity;
    }
    capacitor = new Map();
    // 在获取的时候如果有就记录该值,并且删除其在map中的值,并且重新存入该值,返回获取的值
    get(key: number): number {
        if (this.capacitor.has(key)) {
            let value = this.capacitor.get(key);
            this.capacitor.delete(key)
            this.capacitor.set(key,value)
            return value
        } else {
            return -1
        }
    }
    // 在存入的时候如果有就记录该值,删除其在map中的值,并且重新存入该值
    put(key: number, value: number): void {
        if (this.capacitor.has(key)) {
            this.capacitor.delete(key)
        }
        this.capacitor.set(key, value);
        if (this.capacitor.size > this.capSize) {
            this.capacitor.delete(this.capacitor.keys().next().value)
        }
    }
}