Back to all solutions

#3416 - Subsequences with a Unique Middle Mode II

Problem Description

Given an integer array nums, find the number of subsequences of size 5 of nums with a unique middle mode.

Since the answer may be very large, return it modulo 109 + 7.

A mode of a sequence of numbers is defined as the element that appears the maximum number of times in the sequence.

A sequence of numbers contains a unique mode if it has only one mode.

A sequence of numbers seq of size 5 contains a unique middle mode if the middle element (seq[2]) is a unique mode.

Solution

/**
 * @param {number[]} nums
 * @return {number}
 */
var subsequencesWithMiddleMode = function(nums) {
  const MOD = 1e9 + 7;
  const HALF = (MOD + 1) / 2;
  const [compressedNums, uniqueCount] = compress(nums);
  const totalCounts = new Array(uniqueCount).fill(0);
  compressedNums.forEach(id => totalCounts[id]++);

  const result = countValid(compressedNums, totalCounts, true);
  compressedNums.reverse();

  return add(result, countValid(compressedNums, totalCounts, false));

  function mul(x, y) {
    return Number((BigInt(x) * BigInt(y)) % BigInt(MOD));
  }

  function add(x, y) {
    return (x + y) % MOD;
  }

  function sub(x, y) {
    return (x - y + MOD) % MOD;
  }

  function choose2(n) {
    return mul(mul(n, n - 1), HALF);
  }

  function compress(arr) {
    const valueMap = new Map();
    let nextId = 0;
    arr.forEach(val => !valueMap.has(val) && valueMap.set(val, nextId++));
    return [arr.map(val => valueMap.get(val)), nextId];
  }

  function countValid(sequence, counts, includeEqual) {
    const length = sequence.length;
    const seenCounts = new Array(uniqueCount).fill(0);
    let result = 0;
    let squareSum = 0;
    let weightedSum = 0;
    let weightedSquareSum = 0;

    counts.forEach(count => squareSum = add(squareSum, mul(count, count)));

    for (let pos = 0; pos < length; pos++) {
      const valueId = sequence[pos];
      let remaining = counts[valueId] - seenCounts[valueId];
      const newSquareSum = sub(squareSum, mul(remaining, remaining));
      const newWeightedSum = sub(weightedSum, mul(remaining, seenCounts[valueId]));
      const newWeightedSquareSum = sub(
        weightedSquareSum,
        mul(mul(remaining, remaining), seenCounts[valueId])
      );
      const suffixSize = length - pos - remaining;
      const prefixSize = pos - seenCounts[valueId];

      let contribution = mul(sub(mul(suffixSize, suffixSize), newSquareSum), prefixSize);
      contribution = sub(contribution, mul(mul(2, suffixSize), newWeightedSum));
      contribution = add(contribution, mul(2, newWeightedSquareSum));
      contribution = mul(contribution, mul(seenCounts[valueId], HALF));
      result = add(result, contribution);
      result = add(result, mul(choose2(seenCounts[valueId]), choose2(suffixSize)));

      remaining--;
      result = add(result, mul(choose2(seenCounts[valueId]), mul(remaining, suffixSize)));

      if (includeEqual) {
        result = add(result, mul(mul(seenCounts[valueId], prefixSize), mul(remaining, suffixSize)));
        result = add(result, mul(choose2(seenCounts[valueId]), choose2(remaining)));
      }

      seenCounts[valueId]++;
      squareSum = add(newSquareSum, mul(remaining, remaining));
      weightedSum = add(newWeightedSum, mul(remaining, seenCounts[valueId]));
      weightedSquareSum = add(
        newWeightedSquareSum,
        mul(mul(remaining, remaining), seenCounts[valueId])
      );
    }

    return result;
  }
};