剑指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;
}