1、有一个长度为n-1的数组,包含1-n中不重复的乱序的数,求寻找范围内不在数组中的数,考虑空间占用,性能优化,溢出等情况,至少写两个算法
当n不太大时,可以考虑求和。先算出1~n的所有数的和,然后减去数组中出现的所有自然数的和。时间复杂度为O(n),空间复杂度O(1)。这种方法的缺点是n不能太大,n比较大时,求和容易溢出。
用位图。从头到尾的扫描整个数组,把出现的数相应的位设置为1.然后再扫描位图,找出不为1的那一位,即为要找的数。这种方法的时间复杂度为O(n),空间复杂度为O(n)。
异或有个很巧妙的地方:同一变量和该变量与另一变量的异或值的异或等于这个变量自身。所以我们可以把1~n的所有数异或,再把数组中出现的所有数异或,然后再把这两个异或的结果异或,最后得到的值即为我们要找的值。这样时间复杂度为O(n),空间复杂度为O(1)。在空间上比第二种方法要好,而且不会出现第一种方法中所说的溢出问题。
解法一:用到数组求和
var arr = [9, 5, 7, 8, 6, 3, 4, 1]
var sum = 0; //数组的和
var sum1 = 0; //范围的和
var find = function() {
for (var i = 0; i < arr.length; i++) {
sum += parseInt(arr[i]);
};
for (var i = 1; i <= arr.length + 1; i++) {
sum1 += i;
};
var wow = sum1 - sum;
console.log(wow);
console.log(sum1);
console.log(sum);
};
find();
双层循环,外层循环元素,内层循环时比较值。如果有相同的值则跳过,不相同则push进数组。
此时的跳过不是跳出内层循环,如果内层比较有重复则把当前元素忽略,跳到下一个元素去做循环比较。
Array.prototype.distinct = function(){
var arr = this,
result = [],
for(let i = 0; i < arr.length; i++){
for(let j = i + 1; j < arr.length; j++){
if(arr[i] === arr[j]){
j = ++i;
}
}
result.push(arr[i]);
}
return result;
}
var arra = [1,2,3,4,4,1,1,2,1,1,1];
arra.distinct(); //返回[3,4,2,1]
i++和++i又分不清了
let i=1;
let a=0;
i++ 先运算再赋值 a=i++ => a=i, i=i+1 => a===1;
++i 先赋值再运算 a=++i => i=i+1 , a=i => a===2;
在清除的解释一下原理,以一个简单的数组为例。
function distinct(){
var arr = [1,2,2,1,3];
result = [];
for(let i = 0; i < arr.length; i++){
for(let j = i + 1; j < arr.length; j++){
if(arr[i] === arr[j]){
console.log('内层循环的j-前',j);
j = ++i;
console.log('内层循环的j',j);
console.log('内层循环的i',i);
}
}
console.log('外层循环的i',i);
result.push(arr[i]);
}
document.getElementById("shuzuquchong").innerHTML=result;
return result;
};
// 打印如下
// 内层循环的j-前 3
// 内层循环的j 1
// 内层循环的i 1
// 内层循环的j-前 2
// 内层循环的j 2
// 内层循环的i 2
// 外层循环的i 2
// 外层循环的i 3
// 外层循环的i 4
方法二:利用splice直接在原数组进行操作
双层循环,外层循环元素,内层循环时比较值;值相同时,则删去这个值(删除后面(和前面重复)的值);注意点:删除元素之后,需要将数组的长度也减1.
function distinct(){
var a = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,];
for(let i = 0; i < a.length; i++){
for(let j = i + 1; j < a.length; j++){
if(a[i] == a[j]){
a.splice(j,1); //splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。该方法会改变原始数组。
a.length--;
j--;
}
}
}
document.getElementById("shuzuquchong").innerHTML=a;
return a;
};
splice()方法和slice()方法区别一下
arrayObject.splice(index,howmany,item1,.....,itemX) 注释:该方法会改变原始数组。
splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。
ndex | 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。 |
howmany | 必需。要删除的项目数量。如果设置为 0,则不会删除项目。 |
item1, ..., itemX | 可选。向数组添加的新项目。 |
arrayObject.slice(start,end) slice() 方法可从已有的数组中返回选定的元素。
start | 必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。 |
end | 可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。 |
方法三:利用对象的属性不能相同的特点进行去重
对象的属性不能相同。
function distinct(){
var arr = [1,2,3,4,5,6,5,3,2,4,56,4,1,2,1,1,1,1,1,1,];
obj = {},
result = [];
for(let i = 0; i< arr.length; i++){
if(!obj[arr[i]]){ //如果能查找到,证明数组元素重复了
console.log('obj[arr[i]]',obj[arr[i]]);
obj[arr[i]] = 1;
result.push(arr[i]);
}
}
document.getElementById("shuzuquchong").innerHTML=result;
return result;
};
方法四:数组递归去重
运用递归的思想
先排序,然后从最后开始比较,遇到相同,则删除
什么是递归?:
那么什么是递归呢? 要理解递归,就得先了解什么是递归,实际上这句话就是一个递归.这么说可能不好理解,接下来我举个简单的例子来解释这段话的意义。
假设我们现在都不知道什么是递归,我们自然想到打开浏览器,输入到谷歌的网页,我们点击搜索递归,然后我们在为维基百科中了解到了递归的基本定义,在了解到了递归实际上是和栈有关的时候,你又蒙圈了,什么是栈呢?数据结构没学清楚,此时的你只能又打开谷歌,搜索什么是栈.接下来你依次了解了内存/操作系统.在你基本了解好知识之后,你通过操作系统了解了内存,通过内存了解了栈,通过栈了解了什么是递归这下你恍然大悟!原来这就是递归啊!
这下应该有点明白了吧,这个过程其实就是递归的过程,如果不了解递归,那就先了解什么是递归,可能你会说这是个循环并不是递归,我们前面说到,递归是需要终止条件的,那么你明白递归是什么其实就是终止条件。整个过程,搜索引擎充当递归函数(只是形象的假设);在你去依次查找递归/栈/内存/操作系统的过程为前行阶段,在你都了解完之后,反回去了解含义的过程为退回阶段。
递归和循环区别在于,循环是执行一段代码,仅仅参数不同;递归要调用到自己。
打个比方:
循环就像在同一个地方打转;递归是一层一层进入,再原路返回,虽然每一层都一样。