剑指 Offer 03. 数组中重复的数字
题目描述
- 找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。- 示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
- 来源:力扣(LeetCode)
链接:数组中重复的数字
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:
这题简单,考的不是数组去重的简单版么,一下子就A掉了;事实上这道题针对不同要求有不同的解法;
目前我想到的两种:1. 先排序再遍历对比;2. 哈希 3. 也算哈希吧,不过用的是辅助数组映射,js中的数组的栈操作比对操作快一点;
Solutions
- 排序+遍历
/**
1. @param {number[]} nums
2. @return {number}
*/
var findRepeatNumber = function(nums) {
// 原地排序+指针 88ms 44.1mb
nums.sort((a,b) => a-b);
for(let i = 0, len = nums.length-1; i < len; i++) {
if(nums[i] === nums[i+1]) {
return nums[i]
}
}
};
- 哈希
/**
1. @param {number[]} nums
2. @return {number}
*/
var findRepeatNumber = function(nums) {
// 哈希:652ms 60.2mb
let map = new Map();
let res = [];
nums.forEach(item => {
map.has(item) ? map.set(item, map.get(item) + 1) : map.set(item, 1);
})
map.forEach((value, key) => {
console.log(value);
if(value > 1) {
res.push(key);
}
})
return res[0];
};
- 辅助数组映射
这里能用辅助数组映射主要是因为这里题目有说明是N个数,都在0-N-1范围内
/**
* @param {number[]} nums
* @return {number}
*/
var findRepeatNumber = function(nums) {
// 辅助数组映射
let len = nums.length;
let help = new Array(len).fill(0);
nums.forEach((item, index) => {
help[item]++;
})
for(let i = 0; i < len; i++) {
if(help[i] > 1) return i;
}
};
Results
经过对比,使用数组映射确实比用Map做哈希快,上图最慢的就是用Map哈希的。。。最快的是排序后遍历的
剑指offer 04: 二维数组中的查找
题目描述
- 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
- 示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
来源:力扣(LeetCode)
链接:二维数组中的查找
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:
- 暴力法:双重循环遍历二维数组,这样的话题设给的行列已经排序的条件就没用了,也不能当作一道medium的题目,复杂度O(M*N)
- 从右上角或者左下角开始遍历,双指针,时间复杂度 O(M+N)
Solutions
/**
* @param {number[][]} matrix
* @param {number} target
* @return {boolean}
*/
// 双重循环
var findNumberIn2DArray = function(matrix, target) {
if(!matrix.length) return false;
let lenOne = matrix.length, lenTwo = matrix[0].length;
for(let i = 0; i < lenOne; i++) {
for(let j = 0; j < lenTwo; j++) {
if(matrix[i][j] === target) return true;
}
}
return false;
};
/**
* @param {number[][]} matrix
* @param {number} target
* @return {boolean}
*/
// 双指针
var findNumberIn2DArray = function(matrix, target) {
if(!matrix || !matrix.length) return false;
let m = matrix.length, n = matrix[0].length;
let row = 0, col = n - 1;
while(row < m && col >= 0) {
if(matrix[row][col] > target) {
col--;
} else if(matrix[row][col] < target) {
row++;
} else {
return true;
}
}
return false;
};
Results
这里的执行时间差距看不出来。。。一阶和二阶的差距无法体现;执行出错是边界没有处理,测试用例有空数组
剑指offer 05: 替换空格
题目描述
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = “We are happy.”
输出:“We%20are%20happy.”
来源:力扣(LeetCode)
链接:替换空格
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
正则表达式
/**
* @param {string} s
* @return {string}
*/
var replaceSpace = function(s) {
return s.replace(/\s/g, "%20");
};
执行时间中规中矩
06: 从尾到头打印链表
题目描述:
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
思路: 先从头到尾获取节点值,再反转数组
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {number[]}
*/
var reversePrint = function(head) {
let res = [];
while(head) {
res.push(head.val);
head = head.next
}
return res.reverse();
};
07:重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
来源:力扣(LeetCode)
链接:重建二叉树
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
- 前序遍历:根节点–>左节点–>右节点
- 中序遍历:左节点–>根节点–>右节点
- 后序遍历:左节点–>右节点–>根节点
- 因此前序遍历的第一个元素就是根节点,然后可以根据中序遍历中根节点的位置,根节点左边的就是二叉树的全部左节点,根节点的右边元素就是二叉树的右节点,进行递归,最后重建二叉树
Solutions
递归
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
var buildTree = function(preorder, inorder) {
if(!preorder.length || !inorder.length) return null; // 这里不能直接return, 题设要求,没有的叶子节点用null代替
let rootVal = preorder[0];
let rootIndex = inorder.indexOf(rootVal)
let root = new TreeNode(rootVal);
root.left = buildTree(preorder.slice(1,rootIndex+1), inorder.slice(0,rootIndex));
root.right = buildTree(preorder.slice(rootIndex+1), inorder.slice(rootIndex+1));
return root;
};
所有的递归版本都可以改为迭代版本,待补充。。。
10-I: 斐波那契亚数列
题目描述
- 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。- 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
来源:力扣(LeetCode)
链接:斐波那契亚数列
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
看到斐波那契亚数列首先想到的肯定是递归,但是这题贼鸡贼,递归就会超时,但是所有递归都可以变迭代,多用几个变量的事情
记忆化,使用一个数组对每一项斐波那契亚数进行储存,额外空间复杂度O(N); 也可以不使用数组,用三个变量进行变量替换
Solutions
- 递归
/**
* @param {number} n
* @return {number}
*/
var fib = function(n) {
// 递归---超时
if(n < 0) return
if(n <= 1) return n;
return (fib(n-1) + fib(n-2)) % 1000000007
};
这里有个奇怪的点就是n为81的时候,得到的结果是107920472,而不是107920470,一开始怀疑超出边界了,但是在代码编辑器中结果是正确的;最后结果要取模1e9+7, dp的时候每一步都取模就可以解决
- 迭代
/**
* @param {number} n
* @return {number}
*/
var fib = function(n) {
// 时间复杂度O(N), 空间复杂度O(N)---前n项斐波那契亚数都可以获取到
if(n < 0) return
if(n <= 1) return n
let res = [0,1]
for(let i = 2; i <= n; i++) {
res[i] = (res[i-1] + res[i-2]) % 1000000007;
}
return res[n];
};
- 不用数组
/**
* @param {number} n
* @return {number}
*/
var fib = function(n) {
// 节省空间,不用数组,用三个变量,相差无几
if(n < 0) return
if(n <= 1) return n
let num1 = 0, num2 = 1, res = 0;
for(let i = 2; i <= n; i++) {
res = (num1 + num2) % 1000000007;
num1 = num2;
num2 = res;
}
return res;
};
Results
四次提交错误顺利拉低了通过率
10- II. 青蛙跳台阶问题
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
来源:力扣(LeetCode)
链接:青蛙跳台阶问题
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这题就是斐波那契亚数列的变种,实际上还是斐波那契亚数列的问题,不过首项变成了1。
思路:假设从跳到第n阶台阶有F(n)种 跳法,由题意可知,跳到第n阶可以从第n-1阶跳1级到第n阶,也可以从第n-2阶的地方跳一级到第n阶;而跳到第n-1阶有F(n-2)种方法,跳到第n-1阶有F(n-1)种方法;因此有F(n) = F(n-1)+F(n-2);这就变成了斐波那契亚数列的问题了,注意下边界就可以A掉了。
/**
* @param {number} n
* @return {number}
*/
var numWays = function(n) {
if(n < 0) return
if(n <= 1) return 1
let num1 = 1, num2 = 1, res = 0;
for(let i = 2; i <= n; i++) {
res = (num1 + num2) % 1000000007;
num1 = num2;
num2 = res;
}
return res;
};
旋转数组的最小数字
题目描述:点击链接跳转原题链接
/**
* @param {number[]} numbers
* @return {number}
*/
var minArray = function(numbers) {
if(!numbers || !numbers.length) return
for( let i = 0; i < numbers.length - 1; i++) {
if(numbers[i] > numbers[i+1]) {
return numbers[i+1];
}
}
return numbers[0]
};
15: 二进制数中1的个数
api调用
/**
* @param {number} n - a positive integer
* @return {number}
*/
var hammingWeight = function(n) {
let arr = n.toString(2).split('');
let res = 0;
arr.forEach(item => {
if(~~item === 1) {
res++;
}
})
// console.log(typeof num);
return res;
};
17:打印从1到最大的n位数
/**
* @param {number} n
* @return {number[]}
*/
var printNumbers = function(n) {
if(n < 0) return
let res = [];
let temp = '9';
for(let i = 1; i < n; i++) {
temp += '9'
}
let end = ~~temp;
for(let j = 1; j <= end; j++) {
res.push(j);
}
return res;
};
18:删除链表的节点
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var deleteNode = function(head, val) {
let preNode = head;
let nextNode = head.next;
if(preNode.val === val) {
return head.next;
}
while(nextNode) {
if(nextNode.val === val) {
preNode.next = nextNode.next
}
preNode = nextNode
nextNode = nextNode.next
}
return head
};
思路:遍历找到对应的节点,将前一个节点的指针指向下一个节点,当前节点就从链表中删除了。这题用递归也可以,但是感觉没有必要。。。
21.调整数组顺序使奇数位于偶数前面
思路:1. 一次从头到尾遍历+辅助数组,时间复杂度O(N),空间复杂度 O(N), 这种方法的耗时和内存都比较大;
2.双指针(头尾指针遍历原地交换):
个人感觉不最求性能的话借助js的函数来解决最优雅,虽然有点流氓。。。
遍历+辅助数组:
/**
* @param {number[]} nums
* @return {number[]}
*/
var exchange = function(nums) {
// 一次遍历,借助辅助数组,O(N),空间复杂度O(N)
let arr = [];
nums.map(item => {
item % 2 === 0 ? arr.push(item) : arr.unshift(item)
})
return arr;
};
双指针:
/**
* @param {number[]} nums
* @return {number[]}
*/
var exchange = function(nums) {
// 一次遍历,借助辅助数组,O(N),空间复杂度O(N)
// let arr = [];
// nums.map(item => {
// item % 2 === 0 ? arr.push(item) : arr.unshift(item)
// })
// return arr;
// 双指针
let left = 0, right = nums.length - 1;
while(left < right) {
while(left < right && nums[left] % 2 !== 0) {
left++
}
while(right > left && nums[right] % 2 === 0) {
right--;
}
if(left < right) {
let temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
return nums;
};
js hack做法:利用js的排序函数Array.sort(),可以自定义排序方法,传送门
一行代码搞定
/**
* @param {number[]} nums
* @return {number[]}
*/
var exchange = function(nums) {
// 流氓写法
return nums.sort((a,b) => b % 2 - a % 2);
};
- 后续待补充,这波是清库存。。。