数学套路深

349 阅读17分钟

数学算法

涉及到数学的算法,基本都有固定的算法公式,如果知道就知道,不知道也不好想出来,比如求质数,进制转换等

这些题目首先你得了解这些数学知识,然后才能做,不然根本没有任何思绪。

9.1 最小公倍数

最小公倍数就是能够整除两个数的最小数

1 首先求两个数的最大公因数,然后再把两个数相乘再除以最大公因数就可以了,也就是辗转相除法。

求最大公因数代码如下:

const gcd = (a, b) => {
  return b === 0 ? a : gcd(b, a % b);
};

求最大公倍数代码如下:

const gcd = (a, b) => {
  return b === 0 ? a : gcd(b, a % b);
};

export default (a, b) => {
  return Math.floor((a * b) / gcd(a, b));
};

9.2 质数

质数又称素数,指的是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然 数。值得注意的是,每一个数都可以分解成质数的乘积。

204 计算质数的数量

题目描述

给定一个数字 n,求小于 n 的质数的个数。

例子1

Input: n = 10

output: 4

解释: 2, 3, 5, 7 是小于10的质数

例子2

Input: 0

output: 0

例子3

Input: 1

output: 0

思考

1 直接使用暴力解法就可以

2 使用一些技巧,这里涉及很多数学特性,不过也很简单,看下代码就可以了

参考实现1

参考实现2

实现1
/**
 * @param {number} n
 * @return {number}
 */

// Runtime: 124 ms, faster than 92.38% of JavaScript online submissions for Count Primes.
// Memory Usage: 52.1 MB, less than 45.59% of JavaScript online submissions for Count Primes.
export default (n) => {
  const notPrime = new Array(n).fill(0);
  let count = 0;
  for (let i = 2; i < n; i++) {
    if (notPrime[i] === 0) {
      count++;
      // 找到另外一个因子,如果存在另外一个因子,则不是质数
      for (let j = 2; i * j < n; j++) {
        notPrime[i * j] = 1;
      }
    }
  }

  return count;
};

实现2
/**
 * @param {number} n
 * @return {number}
 */

// Runtime: 120 ms, faster than 94.49% of JavaScript online submissions for Count Primes.
// Memory Usage: 52.1 MB, less than 45.59% of JavaScript online submissions for Count Primes.
export default (n) => {
  const excludes = new Array(n).fill(false);

  if (n < 3) return 0;

  // 最大的素数,因为偶数都不是质数
  let maxCount = Math.floor(n / 2);

  // 如果奇数相乘大于n,所以肯定不存在
  for (let i = 3; i * i < n; i += 2) {
    //说明已经排除了
    if (excludes[i]) {
      continue;
    }
    // 比如3,那么9,15,21肯定不是质数
    for (let j = i * i; j < n; j += i * 2) {
      if (!excludes[j]) {
        excludes[j] = true;
        maxCount--;
      }
    }
  }
  return maxCount;
};

504. 七进制数

题目描述

给定一个整数,将其转化为7进制,并以字符串形式输出。

例子1

Input: 100

output: "202"

例子2

Input: -7

output: 10

思考

1 固定的转换套路,很简单

参考实现1

实现1
/**
 * @param {number} num
 * @return {string}
 */

// Runtime: 76 ms, faster than 90.91% of JavaScript online submissions for Base 7.
// Memory Usage: 39 MB, less than 13.29% of JavaScript online submissions for Base 7.
export default (num) => {
  if (num == 0) return "0";
  const is_negative = num < 0;
  if (is_negative) {
    num = -num;
  }
  let res = "";
  while (num > 0) {
    const a = Math.floor(num / 7);
    const b = num % 7;
    res = b + res;
    num = a;
  }
  return is_negative ? "-" + res : res;
};

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

168. Excel表列名称

题目描述

给定一个正整数,返回它在 Excel 表中相对应的列名称。

例如:

    1 -> A
    2 -> B
    3 -> C
    ...
    26 -> Z
    27 -> AA
    28 -> AB 

例子1

Input: 1

output: "A"

例子2

Input: 28

output: "AB"

例子3

Input: 701

output: "ZY"

思考

1 这题和上面差不多,也是类似数制转换的问题,也就是转换成26进制,但是这里会涉及到0的处理,所以得想办法处理0的问题

这里有一种是使用数组处理0,因为如果发现是0的话,必须上高位借1。

另外需要主要的是js的相除会有可能是小数,所以得向下取整

参考实现1

2 然后还有一种使用递归的方法,该方法有一个奇妙的地方就是为了避免处理0,是首先把n减去1,然后再处理

参考实现2

3 这里是把递归改成不递归

参考实现3

实现1
/**
 * @param {number} n
 * @return {string}
 */

const map = {
  0: "0",
  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",
};
// Runtime: 76 ms, faster than 70.41% of JavaScript online submissions for Excel Sheet Column Title.
// Memory Usage: 38.6 MB, less than 23.60% of JavaScript online submissions for Excel Sheet Column Title.
export default (n) => {
  if (n <= 26) return map["" + n];
  let res = [];
  while (n > 26) {
    let a = Math.floor(n / 26);
    let b = n % 26;
    res.unshift(b);
    n = a;
  }
  res.unshift(n);
  for (let i = 1; i < res.length; i++) {
    if (res[i] === 0) {
      let j = i - 1;
      res[i] = 26;
      res[j]--;
      while (res[j] === 0 && j >= 1 && res[j - 1] > 0) {
        res[j] = 26;
        res[j - 1]--;
        j--;
      }
    }
  }
  let k = 0;
  while (res[k] === 0) {
    res.shift();
    k++;
  }
  console.log(res);
  const temp = res
    .map((item) => {
      return map["" + item];
    })
    .join("");

  return temp;
};

实现2
/**
 * @param {number} n
 * @return {string}
 */

const convertToTitle = (n) => {
  if (n === 0) return "";
  return convertToTitle(Math.floor(--n / 26)) + String.fromCharCode("A".charCodeAt() + (n % 26));
};
export default convertToTitle;

实现3
/**
 * @param {number} n
 * @return {string}
 */
// Runtime: 72 ms, faster than 88.06% of JavaScript online submissions for Excel Sheet Column Title.
// Memory Usage: 38.2 MB, less than 73.13% of JavaScript online submissions for Excel Sheet Column Title.
export default (n) => {
  if (n === 0) return "";
  let res = "";
  while (n > 0) {
    --n;
    const a = Math.floor(n / 26);
    const b = n % 26;
    res = String.fromCharCode("A".charCodeAt() + b) + res;
    n = a;
  }

  return res;
};

172. 阶乘后的零

题目描述

给定一个整数 n,返回 n! 结果尾数中零的数量。

例子1

Input: 3

output: 0

例子2

Input: 5

output: 1

解释:5!=120

思考

1 刚开始的时候想直接使用暴力解法,但是发现如果输入30之后,就会用科学计数法来表示,基本上就得不到正确的结果了

2 另外一种思路是可以发现10!= 362800,这时候我们要求出最后有几个0,很明显就是看362800可以除以10得到正整数。

此时已经发现先得到10的结果再求多少个0已经得不到正确的结果了,可以想一下10!还可以怎么表示?

10! = 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10

那么10!/ 10 就可以表示成1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 / 10

同时除以10可以看成除以2 * 5,同时10!也可以转换成 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 2 * 5,那么公式就变成了1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 2 * 5/ 2 * 5

此时我们基本上就可以发现其实就是求10!里边含有多少对2 * 5了

那么如果求呢?

这里可以发现10!里边肯定含有的2比5多,因为任何一个偶数都含有2,但是不一定含有5

所以我们只需要求5出现了多少次就可以了

那么怎么求5出现了多少次呢?

我们可以分成几步来求
第一步求含有1个5的数量,比如在10!中那就是5 和 10
第二步求含有2个5的数量,比如在30!中那就是25
依次类推

参考实现1

实现1
/**
 * @param {number} n
 * @return {number}
 */
// Runtime: 88 ms, faster than 68.48% of JavaScript online submissions for Factorial Trailing Zeroes.
// Memory Usage: 39.4 MB, less than 43.21% of JavaScript online submissions for Factorial Trailing Zeroes.
const trailingZeroes = (n) => {
  if (n <= 4) return 0;
  let res = 0;
  while (n > 0) {
    n = Math.floor(n / 5);
    res += n;
  }
  return res;
};
export default trailingZeroes;

时间复杂度O(lgn)空间复杂度O(1)

415. 字符串相加

题目描述

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。

提示:

1 num1 和num2 的长度都小于 5100
2 num1 和num2 都只包含数字 0-9
3 num1 和num2 都不包含任何前导零
4 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式

思考

1 题目比较简单,直接使用charCodeAt获取字符的值就可以

参考实现1

实现1
/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
// Runtime: 76 ms, faster than 98.86% of JavaScript online submissions for Add Strings.
// Memory Usage: 40.9 MB, less than 47.14% of JavaScript online submissions for Add Strings.
export default (num1, num2) => {
  const len1 = num1.length;
  const len2 = num2.length;
  let len = len1;
  const maxLen = Math.max(len1, len2);
  if (len2 < len1) {
    const temp = num2;
    num2 = num1;
    num1 = temp;
    len = len2;
  }
  let flag = 0;
  const res = [];
  for (let i = len - 1; i >= 0; i--) {
    const sum = num1.charCodeAt(i) + num2.charCodeAt(maxLen - len + i) - 96 + flag;
    if (sum >= 10) {
      res.unshift(sum % 10);
      flag = 1;
    } else {
      res.unshift(sum);
      flag = 0;
    }
  }
  if (maxLen > len) {
    for (let i = maxLen - 1 - len; i >= 0; i--) {
      const sum = num2.charCodeAt(i) - 48 + flag;
      if (sum >= 10) {
        res.unshift(sum % 10);
        flag = 1;
      } else {
        res.unshift(sum);
        flag = 0;
      }
    }
  }

  if (flag === 1) {
    res.unshift(1);
  }
  return res.join("");
};

时间复杂度O(Math.max(len1,len2)) 空间复杂度O(len2)

67. 二进制求和

题目描述

给你两个二进制字符串,返回它们的和(用二进制表示)。

输入为 非空 字符串且只包含数字 1 和 0。

提示:

1 每个字符串仅由字符 '0''1' 组成。
2 1 <= a.length, b.length <= 10^4
3 字符串如果不是 "0" ,就都不含前导零。

例子1
input:a = "11", b = "1"
output:"100"

例子2
input:a = "1010", b = "1011"
output:"10101"

思考

1 和415题目一样的逻辑

参考实现1

实现1
/**
 * @param {string} a
 * @param {string} b
 * @return {string}
 */
// Runtime: 92 ms, faster than 53.83% of JavaScript online submissions for Add Binary.
// Memory Usage: 40.4 MB, less than 66.69% of JavaScript online submissions for Add Binary.
export default (a, b) => {
  const len1 = a.length;
  const len2 = b.length;
  let len = len1;
  const maxLen = Math.max(len1, len2);
  if (len2 < len1) {
    const temp = b;
    b = a;
    a = temp;
    len = len2;
  }
  let flag = 0;
  const res = [];
  for (let i = len - 1; i >= 0; i--) {
    const sum = a.charCodeAt(i) + b.charCodeAt(maxLen - len + i) - 96 + flag;
    if (sum === 2) {
      res.unshift(0);
      flag = 1;
    } else if (sum === 3) {
      res.unshift(1);
      flag = 1;
    } else {
      res.unshift(sum);
      flag = 0;
    }
  }
  if (maxLen > len) {
    for (let i = maxLen - 1 - len; i >= 0; i--) {
      const sum = b.charCodeAt(i) - 48 + flag;
      if (sum === 2) {
        res.unshift(0);
        flag = 1;
      } else if (sum === 3) {
        res.unshift(1);
        flag = 1;
      } else {
        res.unshift(sum);
        flag = 0;
      }
    }
  }

  if (flag === 1) {
    res.unshift(1);
  }
  return res.join("");
};


时间复杂度O(Math.max(len1,len2)) 空间复杂度O(len2)

9.5 随机与取样

384. 打乱数组和恢复

题目描述

给定一个数组,要求实现两个指令函数。第一个函数“shuffle”可以随机打乱这个数组,第 二个函数“reset”可以恢复原来的顺序。

例子1
input:nums = [1,2,3], actions: ["shuffle","shuffle","reset"]
output:[[2,1,3],[3,2,1],[1,2,3]]

思考

1 题目很简单,就是一个简单的洗牌算法,不过面试中经常会被问到。

参考实现1

实现1
/**
 * @param {number[]} nums
 */

//  Runtime: 236 ms, faster than 67.68% of JavaScript online submissions for Shuffle an Array.
//  Memory Usage: 52.4 MB, less than 55.56% of JavaScript online submissions for Shuffle an Array.
var Solution = function (nums) {
  this.nums = nums || [];
};

/**
 * Resets the array to its original configuration and return it.
 * @return {number[]}
 */
Solution.prototype.reset = function () {
  return this.nums;
};

/**
 * Returns a random shuffling of the array.
 * @return {number[]}
 */
Solution.prototype.shuffle = function () {
  const tempNums = [...this.nums];
  const len = tempNums.length;
  for (let i = 0; i < tempNums.length; i++) {
    const index = Math.floor(Math.random() * (len - i) + i);
    // console.log(index)
    const temp = tempNums[index];
    tempNums[index] = tempNums[i];
    tempNums[i] = temp;
  }
  return tempNums;
};

/**
 * Your Solution object will be instantiated and called as such:
 * var obj = new Solution(nums)
 * var param_1 = obj.reset()
 * var param_2 = obj.shuffle()
 */


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

528. 按权重随机选择

题目描述

给定一个正整数数组 w ,其中 w[i] 代表下标 i 的权重(下标从 0 开始),请写一个函数 pickIndex ,它可以随机地获取下标 i,选取下标 i 的概率与 w[i] 成正比。

例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即,75%)。

也就是说,选取下标 i 的概率为 w[i] / sum(w) 。

例子1
input:["Solution","pickIndex"]
[[[1]],[]]
output:[null,0]
解释:Solution solution = new Solution([1]); solution.pickIndex(); // 返回 0,因为数组中只有一个元素,所以唯一的选择是返回下标 0。

例子2
input:["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"]
[[[1,3]],[],[],[],[],[]]
output:[null,1,1,1,1,0]
解释:Solution solution = new Solution([1, 3]);
solution.pickIndex(); // 返回 1,返回下标 1,返回该下标概率为 3/4 。
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 0,返回下标 0,返回该下标概率为 1/4 。

由于这是一个随机问题,允许多个答案,因此下列输出都可以被认为是正确的:
[null,1,1,1,1,0]
[null,1,1,1,1,1]
[null,1,1,1,0,0]
[null,1,1,1,0,1]
[null,1,0,1,0,0]
......
诸若此类。

思考

1 刚开始想按照次数,比如当输入[1,3]的时候,如果选择4次,一次输出1,一次输出3,但是发现这样不行,这样就不是随机的了

后来看了下题解,其实道理很简单,就是使用前缀和,搞成一个类似于区间的

39dc9688b0a758ec21c569ea75978e3a.png

然后使用随机数,看落在那个区间,刚好符合概率

参考实现1

还可以使用二分法查找,参考实现2

实现1
/**
 * @param {number[]} w
 */
var Solution = function (w) {
  const len = w.length;
  this.chances = new Array(len).fill(0);
  const sum = w.reduce((a, b) => a + b);
  for (let i = 0; i < w.length; i++) {
    w[i] += i === 0 ? 0 : w[i - 1];
    this.chances[i] = w[i] / sum;
  }
};

/**
 * @return {number}
 */
Solution.prototype.pickIndex = function () {
  if (this.chances.length === 1) {
    return 0;
  }
  const random = Math.random().toFixed(2);
  // console.log(this.chances);
  for (let i = 0; i < this.chances.length; i++) {
    if (random <= this.chances[i]) {
      return i;
    }
  }
  return this.chances.length - 1;
};

/**
 * Your Solution object will be instantiated and called as such:
 * var obj = new Solution(w)
 * var param_1 = obj.pickIndex()
 */
export default Solution;

实现2

/**
 * @param {number[]} w
 */
var Solution = function (w) {
  const len = w.length;
  this.chances = new Array(len).fill(0);
  const sum = w.reduce((a, b) => a + b);
  for (let i = 0; i < w.length; i++) {
    w[i] += i === 0 ? 0 : w[i - 1];
    this.chances[i] = w[i] / sum;
  }
};

/**
 * @return {number}
 */
Solution.prototype.pickIndex = function () {
  if (this.chances.length === 1) {
    return 0;
  }
  const random = Math.random().toFixed(2);
  let low = 0;
  let high = this.chances.length - 1;
  while (low <= high) {
    const mid = Math.floor(low + (high - low) / 2);
    if (this.chances[mid] >= random) {
      high = mid - 1;
    } else {
      low = mid + 1;
    }
  }
  return low;
};

/**
 * Your Solution object will be instantiated and called as such:
 * var obj = new Solution(w)
 * var param_1 = obj.pickIndex()
 */
export default Solution;

382. 链表随机节点

题目描述

给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。

进阶: 如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?

例子1

// 初始化一个单链表 [1,2,3].
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
Solution solution = new Solution(head);

// getRandom()方法应随机返回1,2,3中的一个,保证每个元素被返回的概率相等。

思考

1 基本的可以使用数组存储整个链表,然后直接随机就可以了

2 后来看了下这里是典型的水库算法

水库算法比较简单

当遇到第一个节点的时候,我们选择第一个节点

当遇到第二个节点的时候,这个时候要么替换第一个节点,要么不替换第一个节点,如果替换第一个节点的时候,也是1/2

当遇到第三个节点的时候,如果替换那么概率是1/3 * 1 = 1/3,如果不替换,则保持原来的。

以此类推,假如我们遇到到第i个节点的时候,此时如果替换的话,概率计算可以分为两步的概率,第一步是先在前i个节点中选择1个,概率是1/i,第二步是选择替换,也就是1/1,此时概率就是1/i.

所以我们可以在遇到第i个节点的时候,如果概率小于等于1/i,则替换就可以了。

数组存储链表参考实现1
水库算法参考实现2

实现1
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node.
 * @param {ListNode} head
 */
// Runtime: 308 ms, faster than 5.54% of JavaScript online submissions for Linked List Random Node.
// Memory Usage: 50 MB, less than 5.21% of JavaScript online submissions for Linked List Random Node.
var Solution = function (head) {
  let p = head;
  this.nums = [];
  while (p.next) {
    this.nums.push(p.next.val);
    p = p.next;
  }
};

/**
 * Returns a random node's value.
 * @return {number}
 */
Solution.prototype.getRandom = function () {
  const len = this.nums.length;
  const i = Math.floor(Math.random() * (len + 1));
  return this.nums[i];
};

/**
 * Your Solution object will be instantiated and called as such:
 * var obj = new Solution(head)
 * var param_1 = obj.getRandom()
 */


实现2
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node.
 * @param {ListNode} head
 */
// Runtime: 308 ms, faster than 5.54% of JavaScript online submissions for Linked List Random Node.
// Memory Usage: 50 MB, less than 5.21% of JavaScript online submissions for Linked List Random Node.
var Solution = function (head) {
  // let p = head;
  // this.nums = [];
  // while (p.next) {
  //   this.nums.push(p.next.val);
  //   p = p.next;
  // }
  this.head = head;
};

/**
 * Returns a random node's value.
 * @return {number}
 */
//  Runtime: 120 ms, faster than 65.35% of JavaScript online submissions for Linked List Random Node.
//  Memory Usage: 46.3 MB, less than 19.25% of JavaScript online submissions for Linked List Random Node.
Solution.prototype.getRandom = function () {
  // const len = this.nums.length;
  // const i = Math.floor(Math.random() * (len + 1));
  // return this.nums[i];
  let p = this.head;
  let res = p.val;
  for (let i = 1; p != null; i++) {
    p = p.next;
    if (Math.random() <= 1 / (i + 1)) {
      res = p != null ? p.val : res;
    }
  }
  return res;
};

/**
 * Your Solution object will be instantiated and called as such:
 * var obj = new Solution(head)
 * var param_1 = obj.getRandom()
 */

238. 除自身以外数组的乘积

题目描述

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

进阶: 你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)

例子1
input: [1,2,3,4]
output: [24,12,8,6]

思考

1 题目比较简单,首先想到了使用一个前置数组保存i之前的结果,一个后置数组保持i之后的数组的乘积,然后相乘就可以了

2 如果想使用常数时间,可以看看实现1有哪些需要节省的,可以发现可以把前置数组用结果数组表示

参考实现1
参考实现2

实现1
/**
 * @param {number[]} nums
 * @return {number[]}
 */
// Runtime: 692 ms, faster than 13.05% of JavaScript online submissions for Product of Array Except Self.
// Memory Usage: 51.3 MB, less than 8.63% of JavaScript online submissions for Product of Array Except Self.
export default (nums) => {
  const preNums = [1];
  const afterNums = [1];
  for (let i = 1; i < nums.length; i++) {
    preNums[i] = preNums[i - 1] * nums[i - 1];
  }
  for (let j = nums.length - 2, k = 1; j >= 0; j--, k++) {
    const temp = afterNums[afterNums.length - k] * nums[j + 1];
    afterNums.unshift(temp);
  }
  // console.log(preNums, afterNums);
  const res = [];
  for (let i = 0; i < nums.length; i++) {
    res[i] = preNums[i] * afterNums[i];
  }
  return res;
};

实现2
/**
 * @param {number[]} nums
 * @return {number[]}
 */
// Runtime: 112 ms, faster than 92.27% of JavaScript online submissions for Product of Array Except Self.
// Memory Usage: 49.7 MB, less than 54.84% of JavaScript online submissions for Product of Array Except Self.
export default (nums) => {
  const len = nums.length;
  const res = [];
  res[0] = 1;
  for (let i = 1; i < len; i++) {
    res[i] = res[i - 1] * nums[i - 1];
  }
  let right = 1;
  for (let i = len - 1; i >= 0; i--) {
    res[i] *= right;
    right *= nums[i];
  }
  return res;
};

169. 多数元素

题目描述

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。


进阶: 尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题

例子1
input: [3,2,3]
output: 3

例子2
input: [2,2,1,1,1,2,2]
output: 2

思考

1 可以先排序,然后查找超过 ⌊ n/2 ⌋ 的元素

2 进阶是是使用Boyer-Moore Majority Vote算法,这个算法原理也很简单,就是发现数组中两个不同的元素直接删除,最后剩下的就是想要的结果。因为肯定存在多数元素,而多数元素的数量肯定大于 ⌊ n/2 ⌋ 的元素

参考实现1
参考实现2

实现1
/**
 * @param {number[]} nums
 * @return {number}
 */
// Runtime: 92 ms, faster than 31.11% of JavaScript online submissions for Majority Element.
// Memory Usage: 43.1 MB, less than 7.90% of JavaScript online submissions for Majority Element.
export default (nums) => {
  if (nums.length === 1) return nums[0];
  nums.sort((a, b) => a - b);
  const max = Math.floor(nums.length / 2);
  for (let i = 0; i < nums.length; i++) {
    const tempMax = i + max;
    if (nums[tempMax] === nums[i]) {
      return nums[i];
    }
    if (tempMax > nums.length) {
      break;
    }
  }
};


实现2
/**
 * @param {number[]} nums
 * @return {number}
 */
// Runtime: 68 ms, faster than 99.88% of JavaScript online submissions for Majority Element.
// Memory Usage: 40.8 MB, less than 74.90% of JavaScript online submissions for Majority Element.
export default (nums) => {
  if (nums.length === 1) return nums[0];
  let major = nums[0];
  let count = 1;
  for (let i = 1; i < nums.length; i++) {
    if (count === 0) {
      count++;
      major = nums[i];
    } else if (major === nums[i]) {
      count++;
    } else {
      count--;
    }
  }
  return major;
};

470. 用 Rand7() 实现 Rand10()

题目描述

已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10 生成 1 到 10 范围内的均匀随机整数。

不要使用系统的 Math.random() 方法。

例子1
input: 1
output: [7]

例子2
input: 2
output: [8,4]

例子3
input: 3
output: [8,1,10]

提示

1 rand7 已定义。
2 传入参数: n 表示 rand10 的调用次数。

进阶
1 rand7()调用次数的 期望值 是多少 ?
2 你能否尽量少调用 rand7() ?

思考

1 这里使用随机数生成随机数,比较关键的是几点

1.1 首先是每个数出现的概率必须是随机的,比如rand10这里从1到10的概率必须都是1/10.
1.2

已知 rand_N() 可以等概率的生成[1, N]范围的随机数
那么:
(rand_X() - 1) × Y + rand_Y() ==> 可以等概率的生成[1, X * Y]范围的随机数
即实现了 rand_XY()

1.3 如果已经rand49,那么如何求rand10呢?

因为rand10是要求选择从1到10的概率都是1/10,现在已经知道rand49,也就是说使用rand49选择从1到49的概率都是1/49,那么rand10是什么呢?

可以想一下,假设选择2的概率是什么?

假如通过rand49函数执行一次就得到2,那么概率就是1/49

如果需要执行rand49函数两次得到2呢,那么概率就是第一次没有取到2,那么概率就是39/49,第二次取到2,那么概率是1/49,所以这次概率是39/49 * 1 / 49

同理第三次得到2,那么概率是 39/49 * 39/49 * 1 / 49

那么执行n次,总概率就是1/49 * ((1 * (1 - (39 / 49)^n)) / 1- (39/49) = 1 / 10

同理可以得到选择1到10的概率都是 1/10

到这里基本上就可以得到实现1

1.4 从1.3可以看到从rand49到rand10,所有大于10的都被舍弃了,那是不是有其他方法可以更大效率的利用这些从11到49的数呢?

这里有个规律是 要实现rand10(),就需要先实现rand_N(),并且保证N大于10且是10的倍数。这样再通过rand_N() % 10 + 1 就可以得到[1,10]范围的随机数了。

参考实现1

使用1.4 节省时间,可以参考实现2

参考: leetcode-cn.com/problems/im…

实现1
/**
 * The rand7() API is already defined for you.
 * var rand7 = function() {}
 * @return {number} a random integer in the range 1 to 7
 */
// Runtime: 124 ms, faster than 29.03% of JavaScript online submissions for Implement Rand10() Using Rand7().
// Memory Usage: 49.4 MB, less than 6.45% of JavaScript online submissions for Implement Rand10() Using Rand7().
const rand10 = () => {
  let a = rand7();
  let b = rand7();
  // 得到rand49
  let num = (a - 1) * 7 + b;

  if (num <= 10) return num;

  return rand10();
};
export default rand10;

时间复杂度O(1)

实现2
/**
 * The rand7() API is already defined for you.
 * var rand7 = function() {}
 * @return {number} a random integer in the range 1 to 7
 */
//  Runtime: 112 ms, faster than 96.77% of JavaScript online submissions for Implement Rand10() Using Rand7().
//  Memory Usage: 47.4 MB, less than 48.39% of JavaScript online submissions for Implement Rand10() Using Rand7().
const rand10 = () => {
  let a = rand7();
  let b = rand7();
  // 得到rand49
  let num = (a - 1) * 7 + b;

  if (num <= 40) return (num % 10) + 1;
  a = num - 40; // rand 9
  b = rand7();
  num = (a - 1) * 7 + b; // rand 63
  if (num <= 60) return (num % 10) + 1;

  a = num - 60; // rand 3
  b = rand7();
  num = (a - 1) * 7 + b; // rand 21
  if (num <= 20) return (num % 10) + 1;
  return rand10();
};
export default rand10;

202. 快乐数

题目描述

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。

如果 n 是快乐数就返回 True ;不是,则返回 False 。

例子1
input: 19
output: true
解释:

12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

例子2
input: 2
output: false

思考

1 使用一个Set存储已经找到了,如果发现循环了,返回fasle

实现1
/**
 * @param {number} n
 * @return {boolean}
 */
// Runtime: 96 ms, faster than 35.42% of JavaScript online submissions for Happy Number.
// Memory Usage: 39.9 MB, less than 71.78% of JavaScript online submissions for Happy Number.
export default (n) => {
  const inLoop = new Set();
  let squareSum;
  let remain;
  while (!inLoop.has(n)) {
    inLoop.add(n);
    squareSum = 0;
    while (n > 0) {
      remain = n % 10;
      squareSum += remain * remain;
      n = Math.floor(n / 10);
    }
    if (squareSum === 1) {
      return true;
    } else {
      n = squareSum;
    }
  }
  return false;
};