素材牛VIP会员
做了一题JS面试题,写的很不好,寻求更好的方法
 qi***hu  分类:Html5  人气:3735  回帖:26  发布于6年前 收藏

题目:


2017/3/16 16:11 更新

非常感谢各位大大的回答,真的非常感动,每个答案我会一个一个看过去,正在努力学习中...


实现一个算法,寻找字符串中出现次数最少的、并且首次出现位置最前的字符。如cbaacfdeaebb,符合要求的是f,因为他只出现了一次(次数最少)。并且比其他只出现一次的字符(如d)首次出现的位置最靠前。

下面是我的写法,小弟没学过数据结构与算法,半路出家,基础不好,写的太烂了,效率无比低下,希望有各位大大能够给出一些更好的写法。

我的写法:

var str = "cbaacfdeaebb";
var arr = str.split('');

// 各元素以及对应出现的次数
var res = arr.reduce(function (obj, key, index, arr) {
    if(obj.hasOwnProperty(key)) {
        obj[key] ++;
    } else {
        obj[key] = 1;
    }
    return obj;
}, {}); 

// 次数组成的数组
var indexArr = []; 

for(var prop in res) {
    indexArr.push(res[prop]);    
}

// 找出出现次数最少的数
var minTimes = Math.min.apply(null,indexArr);

// 出现次数最少元素的数组
var eleArr = [];

for(var p in res) {
    if(res[p] === minTimes) {
        eleArr.push(p); 
    }
}

console.log(eleArr[0])
 标签:html5javascript

讨论这个帖子(26)垃圾回帖将一律封号处理……

Lv6 码匠
小***6 JS工程师 6年前#1

function findFirstMinString (string) {

var arr = string.split('');
var testedArr = [];            // 已经遍历过的字符
var testedArrLen = [];         // 已经遍历过的字符的数量
var tempTestedArrLen = [];     // 已经遍历过的字符的数量的备份
var resultString;              // 结果

// 将字符串根据字符从头到尾遍历一遍
arr.forEach(function (value, index, array) {

    // 当遍历到一样的字符或者resultString已经被赋值了字符时,跳过
    if (testedArr.indexOf(value) > -1 || typeof resultString === 'String') {
        return;
    }
    
    // 计算当前字符出现的次数
    var newLength = arr.filter(function(val) {
        return val == value;
    }).length;
    
    // 如果这个字符出现的次数才一次的话,直接赋值给resultString,一次肯定是最少了
    if (newLength == 1) {
        resultString = value;
        return;
    }
    testedArr.push(value);
    testedArrLen.push(newLength);
    tempTestedArrLen.push(newLength);
});
if (typeof resultString === 'String') {
    return resultString;
} else {
    // 将遍历过的字符的数量排序,取第一个,为最小,再从备份中找到它的index,这个index与已经遍历过的字符的index互相匹配
    return testedArr[tempTestedArrLen.indexOf(testedArrLen.sort()[0])];
}

}

测试的话是可以的,- -菜鸟,感觉又写了奇怪的东西,感谢指点

Lv7 码师
ad***cn CEO 6年前#2

感谢夜店小新新指出另一个问题:for-in输入的时候,如果属性为纯数字,那么输出顺序不会按照创建属性的顺序,比如字符串为'2211',输出会先输出属性1,根据这个情况,增加一个数组保证输出的顺序,更正后代码如下,如果还有问题欢迎指出:

function leastTime(str) {
      //定义一个对象,把对应字母存储到属性中,若属性已存在,则增加数量
      var obj = {};
      
      /*更正部分*/
      //增加一个数组记录生成属性的顺序
      var arr = [];
      /*更正部分结束*/
      
      //取个超大的数作为lestTimes初始值,如果需要你可以改成无限大
      var lestTimes = 1000000;
      //在这个for循环中完成字母次数统计
      for (var i = 0; i < str.length; i++) {
        var key = str[i];

        if (obj.hasOwnProperty(key)) {
          //若存在,对应属性加1
          obj[key]++;
        } else {
          //不存在则增加对应属性,并且设置出现次数为1
          obj[key] = 1;
           /*更正部分*/
          //在数组中记录下新增属性
          arr.push(key)
          /*更正部分结束*/
        }

      }
     
      //第一次遍历生成的对象 求出leastTime的结果
      for (var j in obj) {
        if (obj[j] < lestTimes)
          lestTimes = obj[j];
      }
      console.log(arr)
     
      //第二次遍历生成的对象 第一次检测某个属性的值等于最小次数即可
      /*更正部分*/
      //第二次遍历的时候不用for-in 而是按照数组存储的顺序 这样就可以解决for-in带来的不确定性
      for (var j = 0; j < arr.length; j++) {
        var key = arr[j];
        if (obj[key] === lestTimes)
          return key;
      }
       /*更正部分结束*/
    }
    var str = '2211';
    console.log(leastTime(str))

答案再次更新的分割线


感谢JayZangwill指出错误,更正一下之前的问题,在第一次for循环存储字母的时候,不应该更新leastTime的值,因为例如abab这种字符串可能导致leastTime的值,在第一次碰到a被更新成1,实际上a和b都是2个,导致最后结果不准确,所以把求leastTime的步骤放在字符串存储之后,更正后代码如下:

function leastTime(str) {
    //定义一个对象,把对应字母存储到属性中,若属性已存在,则增加数量
    var obj = {};
    //取个超大的数作为lestTimes初始值,如果需要你可以改成无限大
    var lestTimes = 1000000;
    //在这个for循环中完成字母次数统计
    for (var i = 0; i < str.length; i++) {
      var key = str[i];
      if (obj.hasOwnProperty(key)) {
        //若存在,对应属性加1
        obj[key]++;
      } else {
        //不存在则增加对应属性,并且设置出现次数为1
        obj[key] = 1;
      }
     
    }
    /*--更正部分开始--*/
    //第一次遍历生成的对象 求出leastTime的结果
    for (var j in obj) {
      if (obj[j] < lestTimes)
        lestTimes = obj[j];
    }
    /*--更正部分结束--*/
    //第二次遍历生成的对象 第一次检测某个属性的值等于最小次数即可
    for (var j in obj) {
      if (obj[j] === lestTimes)
        return j;
    }
  }
  var str = 'cbaacfdeaebb';
  console.log(leastTime(str))

答案更新的分割线,以下是原答案


最最近刚好复习了一些js的基础,这种题其实和数组去重之类的很相似,先上代码:

 function leastTime(str) {
    //定义一个对象,把对应字母存储到属性中,若属性已存在,则增加数量
    var obj = {};
    //取个超大的数作为lestTimes初始值,如果需要你可以改成无限大
    var lestTimes = 1000000;
    //在这个for循环中完成字母次数统计
    for (var i = 0; i < str.length; i++) {
      var key = str[i];
      if (obj.hasOwnProperty(key)) {
        //若存在,对应属性加1
        obj[key]++;
      } else {
        //不存在则增加对应属性,并且设置出现次数为1
        obj[key] = 1;
      }
      //存储的同时顺便更新最小出现的次数
      if (obj[key] < lestTimes)
        lestTimes = obj[key];
    }
    //遍历生成的对象 第一次检测某个属性的值等于最小次数即可
    for (var j in obj) {
      if (obj[j] === lestTimes)
        return j;
    }
  }
  var str = 'cbaacfdeaebb';
  console.log(leastTime(str))

在注释里面写的比较清楚了,不过还是讲一下思路:

  • 首先对输入的字符串遍历一次,用一个对象的属性来存储这些字符串出现的次数。同时在循环的过程中,让leastTime这个变量始终保存着出现次数最少的那个值。

  • 对生成的对象,检测每个属性对象的值(就是每个字母出现的次数),第一次检测到等于leastTime,就是出现次数最少并且第一次的

补充一下,这种算法的好处是计算速度快,时间复杂度基本是最低的,因为用的是数据散列的思路,缺点是开的空间比较大,因为对于每个不同的字母都增加一个对应的属性。至于时间和空间如何取舍就看题主自己了,不过这道题本身空间复杂度大不到那哪里去,因为最多26个字母最多加上数字,所以这种算法我认为基本上是完美符合你的意思。如果满意清采纳~如果想看更多前端方面的基础,欢迎关注我的专栏~

Lv2 入门
Br***23 产品经理 6年前#3

我也写一段玩玩:

const all = "cbaacfdeaebb".split("")
    .reduce((all, ch, i) => {
        const m = all[ch] || (all[ch] = { ch: ch, index: i, count: 0 });
        m.count++;
        return all;
    }, {});

const theOne = Object.keys(all)
    .map(ch => all[ch])
    .reduce((min, t) => min.count === t.count
        ? (min.index > t.index ? t : min)
        : (min.count > t.count ? t : min));

console.log(`${theOne.ch}: ${theOne.count}`);
// 输出 f: 1

我不确定 {} 在用 Object.keys 遍历的时候,键是不是加入的顺序,所以加上了对 index 的判断。如果可以保证 keys 是按加入序的,那 @莲_涳栢__ 的应该是最简洁的。

Lv6 码匠
bi***am 技术总监 6年前#4

也试着写了下,恳请点评

function findFirstLessAplha(ystring){
var result=[];
var hash={};
for(var i=0,elem;(elem=ystring[i])!=null;i++){

  if(!hash[elem]){
      //第一次找到的,次数记1
    var ele={
      count : 1,
      fisrt : i,
      element :elem
  };
    result.push(ele);
    hash[elem]=true;
  }else{
    var t1 = result.findIndex(function(value,index,arr){
    return arr[index].element == elem;
  });//返回等于elem的下标
  result[t1].count += 1;//再次找到的次数+1
  }

}
//排序
result.sort(function(v1,v2){

  return(v1.count>v2.count)?1:-1;

});

return result;
}

var findFirstLessAplha = findFirstLessAplha("afedaf1oflopwekl");
console.log(findFirstLessAplha);

Lv4 码徒
飞***6 Linux系统工程师 6年前#5

统计一下每个字符出现的顺序和次数,最后遍历一下就行了。

总共的字符数是有限的,如果字符串足够长,所以后一次循环的次数理论上会远小于第一次。

Lv1 新人
in***ex 职业无 6年前#6
var testMin = (str) => {
    //var str = "cbaacfdeaebb";
    var obj = str.split('').reduce((p, k) => (p[k]++ || (p[k] = 1), p), {}); //整合出现次数
    var reskey = "";
    var resval = Number.MAX_SAFE_INTEGER; //安全最大整数
    for(var x in obj){  //循环,保存最小值
        if(obj[x] < resval){
            reskey = x;   //key
            resval = obj[x];  //对应最小值
        }
    }
    return reskey;
}

testMin("cbaacfdeaebb");
//返回"f"
Lv6 码匠
d***悠 Web前端工程师 6年前#7
var string = 'cbaacfdeaebb';
var min = Number.MAX_VALUE;
var char = '';
var index = 0;

console.time('char');
(func => func(func))(get => (str => {
  var cur = str.charAt(0);
  var count = 0;
  str = str.replace(new RegExp(cur, 'g'), (str) => {
    ++count;
    return '';
  });
  if (min > count) {
    min = count;
    char = cur;
  }
  str && get(get)(str);
}))(string);
console.timeEnd('char');
console.log(char);

console.time('index');
index = string.indexOf(char);
console.timeEnd('index');
console.log(index);
Lv3 码奴
mo***id 站长 6年前#8

斗胆来评价一下诸位大神的答案。。。

@莲_涳栢__ :

var o = [].reduce.call('cbaacfdeaebb',function(p,n){
          return p[n] = (p[n] || 0) + 1,p;
       },{}),
   s = Object.keys(o).reduce(function(p,n){
       return o[p] <= o[n] ? p : n;
   });
   console.log(s,o[s]); 

正统的Hash Table思路。缺点是Object.keys()不能保证顺序,所以存在风险。


@边城 :

const all = "cbaacfdeaebb".split("")
    .reduce((all, ch, i) => {
        const m = all[ch] || (all[ch] = { ch: ch, index: i, count: 0 });
        m.count++;
        return all;
    }, {});

const theOne = Object.keys(all)
    .map(ch => all[ch])
    .reduce((min, t) => min.count === t.count
        ? (min.index > t.index ? t : min)
        : (min.count > t.count ? t : min));

console.log(`${theOne.ch}: ${theOne.count}`);

引入了index来解决顺序问题,比较健康和简练。


@u3u

function findFirstChar(string) {
  const desc = [];
  [...string].forEach((char, index) => {
    const item = desc.find(item => item.char === char)
    item ? item.count++ : desc.push({ char, index, count: 1 })
  })
  return desc.sort((a, b) => a.count - b.count)[0]
}

利用数组代替Hash Table,解决了顺序问题,思路不错。
但是Array.sort()并不一定是stable的,这个造成的风险更大。


@hsfzxjy

const less = (x, y) => x.count <= y.count && x.first < y.first

function firstSingle (string) {
  let map = {}
  string.split('')
    .forEach((char, index) => {
      if (map[char]) 
        map[char].count++
      else
        map[char] = { count: 1, first: index, char }
    })
  return Object.values(map).reduce((x, y) => less(x, y) ? x : y).char
}

思路相似,利用Hash Table,并引入了index解决顺序问题。
ES2017还没有正式发布,Object.values目前还是草案。
另外原谅我强迫症重新排个版:

const less = (x, y) => (x.count <= y.count && x.first < y.first) ? x : y;

function firstSingle (string) {
  let map = {}
  string.split('')
        .forEach((char, index) => {
            map[char] ? map[char].count++ : map[char] = { count: 1, first: index, char } 
        });
  return Object.values(map).reduce(less).char
}

最后是我的two cents:

var str = "cbaacfdeaebb";

var result = [...new Set(str)]
            .map(el => ({el, len: str.split(el).length}))
            .reduce((a,e) => (a.len > e.len ? e : a))
            .el;
Lv4 码徒
Ro***ng Linux系统工程师 6年前#9

不知道这样子行不行

  var str = "aabbccdeef";
    var strArray = str.split('');
    var arr=[];

    for(var i=0;i<strArray.length;i++){
        arr.push((str.split(strArray[i])).length-1);
    }

    var min = Math.min.apply(null, arr);

    var cs = arr.indexOf(min);

    var re = strArray[cs];

    alert(re);
Lv6 码匠
追***忆 UI设计师 6年前#10
其实很好理解的, 
缩减一些代码量也是可以的.

其次我想说一下, 有很多不运行自测就发上来的错误答案. 呵呵哒

更新版本(加注释):

var str = "aaasdsrhksaoashgas";
function findMinLetter(str){
  if(!!!str && typeof(str) !== "string")return; //如果是空串或者非字符串返回
  var arr = [], letter = str.split("")[0], count = 0;//arr 目标数组 内容为letter: 字母, count: 字母存在的个数, letter第一个字母(初始值), count 个数(初始值0)
  
  while(str.indexOf(letter) > -1){//循环如果str中含有letter, 执行以下代码, 首次肯定会往下执行
    count ++;//count++
    str = str.replace(letter, "");//从str中删除当前letter并重新赋值给str, 从前往后
    if(str.indexOf(letter) < 0){//再次判断是否含有letter 如果没有执行以下代码
      arr.push({letter: letter, count: count});//往arr中push当前letter的字母和个数
      letter = str.split("")[0];//letter重新赋值为字符串的第一个字母
      count = 0;//同时count重置为0
    }
  }
  //返回一个立即执行函数 获取arr中count最少的且最先出现的字母, 输出为字符串
  return (function (arr){
    var letter = "", count = 0;
    arr.forEach(function(o, i){
      if(!!!letter || count > o.count){//这里判断 如果letter不存在(即为第一次, 非第一次均为false) 或 count > 当前循环的o.count时, 相等时也不会进入函数域赋值, 所以一定会循环到一个最少且最先出现的值
        letter = o.letter;
        count = o.count;
      }
    });
    return "最少且最靠前的字符为: " + letter + ", 出现次数为: " + count;
  })(arr);
}
findMinLetter(str);
总结: 简单易懂, 且比较完善
更新, 函数第一行判断增加 `!` !== "string", 之前是代码错误少了!
感谢 @一只会飞的猪[liutong] 提醒
 文明上网,理性发言!   😉 阿里云幸运券,戳我领取