Coder Social home page Coder Social logo

aaa-leetcode-record's People

Watchers

 avatar

aaa-leetcode-record's Issues

780. Reaching Points

var reachingPoints = function(sx, sy, tx, ty) {
  const history = {};
  let preRound = [[sx, sy]], round = [],
      x, y, newPosStr;
  while(preRound.length)
  {
    for(let pos of preRound)
    {
      x = pos[0];
      y = pos[1];
      if(nextPos(x + y, y, tx, ty, round, history)) return true;
      if(nextPos(x, x + y, tx, ty, round, history)) return true;
    }
    preRound = round;
    round = [];
  }
  return false;
};

function nextPos(x, y, sx, sy, round, history){
  if(x === sx && y === sy) return true;
  if(x > sx || y > sy) return false;
  let newPosStr = x + '_' + y; // Let's encode position.
  if(history[newPosStr]) return;
  round.push([x, y]);
  history[newPosStr] = true;
  return false;
}

广度优先搜索, 直接超时, OTL+1
思考题目, 明显有规律的!!!

var reachingPoints = function(sx, sy, tx, ty) {
  let [x, y] = [tx, ty];
  while(x >= sx && y >= sy)
  {
    if(x === sx && y === sy) return true;
    if(x === sx) return (y - sy) % x === 0;
    if(y === sy) return (x - sx) % y === 0;    
    [x, y] = (x > y) ? [x - y, y] : [x, y - x];
  }
  return false;
};

二分查找

1283. Find the Smallest Divisor Given a Threshold

利用好单调性,if(a < b) 则 f(a) <= f(b)

/**
 * @param {number[]} nums
 * @param {number} threshold
 * @return {number}
 */
var smallestDivisor = function(nums, threshold) {
  let start = 1, end = Math.max(...nums);
  if (nums.length === threshold) return end;

  while(start < end) {
    let mid = (start + end) / 2 | 0;

    let sum = 0;
    for(const num of nums) {
      sum += Math.ceil(num / mid);
    }

    sum > threshold ? (start = mid + 1) : (end = mid);
  }
  return start;
};

各种股票买卖类问题(Dp)

121. 买卖股票的最佳时机

思路一:先假定在确定的某天 i 天卖出,求买入时机,以使得收益最大为

题设限制我们只能在买卖一次,那么考虑对于任何一天 i 卖出,我们必定需要在 [0..i] 内进行过一次买入,
我们能获得的最大收益是在 [0..i] 天内,股价最低的时候进行买入。
那么为了方便查询任意 [0..i] 内的最小股价,我们可以使用前缀最小值来方便查询。
那么任何一天卖出所能获得的最大收益,可以表述为公式:

profit[i] = prices[i] - preMin[i]

思路二:动态规划,考虑到状态转移

让我们考虑我们当前的股票交易状态如下:

  1. waiting 观望,未持有
  2. holding 以持有
  3. liquidation 清仓,消耗掉所有的买卖机会

对于任意时刻 i,用户可以通过执行一些操作(买入或者卖出股票)转移到另外一种状态中:

type Status = "waiting" | "holding" | "liquidating";

function maxProfit(prices: number[]): number {
  const N = prices.length;

  // dp[0].liquidating 为 0,代表不炒股的策略,如果必须至少进行一次交易,这个值就为负无穷
  const dp: Array<Record<Status, number>> = Array(N).fill(null).map(v => ({
    waiting: 0,
    holding: -prices[0],
    liquidating: 0,
  }));
  for(let i = 1; i < N; i++) {
    dp[i].waiting = dp[i - 1].waiting;
    dp[i].holding = Math.max(
      dp[i - 1].waiting - prices[i],
      dp[i - 1].holding
    );
    dp[i].liquidating = Math.max(
      dp[i - 1].holding + prices[i],
      dp[i - 1].liquidating
    );
  }
  return dp[N - 1].liquidating;
};

思路三:从差分的角度考虑问题

611. Valid Triangle Number

题目: 从一个数组里面找到所有能构成三角形三边的三个数, 保证数组长度不超过1000, 数字范围0 ~ 1000
d

594. Longest Harmonious Subsequence

令count(n)为数字n在数组nums中出现的次数,
根据题目描述我们需要找到两个相邻的数字: n, n + 1,使得: count(n) + count(n + 1)最大, 就可以了,
所以我们的解题步骤分为2步:

  1. 遍历nums, 统计各个数字的出现次数, 生成一张查询表countTable, 这里直接hash, 复杂度nlogn
  2. 遍历countTable, 找到count(n) + count(n+1)的最大值
    ps: 注意js的类型转换, Object的key会被转成string来处理
var findLHS = function(nums) {
  const countTable = countNums(nums);
  let max = 0;
  for(let num in countTable)
  {
    num = Number(num); // 注意这里转一下!!!
    if(countTable.hasOwnProperty(num + 1)) max = Math.max(max, countTable[num] + countTable[num + 1]);
    if(countTable.hasOwnProperty(num - 1)) max = Math.max(max, countTable[num] + countTable[num - 1]);
  }
  return max;
};

function countNums(nums){
  const countTable = {};
  for(let num of nums)
  {
    if(!countTable[num]) countTable[num] = 0;
    countTable[num] += 1;
  }
  return countTable;
}

数组的累加和问题

算法模板

https://leetcode.com/problems/range-sum-query-immutable/

const cumSum = nums => {
  const sumNums = [0], len = nums.length;
  for(let i = 1; i <= len; i++) {
    sumNums[i] = sumNums[i - 1] + nums[i - 1];
  }
  return sumNums;
}

1314. Matrix Block Sum

这个问题可以看作数组的累加和在矩阵上的推广

const cumNums = nums => {
  const cumedNums = [0], len = nums.length;
  for(let i = 1; i <= len; i++) {
    cumedNums[i] = cumedNums[i - 1] + nums[i - 1];
  }
  return cumedNums;
}

/**
 * @param {number[][]} mat
 * @param {number} K
 * @return {number[][]}
 */
var matrixBlockSum = function(mat, K) {
  const cumedRowMat = mat.map(cumNums);
  const rowN = mat.length, colN = mat[0].length;
  
  const sumMatrixBlock = (i, j) => {
    const startRow = i - K < 0 ? 0 : i - K;
    const endRow = i + K >= rowN ? rowN - 1 : i + K;
    const startCol = j - K < 0 ? 0 : j - K;
    const endCol = j + K >= colN ? colN - 1 : j + K;

    let sum = 0;
    for(let row = startRow; row <= endRow; row++) {
      const cumedRowMatRow = cumedRowMat[row];
      const rangeSum = cumedRowMatRow[endCol + 1] - cumedRowMatRow[startCol];      
      sum += rangeSum;
    }
    return sum;
  }
  
  const blockSumMat = new Array(rowN).fill(null).map(() => new Array(colN).fill(0));
  for(let row = 0; row < rowN; row++) {
    for(let col = 0; col < colN; col++) {
      blockSumMat[row][col] = sumMatrixBlock(row, col);
    }    
  }
  return blockSumMat;
}

671. Second Minimum Node In a Binary Tree

如果left.val === root.val, 那么我们需要对left, 进行一次寻找第二小值得运算, 对于right亦然.
这里对左右分别作了处理以后, 进行一次交换, 保证leftVal < rightVal, 由于整个树的值都是正数,
所以-1必然在左边. 下面是代码:

var findSecondMinimumValue = function(root) {
  if(!root.left) return -1;
    
  let [leftVal, rightVal] = [root.left.val, root.right.val];  
  if(leftVal === root.val) leftVal = findSecondMinimumValue(root.left);
  if(rightVal === root.val) rightVal = findSecondMinimumValue(root.right);

  // 保证leftVal <= rightVal, 我们这里做一次交换!
  if(leftVal > rightVal) [leftVal, rightVal] = [rightVal, leftVal];
  
  if(-1 === leftVal) return rightVal;
  return leftVal;
};

525. Contiguous Array

  1. 果断暴♂力
var findMaxLength = function(nums) {
  const sum = [0], len = nums.length;
  for(let i = 0, num; i < len; i++) sum[i + 1] = nums[i] + sum[i];  
  let slow, fast, max = 0;
  for(slow = 0; slow < len; slow++)
  {
    for(fast = slow + 1; fast < len; fast++)
    {
      if((sum[fast + 1] - sum[slow]) * 2 === (fast - slow + 1)) 
      {
        max = Math.max(max, fast - slow + 1);
      }
    }    
  }
  return max;
};

果断超时, 跪+1

  1. 痛定思痛, 思考问题.
    首先我们来看基础的子数组和问题的公式: sum[i ,j] = sum[0, j] - sum[0, i - 1] (令: sum[-1] = 0).
    对于这道题, 我们定义数组索引为idx, 则区间[0, idx], x为区间中1的总数, y为区间中0的总数,
    于是我们获得一个三元组(idx, x, y).
    那么对于一前一后2个点有(idx1, x1, y1), (idx2, x2, y2).
    即当: y2 - y1 == x2 - x1 时, 区间[idx1 + 1, idx2]截取的子数组为满足题设条件的子数组.
    y2 - y1 == x2 - x1经过一个简单的变换得: x1 - y1 == x2 - y2. 我们令k = x - y.
    又由于x + y == idx + 1, 则 k = x - y = 2 * x - idx - 1.
    那么对于一个给定的idx我们只需要查找是否有某个对应的位置存在k相同的点就可以了.
    利用hash可以很快的查找.
var findMaxLength = function(nums) {
  const sums = [0], offsetToIdxs = { 0: [-1] }, len = nums.length;
  let max = 0, offset;
  for(let i = 0; i < len; i++) sums[i + 1] = nums[i] + sums[i];
  for(let i = 0; i < len; i++)
  {
    offset = 2 * sums[i + 1] - i - 1;
    if(!offsetToIdxs[offset]) offsetToIdxs[offset] = [];
    offsetToIdxs[offset].push(i);
    max = Math.max(max, i - offsetToIdxs[offset][0]);
  }
  return max;
};

各种 Island 问题, 求面积, 求周长...

1254. Number of Closed Islands

const VERTEX_STATUS = {
  unvisit: 0,
  water: 1,
  visited: 2
}

/**
 * @param {number[][]} grid
 * @return {number}
 */
var closedIsland = function(matrix) {
  const rowN = matrix.length, colN = matrix[0].length;
  let count = 0;
  for(let row = 0; row < rowN; row++) {
    for(let col = 0; col < colN; col++) {
      if(matrix[row][col] === VERTEX_STATUS.unvisit) {
        bfs(matrix, [{ row, col }]) && (count += 1);
      }
    }
  }
  return count;
};

const TOWARDS = [
  [-1, 0],
  [0, 1],
  [1, 0],
  [0, -1]
];

const getNbrs = (sRow, sCol, rowN, colN) => {
  const nbrs = [];
  for(const [dRow, dCol] of TOWARDS) {
    const [row, col] = [sRow + dRow, sCol + dCol];
    if(0 <= row && row < rowN && 0 <= col && col < colN) {
      nbrs.push({ row, col });
    }
  }
  return nbrs;
}

const bfs = (matrix, startVs) => {
  const rowN = matrix.length, colN = matrix[0].length;  
  let [vs, nextVs] = [startVs, []], isClosed = true;
  while(vs.length > 0) {
    for(const v of vs) {
      if(
        v.row === 0 || (v.row === rowN - 1) || 
        v.col === 0 || (v.col === colN - 1)
      ) {
        isClosed = false;
      }
      const nbrs = getNbrs(v.row, v.col, rowN, colN);      
      for(const nbr of nbrs) {
        if(matrix[nbr.row][nbr.col] === VERTEX_STATUS.unvisit) {
          nextVs.push(nbr);
          matrix[nbr.row][nbr.col] = VERTEX_STATUS.visited;
        }
      }
    }
    [vs, nextVs] = [nextVs, []];
  }
  return isClosed;
}

200. Number of Islands

这道题为了保持代码结构和下面那道一致, 做了一些无用的计算.

var numIslands = function(grid) {
  if(!grid || !grid[0]) return 0;
  const rowN = grid.length, colN = grid[0].length;
  let count = 0;
  for(let row = 0; row < rowN; row++)
  {
    for(let col = 0; col < colN; col++)
    {
      count += dfs(grid, row, col) ? 1 : 0;
    }
  }
  return count;
};

function dfs(grid, row, col){    
  if(!grid[row] || !grid[row][col] || '0' === grid[row][col]) return 0;
  grid[row][col] = '0';
  return 1 + dfs(grid, row - 1, col) + dfs(grid, row + 1, col) +
    dfs(grid, row, col - 1) +dfs(grid, row, col + 1);
}

695. Max Area of Island

var maxAreaOfIsland = function(grid) {
  if(!grid || !grid[0]) return 0;
  const rowN = grid.length, colN = grid[0].length;
  let maxArea = 0, area;
  for(let row = 0; row < rowN; row++) 
  {
    for(let col = 0; col < colN; col++) 
    {
      area =  dfs(grid, row, col);
      maxArea = maxArea > area ? maxArea : area;
    }
  }
  return maxArea;
};

function dfs(grid, row, col){
  if(!grid[row] || !grid[row][col]) return 0;
  grid[row][col] = 0;
  return 1 + dfs(grid, row - 1, col) + dfs(grid, row + 1, col) +
    dfs(grid, row, col - 1) + dfs(grid, row, col + 1);
}

733. Flood Fill

唯一注意当newColor和oldColor一样的时候出现corner case

var floodFill = function(image, sr, sc, newColor) {
  const rowN = image.length, colN = image[0].length;
  if(image[sr][sc] === newColor) return image;
  return dfs(image, sr, sc, image[sr][sc], newColor);
};

function dfs(grid, row, col, oldColor, newColor){
  if(!grid[row] || oldColor !== grid[row][col]) return grid;
  grid[row][col] = newColor;
  return dfs(grid, row - 1, col, oldColor, newColor) && 
    dfs(grid, row + 1, col, oldColor, newColor) &&
    dfs(grid, row, col - 1, oldColor, newColor) && 
    dfs(grid, row, col + 1, oldColor, newColor);
}

547. Friend Circles

找基友的故事, 这次dfs时候走的方向不再是上, 下, 左, 右, 而是直接是一行(或者一列),
当M[i][j]为1的时候, 那么我们需要继续遍历所有i的朋友, j的朋友, 极其朋友的朋友,
直到访问完整个朋友圈. 即M[i][0 ~ n], M[j][0 ~ n].

var findCircleNum = function(M) {
  if(!M || !M[0]) return 0;
  const len = M.length;
  let count = 0;
  for(let n = 0; n < len; n++)
  {
    count += dfs(M, n, n);
  }
  return count;
};

function dfs(M, row, col){
  if(!M[row][col]) return 0;
  M[row][col] = M[col][row] = 0;
  const n = M.length;
  for(let i = 0; i < n; i++) 
  {
    dfs(M, row, i);
    dfs(M, col, i);
  }
  return 1;
}

463. Island Perimeter

求岛屿周长的题目, 岛屿的周边就是岛屿与外界的边界, 可以表述为岛屿与水的相交处,
而显然这个相交处对于一个岛屿各自和水各自来说是唯一的。一条边在这个系统里面被
2个各自共享。
最开始的dfs解法

var islandPerimeter = function(grid) {
  const rowN = grid.length, colN = grid[0].length;
  for(let row = 0; row < rowN; row++)
  {
    for(let col = 0; col < colN; col++) 
    {
      if(grid[row][col]) return dfs(grid, row, col);
    }
  }
};
function dfs(grid, row, col){
  if(!grid[row] || !grid[row][col]) return 1;
  if(2 === grid[row][col]) return 0;
  grid[row][col] += 1; // this island is checked !!!
  return dfs(grid, row - 1, col) + dfs(grid, row + 1, col) +
    dfs(grid, row, col - 1) + dfs(grid, row, col + 1);
}

不过后来看到题解有利用期数学原理的更强解法!!!
公式: 边长 = 岛屿数量 * 4 - 两个岛屿的临边数量 * 2;
这个解法的好处是不需要修改输入的gird, 而且能在O(n^2)时间复杂度内解决问题。

var islandPerimeter = function(grid) {
  const rowN = grid.length, colN = grid[0].length;
  let islandCount = 0, innerEdgeCount = 0;
  for(let row = 0; row < rowN; row++)
  {
    for(let col = 0; col < colN; col++) 
    {
      if(!grid[row][col]) continue;
      islandCount += 1;
      if(grid[row][col + 1] ) innerEdgeCount += 1; 
      if(grid[row + 1] && grid[row + 1][col]) innerEdgeCount += 1;
    }
  }
  return 4 * islandCount - 2 * innerEdgeCount;
};

419. Battleships in a Board

题设要求不修改gird, 并且时间复杂度为O(N^2). 题目关键在于:

  1. 判断一个格子是否是一个矩形的开头:
    任意横放或者竖放的矩形条的首格必然在上, 左方向上与水域相交。
    即: 0 === board[row - 1][col] && 0 === board[row][col - 1]
  2. 判断是否存在十字交叉的部分:
    如果出现十字交叉的点, 假设坐标为p(row, col), 那么这个格子右, 下边界必然与岛屿相邻。
    即: 1 === board[row - 1][col] && 1 === board[row][col - 1]
var countBattleships = function(board) {
  const rowN = board.length, colN = board[0].length;
  let count = 0, tmp;
  for(let row = 0; row < rowN; row++)
  {
    for(let col = 0; col < colN; col++)
    {
      if(!matrixVal(board, row, col)) continue;
      if(0 === matrixVal(board, row - 1, col) + matrixVal(board, row, col - 1)) count += 1;
      if(2 === matrixVal(board, row + 1, col) + matrixVal(board, row, col + 1)) return 0; // 这是一个非法的排列
    }
  }
  return count;
};

function matrixVal(matrix, row, col){
  if(!matrix[row] || !matrix[row][col]) return 0;
  return matrix[row][col] === '.' ? 0 : 1;
}

---------我是猥琐的分割线

657. Judge Route Circle

leetcode说这个题目是 547 朋友圈的类似题目, 但是怎么看都不像啊,
既然leetcode都说是了, 那就是吧, 勉强放这里.
�两种解法:

  1. 第一种直接模拟直, 解释器模式走起:
var judgeCircle = function(moves) {
  let posX = posY = 0;
  for(let move of moves)
  {
    switch(move)
    {
    case 'U':
      posY -= 1;
      break;
    case 'D':
      posY += 1;
      break;
    case 'L':
      posX -= 1;
      break;
    case 'R':
      posX += 1;
      break;
    default:
      throw '位置的指令: ' + move;
    }
  }
  return 0 === posX && 0 === posY;
};
  1. 实际上只要对应方向的指令数量一致,那么对应方向最后一定回到原点:
var judgeCircle = function(moves) {
  const record = { R: 0, L: 0, U: 0, D: 0 };
  for(let move of moves) record[move] += 1;
  return (record.R === record.L) && (record.U === record.D);
};

各类DFS, BFS问题

872. Leaf-Similar Trees

分解为2个基本的子问题:

  1. 求取一颗二叉树的所有叶子节点,preOrder 遍历整棵树,并记录叶子节点。
  2. 比较2颗数的叶子节点是否等价。
    注意:�bfs 遍历只能获得最后一层的叶子节点。
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root1
 * @param {TreeNode} root2
 * @return {boolean}
 */
var leafSimilar = function(root1, root2) {
  const [leaves1, leaves2] = [dfs(root1), dfs(root2)];  
  if(leaves1.length !== leaves2.length) return false;
  for(let i = 0, len = leaves1.length; i < len; i++) {
    if(leaves1[i].val !== leaves2[i].val) return false;
  }
  return true;
};

const dfs = (root, vs = []) => {
  if(!root) return vs;
  if(!root.left && !root.right) {
    vs.push(root);
  }
  dfs(root.left, vs);
  dfs(root.right, vs);
  return vs;
}

688. Knight Probability in Chessboard

棋盘大小为N, 假设马儿每次往8个方向上的移动都是随机的,
问在走了K步以后, 马儿留在棋盘上的概率是多少?

  1. 首先考虑暴力破解, 利用dfs进行求解, 算法时间复杂度O(8^K):
var knightProbability = function(N, K, r, c) {
  let count = dfs(N, K, r, c, 0);
  return count / (8 ** K);
};

const Moves = [[-2, -1], [-2, 1], [2, -1], [2, 1], [-1, -2], [1, -2], [-1, 2], [1, 2]];
function dfs(N, maxDeep, posX, posY, deep){
  const isOnTheBoard = (posX >= 0 && posX < N && posY >= 0 && posY < N);
  if(!isOnTheBoard) return 0;
  if(deep >= maxDeep) return 1;

  let count = 0;
  for(let move of Moves) count += dfs(N, maxDeep, posX + move[0], posY + move[1], deep + 1) ;
  return count;
}

果断超时, Time Limit Exceeded, OTL, 跪+1.

  1. 考虑记录每一个位置, 还有K步要走的时候的生存概率
var knightProbability = function(N, K, r, c) {
  const record = { }; // 三维矩阵...算了还是hash表好点
  const count = dfs(N, K, r, c, 0, record);
  return count / (8 ** K);
};

const Moves = [[-2, -1], [-2, 1], [2, -1], [2, 1], [-1, -2], [1, -2], [-1, 2], [1, 2]];
function dfs(N, maxDeep, posX, posY, deep, record){
  const isOnTheBoard = (posX >= 0 && posX < N && posY >= 0 && posY < N);
  if(!isOnTheBoard) return 0;
  if(maxDeep === deep) return 1;
  
  // 查表, 如果有记录那么直接返回记录!!!
  let count = record[encodeStatus(posX, posY, maxDeep - deep)];
  if(count) return count;

  count = 0;
  for(let move of Moves) count += dfs(N, maxDeep, posX + move[0], posY + move[1], deep + 1, record);
  record[encodeStatus(posX, posY, maxDeep - deep)] = count;
  return count;
}

function encodeStatus(posX, posY, step){
  return [posX, posY, step].join('_');
}

通过对历史的记录这样减少了很多重复的搜索,AC掉, 算法时间复杂度O(N^2 * K).

  1. TODO 用DP的方式重新写一次
    状态转移函数!!!!

397. Integer Replacement

var integerReplacement = function(n) {
  const record = { 1: Number.MAX_SAFE_INTEGER };
  dfs(record, n, -1);
  return record[1];
};

function dfs(record, n, distance){
  if(record[n] > ++distance) record[n] = distance;
  if(1 === n || distance >= record[1]) return;
  n % 2 ? (dfs(record, n + 1, distance) || dfs(record, n - 1, distance)) :
    dfs(record, n / 2, distance);
}

752. Open the Lock

这个是个BFS的问题, 如果用DFS那么可能的最大搜索深度会是10^4, 只能用BFS来解,
这道题注意一点, 密码锁是可以反着拨的, 1200, 可以变成1100, 也就是说每个状态的邻接状态是8个!

var openLock = function(deadends, target) {
  if(-1 !== deadends.indexOf('0000')) return -1;
  const record = { "0000": 0 };
  for(let deadend of deadends) record[deadend] = -1;
  return bfs(record, [[0, 0, 0, 0]], target, 0);
};

function bfs(record, statusList, target, deep){
  if(!statusList.length) return -1;
  let len = statusList.length, status, statusNum, nextStatus;
  while(len--)
  {
    status = statusList.shift();
    statusNum = status.join('');
    if(statusNum === target) return deep;

    for(let i = 0; i < 4; i++)
    {
      for(let j of [1, 9])
      {
        nextStatus = status.slice();
        nextStatus[i] = (status[i] + j) % 10;
        if(undefined !== record[nextStatus.join('')]) continue;
        record[nextStatus.join('')] = deep + 1;
        statusList.push(nextStatus);
      }
    }
  }
  return bfs(record, statusList, target, deep + 1);
}

238. Product of Array Except Self

  1. 这道题目在于不准用除法, 上来还是先把公式写出来:
    令: product[i, j]表示数组nums在i~j区间上的连乘, len为nums的长度,
    p[i]为procutst[0, len - 1] / nums[i].
    则: p[i] = product[0 , i - 1] * product[i + 1, len - 1]
    那么我们需要对应的从左到右, 以及从右到左, 乘一次生成查询数组就可以了.
var productExceptSelf = function(nums) {
  const len = nums.length, leftProducts = [], rightProducts = [], ans = [];

  leftProducts[0] = nums[0];
  for(let i = 1; i < len; i++) leftProducts[i] = leftProducts[i - 1] * nums[i];
  rightProducts[len - 1] = nums[len - 1];
  for(let i = len - 2; i >= 0; i--) rightProducts[i] = rightProducts[i + 1] * nums[i];
  
  ans[0] = rightProducts[1];
  ans[len - 1] = leftProducts[len - 2];
  for(let i = 1; i < len - 1; i++)
  {
    ans[i] = leftProducts[i - 1] * rightProducts[i + 1];
  }
  
  return ans;
};
  1. 要求充分利用空间. todo 容我三思

parse 相关问题

1309. Decrypt String from Alphabet to Integer Mapping

TODO: 可以优化的点:

  1. 字母表的生成
  2. 字符串截取用其他的函数
const ALPHA_MAP = {
  '1': 'a',
  '2': 'b',
  '3': 'c',
  '4': 'd',
  '5': 'e',
  '6': 'f',
  '7': 'g',
  '8': 'h',
  '9': 'i',
  '10#': 'j',
  '11#': 'k',
  '12#': 'l',
  '13#': 'm',
  '14#': 'n',
  '15#': 'o',
  '16#': 'p',
  '17#': 'q',
  '18#': 'r',
  '19#': 's',
  '20#': 't',
  '21#': 'u',
  '22#': 'v',
  '23#': 'w',  
  '24#': 'x',
  '25#': 'y',
  '26#': 'z',  
}

/**
 * @param {string} s
 * @return {string}
 */
var freqAlphabets = function(text) {
  let offset = 0, alphas = [], len = text.length;
  while(offset < len) {
    const token = (text[offset + 2] === '#') ?
          text.slice(offset, offset + 3) : 
          text.slice(offset, offset + 1);
    alphas.push(ALPHA_MAP[token]);
    offset += token.length;
  }
  return alphas.join('');
};

数学相关的问题

258. Add Digits

解法一:就是简单的递归

/**
 * @param {number} num
 * @return {number}
 */
var addDigits = function(num) {
  while(num > 9) {
    num = addDigitsOnce(num);
  }
  return num;
};

const addDigitsOnce = num => {
  let sum = 0;
  while(num > 0) {
    sum += (num % 10);
    num = (num / 10 | 0);
  }
  return sum;
}

解法二:

各种二叉树问题

530. Minimum Absolute Difference in BST

求二叉树上两值得最小绝对值, 一个很朴素的解法, 先用中序遍历生成数组, 然后就很简单了.

var getMinimumDifference = function(root) {
  const vals = inOrder(root, []);  
  let min = Number.MAX_SAFE_INTEGER;
  for(let i = 1, len = vals.length; i < len; i++)
  {
    min = Math.min(min, vals[i] - vals[i - 1]);
  }
  return min;
};

function inOrder(root, vals){
  if(!root) return vals;
  inOrder(root.left, vals);
  vals.push(root.val);
  inOrder(root.right, vals);
  return vals;
}

各种二叉树问题

1315. Sum of Nodes with Even-Valued Grandparent

对于一个节点是否满足条件,依赖于其祖父节点。
如果 node 结构有 parent 域,是最方便的,
不过我们可以通过传入 partent 和 grandParent 来解决问题,

var sumEvenGrandparent = function(root, parent = null, grandParent = null) {
  if(!root) return 0;
  const isAccept = grandParent && (grandParent.val % 2 === 0);
  const val = isAccept ? root.val : 0;
  return val + sumEvenGrandparent(root.left, root, parent) +
    sumEvenGrandparent(root.right, root, parent);
};

543. Diameter of Binary Tree

考察数的后续遍历,必然存在一个节点,是整个最长路劲的根节点,也就是整个路径可以表述为:
path = subTree(node.left) + subTree(node.right) + node;
那么作为最长路径即:左子树的最大深度 + 右子树的最大深度,最大的节点为目标节点。

var diameterOfBinaryTree = function(root) {
  let maxDiameter = 0;
  const treeHeight = root => {
    if(!root) return 0;
    const leftHeight = treeHeight(root.left);
    const rightHeight = treeHeight(root.right);
    maxDiameter = Math.max(maxDiameter, leftHeight + rightHeight);
    return Math.max(leftHeight, rightHeight) + 1;
  }
  treeHeight(root);
  return maxDiameter;
};

1325. Delete Leaves With a Given Value

考察树的后续遍历,这个题目当子树满足:

  1. isLeaf,子树为叶子节点
  2. 值为 target 的时候
    将该子节点自身从父节点上移除,并且在左右子树都被移除,父节点成为新的叶子节点的时候,
    递归的作用到父节点,以及父父节点上。
    采用 postOrder 后续遍历,然后对每一个子树做同样的判断操作即可。

解法一:尝试利用了 js 的闭包特性,把删除操作作为回调传递给子树。

var removeLeafNodes = function(root, target) {
  postOrder(root, target, () => root = null);
  return root;
};

const postOrder = (root, target, delSelfFn) => {
  if(!root) return;
  postOrder(root.left, target, () => root.left = null);
  postOrder(root.right, target, () => root.right = null);
  if(
    root.val === target &&
    !root.left &&
    !root.right
  ) {
    delSelfFn();
  }
}

解法二:按照递归定义,

var removeLeafNodes = function(root, target) {
  return postOrder(root, target);
};

const isLeaf = root => root && !root.left && !root.right;

const postOrder = (root, target) => {
  if(!root) return null;
  root.left = postOrder(root.left, target);
  root.right = postOrder(root.right, target);  
  return (isLeaf(root) && root.val === target) ? null : root;
}

513. Find Bottom Left Tree Value

  1. bfs解法
var findBottomLeftValue = function(root) {
  if(!root) return null;
  let row = [root], value;
  while(row.length)
  {
    value = row[0].val;    
    row = bfs(row);
  }
  return value
};

function bfs(row){
  let nextRow = [];
  for(let node of row)
  {
    if(node.left) nextRow.push(node.left);
    if(node.right) nextRow.push(node.right);
  }
  return nextRow;
}
  1. dfs解法
var findBottomLeftValue = function(root) {
  if(!root) return null;
  const nice = { deep: 0, val: root.val };
  dfs(root, 0, nice);
  return nice.val;
};

function dfs(root, deep, nice){
  if(!root) return;
  if(!root.left && !root.right)
  {
    if(deep > nice.deep)
    {
      nice.deep = deep;
      nice.val = root.val;
    }
    return;
  }
  dfs(root.left, deep + 1, nice);
  dfs(root.right, deep + 1, nice);
}

二叉搜索树类题目

https://leetcode.com/problems/search-in-a-binary-search-tree
https://leetcode.com/problems/insert-into-a-binary-search-tree

938. Range Sum of BST

暴力遍历所有的节点,然后过滤掉不在范围中的值

var rangeSumBST = function(root, L, R) {
  if(!root) return 0;
  let { left, right, val } = root;
  if(val < L || val > R) val = 0;
  return val + rangeSumBST(left, L, R) + rangeSumBST(right, L, R);
};

进化一下,添加剪纸策略

var rangeSumBST = function(root, L, R) {
  if(!root) return 0;
  let { left, right, val } = root;
  const leftSum = (val >= L) ? rangeSumBST(left, L, R) : 0;
  const rightSum = (val <= R) ? rangeSumBST(right, L, R): 0;  
  if(val < L || val > R) val = 0;
  return val + leftSum + rightSum;
};

做题记录 stash

922. Sort Array By Parity II

双指针,利用一个 evenIdx 和 oddIdx 进行两趟遍历

var sortArrayByParityII = function(nums) {
  let evenIdx = 0, oddIdx = 1, len = nums.length;
  while(evenIdx < len) {
    [nums[evenIdx], nums[oddIdx]] = [nums[oddIdx], nums[evenIdx]];
    while(nums[evenIdx] % 2 === 0) evenIdx += 2;
    while(nums[oddIdx] % 2 === 1) oddIdx += 2;
  }
  return nums;
};

1346. Check If N and Its Double Exist

主要是 coner case 当数组值为 0 的时候,0 * 2 === 0,0 / 2 === 0,会漏掉 i !== j 的判断。
所以 num 的值为 0的时候需要添加一个额外判断。

/**
 * @param {number[]} arr
 * @return {boolean}
 */
var checkIfExist = function(nums) {
  const record = {};
  for(const num of nums) {
    record[num] ? record[num] += 1 : record[num] = 1;
    const isZeroDouble = num === 0 && record[num] === 2;
    const isNonZeroDouble = num !== 0 && (
      record[num * 2] || (num % 2 === 0 && record[num / 2])
    );
    if(isZeroDouble || isNonZeroDouble) {
       return true;
    }
  }    
  return false;
};

1347. Minimum Number of Steps to Make Two Strings Anagram

TODO: 更为细化一点

分解为3个基础操作:
1. �如何生成所有英语字母的字母表
2. 遍历一个字符串,通过 hash 表统计每个字符出现的次数
3. 计算2个字符串,之间字母构成的差异

const ALPHAS = [];
for(let i = 0; i < 26; i++) {
  ALPHAS.push(String.fromCharCode((97 + i)));
}
const countAlpha = s => {
  const record = {};
  for(const alpha of ALPHAS) {
    record[alpha] = 0;
  }
  for(const char of s) {
    record[char] += 1;
  }
  return record;
}

/**
 * @param {string} s
 * @param {string} t
 * @return {number}
 */
var minSteps = function(s, t) {
  const [recordS, recordT] = [countAlpha(s), countAlpha(t)];
  let sum = 0;
  for(const alpha of ALPHAS) {
    sum += (Math.abs(recordS[alpha] - recordT[alpha]));
  }
  return sum / 2;
};

1333. Filter Restaurants by Vegan-Friendly, Price and Distance

利用 filter,sort,map 操作按照题设计算就好,
注意 Vegan-Friendly 这个过滤项是在 true 的时候要求 Restaurant 必为 true,为 false 的时候为任意值

1329. Sort the Matrix Diagonally

这道题主要考察的是如何按照对角线来遍历一个矩阵

矩阵对角线的起始点为矩阵的第一行和第一列即:

1. row = 0 时 col 的范围为 [0, rowN - 1]
2. col = 0 时 row 的范围为 [0, colN - 1]

已知一个点 matrix[row][col] 则,对角线上的下一个点为 matrx[row + 1, col + 1]

对对角线进行编码标记

令 k = row - col,我们可以标记对应的对角线 k,并且对应起始点偏移量为 offset 的点
则由对角线的起始点必然 startRow === 0 || startCol === 0,易得 offset = min(row, col);
我们可以得到 row-col 坐标系 和对角线 k-offset 坐标系之间得转换关系:

 // 已知 row,col
 k = row - col;
 offset = Math.min(row, col);

方法一:分类讨论对角线的起始点分别在在第一行和第一列时的情况,这种方法较为繁琐。

/**
 * @param {number[][]} mat
 * @return {number[][]}
 */
var diagonalSort = function(matrix) {
  const rowN = matrix.length, colN = matrix[0].length;
  
  const sortMatrixDiagonal = (row, col) => {
    const nums = [], n = matrix.length;
    for(let k = 0; row + k < rowN && col + k < colN; k++) {
      nums.push(matrix[row + k][col + k]);
    }
    nums.sort((x, y) => x > y ? 1 : -1);

    for(let k = 0; row + k < rowN && col + k < colN; k++) {
      matrix[row + k][col + k] = nums[k];      
    }
  }
  
  for(let row = 0, col = 0; col < colN; col++) {
    sortMatrixDiagonal(row, col);
  }
  for(let row = 0, col = 0; row < rowN; row++) {
    sortMatrixDiagonal(row, col);
  }
  return matrix;
};

方法二:这里巧妙得利用 stack 来保存和还原了对角线上的值

/**
 * @param {number[][]} mat
 * @return {number[][]}
 */
var diagonalSort = function(matrix) {
  const rowN = matrix.length, colN = matrix[0].length, record = {};
  for(let row = 0; row < rowN; row++) {
    for(let col = 0; col < colN; col++) {
      const k = row - col;
      if(!record[k]) record[k] = [];
      record[k].push(matrix[row][col]);
    }
  }
  Object.values(record).map(nums => nums.sort((x, y) => x > y ? -1 : 1));  
  for(let row = 0; row < rowN; row++) {
    for(let col = 0; col < colN; col++) {
      const k = row - col;
      matrix[row][col] = record[k].pop();
    }
  }
  return matrix;
};

1109. Corporate Flight Bookings

解法一:暴力破解

/**
 * @param {number[][]} bookings
 * @param {number} n
 * @return {number[]}
 */
var corpFlightBookings = function(bookings, n) {
  const record = [];
  for(let i = 1; i <= n; i++) {
    record[i] = 0;
  }
  for(const [start, end, k] of bookings) {
    for(let i = start; i <= end; i++) {
      record[i] += k;
    }
  }
  record.shift();
  return record;
};

1014. Best Sightseeing Pair

关键在理解公式:A[i] + A[j] + i - j

第一种做法,思路:考虑选择了 nums[i],那么结合公式距离 i 超过 A[i] 的地点实际上就没有必须再去了,这样负责的就被限制在了,N * max(nums)。

/**
 * @param {number[]} A
 * @return {number}
 */
var maxScoreSightseeingPair = function(nums) {
  let max = -1, len = nums.length;  
  nums.forEach((num, i) => {
    let offset = 1, j;
    while(offset <= num && (j = i + offset) < len) {
      const score = nums[i] + nums[j] + i - j;
      (score > max) && (max = score); 
      offset += 1;
    }
  });
  return max;
};

第二种思路,我们整理一下公式可得:(A[i] + i) + (A[j] - j),取最大:
那么当 i 一定的时候,A[i] + i + max(A.map((val, j) => val - j)),使得最大。
那么当 j 一定的时候,A[j] - j + max(A.map((val, i) => val + i)),使得最大。

第一个版本显得非常繁琐:

/**
 * @param {number[]} A
 * @return {number}
 */
var maxScoreSightseeingPair = function(nums) {
  const len = nums.length;
  const aNums = nums.map((n, i) => n + i);
  const bNums = nums.map((n, j) => n - j);

  const cNums = [];
  cNums[len - 1] = bNums[len - 1];
  for(let i = len - 2; i >= 0; i--) {
    cNums[i] = Math.max(bNums[i], cNums[i + 1]);
  }
  
  aNums.pop();
  const dNums = aNums.map((val, i) => val + cNums[i + 1]);
  const max = Math.max(...dNums);
  return max;
};

实际上经过合适的调整,可以在一趟遍历中通过缓存 max = Math.max(A[i] + i) 解决问题:

var maxScoreSightseeingPair = function(nums) {
  const len = nums.length;
  let tmp = nums[0], max = -1;
  for(let j = 1; j < len; j++) {
    max = Math.max(nums[j] - j + tmp, max);
    tmp = Math.max(tmp, nums[j] + j);
  }
  return max;
};

1232. Check If It Is a Straight Line

几何类的题目,最近都快忘光了。TODO: 后面好好补习下

/**
 * @param {number[][]} coordinates
 * @return {boolean}
 */
var checkStraightLine = function(coordinates) {
  const [[x1, y1], [x2, y2]] = coordinates;
  const [dx, dy] = [x1 - x2, y1 - y2];
  const crossProduct  = x1 * dy - y1 * dx;
  
  for(const [x, y] of coordinates) {
    if(x * dy - y * dx !== crossProduct) return false;
  }
  return true;
};

661. Image Smoother

很直白的矩阵遍历类的题目,可以分成几个基本过程:

  1. 判断 pos = { row, col } 是否在矩阵的范围内
  2. 由点 pos,按照题设获取他所有的相邻点
  3. 由点 pos,获取其周围点的平均值
  4. 遍历矩阵按照规则映射出新的矩阵
const TOWARDS = [
  [0, 0],
  [-1, 0],
  [-1, 1],
  [0, 1],
  [1, 1],
  [1, 0],
  [1, -1],
  [0, -1],
  [-1, -1]
];

/**
 * @param {number[][]} M
 * @return {number[][]}
 */
var imageSmoother = function(matrix) {
  const rowN = matrix.length, colN = matrix[0].length;
  
  const isInRange = (row, col) => row >= 0 && row < rowN && col >= 0 && col < colN;
  
  const getNbrsAverVal = (curRow, curCol) => {
    const vals = [];
    TOWARDS.forEach( ([dRow, dCol]) => {
      const [row, col] = [curRow + dRow, curCol + dCol];
      isInRange(row, col) && vals.push(matrix[row][col]);
    });
    const sum = vals.reduce((total, val) => total += val, 0);
    const averVal = sum / vals.length | 0;
    return averVal;
  };
  
  const smoothMatrix = matrix.map((nums, row) => {
    return nums.map((num, col) => getNbrsAverVal(row, col));
  });
  
  return smoothMatrix;
};

1299. Replace Elements with Greatest Element on Right Side

从右往左,执行一次冒泡。


1291. Sequential Digits

这道题比较有技巧的地方是如何巧妙的生成范围内所有有效的数字。

const DIGITS = "123456789";
const sequentialNums = [];
for(let offset = 0; offset < DIGITS.length; offset++) {
  for(let len = 1; offset + len <= DIGITS.length; len++) {
    sequentialNums.push(Number(DIGITS.substr(offset, len)));
  }
}
sequentialNums.sort((x, y) => x > y ? 1 : -1);

var sequentialDigits = function(low, high) {
  return sequentialNums.filter(num => num >= low && num <= high);
};

605. Can Place Flowers

/**
 * @param {number[]} flowerbed
 * @param {number} n
 * @return {boolean}
 */
var canPlaceFlowers = function(flowerbed, n) {
  flowerbed = [1, 0, ...flowerbed, 0, 1];
  let [slow, fast, len, count, delta] = [0, 2, flowerbed.length, 0, 0];
  while(fast < len) {
    while(flowerbed[fast] === 0) fast += 1;
    delta = parseInt((fast - slow) / 2) - 1;
    delta < 0 && (delta = 0);
    count += delta;
    [slow, fast] = [fast, fast + 1];
  }  
  return count >= n;
};
  1. 这道题先考虑最简形式,考虑一个所有值为0的数组,放入若干个 1,并要求所有 1 不相邻,显然我们使用贪心策略,可以使得放入的1的数量最大化(如何严格证明是个很有趣的事情)。
  2. 考虑数组中已经有若干个1的情况,这个时候实际上若干个1,把整个数组分割成了多个独立的类似形式 1 的区域。求出分割了多少个这样的区域,然后每个区域单独求解后取累计,即为所求。
    这里用了在数组前后添加 10 和 01 的前后缀来简化中间的判断,还有要注意循环的 break 条件。

989. Add to Array-Form of Integer

标准的大整数相加,最开始想苟的,不过没苟住,看来数据没那么弱。
苟不住的版本:

var addToArrayForm = function(A, K) {
  const val = parseInt(A.join('')) + K;
  return val.toString().split('');
};

558. Quad Tree Intersection

/**
 * // Definition for a QuadTree node.
 * function Node(val,isLeaf,topLeft,topRight,bottomLeft,bottomRight) {
 *    this.val = val;
 *    this.isLeaf = isLeaf;
 *    this.topLeft = topLeft;
 *    this.topRight = topRight;
 *    this.bottomLeft = bottomLeft;
 *    this.bottomRight = bottomRight;
 * };
 */
/**
 * @param {Node} quadTree1
 * @param {Node} quadTree2
 * @return {Node}
 */
var intersect = function(t1, t2) {  
  if(isTrueLeaf(t1) || isTrueLeaf(t2)) {
    return createQuadTree(true);
  }
  if(isFalseLeaf(t1) && isFalseLeaf(t2)) {
    return createQuadTree(false);
  }
  if(!t1.isLeaf && !t2.isLeaf) {
    const [t1Children, t2Children] = [
      getQuadTreeChildren(t1),
      getQuadTreeChildren(t2)
    ];
    const intersectChildren = [];
    for(let i = 0, len = t1Children.length; i < len; i++) {
      const intersectChild = intersect(t1Children[i], t2Children[i]);
      intersectChildren.push(intersectChild);
    }
    const intersectedTree = createQuadTree(true, intersectChildren);
    const mergedTree = mergeQuadTreeChildren(intersectedTree);
    return mergedTree;
  }
  return !t1.isLeaf ? t1 : t2;
};
const createQuadTree = (val, children) => { 
  const isLeaf = !children;
  if(!children) {
    children = [null, null, null, null];
  }
  return {
    val,
    isLeaf,
    topLeft: children[0],
    topRight: children[1],
    bottomLeft: children[2],
    bottomRight: children[3]
  };
};
const getQuadTreeChildren = (t) => {
  return [t.topLeft, t.topRight, t.bottomLeft, t.bottomRight];
};
const isTrueLeaf = (quadTree) => {
  return quadTree.isLeaf && quadTree.val;
};
const isFalseLeaf = (quadTree) => {
  return quadTree.isLeaf && quadTree.val;
};
const mergeQuadTreeChildren = (t) => {
  const children = getQuadTreeChildren(t);

  const leafCount = children.filter(child => child && child.isLeaf).length;
  if(leafCount !== children.length) {
    return t;
  }
  const vals = children.map(child => child.val);
  const valCount = vals.filter(val => vals[0] === val).length
  if(valCount !== children.length) {
    return t;
  }

  const mergedQuadTree = createQuadTree(vals[0]);
  return mergedQuadTree;
};

总的来说这一版,执行效率不是太好,不过函数接口上还算清晰吧,
TODO: 复盘

1004. Max Consecutive Ones

1004. Max Consecutive Ones III

这道题感觉类似贪心的经典题目,问一个数组最大连续和问题的变种。

字符数量统计类题目(名字 todo)总结

相同结构的问题:
349. Intersection of Two Arrays
1002. Find Common Characters

350. Intersection of Two Arrays II

这一类的问题可以总结出3基本的过程:

  1. itemCount:统计一个字符串或数组内各个元素的出现次数,生成一张统计表
  2. mergeRecord:将多个统计表依据一定的规则合并成一张新的统计表
  3. expandRecord: 将统计表中每一项按照对应的重复次数映射成一个数组,然后将获得的结果拍平

用到的一些基础操作:

  1. 在数组的 reduce 方法中,可以利用逗号运算符来达道精简代码的目的。
  2. 已知一个字符串数组将其映射成一个 object:strs.reduce((total, s) => (total[s] = true, total), {});
  3. 利用 Array.fill 生成一个所有值为特定值的数组:Array(count).fill(item)
  4. 利用 Array.push 和数组的解构运算符,向数组添加项目:arrA.push(...arrB);
/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var intersect = function(nums1, nums2) {
  const records = [
    itemCount(nums1),
    itemCount(nums2)
  ];
  const totalRecord = mergeRecord(records);
  const alphas = expandRecord(totalRecord);
  return alphas.map(Number);
};

const itemCount = (arr) => arr.reduce((total, item) => (
  total[item] = (total[item] || 0) + 1,
  total
), {});

const mergeRecord = records => Object.keys(records[0]).reduce((total, key) => (
  total[key] = Math.min(...records.map(r => r[key] || 0)),
  total
), {});

const expandRecord = record => Object.entries(record).reduce((total, [item, count]) => (
  total.push(...Array(count).fill(item)),
  total
), []);

Power of X

各种判断一个数字是不是某个数的次幂

231. Power of Two

  1. loop!
var isPowerOfTwo = function(n) {
  if(0 === n) return false;
  while(0 === (1 & n)) n >>= 1;
  return 1 === n ? true : false;
};
  1. 利用 n & n - 1 还能更加简洁
var isPowerOfTwo = function(n) {
  return n > 0 && 0 === (n & n - 1);
};

342. Power of Four

  1. 比较直观的一个解 loop!:
var isPowerOfFour = function(num) {
  let n = num;
  if(0 === n) return false;
  while(0 === (n & 3)) n >>= 2;
  return 1 === n ? true : false;
};
  1. 更为简洁的解法:
var isPowerOfFour = function(num) {
  return (num&(num-1)) == 0 &&  // 在二进制表示下只有一位为1,其余都是0
    (num & 0x55555555) != 0; // 在二进制表示下唯一为1的位,在奇数位,0x5 -> 二进制计数: 101,
};
  1. 利用数论结论的解法
var isPowerOfFour = function(num) {
  return num > 0 && (num & (num - 1)) == 0 && num % 3 == 1;
};

326. Power of Three

这个实在想不出怎么解, 抄答案了, 这里用到一个数论原理,
TODO 待我去学习下

const Max3PowerInt = 1162261467; // 3^19, 3^20 = 3486784401 > MaxInt32
var isPowerOfThree = function(n) {
  if (n <= 0 || n > Max3PowerInt) return false;
  return Max3PowerInt % n == 0;
};

位运算相关题目

相关知识点:

  1. 移动:a << n,数 a 向左移动 n 位,等效于 a * (2**n)
  2. 按位操作:或 a | b,

1290. Convert Binary Number in a Linked List to Integer

一个关键的优化点是,curNode.val && (total += curNode.val),做了一个短路

var getDecimalValue = function(head) {
  let total = 0, curNode = head;
  while(curNode) {
    total <<= 1;
    curNode.val && (total += curNode.val);
    curNode = curNode.next;
  }
  return total;
};

1323. Maximum 69 Number

思路一:数字转 digits 数组,处理后再转换回来
思路二:从最高位开始依次获取十进制位的值。(num / 10**k) % 10 获取对应 k 位的十进制 digit

/**
 * @param {number} num
 * @return {number}
 */
var maximum69Number  = function(num) {
  let tmp = 10**12;
  while(tmp > 0) {
    const digit = (num / tmp | 0) % 10;    
    if(digit === 6) {
      num += (tmp * 3);
      break;
    }
    tmp /= 10;
  }
  return num;
};

1342. Number of Steps to Reduce a Number to Zero

  1. 统计一个数的二进制表示中,1 和 0的数量
  2. 对于一个二进制数,除了最高位的 1 需要消耗 2 次操作消去,0 需要消耗一次操作
/**
 * @param {number} num
 * @return {number}
 */
var numberOfSteps  = function(num) {
  const { zeroCount, oneCount } = countDigit(num);
  const step = zeroCount + oneCount * 2 - 1;
  return step;
};

const countDigit = num => {
  let zeroCount = 0, oneCount = 0;
  while(num) {
    (num & 1) ? (oneCount += 1) : (zeroCount += 1);
    num >>= 1;
  }
  return { oneCount, zeroCount };
}

572. Subtree of Another Tree

判断一个树t是不是另外一个树s的子树: 令Size(root)表示树root中节点的数量,

  1. 我们先暴力判断, 算法复杂度O(size(t) * size(s))
var isSubtree = function(s, t) {
  if(!s) return false;
  if(isEqualTree(s, t)) return true;
  return isSubtree(s.left, t) || isSubtree(s.right, t);
};

function isEqualTree(t1, t2){
  if(!t1 && !t2) return true;
  if(!t1 || !t2) return false;
  if(t1.val !== t2.val) return false;
  return isEqualTree(t1.left, t2.left) && isEqualTree(t1.right, t2.right);
}
  1. todo 改进效率亚克西

two-sum problems

1. Two Sum

要求在一个无序数组中找到2个和为target的数, 然后返回这两个数的位置,
利用HashMap来做, 直接通过记录和查表,时间复杂度 O(NlogN)。唯一注意比较特殊一点,
同一个数字不能用2次, 但是相等的2个不同数字是合法的。
解法:

var twoSum = function(nums, target) {
  const numTable = {};
  for(let i = 0, len = nums.length, j, num; i < len; i++)
  {
    num = nums[i];
    j = numTable[target - num];
    if(undefined !== j && i !== j) return [j, i];
    numTable[num] = i;
  }
}

167. Two Sum II - Input array is sorted

这个可以利用DP来做, 算法时间复杂度为O(N), 如果数组没有排序那么加上排序那么就是O(NlogN)

var twoSum = function(numbers, target) {
  let begin = 0, end = numbers.length - 1, tmp;
  while(begin < end)
  {
    tmp = numbers[begin] + numbers[end];
    if(tmp === target) return [begin + 1, end + 1]; 
    tmp > target ? end -= 1 : begin += 1;
  }    
};

653. Two Sum IV - Input is a BST

var findTarget = function(root, k) {
  const nums = inOrder(root, []);
  return twoSum(nums, k);
};

function inOrder(root, vals){
  if(!root) return vals;  
  inOrder(root.left, vals);
  vals.push(root.val);
  inOrder(root.right, vals);
  return vals;
}

function twoSum(nums, target){
  let begin = 0, end = nums.length - 1, sum;
  while(begin < end)
  {
    sum = nums[begin] + nums[end];
    if(target === sum) return true;
    sum > target ? end -= 1 : begin += 1;   
  }
  return false;
}

各种edit distance问题

72. Edit Distance

Levenshtein Distance 令函数edit(i, j)表示字符串s1的子串(0, i), s2的子串(0, j)的最小编辑距离,
则有: edit(i, j) = min(edit(i - 1, j), edit(i, j - 1), edit(i - 1, j - 1) + equal(s1[i - 1], s2[j - 1]));

var minDistance = function(word1, word2) {
  let curStatus = [], preStatus = [];
  for(let i = word2.length; i >= 0; i--) preStatus[i] = i;
  for(let row = 0, rowLen = word1.length; row < rowLen; row++)
  {
    curStatus[0] = row + 1;
    for(let col = 1, colLen = word2.length; col <= colLen; col++)
    {
      curStatus[col] = Math.min(
        curStatus[col - 1] + 1,
        preStatus[col] + 1,
        preStatus[col - 1] + (word1[row] === word2[col - 1] ? 0 : 1)
      );
    }
    [preStatus, curStatus] = [curStatus, preStatus];
  }
  return preStatus.pop();
};

583. Delete Operation for Two Strings

这里我们需要理解, 每次状态转移的时候, 对应于i, j值得变化, 对应的编辑操作是什么.

  1. 对s1进行添加字符操作, 对应的状态转移是s(i, j) + 1=> s(i, j + 1)
  2. 对s1进行删除字符操作, 对应的状态转移是s(i, j) + 1 => s(i + 1, j)
  3. 对s1的添加, 等效于对s2的删除, 反之亦然, 这里蕴含着对称性.
  4. (已被ban)对s1进行修改操作, 对应的状态转移是s(i, j) => s(i + 1, j + 1), 不过如果对应的word1[i] === word2[j], 实际上就不需要修改.
  5. 由于修改操作被ban, s(i, j) => s(i + 1, j + 1), 可以等效为同时对word1, word2做了删除字符操作.
    综上所述, 这道题目实际上我们只需要, 在上一道题目的基础上改动一个字符, 就可以AC了.
var minDistance = function(word1, word2) {
  let preStatus = [], curStatus = [];
  for(let i = 0, len = word2.length; i <= len; i++) preStatus[i] = i;
  for(let row = 0, rowLen = word1.length; row < rowLen; row++)
  {
    curStatus[0] = row + 1;
    for(let col = 1, colLen = word2.length; col <= colLen; col++)
    {
      curStatus[col] = Math.min(
        curStatus[col - 1] + 1,
        preStatus[col] + 1,
        preStatus[col - 1] + (word1[row] === word2[col - 1] ? 0 : 2)
      );
    }
    [preStatus, curStatus] = [curStatus, preStatus];
  }
  return preStatus.pop();
};

@todo LCS !!!!

712. Minimum ASCII Delete Sum for Two Strings

这道题相当不错, 很NICE, 现在我们在编辑操作中引入了对于不同字符的权重的考虑, 根据字符的不同现在字符串操作带来的开销会变得不一样. 这里我们先整理下, 各种操作对应的状态转移:

  1. 对s1进行删除字符操作, 对应的状态转移是s(i, j) + weight(word1[i]) => s(i + 1, j)
  2. 对s2进行删除字符操作, 对应的状态转移是s(i, j) + weight(word2[j])=> s(i, j + 1)
  3. 对s1的添加, 现在不等效于对s2的删除, 因为字符不同, 权重不同.
  4. 修改操作已被ban
  5. 由于修改操作被ban, 但是可以等效为同时对s1, s2做了删除字符操作,
    s(i, j) + weight(word1[i]) + weight(word2[j]) => s(i + 1, j + 1)
    综上所述, 这道题目实际上可以在72的基础上, 引入权重, 这里一定要注意,初始化和状态转移的过程中,
    对应的状态的值要做好对应的处理!
var minimumDeleteSum = function(word1, word2) {
  let preStatus = [0], curStatus = [];
  for(let i = 1; i <= word2.length; i++) 
  {
    preStatus[i] = preStatus[i - 1] + word2.charCodeAt(i - 1);
  }
  
  for(let row = 0, rowLen = word1.length; row < rowLen; row++)
  {
    curStatus[0] = preStatus[0] + word1.charCodeAt(row);
    for(let col = 1, colLen = word2.length; col <= colLen; col++)
    {
      curStatus[col] = Math.min(
        curStatus[col - 1] + word2.charCodeAt(col - 1),
        preStatus[col] + word1.charCodeAt(row),
        preStatus[col - 1] + (word1[row] === word2[col - 1] ? 0 :
          word1.charCodeAt(row) + word2.charCodeAt(col - 1))
      );
    }    
    [preStatus, curStatus] = [curStatus, preStatus];
  }
  return preStatus.pop();
};

718. Maximum Length of Repeated Subarray

最长公共子数组问题

var findLength = function(A, B) {
  const rowN = A.length, colN = B.length;    
  let preRound = [], round = [0], max = 0, tmp;
  for(let i = 0; i <= colN; i++) preRound[i] = 0;  
  for(let row = 1; row <= rowN; row++)
  {
    for(let col = 1; col <= colN; col++)
    {
      tmp = A[row - 1] === B[col - 1] ? 1 : 0;
      round[col] = tmp ? preRound[col - 1] + 1 : 0;
      max = Math.max(round[col], max); 
    }
    preRound = round;
    round = [0];
  }
  return max;
};

图论

模板

class VertexNode<T> {
  indegree = 0;
  private nbrSet = new Set<T>();

  addNbr(nbr: T) {
    this.nbrSet.add(nbr);
  }

  get nbrs() {
    return this.nbrSet.values();
  }

  get outgree() {
    return this.nbrSet.size;
  }
}

const buildGraph = <T,>(edges: ReadonlyArray<[T, T, number]>) => {
  type VNode = VertexNode<T>;
  const graph = new Map<T, VNode>();
  const weightMap = new Map<T, Map<T, number>>();
  const getNode = (v: T): VNode => {
    if(!graph.has(v)) {
      graph.set(v, new VertexNode());
      weightMap.set(v, new Map<T, number>());
    }
    return graph.get(v);
  }
  const getNbrs = (v: T) => getNode(v).nbrs;
  const getWeight = (src: T, dist: T) => weightMap.get(src).get(dist);
  edges.forEach(([src, dist, cost]) => {
    getNode(src).addNbr(dist);
    getNode(dist).indegree += 1;
    weightMap.get(src).set(dist, cost);
  });
  return {
    getNode,
    getNbrs,
    getWeight
  };
}

const bfsOnce = <T,>(
  vs: T[],
  getNbrs: (v: T) => Iterable<T>,
  callbacks: {
    on?: (v: T, nbrs: Iterable<T>) => void,
    pre?: (nbr: T, v: T) => boolean;
  } = {}
) => {
  const { on, pre = () => true } = callbacks;
  const N = vs.length;
  for(let i = 0; i < N; i++) {
    const v = vs[i];
    const nbrs = getNbrs(v);
    on && on(v, nbrs);
    for(const nbr of nbrs) {
      if(!pre(nbr, v)) {
        continue;
      }
      vs.push(nbr);
    }
  }
  vs.splice(0, N);
}

判断有向图是否有环

https://leetcode.com/problems/course-schedule

判断有向图上某点是否可到达

  1. Jump Game III

这个算是一个标准的模板题目了

bfs 算法

  1. 构建 nextMap 和 indegreeMap(用于记录每个 vertex 的 indegree)
  2. 找到所有 indegree 为零的 sourceVertex
  3. 移除所有的 sourceVertex 节点,更新 sourceVertex 指向的 vertex 对应的 indegree 减1s
  4. 经过步骤3,已经产生了新的 sourceVertex,这时候回到步骤2,直到找不到 sourceVertex 为止
  5. 如果 indegreeMap 为空,说明图上没有环。

dfs 算法

1. 构建 nextMap 和 statusMap �(0:未访问,1已访问,2在路径中)
2. 遍历每一个 vertex ,如果已访问则跳过,变更其状态为 IN_PATH
3. 深度优先遍历 vertex 的每一个 nextVertex,
TODO:

代码如下:

/**
 * @param {number} numCourses
 * @param {number[][]} prerequisites
 * @return {boolean}
 */
var canFinish = function(numVertexs, edges) {  
  const { nextMap, indegreeMap, statusMap } = buildGraph(numVertexs, edges);
  
  return !dfs(nextMap, statusMap);
  // return !bfs(nextMap, indegreeMap);
};

const UNVISIT = 0;
const VISIT = 1;
const IN_PATH = 2;

function dfs(nextMap, statusMap) {
  for(let vertex in statusMap) {
    if(statusMap[vertex] !== UNVISIT) continue;
    if(dfsVertex(vertex, nextMap, statusMap)) return true;
  }
  return false;
}

function dfsVertex(vertex, nextMap, statusMap) {
  if(statusMap[vertex] === IN_PATH) return true;
  if(statusMap[vertex] === VISIT) return false;
  statusMap[vertex] = IN_PATH;
  for(let next of nextMap[vertex]) {
    if(dfsVertex(next, nextMap, statusMap)) return true;
  }  
  statusMap[vertex] = VISIT;
  return false;
}

function bfs(nextMap, indegreeMap) {
  let [sourceVertexs, nextSourceVertexs] = [[], []];
  for(let vertex in indegreeMap) {
    if(indegreeMap[vertex] === 0) sourceVertexs.push(vertex);
  }  
  while(sourceVertexs.length !== 0) {    
    while(sourceVertexs.length > 0) {
      const sourceVertex = sourceVertexs.pop();
      for(let next of nextMap[sourceVertex]) {
        indegreeMap[next] -= 1;
        if(indegreeMap[next] === 0) nextSourceVertexs.push(next);
      }
      delete indegreeMap[sourceVertex]; 
    }
    [sourceVertexs, nextSourceVertexs] = [nextSourceVertexs, sourceVertexs];
  }
  return Object.keys(indegreeMap).length !== 0;
}

const buildGraph = (numVertexs, edges) => {
  const nextMap = {}, indegreeMap = {}, statusMap = {};
  for(let vertex = 0; vertex < numVertexs; vertex ++) {
    nextMap[vertex] = [];
    indegreeMap[vertex] = 0;
    statusMap[vertex] = UNVISIT;
  }
  for(let [start, end] of edges) {
    nextMap[start].push(end);
    indegreeMap[end] += 1;
  }
  return {
    nextMap,
    indegreeMap,
    statusMap
  }
}

1030. Matrix Cells in Distance Order

/**
 * @param {number} R
 * @param {number} C
 * @param {number} r0
 * @param {number} c0
 * @return {number[][]}
 */
const VERTEX_STATUS = {
  unvisit: 0,
  visited: 1
};

var allCellsDistOrder = function(maxX, maxY, startX, startY) {
  const matrix = createMatrix(maxX, maxY, VERTEX_STATUS.unvisit);
  const startV = { x: startX, y: startY };
  matrix[startV.x][startV.y] = VERTEX_STATUS.visited;
  const vs = [startV];

  bfs([startV], matrix, v => vs.push(v));
  
  const cells = vs.map(v => [v.x, v.y]);
  return cells;
};

/**
 * 初始化一个二维矩阵
 */
const createMatrix = (maxX, maxY, initVal) => {
  const matrix = [];
  for(let x = 0; x < maxX; x++) {
    const row = [...new Array(maxY)].map(val => initVal);
    matrix.push(row);
  }
  return matrix;
};

const bfs = (startVs, matrix, visitor) => {
  let [vs, nextVs] = [startVs, []], topOrder = 0, v;

  while(vs.length > 0) {
    topOrder += 1;
    while((v = vs.pop()) !== undefined) {
      const nbrs = getBrs(v, matrix);
      for(let nbr of nbrs) {
        const nbrVal = matrix[nbr.x][nbr.y];
        if(nbrVal !== VERTEX_STATUS.unvisit) continue;
        visitor(nbr, nbrVal, topOrder);
        nextVs.push(nbr);
        matrix[nbr.x][nbr.y] = VERTEX_STATUS.visited;
      }
    }
    [vs, nextVs] = [nextVs, vs];
  }
};

const isValidVertex = ({ x, y }, maxX, maxY) => {
  return 0 <= x && x < maxX && 0 <= y && y < maxY;
}

const getBrs = ({ x, y }, matrix) => {
  const [maxX, maxY] = [matrix.length, matrix[0].length];
  const vs = [
    { x: x - 1, y },
    { x, y: y + 1 },
    { x: x + 1, y },
    { x, y: y - 1 }
  ];
  return vs.filter(v => isValidVertex(v, maxX, maxY));
}

1162. As Far from Land as Possible

/**
 * @param {number[][]} grid
 * @return {number}
 */
var maxDistance = function(matrix) {
  if(!matrix) return -1;

  const startVs = [];
  const [maxX, maxY] = [matrix.length, matrix[0].length];
  for(let x = 0; x < maxX; x++) {
    for(let y = 0; y < maxY; y++) {
      const val = matrix[x][y];
      if(val !== VERTEX_STATUS.visited) continue;
      startVs.push({ x, y });
    }
  }
  
  let maxTopOrder = -1;
  bfs(startVs, matrix, (v, vVal, topOrder) => maxTopOrder = topOrder);
  return maxTopOrder;
};

const VERTEX_STATUS = {
  unvisit: 0,
  visited: 1,  
};

const bfs = (startVs, matrix, visitor) => {
  let [vs, nextVs] = [startVs, []], topOrder = 0, v;  
  while(vs.length > 0) {
    topOrder += 1;
    while((v = vs.pop()) !== undefined) {
      const nbrs = getNbrs(matrix, v);      
      for(let nbr of nbrs) {
        const nbrVal = matrix[nbr.x][nbr.y];
        if(nbrVal !== VERTEX_STATUS.unvisit) continue;
        visitor(nbr, nbrVal, topOrder);
        nextVs.push(nbr);
        matrix[nbr.x][nbr.y] = VERTEX_STATUS.visited;
      }
    }
    [vs, nextVs] = [nextVs, vs];
  }
};

const getNbrs = (matrix, { x, y }) => {
  const [maxX, maxY] = [matrix.length, matrix[0].length];
  const vs = [
    { x, y: y - 1 },
    { x: x + 1, y },
    { x, y: y + 1 },
    { x: x - 1, y },
  ];
  return vs.filter(v => isValidVertex(v, maxX, maxY));
  
};

const isValidVertex = ({ x, y }, maxX, maxY) => {
  return 0 <= x && x < maxX && 0 <= y && y < maxY;
}

Binary Search Problems

最为传统的二分查找是在一个单调的序列中找到对应的那个数字, 数轴上的3个位置: begin, middle, end.
当array[middle] > target时, 我们所需求的值在区间: [begin, middle - 1],
当array[middle] < target时, 我们所需求的值在区间: [middle + 1, end]
不断循环这个过程并更新begin, end直到begin > end.

69. Sqrt(x) 二分查找解法:

  1. 和经典的模型比起来, 唯一的区别在于当middle < sqrt(x) 的时候, 我们的搜索区间变成[middle, end],
    注意这里的左边界是关闭的, 这时边界的处理需要小心一点:
var mySqrt = function(x) {
  let begin = 0, end = x, middle, tmp;
  while(1 < end - begin)
  {
    middle = (begin + end) / 2 | 0;
    middle * middle > x ? end = middle - 1 : begin = middle;
  }
  return end * end <= x ? end : begin;
};
  1. 牛顿法开平方
/*
 * 牛顿法开平方
 * 令方程: y= x^2 - n, 
 * 当x > 0时过任意一点x做切线与x轴交点, x = (a^2 + n)/2a, y = 0
 */
var mySqrt = function(x) {
  let n = x;
  while(x * x > n) x = ((x * x + n) / (2 * x)) | 0;
  return x;
}

todo 看到讨论中的bit操作方法

367. Valid Perfect Square

同69题:

var isPerfectSquare = function(num) {
  let begin = 1, end = num, middle;
  while(begin <= end)
  {
    middle = (begin + end) / 2 | 0;
    middle * middle > num ? end = middle - 1 : begin = middle + 1;
  }
  return Math.pow(begin - 1, 2) === num;
};

781. Rabbits in Forest

具有相同报数的兔子,可以认为是一个组的, 直到相同报数的兔子溢出

var numRabbits = function(answers) {
  let sum = 0, record = [];
  for(let answer of answers)
  {
    if(!record[answer]) 
    {
      record[answer] = 0;
      sum += (answer + 1);
    }
    record[answer] += 1;
    if(record[answer] > answer) record[answer] = 0;    
  }
  return sum;
};

leetcode 周周乐

第 248 场周赛 2021-07-04

最近都在医院状态不好,前一天凌晨5点才入睡,第二天就没有参加早上 10:30 的比赛,后面进行了模拟赛。
完成情况:

总结

5800 有手就行
5801 排序后想清楚贪心策略
5802 快速幂类问题,卡死在数据取模的处理上了,缺失知识点:

  1. 1e7 + 7 的两个数字相乘的情况下会溢出。所以需要更高精度的运算来做,需要使用 BigInt 来完成相乘后取模的操作
  2. 题目中 n 的取值最大为 1e15 ,范围较大需要分成多个阶段分别取模后计算,这方面敏感度还是不够。
  3. 取模运算在乘法下的性质,比赛时候临时推导浪费了不少时间。需记住,(a * b) % p = (a % p) * (b % p) % p,和加法时候的性质是类似的。
    5803 直接没有思路

127. Word Ladder

这道题目卡了很久, 于是这里记录下来做题思路.

  1. 起手直接BFS
const Alphas = [];
for(let i = 'a'.charCodeAt(0), len = 'z'.charCodeAt(0), alpha; i <= len; i++)
{
  Alphas.push(String.fromCharCode(i));
}
var ladderLength = function(beginWord, endWord, wordList) {
  const record = { }, words = {};
  record[beginWord] = 1;
  for(let word of wordList) words[word] = true;
  return bfs(words, record, [beginWord.split('')], endWord, 1);
};

function bfs(words, record, statusList, target, deep){
  let len;
  while(true)
  {
    len = statusList.length;
    if(0 === len) return 0;

    while(len--)
    {
      getNewStatusList(words, record, statusList, statusList.shift(), target, deep);
      if(record[target]) return record[target];
    }
    deep += 1;
  }
}

function getNewStatusList(words, record, statusList, status, target, deep){
  let newStatus, word;
  for(let i = 0, len = status.length; i < len; i++)
  {
    // 这个判断可以使速度提升5倍左右, 但是会导致一些解无法搜索出来, 剪枝失败, OTL 跪+2
    // if(status[i] === target[i]) continue;
    for(let alpha of Alphas)
    {
      newStatus = status.slice();
      newStatus[i] = alpha;
      word = newStatus.join('');
      if(!words[word] || undefined !== record[word]) continue;
      record[word] = deep + 1;
      statusList.push(newStatus);
    }
  }
}

直接超时, OTL 跪+1,
好好分析题目: 令单词长度为n, 每一位可选字母数为k, 搜索深度为deep. 那么单词总的状态空间大小为: k^n.
当前策略是, 每次对一个单词按照每一位进行变换, 那么每一次状态变换搜索状态数为k * n,
那么算法时间复杂度可以估计为: k * n * deep

  1. 思考现实中有效的英语单词数量, 远小于可生成的单词数量。那么每一次状态转移,我们不是去生成,
    而是搜索现有单词(wordList)中有哪些单词与当前单词的距离为1, 是否能更快的得到答案?
    每一次状态转移需要搜索的状态数为: wordListLen * n,
    那么算法时间复杂度可以估计为: wordListLen * n * deep.
    而这里wordListLen > k, 更慢了 OTL 跪+3

  2. 马勒戈壁啊混蛋!!!!!

PS: 时间18/1/18, 这个笔记当时写了20多分钟忘了存, 点了其他页面数据全部清空, 啊~~~.

回溯类问题汇总

39. Combination Sum

已知一组正整数的集合 nums,nums 的长度为 len,
求满足:c0N0 + c1N1 + ... ckNk == target,k 为 0 ~ len - 1 的解的数量。
考虑当 N0 已知时,问题就转化为:求 nums[1..len - 1] 下,target - c0N0 的解的数量。
那么剩下就很简单了,利用 dfs 来解,不过运行速度确实比较糟糕。

/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum = function(
  nums, target, deep = 0, 
   solution = [], solutionSet = []
) {
  if(target === 0) {
    solutionSet.push(solution.slice());
    return solutionSet;
  };
  if(deep >= nums.length) return solutionSet;
  
  let solutionCount = 0, count = 0, rest;
  while((rest = target - count * nums[deep]) >= 0) {
    solutionCount += combinationSum(nums, rest, deep + 1, solution, solutionSet);
    solution.push(nums[deep]);
    count += 1;
  }
  for(let i = 0; i < count; i++) {
     solution.pop();
  }
  return solutionSet;
};

桶排序

  1. Divide Array in Sets of K Consecutive Numbers
    最开始没完全读懂题,这里有个很强的条件,不要被分割的各个子序列的顺序和原数组中数据一致。
    想起了整理一副扑克牌的日子。有了以下解法,不过效率很低,速度只排到 17%。
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {boolean}
 */
var isPossibleDivide = function(nums, k) {
  if(nums.length % k !== 0) return false;
  if(k === 1) return true;
  
  const sortedNums = nums.sort((x, y) => x > y ? 1 : -1);
  let record = {}, sequenceCount = 0;
  for(let num of sortedNums) {
    const slot = record[num - 1];
    
    (!record[num]) && (record[num] = []);

    if(slot && slot.length > 0) {
      let sequence = slot.pop();
      sequence += 1;
      if(sequence === k) {
        sequenceCount -= 1;
        continue;
      }
      record[num].push(sequence);
    } else {
      record[num].push(1);
      sequenceCount += 1;
    }
  }
  return sequenceCount === 0;
};

进一步考虑到不要求顺序,可以利用一个 hashMap 构成桶来计数

652. Find Duplicate Subtrees

这道题的关键是二叉树的序列化

var findDuplicateSubtrees = function(root) {
  const duplicateSubtrees = [ ], table = { };
  treeToString(root, table, duplicateSubtrees);
  return duplicateSubtrees;
};

// 序列化树为字符串
function treeToString(root, table, duplicateSubtrees){
  let s;
  if(!root) return '';

  s = root.val.toString();
  if(root.left || root.right)
  {
    s += '(' + treeToString(root.left, table, duplicateSubtrees) + ')';
    if(root.right) s += '(' + treeToString(root.right, table, duplicateSubtrees) + ')';
  }
  
  if(!table[s]) table[s] = 0;
  table[s] += 1;
  if(table[s] === 2) duplicateSubtrees.push(root);
  
  return s;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.