https://blog.csdn.net/charles_tian/article/details/79938010
题目:实现一个函数,去除输入字符串中的重复字符。
题目:实现一个函数,去除输入数组中重复的元素。
我想这道题是大家经常遇见的吧,是不是好像每次遇到的时候又变的不会了?那是因为不够专注,当初学习的时候知识初浅的看了看答案的思路便不再关注了,这肯定不行,要想在程序员的道路上走的更远,必须得明白解这道题的原理和做法,用了哪些你不知道的方法,甚至是还要比较哪个方法更优,明白更优的这个方法的原理出自哪里,这样才能越走越远。
对于这样的题,网上已经有很多的答案了,这里我只是做个总结,多数部分属于转载,但是希望大家坚持看完,因为要着重理解思路和逻辑,这样才对大家有所帮助。
1)字符串去重
1.for遍历
主要思路:首先新建一个空的字符串,然后声明一个flag作为下文的条件判断,随后就开始for循环输入的字符串,在for循环里面给flag赋值为1,然后在for循环又for循环一次,里面的循环是循环新字符串里的字符,然后给出判断,如果相同,flag=0,且跳出循环,最后对flag进行判断,如果为1,表示循环的字符在新字符串里没有出现过,那么就将该字符添加到新字符串中;如果为0,则不进行处理,直接跳过,最后返回新字符串的值。
该方法不是很推荐,因为执行了两次遍历,比较消耗性能,大家也可以通过测试数据得知,这个方法相对来说比较耗时。
代码实现如下:
function removeRepeatStr(str){
var newStr = '';
var flag;
var len = str.length;
for(var i=0; i<len; i++){
flag = 1;
var newLen = newStr.length;
for(var j=0; j<newLen; j++){
if(str[i] == newStr[j]){
flag = 0;
break;
}
}
if(flag){
newStr = newStr + str[i];
}
}
return newStr;
}
2)indexOf方法(无兼容问题)
主要思路:也是要新建一个空的字符串,然后用遍历的形式遍历输入的字符串,然后判断遍历的字符在新的字符串中是否是第一次出现,如果是,则添加到空的字符串中;如果不是,则跳过,返回-1。最后返回新的字符串。
该方法本质上来说跟第一种没有太大的区别,只不过判断的条件变成了是否是第一次出现,而由于indexOf这个方法本质上也是通过遍历字符串的形式查找某个字符在字符串中第一次出现的位置,如果没有在字符串中找到,则返回-1。所以该方法也不是太推荐,因为也是比较耗性能。不如直接查找字符索引值来的快。
代码实现如下:
function removeRepeatStr(str){
var newStr = '';
var len = str.length;
for(var i=0; i<len; i++){
if(newStr.indexOf(str[i])==-1){
newStr = newStr + str[i];
}
}
return newStr;
}
3)search方法
主要思路:与第二种方法一致,只不过换了一种检测方法。即将indexOf方法变成了search方法,indexOf方法我们知道是,遍历字符串的形式查找某个字符在字符串中第一次出现的位置,如果没有在字符串中找到,则返回-1;而search方法是这样的,用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。如果没有找到任何匹配的子串,则返回 -1。
这个方法相对第二种来说,好了一点点,但是仅此一点点。
代码实现如下:
function removeRepeatStr(str){
var newStr = '';
var len = str.length;
for(var i=0; i<len; i++){
if(newStr.search(str[i])==-1){
newStr = newStr + str[i];
}
}
return newStr;
}
4)对象属性
主要思路:新建一个空的对象和一个空的字符串,遍历输入的字符串,将遍历到的字符作为对象的属性并赋值为1,然后通过对象属性值来判断数组元素是否重复(亦即每次遍历arr的元素,这里需要说明的是,此时并没有把这个元素存入至对象中,是先将其作为对象属性判断当前对象中是否已经存在了这个属性),如果不是,则添加到新的字符串中;如果是,则跳过。最后返回新的字符串。
这里为了更好的理解,我着重解释两个地方,也是相对难以理解的地方。
第一个:判断代码,if( !obj[str[i]] ){},刚才我也解释了,这里判断的条件是对象属性的值,str[i]是对象属性,如果对象属性的值存在,那么便说明这个属性已经在对象中存在了;没有值,则把当前的字符存进字符串中;
第二个:赋值代码,obj[str[i]],这个是为了保证对象属性有值,如果不赋值,那个这个对象属性的值默认为undefined;
接下来是联合理解:在遍历第一个字符时,对象初始又为空,所以当第一个字符作为对象属性的时候,它是没有值的,没有值的话就默认为undefined,亦即obj[str[0]] == undefined,在判断语句中,undefined会被转为false,如果转化为数字型,则为0;亦即在判断语句中obj[str[0]] == fasle,所以if( !obj[str[0]] ) == if( true ),所以会执行下面的语句,也就是把第一个字符存进字符串中,并给obj[ str[0] ]赋值,如果不赋值,相当于还是undefined,等到遍历到重复的元素时,根本不会跳过,还是会存进字符串中,这就起不到去重的效果了。如果赋值了,obj[str[0]] = 1,在判断语句中就将值转化为true(这里不懂为什么要转为true的话,那你得回去好好看书了),而前面又有一个非号,那么判断语句的结果就是false,就不会执行下面的语句了,就实现了跳过的操作,也就达到了去重的效果。
这个方法比上述几个方法好一些,毕竟在对象里面找属性值,只要匹配到属性名就可以了,不用全部遍历,节省时间。
代码实现如下:
function removeRepeatStr(str){
var obj = {};
var newStr = '';
var len = str.length;
for(var i=0; i<len; i++){
if(!obj[str[i]]){
newStr = newStr + str[i];
obj[str[i]] = 1;//注意,这里的1是给对象属性赋值,这个值可以任意取。意思是把每个遍历的字符作为对象属性并赋值保存,保证该属性的唯一性
}
}
return newStr;
}
2)数组去重
1.indexOf方法
主要思路:跟上面字符串去重类似。新建一个空的数组,然后遍历传入的数组,如果新的数组里没有遍历的元素,那么就加入到新建的数组内;如果有,则跳过。最后返回新建的数组,这个数组就是不含重复元素的数组。
跟之前分析字符串类似,该方法也比较耗时和浪费资源。
代码实现如下:
function removeRepeatArrElement(arr){
var newArr = [];
var len = arr.length;
for(var i=0; i<len; i++){
if(newArr.indexOf(arr[i])==-1){
newArr.push(arr[i]);
}
}
return newArr;
}
2.对象属性
主要思路:跟字符串去重类似,这里不再赘述。反正比双重for循环和indexOf方法要好。
代码实现如下:
function removeRepeatArrElement(arr){
var obj = {};
var newArr = [];
var len = arr.length;
for(var i=0; i<len; i++){
if(!obj[arr[i]]){//这里也可以通过对对象的属性是否存在进行判断,即if(!obj.hasOwnProperty(arr[i]))
newArr.push(arr[i]);
obj[arr[i]] = true;
}
}
return newArr;
}
3.for循环
主要思路:就是将数组逐个搬到另一个数组中去,当遇到重复元素时,不移动;若不重复则移动到新的数组中。
这个方法不推荐,因为它使用的是双重for循环,性能消耗较大,因为在遍历i时,又在i里面遍历j,比如i和j各取10,那么遍历的总次数为100次,即当i=0时,j分别取1,2,3,4,5,6,7,8,9,10,然后当i=1时,j又去分别取这10个值,直到i=10,j=10才结束,相当耗性能和时间。而这里是对第一个元素匹配的时候,查找其后面的元素是否与其重复,即当i=0时,j取1-9;当i=1时,j取2-9...;当i=8时,j取9;当i=9时,j取不到值,为undefined,故判断为false,直接输出第索引值为9(第十个元素)的元素。
代码实现如下:
function removeRepeatArrElement(arr){
var newArr = [];
var len = arr.length;
var flag;
for(var i=0; i<len; i++){
flag = true;
for(var j=i; j<len-1; j++){
if(arr[i] == arr[j+1]){
flag = false;
break;
}
}
if(flag){
newArr.push(arr[i]);
}
}
return newArr;
}
4.value -> key方法
主要思路:顾名思义,就是将数组的值赋值给另一个数组的键,此时重复的元素都被删除,然后取新数组的键作为去重后的结果
代码实现如下:
function removeRepeatArrElement(arr){
var newArr = [];
var temp = [];
for(var i in temp){
temp[arr[i]] = 1;
}
for(var i in temp){
newArr.push(i);
}
return newArr;
}
但是这个方法有一点需要注意,就是输出数组里的元素是以字符串的形式给出的,而不是number类型或者其他类型。所以后面要想得到自己想要的数据类型,那么后期还要进一步处理。
5.sort()方法
主要思路:首先将数组进行排序,排序了之后我们都知道,相同的元素会在一起,然后进行循环,依次对比相邻的两个元素是否相同,若相同,则跳过;若不同,则加入到新建的数组中。
代码实现如下:
function removeRepeatArrElement(arr){
var newArray = arr.sort();
var newArr = [];
var len = newArray.length;
for(var i=0; i<len; i++){
if(newArray[i] != newArray[i+1]){
newArr.push([newArray[i]]);
}
}
}
return newArr;
}
console.log(removeRepeatArrElement([12,4,3,5,6,23,4,5,6]));//[ 12, 23, 3, 4, 5, 6 ]
下面一行是我测试用的,大家可以看到重复的数字已经去除了,然后输出[ 12, 23, 3, 4, 5, 6 ]。
但是这里我产生了一个疑惑:sort()方法括号不带函数默认是升序排列输出(是按照字符编码顺序输出的),所以现在输出的却是[ 12, 23, 3, 4, 5, 6 ],而不是[ 3, 4, 5, 6 , 12, 23 ]。
如果感兴趣的同学可以了解其他的原生j排序s实现代码,可以参考以下链接:js排序算法和js原生排序算法。
最后还要提到的一点就是,不知道有没有同学有疑问,为什么在去重方法中,对象属性方法中的判断对象属性值的时候,对象取属性的时候用的是中括号,而不是点方法?
因为点语法和中括号法还是有区别的:
1、中括号法可以用变量作为属性名,而点方法不可以;
2、中括号法可以用数字作为属性名,而点语法不可以;
3、中括号法可以使用js的关键字和保留字作为属性名,而点语法不可以(尽量避免在变量或者属性中使用关键字或保留字);
所以为了保证能取到对象里的属性,用中括号法比较实际。
6.ES6新特性:set数据结构
基本用法:ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成 Set 数据结构。
调用:
const set = new Set([1, 2, 3, 4, 4]);
console.log(set)//[1,2,3,4]
是不是特别的方便,所以说,还是尽快学习ES6吧~
---------------------