js解leetcode(45)-中等

本文分享了使用JavaScript解决任务调度问题、设计循环队列、在二叉树中增加一行、计算函数的独占时间以及如何处理大礼包的算法思路和解题技巧。探讨了不同问题的时间复杂度和空间复杂度。

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

1.任务调度器

题目:

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。

然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。

你需要计算完成所有任务所需要的 最短时间 。

思路:因为有冷却时间,所以任务执行的最短时间是n,最长时间取决于次数最多的任务。先计算任务出现的次数及出现最多的任务和数量,只需要考虑这些任务的执行。假设最多次数的任务,出现次数是k,有m个最多任务。那么时间就是(m-1)*(n+1)+k。然后取二者的较大值

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

/**
 * @param {character[]} tasks
 * @param {number} n
 * @return {number}
 */
var leastInterval = function(tasks, n) {
  if (!n) return tasks.length;
  const map = new Map();
  let max = 0;
  let list = [];
  for (const k of tasks) {
    map.set(k, (map.get(k) || 0) + 1);
    if (max < map.get(k)) {
      max = map.get(k);
      list.splice(0, Infinity, k);
    } else if (max === map.get(k)) {
      list.push(k);
    }
  }
  return Math.max((max - 1) * (n + 1) + list.length,tasks.length);
};

2.设计循环队列

题目:

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。

思路:用数组即可

var MyCircularQueue = function (k) {
  this.list = [];
  this.max = k;
};

/**
 * @param {number} value
 * @return {boolean}
 */
MyCircularQueue.prototype.enQueue = function (value) {
  if (this.list.length >= this.max) return false;
  this.list.push(value);
  return true;
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.deQueue = function () {
  if (!this.list.length) return false;
  this.list.shift();
  return true
};

/**
 * @return {number}
 */
MyCircularQueue.prototype.Front = function () {
  if (!this.list.length) return -1;
  return this.list[0];
};

/**
 * @return {number}
 */
MyCircularQueue.prototype.Rear = function () {
  if (!this.list.length) return -1;
  return this.list[this.list.length - 1];
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.isEmpty = function () {
  return !this.list.length;
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.isFull = function () {
  return this.list.length === this.max;
};

3.在二叉树中增加一行

题目:

给定一个二叉树,根节点为第1层,深度为 1。在其第 d 层追加一行值为 v 的节点。

添加规则:给定一个深度值 d (正整数),针对深度为 d-1 层的每一非空节点 N,为 N 创建两个值为 v 的左子树和右子树。

将 N 原先的左子树,连接为新节点 v 的左子树;将 N 原先的右子树,连接为新节点 v 的右子树。

如果 d 的值为 1,深度 d - 1 不存在,则创建一个新的根节点 v,原先的整棵树将作为 v 的左子树。

思路:递归。利用层序遍历的方式。先判断是否是跟节点,然后遍历节点,记录该节点的层级k,如果k===d-1,那么该节点就是需要插入的节点的父节点

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

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} v
 * @param {number} d
 * @return {TreeNode}
 */
var addOneRow = function(root, v, d) {
  if (d < 2) {
    const node = new TreeNode(v);
    node.left = root;
    return node;
  }
  const search = (root, level) => {
    if (!root) return;
    if (level === d - 1) {
      const left = root.left;
      const right = root.right;
      const leftNode = new TreeNode(v);
      const rightNode = new TreeNode(v);
      leftNode.left = left;
      rightNode.right = right;
      root.left = leftNode;
      root.right = rightNode;
    } else {
      search(root.left, level + 1);
      search(root.right, level + 1);
    }
    return root;
  };
  return search(root, 1);
};

4.函数的独占时间

题目:

给出一个非抢占单线程CPU的 个函数运行日志,找到函数的独占时间。

每个函数都有一个唯一的 Id,从 0 到 n-1,函数可能会递归调用或者被其他函数调用。

日志是具有以下格式的字符串:function_id:start_or_end:timestamp。例如:"0:start:0" 表示函数 0 从 0 时刻开始运行。"0:end:0" 表示函数 0 在 0 时刻结束。

函数的独占时间定义是在该方法中花费的时间,调用其他函数花费的时间不算该函数的独占时间。你需要根据函数的 Id 有序地返回每个函数的独占时间。

思路:这题开始想的是递归的思路。如果遇到两个start,就进入递归,否则在同一层处理。

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

/**
 * @param {number} n
 * @param {string[]} logs
 * @return {number[]}
 */
const findIndex = (logs, index, id) => {
  let c = 0;
  for (let i = index; i < logs.length; i++) {
    let [cid, flag, t] = logs[i];
    if (cid !== id) continue;
    if (flag === "start") {
      c++;
    } else {
      c--;
    }
    if (!c) return i + 1;
  }
};
var exclusiveTime = function (n, logs) {
  const time = new Array(n).fill(0);
  logs = logs.map((item) => {
    const res = item.split(":");
    res[2] = Number(res[2]);
    return res;
  });
  const search = (logs) => {
    const l = logs.length;
    let cur = 0;
    const stack = [];
    for (let i = 0; i < l; i) {
      let [id, flag, t] = logs[i];
      if (flag === "end") {
        const curTime = Number(t) - stack.pop() - cur + 1;
        time[id] += curTime;
        i++;
      } else if (flag === "start" && stack.length) {
        const index = findIndex(logs, i, id);
        search(logs.slice(i, index));
        const curTime = logs[index - 1][2] - logs[i][2] + 1;
        cur += curTime;
        i = index;
      } else {
        stack.push(t);
        i++;
      }
    }
  };
  search(logs);
  return time;
};

然后,利用栈的思想,记录start的任务下标。如果栈内有超过一个start,那么,start之间的时间就是栈顶任务的独占时间。所以只需要一次遍历,用一个遍历记录上个节点的时间即可

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

/**
 * @param {number} n
 * @param {string[]} logs
 * @return {number[]}
 */

var exclusiveTime = function (n, logs) {
  const time = new Array(n).fill(0);
  let pre = 0;
  const stack = [];
  for (let log of logs) {
    let [id, flag, t] = log.split(":");
    t = Number(t);
    if (flag === "start") {
      if (stack.length) {
        time[stack[stack.length - 1]] += t - pre - 1;
      }
      stack.push(id);
      pre = t;
    } else {
      stack.pop();
      time[id] += t - pre + 1;
      pre = t;
    }
  }

  return time;
};

换个角度,对于一个任务的开始和结束,每次都加,这样存在嵌套,就需要减去嵌套的时间。

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

/**
 * @param {number} n
 * @param {string[]} logs
 * @return {number[]}
 */

var exclusiveTime = function (n, logs) {
  const time = new Array(n).fill(0);
  logs = logs.map((item) => {
    const res = item.split(":");
    res[2] = Number(res[2]);
    return res;
  });
  const stack = [];
  let preStart = [];
  for (const log of logs) {
    const [id, flag, t] = log;
    if (flag === "start") {
      stack.push(id);
      preStart.push(t);
    } else {
      stack.pop();
      const duration = t - preStart.pop() + 1;
      time[id] += duration;
      if (stack.length) {
        time[stack[stack.length - 1]] -= duration;
      }
    }
  }

  return time;
};

5.大礼包

题目:

在LeetCode商店中, 有许多在售的物品。

然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。

现给定每个物品的价格,每个大礼包包含物品的清单,以及待购物品清单。请输出确切完成待购清单的最低花费。

每个大礼包的由一个数组中的一组数据描述,最后一个数字代表大礼包的价格,其他数字分别表示内含的其他种类物品的数量。

任意大礼包可无限次购买。

思路:递归+回溯。用map记录每一种组合花费的金额,在遍历礼包时,如果礼包不能加入组合,就跳过,加入,就和之前同样的组合比较

/**
 * @param {number[]} price
 * @param {number[][]} special
 * @param {number[]} needs
 * @return {number}
 */
var shoppingOffers = function(price, special, needs) {
  let map = new Map();

  let f = function (curNeeds, preCost) {
    let key = curNeeds.join("-") + "_" + preCost;
    if (map.has(key)) return map.get(key);
    let ans = Infinity;

    for (let present of special) {
      let flag = true;
      let newNeeds = new Array(curNeeds.length);
      for (let j = 0; j < curNeeds.length; j++) {
        newNeeds[j] = curNeeds[j] - present[j];
        if (present[j] > curNeeds[j]) {
          flag = false;
          break;
        }
      }
      if (flag) {
        ans = Math.min(ans, f(newNeeds, preCost + present[present.length - 1]));
      }
    }

    let sum = 0;
    for (let i = 0; i < curNeeds.length; i++) {
      sum += price[i] * curNeeds[i];
    }
    ans = Math.min(ans, preCost + sum);
    map.set(key, ans);
    return ans;
  };
  return f(needs, 0);
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值