剑指offer - 时间空间效率的平衡

1.丑数

问题描述:

把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含质因子 7。 习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。

解题思路:

首先从丑数的定义我们知道,一个丑数的因子只有 2,3,5,那么丑数 p = 2 ^ x * 3 ^ y * 5 ^ z

换句话说一个丑数一定由另一个丑数乘以 2 或者乘以 3 或者乘以 5 得到

那么我们从 1 开始乘以 2,3,5,就得到 2,3,5 三个丑数,在从这三个丑数出发乘以 2,3,5 就得到 4,6,10, 6,9,15, 10,15,25 九个丑数

因为这 9 个数可能会有重复的,所以从这 9 个丑数中拿出最小的数(要比丑数数组中的数大)加入丑数数组

  • (1) 丑数数组 [1]
  • 乘以 2:2
  • 乘以 3:3
  • 乘以 5:5
  • (2) 丑数数组 [1,2]
  • 乘以 2:2 4
  • 乘以 3:3 6
  • 乘以 5:5 10
  • (3) 丑数数组 [1,2,3]
  • 乘以 2:2 4 6
  • 乘以 3:3 6 9
  • 乘以 5:5 10 15
  • (4) 丑数数组 [1,2,3,4]
  • 乘以 2:2 4 6 8
  • 乘以 3:3 6 9 12
  • 乘以 5:5 10 15 20
function GetUglyNumber_Solution(index) {
  // write code here
  if (index === 0) return 0;
  var res = [1];
  while (res.length < index) {
    var n1 = res.map((x) => x * 2).find((x) => x > res[res.length - 1]);
    var n2 = res.map((x) => x * 3).find((x) => x > res[res.length - 1]);
    var n3 = res.map((x) => x * 5).find((x) => x > res[res.length - 1]);
    res.push(Math.min(n1, n2, n3));
  }
  return res[index - 1];
}

2.第一次只出现一次的字符

问题描述:

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写)

解题思路:

遍历字符串的每一个字符,判断 indexOf 和 lastIndexOf 是不是相同

function FirstNotRepeatingChar(str) {
  // write code here
  for (let i = 0; i < str.length; i++) {
    if (str.indexOf(str[i]) === str.lastIndexOf(str[i])) {
      return i;
    }
  }
  return -1;
}

3.数组中的逆序对

问题描述:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数 P。并将 P 对 1000000007 取模的结果输出。 即输出 P%1000000007

输入描述:

题目保证输入的数组中没有的相同的数字

数据范围:

  • 对于%50 的数据,size<=10^4

  • 对于%75 的数据,size<=10^5

  • 对于%100 的数据,size<=2*10^5

输入:1,2,3,4,5,6,7,0

输出:7

归并排序

function InversePairs(data) {
  if (!data || data.length < 2) return 0;
  let copy = data.slice(),
    count = 0;
  count = mergeSort(data, copy, 0, data.length - 1);
  return count % 1000000007;
}

function mergeSort(data, copy, start, end) {
  if (end === start) return 0;
  let mid = (end - start) >> 1,
    left = mergeSort(copy, data, start, start + mid),
    right = mergeSort(copy, data, start + mid + 1, end),
    count = 0,
    p = start + mid, //前一个数组的最后一个下标
    q = end, //后一个数组的下标
    copyIndex = end; //辅助数组下标,从最后一个算起
  while (p >= start && q >= start + mid + 1) {
    if (data[p] > data[q]) {
      count += q - start - mid;
      copy[copyIndex--] = data[p--];
    } else {
      copy[copyIndex--] = data[q--];
    }
  }
  while (p >= start) {
    copy[copyIndex--] = data[p--];
  }
  while (q >= start + mid + 1) {
    copy[copyIndex--] = data[q--];
  }
  return left + right + count;
}

4.两个链表的第一个公共结点

问题描述:

输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

解题思路:
首先我们要知道什么是公共结点,两个链表从某一节点开始,他们的 next 都指向同一个结点。但由于是单向链表的结点,每个结点只有一个 next,因此从第一个公共结点开始,之后他们的所有结点都是重合的,不可能再出现分叉。

双指针法。创建两个指针 p1 和 p2,分别指向两个链表的头结点,然后依次往后遍历。如果某个指针到达末尾,则将该指针指向另一个链表的头结点;如果两个指针所指的结点相同,则循环结束,返回当前指针指向的结点。比如两个链表分别为:1->3->4->5->6 和 2->7->8->9->5->6。短链表的指针 p1 会先到达尾部,然后重新指向长链表头部,当长链表的指针 p2 到达尾部时,重新指向短链表头部,此时 p1 在长链表中已经多走了 k 步(k 为两个链表的长度差值),p1 和 p2 位于同一起跑线,往后遍历找到相同结点即可。其实该方法主要就是用链表循环的方式替代了长链表指针先走 k 步这一步骤。

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/

function FindFirstCommonNode(pHead1, pHead2) {
  // write code here
  var p1 = pHead1;
  var p2 = pHead2;
  while (p1 !== p2) {
    p1 = p1 == null ? pHead2 : p1.next;
    p2 = p2 == null ? pHead1 : p2.next;
  }
  return p1;
}
posted @ 2020-04-17 21:19  木子呆头  阅读(120)  评论(0编辑  收藏  举报