Bootstrap

【力扣hot100】day3

目录

21、跳跃游戏

题目内容

题解

22、合并区间

题目内容

题解

23、不同路径

题目内容

题解

 24、最小路径和

题目内容

题解

25、爬楼梯

题目内容

题解

26、颜色分类

题目内容

题解

27、子集

题目内容

题解

28、单词搜索

题目内容

题解

29、二叉树的中序遍历

题目内容

题解

30、不同的二叉搜索树

题目内容

题解

 

 

 


21、跳跃游戏

题目内容

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。

示例 1:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。


示例 2:

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

题解

也是最基本的动态规划类型,不再多说。 

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canJump = function (nums) {
  let n = nums.length;
  // 记录最左边的能够到达最后一个地方的位置
  let pos = n - 1;
  for (let i = n - 2; i >= 0; i--) {
    if (i + nums[i] >= pos) {
      pos = i;
    }
  }
  return pos === 0;
};

22、合并区间

题目内容

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].


示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

题解

这个曾在面试中手撕过,其实也是动态规划的思想。 

/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function (intervals) {
  // 升序排序
  intervals.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
  // 区间起止
  let start = -1,
    end = -1;
  // 结果
  let res = [];
  for (let interval of intervals) {
    if (start === -1) {
      start = interval[0];
      end = interval[1];
    } else if (interval[0] <= end && interval[1]>end) {
      end = interval[1];
    } else if (interval[0] > end) {
      res.push([start, end]);
      start = interval[0];
      end = interval[1];
    }
  }
  if (start > -1) {
    res.push([start, end]);
  }
  return res;
};

23、不同路径

题目内容

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:

输入:m = 3, n = 7
输出:28


示例 2:

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下


示例 3:

输入:m = 7, n = 3
输出:28


示例 4:

输入:m = 3, n = 3
输出:6

题解

还是动态规划,规划麻了...... 

/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function (m, n) {
  // 到该点的路径数目
  let dp = new Array(m).fill(0).map((item) => new Array(n).fill(-1));
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (i === 0 || j === 0) {
        // 左边界和上边界只能有一条路过来
        dp[i][j] = 1;
      } else {
        dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
      }
    }
  }
  return dp[m - 1][n - 1];
};

 24、最小路径和

题目内容

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例 1:

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。


示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

题解

又又又是动态规划...... 

/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function (grid) {
  let m = grid.length,
    n = grid[0].length;
  // 到该点的最短路径长度
  let dp = new Array(m).fill(0).map((item) => new Array(n).fill(-1));
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (i === 0 && j === 0) {
        dp[i][j] = grid[i][j];
      } else if (i === 0) {
        dp[i][j] = dp[i][j - 1] + grid[i][j];
      } else if (j === 0) {
        dp[i][j] = dp[i - 1][j] + grid[i][j];
      } else {
        dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
      }
    }
  }
  return dp[m - 1][n - 1];
};

25、爬楼梯

题目内容

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶


示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

题解

类似于斐波那契数列。 

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function (n) {
  // f(n)=f(n-1)+f(n-2)
  if (n <= 2) {
    return n;
  }
  let p = 1,
    q = 2,
    x = -1;
  for (let i = 3; i <= n; i++) {
    x = p + q;
    p = q;
    q = x;
  }
  return x;
};

26、颜色分类

题目内容

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

必须在不使用库的sort函数的情况下解决这个问题。

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]


示例 2:

输入:nums = [2,0,1]
输出:[0,1,2]

题解

我比较暴力,把0和2全删了,然后在开头和结尾插入即可......

这个思路显然不够优雅。 

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var sortColors = function (nums) {
  // 把0和2都删除,并存入数组
  let zeros = [],
    twos = [];
  for (let i = 0; i < nums.length; ) {
    if (nums[i] === 0) {
      nums.splice(i, 1);
      zeros.push(0);
    } else if (nums[i] === 2) {
      nums.splice(i, 1);
      twos.push(2);
    } else {
      i++;
    }
  }
  nums.unshift(...zeros);
  nums.push(...twos);
};

27、子集

题目内容

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]


示例 2:

输入:nums = [0]
输出:[[],[0]] 

题解

这道题我解出来了,但解法肯定不是最优的。 

具体思路在代码中写的很清楚了,简单来说,就是对于一个长度为n的数组,求0-n组合,用到了递归。与之前求全排列思路类似,但有些不同。全排列的时候,是标记第几个数字,并且在没有访问过的元素中选择这个数字,要求顺序,因此,在后面还是有可能取到这个数字,所以把它作为开头数字的所有情况都取到之后,还需要把它置为没有访问过;但对于组合,就必须把它置为访问过,且它之后的数字都置为没访问过,这样才能保证全部情况都能取到。

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function (nums) {
  let size = nums.length;
  let visited = new Array(size);
  let res = [];
  for (let i = 0; i <= size; i++) {
    visited.fill(false);
    res.push(...nSubSet(nums, visited, i));
  }
  return res;
};

// 取n个元素的子集
function nSubSet(nums, visited, n) {
  // 返回数据格式:一个数组,数组的每个元素都是数组,代表所有元素个数为n的子集
  if (n === 0) {
    return [[]];
  }
  if (n === nums.length) {
    return [nums];
  }
  // 思路:按顺序枚举
  // 例如:1 2 3 4 5,将会得到1 2 3、1 2 4、1 2 5等等
  let res = [];
  // 先找出所有还没访问过的元素的位置
  let hasNotVisited = [];
  for (let i = 0; i < nums.length; i++) {
    if (!visited[i]) {
      hasNotVisited.push(i);
    }
  }
  // 从前往后,找出一个来,作为开头元素
  for (let i = 0; i < hasNotVisited.length; i++) {
    // 标记开头元素已经访问过
    visited[hasNotVisited[i]] = true;
    // 在开头元素确定的情况下,再找n-1个子集
    let sub = nSubSet(nums, visited, n - 1);
    // 把以当前元素开头的n子集放入结果数组
    for (let s of sub) {
      let temp = JSON.parse(JSON.stringify(s));
      temp.unshift(nums[hasNotVisited[i]]);
      res.push(temp);
    }
    // 此时,要把所有i后面的元素均标记为没有访问过
    // 否则,已经访问过的元素无法作为开头元素了
    for (let j = i + 1; j < hasNotVisited.length; j++) {
      visited[hasNotVisited[j]] = false;
    }
  }
  return res;
}

28、单词搜索

题目内容

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例 1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true


示例 2:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出:true


示例 3:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"
输出:false

题解

相比于前面几道递归查找的题目,这道题难度要小些。只是记住,在从某个位置开始查找,如果结果为false,那么记得把这个位置改回未访问,否则再搜索的时候就又无法访问了。 

/**
 * @param {character[][]} board
 * @param {string} word
 * @return {boolean}
 */
var exist = function (board, word) {
  let m = board.length,
    n = board[0].length;
  // 是否访问过
  let visited = null;
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      visited = new Array(m).fill(0).map((item) => new Array(n).fill(false));
      if (find(board, visited, word, i, j)) {
        return true;
      }
    }
  }
  return false;
};

// 从startX,startY出发是否能够找到word
function find(board, visited, word, startX, startY) {
  if (!word.length || board[startX][startY] !== word[0]) {
    return false;
  }
  if (word.length === 1) {
    return true;
  }
  visited[startX][startY] = true;
  let subWord = word.substring(1, word.length);
  // 递归查找
  if (
    startX - 1 >= 0 &&
    !visited[startX - 1][startY] &&
    find(board, visited, subWord, startX - 1, startY)
  ) {
    return true;
  }
  if (
    startY - 1 >= 0 &&
    !visited[startX][startY - 1] &&
    find(board, visited, subWord, startX, startY - 1)
  ) {
    return true;
  }
  if (
    startX + 1 < board.length &&
    !visited[startX + 1][startY] &&
    find(board, visited, subWord, startX + 1, startY)
  ) {
    return true;
  }
  if (
    startY + 1 < board[0].length &&
    !visited[startX][startY + 1] &&
    find(board, visited, subWord, startX, startY + 1)
  ) {
    return true;
  }
  visited[startX][startY] = false;
  return false;
}

29、二叉树的中序遍历

题目内容

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

示例 1:

输入:root = [1,null,2,3]
输出:[1,3,2]


示例 2:

输入:root = []
输出:[]


示例 3:

输入:root = [1]
输出:[1]

题解

唉,真是生疏了,连这种最基础的题目都手生......还是得多写题目才行啊! 

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function (root) {
  if (!root) {
    return [];
  }
  let node = root;
  // 结果
  let res = [];
  // 栈存储
  let stack = [];
  while (stack.length || node) {
    while (node) {
      stack.push(node);
      node = node.left;
    }
    node = stack.pop();
    res.push(node.val);
    node = node.right;
  }
  return res;
};

30、不同的二叉搜索树

题目内容

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例 1:

输入:n = 3
输出:5


示例 2:

输入:n = 1
输出:1

题解

也是老朋友了,就是从1到n依次做根节点,求值即可。 

/**
 * @param {number} n
 * @return {number}
 */
var numTrees = function (n) {
  return treeNumUse(1, n);
};

function treeNumUse(m, n) {
  // 求从m到n的二叉搜索树种类数
  if (m > n) {
    return 0;
  }
  if (m === n) {
    return 1;
  }
  let res = 0;
  // 依次做根节点
  for (let i = m; i <= n; i++) {
    // i做根节点
    let leftNum = treeNumUse(m, i - 1);
    let rightNum = treeNumUse(i + 1, n);
    leftNum = leftNum === 0 ? 1 : leftNum;
    rightNum = rightNum === 0 ? 1 : rightNum;
    res += leftNum * rightNum;
  }
  return res;
}

 

 

 

;