综合利用各种数据结构

384 阅读30分钟

常用的数据结构

常用的数据结构有数组,hash表,

448. 找到所有数组中消失的数字

题目描述

给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。

找到所有在 [1, n] 范围之间没有出现在数组中的数字。

例子1

Input: [4,3,2,7,8,2,3,1]

output: [5,6]

思考

1 首先想到了使用数组的特性,因为这里1 ≤ a[i] ≤ n,所可以把每个a[i]放到数组中a[i]的位置上,到最后发现数组上没有放置数的位置就是缺失的数字

参考实现1

实现1

/**
 * @param {number[]} nums
 * @return {number[]}
 */

const swap = (nums, i, j) => {
  const temp = nums[j];
  nums[j] = nums[i];
  nums[i] = temp;
};
// Runtime: 204 ms, faster than 29.69% of JavaScript online submissions for Find All Numbers Disappeared in an Array.
// Memory Usage: 47.3 MB, less than 48.68% of JavaScript online submissions for Find All Numbers Disappeared in an Array.
export default (nums) => {
  const len = nums.length;
  const res = new Array(len).fill(0);
  for (let i = 0; i < nums.length; i++) {
    if (res[nums[i] - 1] === 0) {
      res[nums[i] - 1] = nums[i];
    }
  }

  for (let i = 0; i < res.length; i++) {
    if (res[i] === 0) {
      res[i] = i + 1;
    } else {
      res[i] = 0;
    }
  }

  return res.filter((item) => item !== 0);
};

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

48. 旋转图像

题目描述

给定一个 n × n 的二维矩阵表示一个图像。

将图像顺时针旋转 90 度。

说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

例子1

给定 matrix = 
[
 [1,2,3],
 [4,5,6],
 [7,8,9]
],

原地旋转输入矩阵,使其变为:
[
 [7,4,1],
 [8,5,2],
 [9,6,3]
]

例子2

给定 matrix =
[
 [ 5, 1, 9,11],
 [ 2, 4, 8,10],
 [13, 3, 6, 7],
 [15,14,12,16]
], 

原地旋转输入矩阵,使其变为:
[
 [15,13, 2, 5],
 [14, 3, 4, 1],
 [12, 6, 8, 9],
 [16, 7,10,11]
]

思考

1 这里首先想到了很明显可以递归,首先交换外层的,然后再递归交换里边的

参考实现1

2 第二种是比较通用的方法,是解决类似的顺时针或者逆时针旋转的方法

 * 顺时针旋转
 * 首先上下交换,这里是指第一行和最后一行交换,第二行和倒数第二行,依次类推, 然后交换对角线的数字 
 * 1 2 3     7 8 9     7 4 1
 * 4 5 6  => 4 5 6  => 8 5 2
 * 7 8 9     1 2 3     9 6 3
 * 逆时针旋转
 * 首先左右交换,这里是指第一列和最后一列交换,第二列和倒数第二列,依次类推, 然后交换对角线的数字 
 * 1 2 3     3 2 1     3 6 9
 * 4 5 6  => 6 5 4  => 2 5 8
 * 7 8 9     9 8 7     1 4 7

参考实现2

实现1

/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */

const rotate1 = (matrix, begin, end) => {
  const len = end - begin;
  if (begin >= end) {
    return;
  }
  let tempTop = [];
  for (let i = begin; i <= end; i++) {
    tempTop.push(matrix[begin][i]);
  }

  let tempRight = [];
  for (let i = begin; i <= end; i++) {
    tempRight.push(matrix[i][end]);
  }
  let tempBottom = [];
  for (let i = end; i >= begin; i--) {
    tempBottom.push(matrix[end][i]);
  }
  let tempLeft = [];
  for (let i = end; i >= begin; i--) {
    tempLeft.push(matrix[i][begin]);
  }
  // 替换最右边
  for (let i = begin; i <= end; i++) {
    matrix[i][end] = tempTop[i - begin];
  }
  // console.log(temp, matrix);
  for (let i = end; i >= begin; i--) {
    matrix[end][i] = tempRight[end - i];
  }
  for (let i = end; i >= begin; i--) {
    matrix[i][begin] = tempBottom[end - i];
  }
  for (let i = begin; i <= end; i++) {
    // console.log(tempLeft);
    matrix[begin][i] = tempLeft[i - begin];
  }

  rotate1(matrix, begin + 1, end - 1);
};
// Runtime: 76 ms, faster than 82.34% of JavaScript online submissions for Rotate Image.
// Memory Usage: 38.9 MB, less than 23.65% of JavaScript online submissions for Rotate Image.
const rotate = (matrix) => {
  const len = matrix.length;
  rotate1(matrix, 0, len - 1);
  // console.log(matrix);
  // return matrix;
};
export default rotate;


实现2

/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */

// Runtime: 72 ms, faster than 93.88% of JavaScript online submissions for Rotate Image.
// Memory Usage: 38.9 MB, less than 32.93% of JavaScript online submissions for Rotate Image.
const rotate = (matrix) => {
  const len = matrix.length;
  let low = 0;
  let high = len - 1;
  while (low < high) {
    for (let i = 0; i < len; i++) {
      const temp = matrix[low][i];
      matrix[low][i] = matrix[high][i];
      matrix[high][i] = temp;
    }
    low++;
    high--;
  }
  for (let i = 1; i < len; i++) {
    for (let j = 0; j < i; j++) {
      const temp = matrix[i][j];
      matrix[i][j] = matrix[j][i];
      matrix[j][i] = temp;
    }
  }
};
export default rotate;

时间复杂度O(n * n) 空间复杂度O(1)

240. 搜索二维矩阵 II

题目描述

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

例子1
16bcdc98c9b99977e0eeedf4edf5fa65.jpeg
input: matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
output: true

思考

1 刚开始想使用二分搜索,可是后来发现不行,后来看了下题解很容易,只是特别不容易想到

d217597169b4f7d1008d3d5f507b1adc.jpeg

参考实现1

实现1

/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
// Runtime: 296 ms, faster than 64.11% of JavaScript online submissions for Search a 2D Matrix II.
// Memory Usage: 41.8 MB, less than 58.69% of JavaScript online submissions for Search a 2D Matrix II.
export default (matrix, target) => {
  if (matrix.length === 0) {
    return false;
  }
  let row = 0;
  let col = matrix[0].length - 1;
  while (col >= 0 && row < matrix.length) {
    if (target === matrix[row][col]) {
      return true;
    } else if (target < matrix[row][col]) {
      col--;
    } else {
      row++;
    }
  }
  return false;
};

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

769. 最多能完成排序的块

题目描述

数组arr是[0, 1, ..., arr.length - 1]的一种排列,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。

我们最多能将数组分成多少块?


例子1
input: arr = [4,3,2,1,0]
output: 1
解释: 将数组分成2块或者更多块,都无法得到所需的结果。 例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。

例子2
input: arr = [1,0,2,3,4]
output: 4
解释: 我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。 然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。

注意:

1 arr 的长度在 [1, 10] 之间。
2 arr[i]是 [0, 1, ..., arr.length - 1]的一种排列。

思考

1 刚开始想遇到最大值就拆开,可是发现不行,比如输入[1,2,0,5]的时候,就不能按照这种策略来执行,后来发现如果我们遇到想要拆开的时候,必须在max前面的所有数字必须都已经在前面的数组中使用过了。所以可以设置一个hasUsed数组来记录数字是否使用过

参考实现1

2 还有特别简单的方法,当然这里的简单是指解法,但是并不是指可以很简单的思考出来。

我们可以遍历数组,记录遇到的最大数字max,如果发现max等于我们遍历数组的下标,就可以拆开了。

这里主要是因为输入数组的里边的数组刚好等于数组的长度减1,所以当遇到max等于数组下标的时候,这时候就可以拆分为一个子数组,因为这时候前面的肯定都排好序了或者都在目前拆分的数组中

参考实现2

实现1

/**
 * @param {number[]} arr
 * @return {number}
 */
// Runtime: 68 ms, faster than 98.04% of JavaScript online submissions for Max Chunks To Make Sorted.
// Memory Usage: 38.5 MB, less than 50.98% of JavaScript online submissions for Max Chunks To Make Sorted.
export default (arr) => {
  const hasUsed = new Array(arr.length).fill(0);
  let res = 1;
  let max = arr[0];
  hasUsed[max] = 1;
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] > max) {
      let flag = hasUsed[0];
      for (let j = 1; j < max; j++) {
        flag &= hasUsed[j];
      }
      if (flag) {
        res++;
      }
      max = arr[i];
    }
    hasUsed[arr[i]] = 1;
  }
  return res;
};

实现2

/**
 * @param {number[]} arr
 * @return {number}
 */
// Runtime: 64 ms, faster than 100.00% of JavaScript online submissions for Max Chunks To Make Sorted.
// Memory Usage: 38.5 MB, less than 50.98% of JavaScript online submissions for Max Chunks To Make Sorted.
export default (arr) => {
  let res = 0;
  let max = arr[0];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] > max) {
      max = arr[i];
    }
    if (max === i) {
      res++;
    }
  }
  return res;
};

769. 最多能完成排序的块

题目描述

使用栈模拟队列,栈只允许两种操作,入栈和出栈

进阶:

是否可以所有的操作都可以均摊为O(1)

思考

1 使用两个栈实现,思路比较简单,就是来回搬运数据

可以参考实现1

2 如果想所有的操作都是0(1),可以使用front代表目前的队列首元素,这样push,peek和empty都很容易是O(1)操作的时间,而push的时间复杂度最坏的情况下是O(n),为了达到O(1)的时间,可以想下办法,push 的操作n次之后,我们才在pop的时候执行一次搬运操作,可以参考下代码

可以参考实现2

实现1

/**
 * Initialize your data structure here.
 */
// Runtime: 72 ms, faster than 87.77% of JavaScript online submissions for Implement Queue using Stacks.
// Memory Usage: 38.7 MB, less than 10.22% of JavaScript online submissions for Implement Queue using Stacks.
var MyQueue = function () {
  this.stack1 = [];
  this.stack2 = [];
};

/**
 * Push element x to the back of queue.
 * @param {number} x
 * @return {void}
 */
MyQueue.prototype.push = function (x) {
  this.stack1.push(x);
};

/**
 * Removes the element from in front of queue and returns that element.
 * @return {number}
 */
MyQueue.prototype.pop = function () {
  if (this.stack1.length === 0) {
    return false;
  }
  while (this.stack1.length > 0) {
    const temp = this.stack1.pop();
    this.stack2.push(temp);
  }
  const res = this.stack2.pop();

  while (this.stack2.length > 0) {
    const temp = this.stack2.pop();
    this.stack1.push(temp);
  }
  return res;
};

/**
 * Get the front element.
 * @return {number}
 */
MyQueue.prototype.peek = function () {
  if (this.stack1.length === 0) {
    return false;
  }
  while (this.stack1.length > 0) {
    const temp = this.stack1.pop();
    this.stack2.push(temp);
  }
  const res = this.stack2.pop();
  this.stack2.push(res);
  while (this.stack2.length > 0) {
    const temp = this.stack2.pop();
    this.stack1.push(temp);
  }
  return res;
};

/**
 * Returns whether the queue is empty.
 * @return {boolean}
 */
MyQueue.prototype.empty = function () {
  return this.stack1.length === 0;
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * var obj = new MyQueue()
 * obj.push(x)
 * var param_2 = obj.pop()
 * var param_3 = obj.peek()
 * var param_4 = obj.empty()
 */

实现2

/**
 * Initialize your data structure here.
 */
// Runtime: 72 ms, faster than 87.77% of JavaScript online submissions for Implement Queue using Stacks.
// Memory Usage: 38.7 MB, less than 10.22% of JavaScript online submissions for Implement Queue using Stacks.
var MyQueue = function () {
  this.stack1 = [];
  this.stack2 = [];
  this.front = "";
};

/**
 * Push element x to the back of queue.
 * @param {number} x
 * @return {void}
 */
MyQueue.prototype.push = function (x) {
  if (this.stack1.length === 0) {
    this.front = x;
  }
  this.stack1.push(x);
};

/**
 * Removes the element from in front of queue and returns that element.
 * @return {number}
 */
MyQueue.prototype.pop = function () {
  // if (this.stack2.length === 0) {
  //   return false;
  // }
  if (this.stack2.length >= 1) {
    const res = this.stack2.pop();
    if (this.stack2.length > 0) {
      this.front = this.stack2.pop();
      this.stack2.push(this.front);
    }
    return res;
  }
  if (this.stack2.length === 0) {
    while (this.stack1.length > 0) {
      const temp = this.stack1.pop();
      this.stack2.push(temp);
    }
  }

  const res = this.stack2.pop();
  if (this.stack2.length > 0) {
    this.front = this.stack2.pop();
    this.stack2.push(this.front);
  }

  return res;
};

/**
 * Get the front element.
 * @return {number}
 */
MyQueue.prototype.peek = function () {
  return this.front;
};

/**
 * Returns whether the queue is empty.
 * @return {boolean}
 */
MyQueue.prototype.empty = function () {
  return this.stack1.length === 0 && this.stack2.length === 0;
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * var obj = new MyQueue()
 * obj.push(x)
 * var param_2 = obj.pop()
 * var param_3 = obj.peek()
 * var param_4 = obj.empty()
 */

155. 最小栈

题目描述

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。

例子1

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

提示:
1 pop、top 和 getMin 操作总是在 非空栈 上调用。

思考

1 思路也很简单,使用一个数组依次把最小的数字放进去,当pop的时候,如果发现了pop出去了最小数,则从最小数组中重新拿下一个最小的

可以参考实现1

实现1

/**
 * initialize your data structure here.
 */
// Runtime: 120 ms, faster than 77.56% of JavaScript online submissions for Min Stack.
// Memory Usage: 45.8 MB, less than 48.96% of JavaScript online submissions for Min Stack.
var MinStack = function () {
  this.arr = [];
  this.min = Number.MAX_VALUE;
  this.minArr = [];
};

/**
 * @param {number} x
 * @return {void}
 */
MinStack.prototype.push = function (x) {
  this.arr.push(x);
  if (this.min >= x || this.minArr.length === 0) {
    this.minArr.push(x);
    this.min = x;
  }
};
/**
 * @return {void}
 */
MinStack.prototype.pop = function () {
  const res = this.arr.pop();
  if (res === this.minArr[this.minArr.length - 1]) {
    this.minArr.pop();
    this.min = this.minArr[this.minArr.length - 1];
  }
  return res;
};

/**
 * @return {number}
 */
MinStack.prototype.top = function () {
  return this.arr[this.arr.length - 1];
};

/**
 * @return {number}
 */
MinStack.prototype.getMin = function () {
  return this.min;
};

/**
 * Your MinStack object will be instantiated and called as such:
 * var obj = new MinStack()
 * obj.push(x)
 * obj.pop()
 * var param_3 = obj.top()
 * var param_4 = obj.getMin()
 */

20. 有效的括号

题目描述

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。


例子1
input:"()"
output: true

思考

1 题目很简单,标准的使用栈解决问题

可以参考实现1

实现1

/**
 * @param {string} s
 * @return {boolean}
 */

// Runtime: 88 ms, faster than 24.85% of JavaScript online submissions for Valid Parentheses.
// Memory Usage: 39.5 MB, less than 31.06% of JavaScript online submissions for Valid Parentheses.
export default (s) => {
  const left = [];
  const leftS = ["(", "{", "["];
  const rightS = [")", "}", "]"];
  for (let i = 0; i < s.length; i++) {
    if (leftS.includes(s.charAt(i))) {
      left.push(s.charAt(i));
    } else {
      switch (s.charAt(i)) {
        case ")":
          if (left[left.length - 1] === "(") {
            left.pop();
          } else {
            return false;
          }
          break;
        case "}":
          if (left[left.length - 1] === "{") {
            left.pop();
          } else {
            return false;
          }
          break;
        case "]":
          if (left[left.length - 1] === "[") {
            left.pop();
          } else {
            return false;
          }
          break;
        default:
          break;
      }
    }
  }
  return left.length === 0 ? true : false;
};

739. 每日温度

题目描述

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

思考

1 暴力解法很简单

可以参考实现1

2 还有一种解法是使用单调栈,不过不是很不好理解,而且这里还融和了逆向思维,但是如果立即了其实也很简单。

从后向前遍历数组,维持一个单调递减的栈,如果发现当前元素大于栈顶元素,则栈pop出当前元素,继续比较。

比如 T =[89, 62, 70, 58, 47, 47, 46, 76, 100, 70]

栈 stack = []

res = []

1 从右向左遍历数组,遇到70的时候,因为此时stack为空,所以res.push(0), 因为我们要求的是距离当前元素的个数,所以stack.push(9)

2 当遇到100的时候,因为100大于当前栈顶元素70,所以执行stack.pop(), stack变成[],因为此时stack为空,所以res.push(0),re=[0,0] , 最后再把当前下标push进栈,stack.push(8)

以此类推

实现1

/**
 * @param {number[]} T
 * @return {number[]}
 */

// Runtime: 160 ms, faster than 70.63% of JavaScript online submissions for Daily Temperatures.
// Memory Usage: 49 MB, less than 72.86% of JavaScript online submissions for Daily Temperatures.
export default (T) => {
  const len = T.length;
  const stack = [];
  const res = [];
  for (let i = len - 1; i >= 0; i--) {
    const current = T[i];
    while (stack.length > 0 && current >= T[stack[stack.length - 1]]) {
      stack.pop();
    }
    res[i] = stack.length === 0 ? 0 : stack[stack.length - 1] - i;

    stack.push(i);
    console.log(stack);
  }
  return res;
};

实现堆

题目描述

实现一个堆

思考

堆排序明白了思路就很容易理解,大体思路就是先遍历非叶子节点,建立一个大顶堆,然后交换第一个元素和最后一个元素,然后重新建立大顶堆,重复此过程就可以了

实现1

class Heap {
  constructor(initArr = []) {
    this.arr = initArr;
    const len = this.arr.length - 1;
    this.buildHeap(len);
    console.log(112, this.arr);
  }
  // 最大值
  top() {
    return this.arr[0];
  }
  // push进来一个数
  push(val) {
    this.arr.push(val);
    this.swim(this.arr.length - 1);
  }
  // push进来一个数
  pop(val) {
    const res = this.arr[0];
    const res1 = this.arr.pop();
    this.arr[0] = res1;
    const len = this.arr.length - 1;
    this.sink(0);
    return res;
  }
  swap(i, j) {
    const temp = this.arr[i];
    this.arr[i] = this.arr[j];
    this.arr[j] = temp;
  }

  buildHeap(pos) {
    for (let j = Math.floor(pos / 2); j >= 0; j--) {
      this.sink(j);
    }
  }

  // 上浮
  swim(pos) {
    while (pos >= 1 && this.arr[Math.floor(pos / 2)] < this.arr[pos]) {
      this.swap(Math.floor(pos / 2), pos);
      pos = Math.floor(pos / 2);
    }
  }
  // 下沉
  sink(pos) {
    const len = this.arr.length;
    while (2 * pos < len) {
      let i = 2 * pos;
      if (i < len && this.arr[i] < this.arr[i + 1]) {
        ++i;
      }
      if (this.arr[pos] >= this.arr[i]) break;
      this.swap(pos, i);
      pos = i;
    }
  }
}
export default Heap;

23. 合并有序链表

题目描述

给定 k 个增序的链表,试将它们合并成一条增序链表。

例子1
input: lists = [[1,4,5],[1,3,4],[2,6]]
output: [1,1,2,3,4,4,5,6]
解释: [ 1->4->5, 1->3->4, 2->6 ] 合并后 1->1->2->3->4->4->5->6

思考

1 直接使用二分合并就可以了

可以参考实现1

实现1

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
// Runtime: 364 ms, faster than 27.05% of JavaScript online submissions for Merge k Sorted Lists.
// Memory Usage: 43.7 MB, less than 74.12% of JavaScript online submissions for Merge k Sorted Lists.
const merge = (list1, list2) => {
  if (!list1 || !list2) return list1 || list2;
  let node = new ListNode(null);
  // 暂存头结点
  const root = node;
  while (list1 && list2) {
    if (list1.val <= list2.val) {
      node.next = list1;
      list1 = list1.next;
    } else {
      node.next = list2;
      list2 = list2.next;
    }
    node = node.next;
  }
  if (list1) node.next = list1;
  if (list2) node.next = list2;
  return root.next;
};

export default (lists) => {
  let root = lists[0];
  for (let i = 1; i < lists.length; i++) {
    root = merge(root, lists[i]);
  }

  return root || null;
};

218 天际线问题

题目描述

给定 k 个增序的链表,试将它们合并成一条增序链表。城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。现在,假设您获得了城市风光照片(图A)上显示的所有建筑物的位置和高度,请编写一个程序以输出由这些建筑物形成的天际线(图B)。 db999339110b304668e9408f89caf0e6.png a73c3d497fcd080cdc02a89b8f9db816.png

每个建筑物的几何信息用三元组[Li,Ri,Hi]表示,其中Li和Ri分别是第 i 座建筑物左右边缘的 x 坐标,Hi 是其高度。可以保证0 ≤ Li, Ri ≤ INT_MAX, 0 < Hi ≤ INT_MAX和Ri - Li > 0。您可以假设所有建筑物都是在绝对平坦且高度为 0 的表面上的完美矩形。
例如,图A中所有建筑物的尺寸记录为:[[2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8]] 。 输出是以 [[x1,y1], [x2, y2], [x3, y3], ... ] 格式的“关键点”(图B中的红点)的列表,它们唯一地定义了天际线。关键点是水平线段的左端点。请注意,最右侧建筑物的最后一个关键点仅用于标记天际线的终点,并始终为零高度。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。
例如,图B中的天际线应该表示为:[[2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0]]。


思考

1 首先是把所有的长方形形成一个拆成两个节点,比如[2,9,10] 拆成[2,-10],[9,10]两个节点,负数表示是长方形的起点,正数表示长方形的结束点

然后维持一个数组pq,并且假设pq的最大值是maxVal,遍历所有节点,如果发现是起始点,并且比maxVal大,则加入到结果中,如果不比maxVal大,则直接把高度push进入pq中去,如果发现是结束点,则从pq中删除该结束点的高度,如果此时maxVal变化了,此时也把该节点的坐标和此时的maxVal加入到结果中。

briangordon.github.io/2014/08/the…

视频 youtu.be/GSBLe8cKu0s

可以参考实现1

实现1

const remove = (arr = [], val) => {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === val) {
      for (let j = i; j < arr.length - 1; j++) {
        arr[j] = arr[j + 1];
      }
      arr.pop();
      return;
    }
  }
};
// Runtime: 384 ms, faster than 52.12% of JavaScript online submissions for The Skyline Problem.
// Memory Usage: 45.6 MB, less than 83.64% of JavaScript online submissions for The Skyline Problem.
export default (buildings) => {
  // 最后的结果
  const res = [];

  // 所有的节点
  const points = [];

  for (let i = 0; i < buildings.length; i++) {
    // 表示起始点
    points.push([buildings[i][0], -buildings[i][2]]);
    // 表示结束点
    points.push([buildings[i][1], buildings[i][2]]);
  }
  // 排序所有节点

  points.sort((a, b) => {
    if (a[0] === b[0]) {
      return a[1] - b[1];
    }
    return a[0] - b[0];
  });
  // 表示遍历过的高度的数组
  const pq = [0];
  // 表示遍历过的高度的数组pq的最大值
  let pqMaxVal = 0;

  for (let i = 0; i < points.length; i++) {
    if (points[i][1] < 0) {
      pq.push(-points[i][1]);
    } else {
      remove(pq, points[i][1]);
    }
    const tempMaxVal = Math.max(...pq);
    if (tempMaxVal !== pqMaxVal) {
      res.push([points[i][0], tempMaxVal]);
      pqMaxVal = tempMaxVal;
    }
  }
  return res;
};

239. 滑动窗口最大值

题目描述

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

例子1

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

思考

1 可以使用双指针,一个begin指向当前最大的值,另外一个是数组下标,如果i-begin+1 大于k,重新寻找最大值,如果小于k,则直接push进入结果就可以了

可以参考实现1

2 在实现1的时候,发现有很多需要重复的,这里可以使用单调栈来进行优化。存储从最大到次大的,比如

// nums = [1, 3, -1, -3, 5, 3, 6, 7], and k = 3 // Monotonic queue max // [1] - // [3] - // [3, -1] 3 // [3, -1, -3] 3 // [5] 5 // [5, 3] 5 // [6] 6 // [7] 7

参考实现2

实现1

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
// Runtime: 1996 ms, faster than 22.94% of JavaScript online submissions for Sliding Window Maximum.
// Memory Usage: 69 MB, less than 38.21% of JavaScript online submissions for Sliding Window Maximum.
export default (nums, k) => {
  const res = [];
  let max = nums[0];
  let begin = 0;
  for (let i = 1; i < k; i++) {
    if (nums[i] > max) {
      begin = i;
      max = nums[begin];
    }
  }
  res.push(max);
  for (let i = k; i < nums.length; i++) {
    if (i - begin + 1 <= k && nums[i] < nums[begin]) {
      res.push(max);
    } else {
      max = nums[begin + 1];
      begin = begin + 1;
      for (let m = begin + 1; m <= i; m++) {
        if (nums[m] > max) {
          max = nums[m];
          begin = m;
        }
      }
      res.push(max);
    }
  }
  return res;
};

实现2

// Runtime: 724 ms, faster than 46.21% of JavaScript online submissions for Sliding Window Maximum.
// Memory Usage: 69.3 MB, less than 29.89% of JavaScript online submissions for Sliding Window Maximum.
export default (nums, k) => {
  const res = [];
  const q = [];

  for (let i = 0; i < nums.length; i++) {
    // 最大单调递减
    while (q.length - 1 >= 0 && nums[i] > q[q.length - 1]) {
      q.pop();
    }
    q.push(nums[i]);

    // 从下标i往前k个数
    const j = i + 1 - k;
    if (j >= 0) {
      res.push(q[0]);
      if (nums[j] === q[0]) {
        q.shift();
      }
    }
  }
  return res;
};

1. 两数之和

题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

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

例子1
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
例子2
输入:nums = [3,2,4], target = 6
输出:[1,2]

提示: 1 只存在一个有效的答案

思考

1 使用hash表很好解决

可以参考实现1

实现1

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
// Runtime: 80 ms, faster than 64.85% of JavaScript online submissions for Two Sum.
// Memory Usage: 38.3 MB, less than 95.29% of JavaScript online submissions for Two Sum.
export default (nums, target) => {
  const res = [];
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    if (!map.has(nums[i])) {
      map.set(nums[i], i);
    }
    if (map.get(target - nums[i]) >= 0 && i !== map.get(target - nums[i])) {
      res.push(i);
      res.push(map.get(target - nums[i]));
      return res;
    }
  }
};

128. 最长连续序列

题目描述

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

进阶:你可以设计并实现时间复杂度为 O(n) 的解决方案吗?

例子1
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。 。
例子2
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
解释:最长数字连续序列是 [0,1, 2, 3, 4,5,6,7,8]。它的长度为9.

思考

1 首先排序数组,然后遍历数组,找到每个数字的最长连续长度

可以参考实现1

2 使用hash表存储每个数字,然后遍历数组,然后找到数组中每个数字的最长长度,这里使用hash快速查找,快速跳过

可以参考实现2

3 遍历数组,对于每个数字nums[i],找出在hash表中比该数字小1的长度len1和找出在hash表中比该数字大1的长度len2,所以此时该数字的最长连续长度就是Math.max(res,len1+len2+1),然后再不断更新hash表中nums[i]-left 的长度为Math.max(res,len1+len2+1),nums[i]+right的长度是Math.max(res,len1+len2+1)

可以参考实现3

实现1

/**
 * @param {number[]} nums
 * @return {number}
 */
// Runtime: 148 ms, faster than 17.10% of JavaScript online submissions for Longest Consecutive Sequence.
// Memory Usage: 40.1 MB, less than 90.23% of JavaScript online submissions for Longest Consecutive Sequence.
export default (nums) => {
  nums.sort((a, b) => a - b);
  let res = 0;
  for (let i = 0; i < nums.length; i++) {
    let count = 1;
    for (let j = i; j < nums.length - 1; j++) {
      if (nums[j + 1] === nums[j] + 1) {
        count++;
      } else if (nums[j + 1] === nums[j]) {
        continue;
      } else {
        break;
      }
    }
    res = Math.max(res, count);
  }
  return res;
};

实现2

/**
 * @param {number[]} nums
 * @return {number}
 */
// Runtime: 76 ms, faster than 95.99% of JavaScript online submissions for Longest Consecutive Sequence.
// Memory Usage: 41.7 MB, less than 9.08% of JavaScript online submissions for Longest Consecutive Sequence.
export default (nums) => {
  const map = new Map();

  for (let i = 0; i < nums.length; i++) {
    if (!map.has(nums[i])) {
      map.set(nums[i], true);
    }
  }
  let res = 0;
  for (let i = 0; i < nums.length; i++) {
    if (!map.has(nums[i])) {
      continue;
    }
    let right = nums[i];
    let left = nums[i] - 1;
    let max = 0;
    while (map.has(right)) {
      map.delete(right);
      max++;
      right++;
    }
    while (map.has(left)) {
      map.delete(left);
      max++;
      left--;
    }
    res = Math.max(res, max);
  }

  return res;
};

实现3

/**
 * @param {number[]} nums
 * @return {number}
 */
// Runtime: 88 ms, faster than 61.43% of JavaScript online submissions for Longest Consecutive Sequence.
// Memory Usage: 40.9 MB, less than 58.81% of JavaScript online submissions for Longest Consecutive Sequence.
export default (nums) => {
  const map = new Map();
  let res = 0;
  for (let i = 0; i < nums.length; i++) {
    if (!map.has(nums[i])) {
      const left = map.get(nums[i] - 1) ? map.get(nums[i] - 1) : 0;
      const right = map.get(nums[i] + 1) ? map.get(nums[i] + 1) : 0;
      const sum = left + right + 1;
      map.set(nums[i], sum);
      res = Math.max(res, sum);
      map.set(nums[i] - left, sum);
      map.set(nums[i] + right, sum);
    } else {
      continue;
    }
  }

  return res;
};

149. 直线上最多的点数

题目描述

给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。

例子1
输入:[[1,1],[2,2],[3,3]]
输出:3
解释:

|
|        o
|     o
|  o  
+------------->
0  1  2  3  4。 

思考

1 题目很简单,就是可以遍历所有的直线,找出那条直线上的点最多。

可以参考实现1

2 另外一种就是使用hash存储相同的斜率的

可以参考实现2

实现1

/**
 * @param {number[][]} points
 * @return {number}
 */

const getK = (arr1, arr2) => {
  if (Math.abs(arr1[0] - arr2[0]) !== 0) {
    return (arr1[1] - arr2[1]) / (arr1[0] - arr2[0]);
  } else {
    // 当是一条垂直线的时候
    return "cur";
  }
};
const qualArr = (arr1, arr2) => {
  for (let i = 0; i < arr1.length; i++) {
    if (arr1[i] !== arr2[i]) {
      return false;
    }
  }
  return true;
};
export default (points) => {
  let max = 0;
  if (!points || points.length === 0) return 0;
  if (points.length === 1) return 1;
  if (points.length === 2) return 2;
  for (let i = 2; i < points.length; i++) {
    let max1 = 0;
    for (let j = i - 1; j >= 0; j--) {
      const k = getK(points[i], points[j]);
      max1 = 2;
      for (let k1 = 0; k1 < i; k1++) {
        if (k1 === j) {
          continue;
        }
        if (k === getK(points[i], points[k1]) || qualArr(points[k1], points[i]) || qualArr(points[k1], points[j])) {
          max1++;
        }

        if (max1 > max) {
          max = max1;
        }
      }
    }
  }
  return max;
};

实现2

/**
 * @param {number[][]} points
 * @return {number}
 */
// Runtime: 100 ms, faster than 79.41% of JavaScript online submissions for Max Points on a Line.
// Memory Usage: 44.2 MB, less than 63.24% of JavaScript online submissions for Max Points on a Line.
export default (points) => {
  const map = new Map();
  let max_count = 0;
  // 相同x坐标的数量
  let same_points_count = 0;
  // 相同y坐标的数量
  let same_y_count = 1;

  for (let i = 0; i < points.length; i++) {
    same_points_count = 0;
    same_y_count = 1;
    for (let j = i + 1; j < points.length; j++) {
      if (points[i][1] === points[j][1]) {
        ++same_y_count;
        if (points[i][0] === points[j][0]) {
          ++same_points_count;
        }
      } else {
        const temp = ((points[i][0] - points[j][0]) * 10000) / ((points[i][1] - points[j][1]) * 10000);
        if (map.has(temp)) {
          const tempArr = map.get(temp);
          const test =
            ((points[j][0] - tempArr[tempArr.length - 1][0]) * 10000) /
            ((points[j][1] - tempArr[tempArr.length - 1][1]) * 10000);
          if (
            test === temp ||
            (points[j][0] === tempArr[tempArr.length - 1][0] && points[j][1] === tempArr[tempArr.length - 1][1])
          ) {
            tempArr.push(points[j]);
          }
          map.set(temp, tempArr);
        } else {
          map.set(temp, [points[i], points[j]]);
        }
      }
    }
    max_count = Math.max(same_y_count, max_count);
    for (let value of map.values()) {
      max_count = Math.max(max_count, same_points_count + value.length);
    }
    map.clear();
  }
  return max_count;
};

332. 重新安排行程

题目描述

给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。

提示:

1 如果存在多种有效的行程,请你按字符自然排序返回最小的行程组合。例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前
2 所有的机场都用三个大写字母表示(机场代码)。
3 假定所有机票至少存在一种合理的行程。
4 所有的机票必须都用一次 且 只能用一次。

例子1
输入:[["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
输出:["JFK", "MUC", "LHR", "SFO", "SJC"]

例子2
输入:[["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出:["JFK","ATL","JFK","SFO","ATL","SFO"]
解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"]。但是它自然排序更大更靠后。

思考

1 这里如果想要解决,就要首先明确一个问题,就是这里的输入肯定是存在一条使用完所有的机票后,可以链接起来

明白这一点后,就比较容易了,一步步寻找,如果走到死胡同,然后退出重新寻找下一条路。
比如下图如果我们从jfk出发,如果发现到了A之后,还有机票没有使用,可以退出A,重新从D开始继续寻找
fdb32c52ce4ca01574e59519f6d939d3.png

可以参考实现1

实现1

/**
 * @param {string[][]} tickets
 * @return {string[]}
 */
const sortArray = (a, b) => {
  if (a[0] === b[0]) {
    return a[1].localeCompare(b[1]);
  }
  return a[0].localeCompare(b[0]);
};
export default (tickets) => {
  if (!tickets || tickets.length === 0) return [];
  const map = new Map();
  const result = [];

  tickets.sort(sortArray);
  for (let i = 0; i < tickets.length; i++) {
    if (map.has(tickets[i][0])) {
      map.get(tickets[i][0]).push(tickets[i][1]);
    } else {
      map.set(tickets[i][0], [tickets[i][1]]);
    }
  }

  let key = "JFK";
  const drawback = [];
  for (let i = 0; i < tickets.length; i++) {
    while (!map.has(key) || map.get(key).length === 0) {
      drawback.push(key);
      key = result.pop();
    }
    result.push(key);
    key = map.get(key).shift();
  }
  result.push(key);
  while (drawback.length > 0) {
    result.push(drawback.pop());
  }

  return result;
};

303. 区域和检索 - 数组不可变

题目描述

给定一个整数数组 nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点。

实现 NumArray 类:

1 NumArray(int[] nums) 使用数组 nums 初始化对象
2 int sumRange(int i, int j) 返回数组 nums 从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点(也就是 sum(nums[i], nums[i + 1], ... , nums[j]))


例子1
输入:["NumArray", "sumRange", "sumRange", "sumRange"] [[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:[null, 1, -1, -3]
解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))

思考

1 刚开始想使用hash把所有的组合都存储起来,这样等到sumRange的时候,就可以直接拿出来用了,但是发现超时了

可以参考实现1

2 后来发现可以使用一个数组copyNums,把输入的数组nums所有从0 到i位置的和存储起来,这样如果求i到j的和的时候,就可以使用 copyNums[j]- copyNums[i]

实现1

/**
 * @param {number[]} nums
 */
var NumArray = function (nums) {
  this.nums = nums;
  this.map = new Map();
  for (let i = 0; i < nums.length; i++) {
    for (let j = i; j < nums.length; j++) {
      if (this.map.has(`${i}${j - 1}`)) {
        const count = this.map.get(`${i}${j - 1}`) + nums[j];
        this.map.set(`${i}${j}`, count);
      } else {
        this.map.set(`${i}${j}`, nums[j]);
      }
    }
  }
};

/**
 * @param {number} i
 * @param {number} j
 * @return {number}
 */
NumArray.prototype.sumRange = function (i, j) {
  return this.map.get(`${i}${j}`);
};

/**
 * Your NumArray object will be instantiated and called as such:
 * var obj = new NumArray(nums)
 * var param_1 = obj.sumRange(i,j)
 */

实现2

/**
 * @param {number[]} nums
 */

// Runtime: 116 ms, faster than 87.76% of JavaScript online submissions for Range Sum Query - Immutable.
// Memory Usage: 45.8 MB, less than 33.85% of JavaScript online submissions for Range Sum Query - Immutable.
var NumArray = function (nums) {
  this.nums = nums;
  this.copyNums = [0];
  for (let i = 0; i < nums.length; i++) {
    this.copyNums[i + 1] = this.copyNums[i] + nums[i];
  }
};

/**
 * @param {number} i
 * @param {number} j
 * @return {number}
 */
NumArray.prototype.sumRange = function (i, j) {
  return this.copyNums[j + 1] - this.copyNums[i];
};

/**
 * Your NumArray object will be instantiated and called as such:
 * var obj = new NumArray(nums)
 * var param_1 = obj.sumRange(i,j)
 */

304. 二维区域和检索 - 矩阵不可变

题目描述

给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2)。

3f99b758a3f6809af2fa7af360832d30.png


例子1

给定 matrix = [  [3, 0, 1, 4, 2],
  [5, 6, 3, 2, 1],
  [1, 2, 0, 1, 5],
  [4, 1, 0, 1, 7],
  [1, 0, 3, 0, 5]
]

sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12

思考

1 这里思想和上面的题目是一样的,只不多是二维变成了一维,不过思想还是一样的

参考实现1

实现1

/**
 * @param {number[][]} matrix
 */
var NumMatrix = function (matrix) {
  this.sumMatrix = [];
  const m = matrix.length;
  const n = matrix[0] ? matrix[0].length : 0;
  for (let i = 0; i <= m; i++) {
    this.sumMatrix[i] = new Array(n + 1).fill(0);
  }
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      this.sumMatrix[i + 1][j + 1] = this.sumMatrix[i][j];
      for (let k = 0; k < j; k++) {
        this.sumMatrix[i + 1][j + 1] += matrix[i][k];
      }
      for (let k = 0; k < i; k++) {
        this.sumMatrix[i + 1][j + 1] += matrix[k][j];
      }
      this.sumMatrix[i + 1][j + 1] += matrix[i][j];
    }
  }
};

/**
 * @param {number} row1
 * @param {number} col1
 * @param {number} row2
 * @param {number} col2
 * @return {number}
 */
NumMatrix.prototype.sumRegion = function (row1, col1, row2, col2) {
  return (
    this.sumMatrix[row2 + 1][col2 + 1] -
    this.sumMatrix[row2 + 1][col1] -
    this.sumMatrix[row1][col2 + 1] +
    this.sumMatrix[row1][col1]
  );
};
// Runtime: 128 ms, faster than 40.59% of JavaScript online submissions for Range Sum Query 2D - Immutable.
// Memory Usage: 44.6 MB, less than 9.90% of JavaScript online submissions for Range Sum Query 2D - Immutable.
export default NumMatrix;
/**
 * Your NumMatrix object will be instantiated and called as such:
 * var obj = new NumMatrix(matrix)
 * var param_1 = obj.sumRegion(row1,col1,row2,col2)
 */

560. 和为K的子数组

题目描述

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

例子1
input: nums = [1,1,1], k = 2
output: 2 , [1,1] 与 [1,1] 为两种不同的情况

例子2
input: nums = [1,2,3], k = 3
output: 2 , [1,2] 与 [3] 为两种不同的情况

思考

1 这里也可以使用前面的类似前缀和,但是发现超时了

参考实现1

2 可以利用空间换时间,使用hash表存储前缀和出现的次数,当遇到一个前缀和sum的时候,可以到hash表中查找是否存在hash[sum-k],如果存在,添加到结果中

参考实现2

实现1

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */

export default (nums, k) => {
  const copyNums = [0];
  for (let i = 0; i < nums.length; i++) {
    copyNums[i + 1] = copyNums[i] + nums[i];
  }

  let res = 0;
  for (let i = nums.length; i >= 1; i--) {
    for (let j = i - 1; j >= 0; j--) {
      if (copyNums[i] - copyNums[j] === k) {
        res++;
      }
      // if (copyNums[i] - copyNums[j] > k && nums[j - 1] > 0) {
      //   break;
      // }
    }
  }
  return res;
};

实现2

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */

// Runtime: 108 ms, faster than 73.38% of JavaScript online submissions for Subarray Sum Equals K.
// Memory Usage: 46.7 MB, less than 67.96% of JavaScript online submissions for Subarray Sum Equals K.
export default (nums, k) => {
  if (nums.length === 1 && nums[0] !== k) return 0;
  const map = new Map();
  map.set(0, 1);
  let res = 0;
  let copyNums = 0;
  for (let i = 0; i < nums.length; i++) {
    copyNums = copyNums + nums[i];
    if (map.has(copyNums - k)) {
      res += map.get(copyNums - k);
    }
    if (map.has(copyNums)) {
      const count = map.get(copyNums) + 1;
      map.set(copyNums, count);
    } else {
      map.set(copyNums, 1);
    }
  }
  return res;
};

566. 重塑矩阵

题目描述

在MATLAB中,有一个非常有用的函数 reshape,它可以将一个矩阵重塑为另一个大小不同的新矩阵,但保留其原始数据。

给出一个由二维数组表示的矩阵,以及两个正整数r和c,分别表示想要的重构的矩阵的行数和列数。

重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。

如果具有给定参数的reshape操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

例子1
input: nums = [[1,2],
[3,4]]
r = 1, c = 4
output: [[1,2,3,4]]

例子2
input: nums = [[1,2],
[3,4]]
r = 2, c = 4
output: [[1,2],[3,4]]

提示:
1 给定矩阵的宽和高范围在 [1, 100]。
2 给定的 r 和 c 都是正数。

思考

1 直接实现就可以了

参考实现1

实现1

/**
 * @param {number[][]} nums
 * @param {number} r
 * @param {number} c
 * @return {number[][]}
 */
// Runtime: 112 ms, faster than 33.95% of JavaScript online submissions for Reshape the Matrix.
// Memory Usage: 43.7 MB, less than 83.95% of JavaScript online submissions for Reshape the Matrix.
export default (nums, r, c) => {
  const m = nums.length;
  const n = nums[0] ? nums[0].length : 0;
  if (r * c !== m * n) {
    return nums;
  }
  const res = [];
  for (let i = 0; i < r; i++) {
    res[i] = new Array(c).fill(0);
  }
  let begin = 0;
  let end = 0;
  for (let i = 0; i < r; i++) {
    for (let j = 0; j < c; j++) {
      if (end < n) {
        res[i][j] = nums[begin][end];
        end++;
      } else {
        begin++;
        end = 0;
        res[i][j] = nums[begin][end];
        end++;
      }
    }
  }
  return res;
};

225. 队列实现栈

题目描述

使用队列实现栈

进阶:

能不能做到平均复杂度O(1)

思考

1 直接实现就可以了,如果想要平均复杂度是O(1),可以在push的时候,时间复杂度是O(n),其他是O(1)

参考实现1

实现1

/**
 * Initialize your data structure here.
 */
// Runtime: 76 ms, faster than 67.91% of JavaScript online submissions for Implement Stack using Queues.
// Memory Usage: 38.5 MB, less than 39.93% of JavaScript online submissions for Implement Stack using Queues.
var MyStack = function () {
  this.queue1 = [];
  this.queue2 = [];
};

/**
 * Push element x onto stack.
 * @param {number} x
 * @return {void}
 */
MyStack.prototype.push = function (x) {
  if (this.queue2.length > 0) {
    while (this.queue2.length > 0) {
      this.queue1.push(this.queue2.pop());
    }
  }
  this.queue2.push(x);
  while (this.queue1.length > 0) {
    this.queue2.push(this.queue1.pop());
  }
};

/**
 * Removes the element on top of the stack and returns that element.
 * @return {number}
 */
MyStack.prototype.pop = function () {
  return this.queue2.shift();
};

/**
 * Get the top element.
 * @return {number}
 */
MyStack.prototype.top = function () {
  return this.queue2[0];
};

/**
 * Returns whether the stack is empty.
 * @return {boolean}
 */
MyStack.prototype.empty = function () {
  return this.queue2.length === 0;
};

/**
 * Your MyStack object will be instantiated and called as such:
 * var obj = new MyStack()
 * obj.push(x)
 * var param_2 = obj.pop()
 * var param_3 = obj.top()
 * var param_4 = obj.empty()
 */

503. 下一个更大元素 II

题目描述

给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
例子1
输入:[1,2,1]
输出:[2,-1,2]
解释: 第一个 1 的下一个更大的数是 2; 数字 2 找不到下一个更大的数; 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

思考

1 直接使用暴力解法

参考实现1

2 可以使用栈的特性,先入后出的特性,把数组中某个位置前面的下标都存储到栈中,如果发现当前数组中的元素比栈中的大,则从栈中弹出,并且更新结果。

这里有个技巧就是不是遍历数组的长度,而是遍历数组长度的两倍

参考实现2

实现1

/**
 * @param {number[]} nums
 * @return {number[]}
 */
// 1, 2, 1;
// 2, -1, 2;
// Runtime: 228 ms, faster than 17.77% of JavaScript online submissions for Next Greater Element II.
// Memory Usage: 45 MB, less than 57.87% of JavaScript online submissions for Next Greater Element II.
export default (nums) => {
  const res = [];
  for (let i = 0; i < nums.length; i++) {
    let j = i + 1;
    while (j !== i) {
      if (nums[j] <= nums[i] || j > nums.length - 1) {
        if (j > nums.length - 1) {
          j = 0;
        } else {
          j++;
        }
      } else {
        break;
      }
    }
    // console.log(j);
    if (j === i) {
      res.push(-1);
    } else {
      res.push(nums[j]);
    }
  }
  return res;
};

实现2

/**
 * @param {number[]} nums
 * @return {number[]}
 */
// Runtime: 144 ms, faster than 44.16% of JavaScript online submissions for Next Greater Element II.
// Memory Usage: 44.4 MB, less than 76.65% of JavaScript online submissions for Next Greater Element II.
export default (nums) => {
  const len = nums.length;
  const res = new Array(len).fill(-1);
  const stack = [];
  for (let i = 0; i < 2 * len; i++) {
    while (stack.length > 0 && nums[stack[stack.length - 1]] < nums[i % len]) {
      const index = stack.pop();
      res[index] = nums[i % len];
    }
    stack.push(i % len);
  }
  return res;
};

217. 存在重复元素

题目描述

给定一个整数数组,判断是否存在重复元素。

如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。
例子1
输入:[1,2,3,1]
输出:true

思考

1 很明显使用hash表快速查找

参考实现1

实现1

/**
 * @param {number[]} nums
 * @return {boolean}
 */

// Runtime: 96 ms, faster than 45.58% of JavaScript online submissions for Contains Duplicate.
// Memory Usage: 45 MB, less than 54.79% of JavaScript online submissions for Contains Duplicate.
export default (nums) => {
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    if (map.has(nums[i])) {
      return true;
    } else {
      map.set(nums[i], 1);
    }
  }

  return false;
};

697. 数组的度

题目描述

给定一个非空且只包含非负数的整数数组 nums, 数组的度的定义是指数组里任一元素出现频数的最大值。

你的任务是找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。
例子1
输入:[1, 2, 2, 3, 1]
输出:2
解释: 输入数组的度是2,因为元素1和2的出现频数最大,均为2. 连续子数组里面拥有相同度的有如下所示: [1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2] 最短连续子数组[2, 2]的长度为2,所以返回2.

思考

1 比较简单,直接实现就可以了

参考实现1

2 可以利用firstMap存储数字首次出现的位置,

参考实现2

实现1

/**
 * @param {number[]} nums
 * @return {number}
 */
// Runtime: 264 ms, faster than 5.02% of JavaScript online submissions for Degree of an Array.
// Memory Usage: 42 MB, less than 91.32% of JavaScript online submissions for Degree of an Array.
export default (nums) => {
  const map = new Map();
  let max = 1;
  for (let i = 0; i < nums.length; i++) {
    if (map.has(nums[i])) {
      const count = map.get(nums[i]) + 1;
      max = Math.max(max, count);
      map.set(nums[i], count);
    } else {
      map.set(nums[i], 1);
    }
  }
  let res = Number.MAX_VALUE;
  for (let [key, val] of map) {
    if (val === max) {
      let begin = -1;
      let end = -1;
      for (let i = 0; i < nums.length; i++) {
        if (nums[i] === key && begin === -1) {
          begin = i;
          end = i;
        }
        if (nums[i] === key && begin !== -1) {
          end = i;
        }
      }
      res = Math.min(res, end - begin + 1);
    }
  }
  return res;
};

实现2

/**
 * @param {number[]} nums
 * @return {number}
 */
// Runtime: 104 ms, faster than 50.23% of JavaScript online submissions for Degree of an Array.
// Memory Usage: 43.8 MB, less than 45.21% of JavaScript online submissions for Degree of an Array.
export default (nums) => {
  const map = new Map();
  const firstMap = new Map();
  let res = 0;
  let degree = 0;
  for (let i = 0; i < nums.length; ++i) {
    if (!firstMap.has(nums[i])) {
      firstMap.set(nums[i], i);
    }

    if (map.has(nums[i])) {
      const count = map.get(nums[i]) + 1;
      map.set(nums[i], count);
    } else {
      map.set(nums[i], 1);
    }
    if (map.get(nums[i]) > degree) {
      degree = map.get(nums[i]);
      res = i - firstMap.get(nums[i]) + 1;
    } else if (map.get(nums[i]) === degree) {
      res = Math.min(res, i - firstMap.get(nums[i]) + 1);
    }
  }
  return res;
};

594. 最长和谐子序列

题目描述

和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1。

现在,给定一个整数数组,你需要在所有可能的子序列中找到最长的和谐子序列的长度。
例子1
输入:[1,3,2,2,5,2,3,7]
输出:5
解释: 最长的和谐数组是:[3,2,2,2,3].

思考

1 很简单,直接使用hash就可以

参考实现1

实现1

/**
 * @param {number[]} nums
 * @return {number}
 */
// Runtime: 112 ms, faster than 92.50% of JavaScript online submissions for Longest Harmonious Subsequence.
// Memory Usage: 48.4 MB, less than 51.88% of JavaScript online submissions for Longest Harmonious Subsequence.
export default (nums) => {
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    if (map.has(nums[i])) {
      const count = map.get(nums[i]) + 1;
      map.set(nums[i], count);
    } else {
      map.set(nums[i], 1);
    }
  }
  let max = 0;
  for (let [key, val] of map) {
    const tempMax = map.get(key + 1) ? map.get(key + 1) : 0;
    const tempMin = map.get(key - 1) ? map.get(key - 1) : 0;
    if (tempMax > 0) {
      max = Math.max(max, val + tempMax);
    }
    if (tempMin > 0) {
      max = Math.max(max, val + tempMin);
    }
  }
  return max;
};

287. 寻找重复数

题目描述

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

例子1
输入:nums = [1,3,4,2,2]
输出:2
解释: 2是重复数

思考

1 很简单,直接使用hash就可以

参考实现1

实现1

/**
 * @param {number[]} nums
 * @return {number}
 */
// Runtime: 68 ms, faster than 99.68% of JavaScript online submissions for Find the Duplicate Number.
// Memory Usage: 41.1 MB, less than 24.13% of JavaScript online submissions for Find the Duplicate Number.
export default (nums) => {
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    if (map.has(nums[i])) {
      return nums[i];
    } else {
      map.set(nums[i], 1);
    }
  }
};

313. 超级丑数

题目描述

编写一段程序来查找第 n 个超级丑数。

超级丑数是指其所有质因数都是长度为 k 的质数列表 primes 中的正整数。

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

例子1
输入:n = 12, primes = [2,7,13,19]
输出:32
解释: 给定长度为 4 的质数列表 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。

说明:
1 是任何给定 primes 的超级丑数。
2 给定 primes 中的数字以升序排列。
3 0 < k ≤ 100, 0 < n ≤ 106, 0 < primes[i] < 1000 。
4 第 n 个超级丑数确保在 32 位有符整数范围内。

思考

1 首先需要理解这里的超级丑数是什么,就是从primes中选择所有数字和前面的超级丑数进行相乘,选择最小的并且不能重复前面的超级丑数

所以可以设置一个和primes相同长度的数组,记录每个primes 对应的超级丑数,防止得到的超级丑数重复

参考实现1

实现1

/**
 * @param {number} n
 * @param {number[]} primes
 * @return {number}
 */
// (12)[(2, 7, 13, 19)];
// Runtime: 108 ms, faster than 75.00% of JavaScript online submissions for Super Ugly Number.
// Memory Usage: 41.7 MB, less than 87.50% of JavaScript online submissions for Super Ugly Number.
export default (n, primes) => {
  const ugly = new Array(n).fill(0);
  const primes2uglyIndexs = new Array(primes.length).fill(0);
  ugly[0] = 1;
  for (let i = 1; i < n; i++) {
    ugly[i] = Number.MAX_VALUE;
    for (let j = 0; j < primes.length; j++) {
      if (primes[j] * ugly[primes2uglyIndexs[j]] < ugly[i]) {
        ugly[i] = primes[j] * ugly[primes2uglyIndexs[j]];
      }
    }

    for (let j = 0; j < primes.length; j++) {
      if (primes[j] * ugly[primes2uglyIndexs[j]] === ugly[i]) {
        primes2uglyIndexs[j]++;
      }
    }
  }
  return ugly[n - 1];
};

870. 优势洗牌

题目描述

给定两个大小相等的数组 A 和 B,A 相对于 B 的优势可以用满足 A[i] > B[i] 的索引 i 的数目来描述。

返回 A 的任意排列,使其相对于 B 的优势最大化。

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

例子1
输入:A = [2,7,11,15], B = [1,10,4,11]
输出:[2,11,7,15]
解释:

例子2
输入:A = [12,24,8,32], B = [13,25,32,11]
输出:[2,11,7,15]
解释:

思考

1 题目的意思是尽可能的得到更多的分数,而一分就是A[i] > B[i], 所以题目就是求出A中尽可能多的比B同位置大的组合

可以使用贪心算法,如果A中的最大数比B中的最大数,可以使用,如果小于,那么说明A中其他数字也肯定小于B,所以直接使用A中最小的数来对应B中最大的数,类似田忌赛马

参考实现1

实现1

/**
 * @param {number[]} A
 * @param {number[]} B
 * @return {number[]}
 */

export default (A, B) => {
  // 按照索引排序数组
  const idxs = B.map((v, i) => i).sort((a, b) => B[b] - B[a]);

  A.sort((a, b) => b - a);
  const res = [];
  for (let i = 0; i < B.length; i++) {
    // 使用田忌赛马,下等马对上上等马
    res[idxs[i]] = A[0] > B[idxs[i]] ? A.shift() : A.pop();
  }
  return res;
};

307. 区域和检索 - 数组可修改

题目描述

给你一个数组 nums ,请你完成两类查询,其中一类查询要求更新数组下标对应的值,另一类查询要求返回数组中某个范围内元素的总和。

实现 NumArray 类:

NumArray(int[] nums) 用整数数组 nums 初始化对象
void update(int index, int val) 将 nums[index] 的值更新为 val
int sumRange(int left, int right) 返回子数组 nums[left, right] 的总和(即,nums[left] + nums[left + 1], ..., nums[right])

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

例子1
输入:["NumArray", "sumRange", "update", "sumRange"] [[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
输出:[null, 9, null, 8]
解释: NumArray numArray = new NumArray([1, 3, 5]);

numArray.sumRange(0, 2); // 返回 9 ,
sum([1,3,5]) = 9

numArray.update(1, 2); // nums = [1,2,5]

numArray.sumRange(0, 2); // 返回 8 ,
sum([1,2,5]) = 9

思考

1 直接使用正常解法,时间复杂度是O(n), 发现时间超时

参考实现1

2 使用线段树, 线段树也比较简单,主要是需要注意边界条件,在什么情况下退出,比如更新的时候。

参考实现2

实现1

/**
 * @param {number[]} nums
 */
var NumArray = function (nums) {
  this.nums = nums;
};

/**
 * @param {number} index
 * @param {number} val
 * @return {void}
 */
NumArray.prototype.update = function (index, val) {
  this.nums[index] = val;
};

/**
 * @param {number} left
 * @param {number} right
 * @return {number}
 */
NumArray.prototype.sumRange = function (left, right) {
  let sum = 0;
  for (let i = left; i <= right; i++) {
    sum += this.nums[i];
  }
  return sum;
};

/**
 * Your NumArray object will be instantiated and called as such:
 * var obj = new NumArray(nums)
 * obj.update(index,val)
 * var param_2 = obj.sumRange(left,right)
 */

实现2

/**
 * @param {number[]} nums
 */
var NumArray = function (nums) {
  this.nums = nums;
  this.tree = [];
  this.build(0, nums.length - 1, 0);
};

/**
 * @param {number} index
 * @param {number} val
 * @return {void}
 */
NumArray.prototype.update = function (index, val) {
  const diff = val - this.nums[index];
  this.nums[index] = val;
  this.updateUtil(0, this.nums.length - 1, 0, index, diff);
};
NumArray.prototype.updateUtil = function (left, right, treeIdx, index, diff) {
  if (index >= left && index <= right) {
    this.tree[treeIdx] += diff;
    if (left === right) return;
    var mid = left + ((right - left) >> 1);
    this.updateUtil(left, mid, treeIdx * 2 + 1, index, diff);
    this.updateUtil(mid + 1, right, treeIdx * 2 + 2, index, diff);
  }
};

/**
 * @param {number} left
 * @param {number} right
 * @return {number}
 */
NumArray.prototype.sumRange = function (left, right) {
  return this.sumUtil(left, right, 0, this.nums.length - 1, 0);
};
NumArray.prototype.sumUtil = function (left, right, currLeft, currRight, treeIdx) {
  if (left > currRight || right < currLeft) return 0;
  if (left <= currLeft && right >= currRight) return this.tree[treeIdx];
  const mid = currLeft + ((currRight - currLeft) >> 1);
  return (
    this.sumUtil(left, right, currLeft, mid, treeIdx * 2 + 1) +
    this.sumUtil(left, right, mid + 1, currRight, treeIdx * 2 + 2)
  );
};
NumArray.prototype.build = function (left, right, idx) {
  if (left > right) return;
  const mid = left + ((right - left) >> 1);
  // 递归建立线段树
  const sum =
    left === right ? this.nums[left] : this.build(left, mid, idx * 2 + 1) + this.build(mid + 1, right, idx * 2 + 2);
  this.tree[idx] = sum;
  return sum;
};

/**
 * Your NumArray object will be instantiated and called as such:
 * var obj = new NumArray(nums)
 * obj.update(index,val)
 * var param_2 = obj.sumRange(left,right)
 */