js解leetcode(66)-中等

本文通过JavaScript解决LeetCode中的几个经典问题:1.救生艇问题,采用排序和双指针策略;2.螺旋矩阵问题,利用螺旋行走规律;3.可能的二分法,采用深度优先搜索进行分组;4.根据前序和后序遍历构造二叉树,利用递归构造;5.查找和替换模式,通过字符映射比较字符串。详细解析了每个问题的思路和时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.救生艇

题目:

第 i 个人的体重为 people[i],每艘船可以承载的最大重量为 limit

每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit

返回载到每一个人所需的最小船数。(保证每个人都能被船载)。

思路:先排序,然后取体重最大的人,如果此时体重最大的人+体重最小的人小于limit,说明这两个人可以在一艘船上,否则体重最大的人单独一搜.可以用数组也可以用双指针的方式遍历

时间复杂度O(nlogn),空间复杂度O(logn)

/**
 * @param {number[]} people
 * @param {number} limit
 * @return {number}
 */
var numRescueBoats = function(people, limit) {
  people.sort((a, b) => a - b);
  let c = 0;
  const l = people.length;
  let right = l - 1,
    left = 0;
  while (right >= left) {
    if (right === left) {
      right--;
      c++;
      continue;
    }
    if (people[right] + people[left] <= limit) {
      left++;
    }
    right--;
    c++;
  }
  return c;
};

2.螺旋矩阵

题目:

在 R 行 C 列的矩阵上,我们从 (r0, c0) 面朝东面开始

这里,网格的西北角位于第一行第一列,网格的东南角位于最后一行最后一列。

现在,我们以顺时针按螺旋状行走,访问此网格中的每个位置。

每当我们移动到网格的边界之外时,我们会继续在网格之外行走(但稍后可能会返回到网格边界)。

最终,我们到过网格的所有 R * C 个空间。

按照访问顺序返回表示网格位置的坐标列表。

思路:观察可知,每次移动的长度是1,1,2,2,3,3...,所以用一个变量记录方向,另一个变量记录当前这次走的长度。终止条件,可以提前计算四个顶点的坐标,判断此时经过了多少个顶点。也可以根据矩阵的宽高和起始位置计算路径矩阵的宽高

时间复杂度O(mn),空间复杂度O(mn)

/**
 * @param {number} R
 * @param {number} C
 * @param {number} r0
 * @param {number} c0
 * @return {number[][]}
 */
var spiralMatrixIII = function(R, C, r0, c0) {
  let flag = 0;
  let res = [[r0, c0]];
  let popsition = new Set([
    `0-${C - 1}`,
    `0-0`,
    `${R - 1}-0`,
    `${R - 1}-${C - 1}`,
  ]);
  let c = popsition.has(`${r0}-${c0}`) ? 1 : 0;
  let i = r0,
    j = c0;
  let l = 0;
  while (c < popsition.size) {
    let cur = ~~(l / 2) + 1;
    l++;
    if (flag === 0) {
      while (cur--) {
        j++;
        res.push([i, j]);
        if (popsition.has(`${i}-${j}`)) {
          c++;
        }
      }
      flag = 1;
    } else if (flag === 1) {
      while (cur--) {
        i++;
        res.push([i, j]);
        if (popsition.has(`${i}-${j}`)) {
          c++;
        }
      }
      flag = 2;
    } else if (flag === 2) {
      while (cur--) {
        j--;
        res.push([i, j]);
        if (popsition.has(`${i}-${j}`)) {
          c++;
        }
      }
      flag = 3;
    } else {
      while (cur--) {
        i--;
        res.push([i, j]);
        if (popsition.has(`${i}-${j}`)) {
        c++;
      }
      }

      flag = 0;
    }
  }
  return res.filter(
    (item) => item[0] >= 0 && item[0] < R && item[1] >= 0 && item[1] < C
  );
};

3.可能的二分法

题目:

给定一组 N 人(编号为 1, 2, ..., N), 我们想把每个人分进任意大小的两组。

每个人都可能不喜欢其他人,那么他们不应该属于同一组。

形式上,如果 dislikes[i] = [a, b],表示不允许将编号为 a 和 b 的人归入同一组。

当可以用这种方法将所有人分进两组时,返回 true;否则返回 false。

思路:这题和之前一题很像,相邻染色即可,

初始化数组dp,dp[i]表示每个人的状态。0表示未分组,1表示分到了组1,2表示分到了组2,3表示不再组1或者组2,也就是出现3的话,返回false

先记录每个人和对应的不喜欢的人,然后遍历每个人,如果这个人已经分组,就递归遍历他不喜欢的人,根据之前的分组,将这些人分到另一个组。在深度优先遍历的时候,遇到当前值和已有值不为0且不同,则该人状态置设为3

时间复杂度O(m+n),空间复杂度O(m+n)

/**
 * @param {number} N
 * @param {number[][]} dislikes
 * @return {boolean}
 */
var possibleBipartition = function(N, dislikes) {
  const dp = new Array(N).fill(0);
  const map = new Map();
  for (const [a, b] of dislikes) {
    if (!map.get(a - 1)) {
      map.set(a - 1, []);
    }
    if (!map.get(b - 1)) {
      map.set(b - 1, []);
    }
    map.get(a - 1).push(b - 1);
    map.get(b - 1).push(a - 1);
  }
  const dfs = (i, v) => {
    if (dp[i]) {
      if (dp[i] !== v) {
        dp[i] = 3;
      }
      return;
    } else {
      dp[i] = v;
      const next = map.get(i);
      if(!next)return
      next.forEach((item) => {
        dfs(item, v === 1 ? 2 : 1);
      });
    }
  };
  for (let i = 0; i < N; i++) {
    if (dp[i]) continue;
    dfs(i, 1);
  }
  return dp.every((item) => item !== 3);
};

4.根据前序遍历和后序遍历构造二叉树

题目:

返回与给定的前序和后序遍历匹配的任何二叉树。

 pre 和 post 遍历中的值是不同的正整数。

思路:前序遍历,是先遍历根节点再遍历左右节点。

后序遍历,是先遍历左右子节点再遍历根节点。

这种根据遍历的结果构造二叉树的,基本上都能用递归做。

前序遍历的第一个节点是根节点,然后是左子树的节点,然后是右子树的节点。

所以将第一个值构造为根节点,然后找到左子树的根节点在后序遍历的下标,这样下标+1就是左子树的节点数量,

然后在前序遍历截取左、右子树的节点,递归处理。

时间复杂度O(n),空间复杂度O(logn)

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {number[]} pre
 * @param {number[]} post
 * @return {TreeNode}
 */
var constructFromPrePost = function(pre, post) {
  if (pre.length == 0) {
    return null;
  }

  let tmp = pre[0];
  let index = post.findIndex((item) => item === pre[1]);
  let root = new TreeNode(tmp);
  root.left = constructFromPrePost(
    pre.slice(1, index + 2),
    post.slice(0, index + 1)
  );
  root.right = constructFromPrePost(
    pre.slice(index + 2),
    post.slice(index + 1, post.length - 1)
  );
  return root;
};

5.查找和替换模式

题目:

你有一个单词列表 words 和一个模式  pattern,你想知道 words 中的哪些单词与模式匹配。

如果存在字母的排列 p ,使得将模式中的每个字母 x 替换为 p(x) 之后,我们就得到了所需的单词,那么单词与模式是匹配的。

(回想一下,字母的排列是从字母到字母的双射:每个字母映射到另一个字母,没有两个字母映射到同一个字母。)

返回 words 中与给定模式匹配的单词列表。

你可以按任何顺序返回答案。

思路:这里其实是个双向映射,原字母一一映射到模式字母,模式字母也要一一映射到原字母,

因为字符串有多个,用字母映射的话,需要重复记录多个映射关系,所以转换为数字即可。

第一个没有出现过的字符记为1,第二个没有出现过的字符记为2..,然后直接比较字符串是否相等即可,记得加分隔符

时间复杂度O(mn),空间复杂度O(,),m是words长度,n是字符串长度

/**
 * @param {string[]} words
 * @param {string} pattern
 * @return {string[]}
 */
var findAndReplacePattern = function(words, pattern) {
  const getS = (item) => {
    const map = new Map();
    let c = 1;
    let res = "";
    for (const s of item) {
      if (!map.get(s)) {
        map.set(s, c++);
      }
      res += `${map.get(s)}-`;
    }
    return res;
  };
  const copyWords = words.map(getS);
  const cPattern = getS(pattern);
  return words.filter((i, index) => {
    return cPattern === copyWords[index];
  });
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值