一、巧用数组下标数组
例:在统计一个字符串中字幕出现的次数时,咱们就能够把这些字母当作数组下标,在遍历的时候,若是字母A遍历到,则arr['a']就能够加1了。bash
法一:利用对象的key值不重复优化
var str = 'hajshdjldjf';
function count(str){
var obj = {};
for(var i = 0; i < str.length; i++){
if(obj[str[i]]){
obj[str[i]]++;
}else{
obj[str[i]] = 1;
}
}
console.log(obj);
return obj;
}
count(str);复制代码
法二:利用数组的下标ui
var str = 'hajshdjldjf';
function count(str){
var arr = [];
for(var i = 0; i < str.length; i++){
if(arr[str[i]]){
arr[str[i]]++;
}else{
arr[str[i]] = 1;
}
}
}
count(str);复制代码
其实这两种方法的思想是一致的。spa
例:给你n个无序的int整型数组arr,而且这些整数的取值范围都在0-20之间,要你在 O(n) 的时间复杂度中把这 n 个数按照从小到大的顺序打印出来。指针
对于这道题,若是你是先把这 n 个数先排序,再打印,是不可能O(n)的时间打印出来的。可是数值范围在 0-20。咱们就能够巧用数组下标了。把对应的数值做为数组下标,若是这个数出现过,则对应的数组加1。
code
利用对象:cdn
var arr = [1,2,3,4,5,4,5,6,6,7,6,9,17,16,15,14,12,11];
//常规解法 利用对象的key值不能重复去计算次数
//res去记录数字出现的顺序
function fn(arr){
arr.sort(function(a,b){
return a - b;
});
var res = [];
var resdetail = [];
for(var i = 0; i < arr.length; i++){
if(res.length === 0 || res[res.length-1] !== arr[i]){
res.push(arr[i]);
var obj = {
key:arr[i],
value:1
};
resdetail.push(obj);
}else{
resdetail[resdetail.length-1].value++;
}
}
console.log(resdetail);
return resdetail;
}
fn(arr);复制代码
利用数组下标对象
var arr = [1,2,3,4,5,4,5,6,6,7,6,9,17,16,15,14,12,11];
//利用数组下标 时间复杂度为O(n)
function fn(arr){
var temp = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
for(var i = 0; i < arr.length; i++){
temp[arr[i]]++;
}
for(var j = 0; j < 21; j++){
for(var k = 0; k < temp[j]; k++){
console.log(j);
}
}
}
fn(arr);复制代码
二、巧用取余blog
有时候咱们在遍历数组的时候,会进行越界判断,若是下标差很少要越界了,咱们就把它置为0从新遍历。特别是在一些环形的数组中,例如用数组实现的队列。每每会写出这样的代码:
for (int i = 0; i < N; i++)
{
if (pos < N) {
//没有越界
// 使用数组arr[pos]
else
{
pos = 0;//置为0再使用数组
//使用arr[pos]
}
pos++;
}
}复制代码
实际上咱们能够经过取余的方法来简化代码
for (int i = 0; i < N; i++) {
//使用数组arr[pos] (咱们假设刚开始的时候pos < N)
pos = (pos + 1) % N;
}复制代码
三、巧用双指针
对于双指针,在作关于单链表的题是特别有用,好比“判断单链表是否有环”、“如何一次遍历就找到链表中间位置节点”、“单链表中倒数第 k 个节点”等问题。对于这种问题,咱们就可使用双指针了,会方便不少。
(1)判断单链表中是否有环
咱们能够设置一个快指针和一个慢指针,慢的一次移动一个节点,快的一次移动两个节点。若是存在环,快指针会在第二次遍历时和慢指针相遇。
(2)如何一次遍历就找到链表中间位置节点
同样是设置一个快指针和慢指针。慢的一次移动一个节点,快的一次移动两个。当快指针遍历完成时,慢指针恰好到达中点。
(3)单链表中倒数第 k 个节点
设置两个指针,其中一个指针先移动k-1步,从第k步开始,两个指针以相同的速度移动。当那个先移动的指针遍历完成时,第二个指针指向的位置即倒数第k个位置。
四、巧用位移
有时候咱们在进行除数或乘数运算的时候,例如n / 2,n / 4, n / 8这些运算的时候,咱们就能够用移位的方法来运算了,这样会快不少。
例如:
n / 2 等价于 n >> 1
n / 4 等价于 n >> 2
n / 8 等价于 n >> 3。
这样经过移位的运算在执行速度上是会比较快的,也能够显的你很厉害的样子,哈哈。
还有一些 &(与)、|(或)的运算,也能够加快运算的速度。例如判断一个数是不是奇数,你可能会这样作
if(n % 2 == 1){
dosomething();
}复制代码
不过咱们用与或运算的话会快不少。例如判断是不是奇数,咱们就能够把n和1相与了,若是结果为1,则是奇数,不然就不会。即
if(n & 1 == 1){
dosomething();
)复制代码
五、设置哨兵位
在链表的相关问题中,咱们常常会设置一个头指针,并且这个头指针是不存任何有效数据的,只是为了操做方便,这个头指针咱们就能够称之为哨兵位了。
例如咱们要删除头第一个节点是时候,若是没有设置一个哨兵位,那么在操做上,它会与删除第二个节点的操做有所不一样。可是咱们设置了哨兵,那么删除第一个节点和删除第二个节点那么在操做上就同样了,不用作额外的判断。固然,插入节点的时候也同样。
有时候咱们在操做数组的时候,也是能够设置一个哨兵的,把arr[0]做为哨兵。例如,要判断两个相邻的元素是否相等时,设置了哨兵就不怕越界等问题了,能够直接arr[i] == arr[i-1]?了。不用怕i = 0时出现越界。
六、与递归相关的一些优化
(1)对于能够递归的问题考虑状态保存。
当咱们使用递归来解决一个问题时,很容易产生重复去算同一个子问题,这个时候咱们要考虑状态保存以防止重复计算。
例:斐波那契数列
function fn(n){
if(n <= 2){
return 1;
}else{
return fn(n-1) + fn(n-2);
}
}
console.log(fn(10));复制代码
不过对于可使用递归解决的问题,咱们必定要考虑是否有不少重复计算。显然对于 f(n) = f(n-1) + f(n-2) 的递归,是有不少重复计算的。如
就有不少重复计算了。这个时候咱们要考虑状态保存。而且能够自底向上。
function fn(n){
var res = [];
res[0] = 1;
res[1] = 1;
for(var i = 2; i < n; i++){
res[i] = res[i-1] + res[i-2];
}
console.log(res[n-1]);
return res[n-1];
}
fn(10);复制代码
进一步优化:使用两个变量。
function fn(n){
var a = 1;
var b = 1;
for(var i = 3; i <= n; i++){
a = a + b;
b = a - b;
}
return a;
}
fn(10);复制代码