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])
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])];
}
}
测试的话是可以的,- -菜鸟,感觉又写了奇怪的东西,感谢指点
感谢夜店小新新指出另一个问题: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个字母最多加上数字,所以这种算法我认为基本上是完美符合你的意思。如果满意清采纳~如果想看更多前端方面的基础,欢迎关注我的专栏~
我也写一段玩玩:
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 是按加入序的,那 @莲_涳栢__ 的应该是最简洁的。
也试着写了下,恳请点评
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);
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"
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);
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来解决顺序问题,比较健康和简练。
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的,这个造成的风险更大。
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;
其实很好理解的,
缩减一些代码量也是可以的.
其次我想说一下, 有很多不运行自测就发上来的错误答案. 呵呵哒
更新版本(加注释):
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] 提醒