Longest Substring Without Repeating Characters corner cases - javascript

I was asked this question in a recent interview. I need to find the longest substring without repeating characters.
Given "abcabcbb", the answer is "abc", which the length is 3.
Given "bbbbb", the answer is "b", with the length of 1.
Given "pwwkew", the answer is "wke", with the length of 3
This is what I came up with, I think it works correctly, but the interviewer was not impressed and said that my solution may not work for all cases.
var str = "pwwkew";
var longSubstring = function(str) {
var obj = {}; //map object
var count = 0;
var c = []; //count array to keep the count so far
for (var i = 0; i < str.length; ++i) {
//check if the letter is already in the map
if (str[i] in obj && obj[str[i]] !== i) {
c.push(count); //we encountered repeat character, so save the count
obj = {};
obj[str[i]] = i;
count = 1;
continue;
} else {
obj[str[i]] = i;
++count;
}
}
return Math.max.apply(null, c);
}
console.log(longSubstring(str)); //prints 3
Can anyone tell me what's the problem with my solution? I think it is one of the best :) and also solves in O(n) time.

I guess the problem with your code is, it gets haywire when there is no repeating letters in the whole sentence to start with. As mentioned in one of the comments "abc" won't produce a correct result. My approach would be slightly different than yours as follows;
var str = "pwwkew",
data = Array.prototype.reduce.call(str, (p,c) => (p.test.includes(c) ? p.test = [c]
: p.test.length >= p.last.length ? p.test = p.last = p.test.concat(c)
: p.test.push(c)
, p), {last:[], test:[]}),
result = data.last.length;
console.log(data);
console.log(result);
In this reduce code we initially start with an object like {last:[], test:[]} and go over the characters one by one. If the received character is in our objects's test array we immediately reset the test array to include only the letter we tested (the p.test = [c] line). However If the received character is not in our test array then we do either one of the two thing as follows. If our test array's length is equal or longer than the last array's length then we add the current character to test array and make last array = test array. (p.test.length >= p.last.length ? p.test = p.last = p.test.concat(c) line). But if our test array's length is shorter than the last array's length we just add the current character to test array and continue likewise all the way to the end of the string one by one over single characters.

Related

How to print all characters by their index in JavaScript?

Just for exercising with indexOf() method and to have a better familiarity with how it works and what can be done with it, I desire to print the index for each character in a phrase, one by one.
I could do it successfully manually, this way:
// Print index for char of phrase (case insensitive):
let phrase = "ab";
alert(phrase.indexOf("a")); // 0
alert(phrase.indexOf("b")); // 1
alert(phrase.indexOf("z")); // -1
But when I tried to use a loop, I failed:
// Print char by index:
let phrase = "ab";
for (let i = -1; i < phrase.indexOf(); i++) {
alert(phrase.length[i]);
}
I also tried with regex to target each character inside phrase but also failed:
let phrase = "ab";
for (let i = -1; i < phrase.indexOf(/./); i++) {
alert(phrase.length[i]);
}
To clarify, my aim is to have a first alert with 0, and the second with 1. I just want to print the index of each character, instead the character length, as can easily be done with:
phrase.length // 2.
How could I print the index of each character in separate alerts?
It is super important for me to use indexOf() inside a loop to know this syntax.
You could do it this way :
for(let i=0; i<phrase.length; i++) {
console.log(phrase.indexOf(phrase[i]));
}
Don't use indexOf. You use this method to get the position (index) of a certain character in the string:
A string has a length property and can be iterated:
let phrase = "aaabbbaaaabbb"
function getAllIndices(stringInput) {
for (let i = 0; i < stringInput.length; i++) {
//you simply need to output i.
console.log(stringInput[i] + " > " + i);
}
}
getAllIndices(phrase);
While not always the case: the most common format for a for loop is:
for (let i = 0; i < someVariable.length; i++) { //...some code }
And indeed that's what you want here.
It means:
start at i being 0 (i = 0)
Do whatever is in my body (// ...some code)
increase i by 1 (i++)
Repeat, until i is equal to one less than the length of someVariable (someVariable.length) *
* That last step may seem kinda confusing. Remember: indices start at 0, but length count, well, counts normal - so starting at 1. So an string of length 3, say 'c'+'a'+'t'. is made up of characters at indices [0, 1, 2]. Sooo you want the loop, to stop at that last index - 2 - which is (always) one less that the length. (But honestly, this is overexplaining it. The short answer is: if you wanna loop through something - 99 times out of 100 you want that exact for loop format given up top.
So to look at your example: we wanna fix three things:
We want to have let i = 0; (remember it doesn't increment until it's run once
We want to say i < phrase.length; (i < phrase.indexOf() doesn't mean anything. phrase.indexOf() is a function, not a number.)
phrase.length means "tell me the length of the variable phrase." It just returns a number. (So phrase.length[i] doesn't mean anything). If you want to alert whatever letter is at this index, it's just phrase[i]. *
* If this syntax for getting a letter by just putting [#] after strings seems weird - well that's because it was more designed for arrays . If you have let arr = ['a', 'b', 'c'], then sure, I could see why the JavaScript devs said "let's make a quick way to get, oh, the value at index 2 of an array. How about just arr[2]? But for consistency, they said "yeah sure, let's give this ability to strings to." And if you think about it, a string is sort of just an array of letters.
Update: I just noticed you wanted the index of each letter, not the letter itselt. Well...technically you could just alert(i) then. That's it, that's the index. But if you wanted to know what letter we're talking about, I would do something like alert("Letter " + phrase[i] + " is at index " + i)
So our final answer is:
let phrase = "ab";
for (let i = 0; i < phrase.length; i++) {
alert("Letter " + phrase[i] + " is at index " + i)
}
It is super important for me to use indexOf() inside a loop to know this syntax.
You would never use indexOf() for something like this. It would not just be unnecessary, it would be wrong. If you had the string banana and tried to use indexOf() to (re)check each letters position, you would get 0,1,2,1,2,1. Why? Because indexOf() only returns the index of the first instance of that letter.
indexOf() is not something you'd likely pair with a for loop - at least not a for loop searching the same string. It is a loop itself. It's job is to prevent you from having to write a for loop to find the index of "b" in "ab". It will do it for you.
To print the index of each character in a loop you don't need indexOf()
let phrase = "ab";
for (let i = 0; i < phrase.length; i++) {
alert (i);
}
because i is the exact index you need.
In fact if you use indexOf() in a loop, that's what it looks like :
let phrase = "ab";
for (let i = 0; i < phrase.length; i++) {
alert (phrase.indexOf (phrase[i]));
}
Same result as before, but this time, instead of displaying i, you retrieve the current element in phrase and use indexOf() to get the already known index.
EDIT : indexOf() in this case work as expected because letters in phrase are different. But as SamVK said if let phrase = "banana";, it can't work because it will return always 1 for letter 'a' so you must not use it for that kind of loop.
I did not use let i = -1 because it is not an index of phrase. It is just what indexOf() returns if it can't find the element given.
I hope it helps.
If you absolutely want to use indexOf in a loop, you could do so :
let phrase = "ab";
Array.from(phrase).forEach(char => {
console.log(phrase.indexOf(char))
})
But this is pretty useless as there are simpler ways to accomplish this but since it looks like it is for a learning purpose, I hope this answer helps you!

Checking if combination of any amount of strings exists

I'm solving a puzzle and I have an idea of how to solve this problem, but I would like some guidance and hints.
Suppose I have the following, Given n amount of words to input, and m amount of word combos without spaces, I will have some functionality as the following.
4
this
is
my
dog
5
thisis // outputs 1
thisisacat // 0, since a or cat wasnt in the four words
thisisaduck // 0, no a or cat
thisismy // 1 this,is,my is amoung the four words
thisismydog // 1
My thoughts
First What I was thinking of doing is storing those first words into an array. After that, I check if any of those words is the first word of those 5 words
Example: check if this is in the first word thisis. It is! Great, now remove that this, from thisis to get simply just is, now delete the original string that corresponded to that equality and keep iterating over the left overs (now is,my,dog are available). If we can keep doing this process, until we get an empty string. We return 1, else return 0!
Are my thoughts on the right track? I think this would be a good approach (By the way I would like to implement this in javascript)
Sorting words from long to short may in some cases help to find a solution quicker, but it is not a guarantee. Sentences that contain the longest word might only have a solution if that longest word is not used.
Take for instance this test case:
Words: toolbox, stool, boxer
Sentence: stoolboxer
If "toolbox" is taken as a word in that sentence, then the remaining characters cannot be matched with other valid words. Yet, there is a solution, but only if the word "toolbox" is not used.
Solution with a Regular Expression
When regular expressions are allowed as part of the solution, then it is quite simple. For the above example, the regular expression would be:
^(toolbox|stool|boxer)*$
If a sentence matches that expression, it is a solution. If not, then not. This is quite straightforward, and doesn't really require an algorithm. All is done by the regular expression interpreter. Here is a snippet:
var words = ['this','is','a','string'];
var sentences = ['thisis','thisisastring','thisisaduck','thisisastringg','stringg'];
var regex = new RegExp('^(' + words.join('|') + ')*$');
sentences.forEach(sentence => {
// search returns a position. It should be 0:
console.log(sentence + ': ' + (sentence.search(regex) ? 'No' : 'Yes'));
});
But using regular expressions in an algorithm-challenge feels like cheating: you don't really write the algorithm, but rely on the regular expression implementation to do the job for you.
Without Regular Expressions
You could use this algorithm: first check whether a word matches at the start of the input sentence, and if so, remove that first occurrence from it. Then repeat this for the remaining part of the sentence. If this can be repeated until no characters are left over, you have a solution.
If characters are left over which cannot be matched with any word... well, then you cannot really conclude there is no solution for that sentence. It might be that some earlier made word choice was the wrong one, and there was an alternative. So to cope with that, your algorithm could backtrack and try other words.
This principle can be implemented through recursion. To gain memory-efficiency, you could leave the original sentence in-tact, and work with an index in that sentence instead.
The algorithm is implemented in arrow-function testString:
var words = ['this','is','a','string'];
var sentences = ['thisis','thisisastring','thisisaduck','thisisastringg','stringg'];
var testString = (words, str, i = 0) =>
i >= str.length || words.some( word =>
str.substr(i, word.length) == word && testString(words, str, i + word.length)
);
sentences.forEach(sentence => {
console.log(sentence + ': ' + (testString(words, sentence) ? 'Yes' : 'No'));
});
Or, the same in non-arrow-function syntax:
var words = ['this','is','a','string'];
var sentences = ['thisis','thisisastring','thisisaduck','thisisastringg','stringg'];
var testString = function (words, str, i = 0) {
return i >= str.length || words.some(function (word) {
return str.substr(i, word.length) == word
&& testString(words, str, i + word.length);
});
}
sentences.forEach(function (sentence) {
console.log(sentence + ': ' + (testString(words, sentence) ? 'Yes' : 'No'));
});
... and without some(), forEach() or ternary operator:
var words = ['this','is','a','string'];
var sentences = ['thisis','thisisastring','thisisaduck','thisisastringg','stringg'];
function testString (words, str, i = 0) {
if (i >= str.length) return true;
for (var k = 0; k < words.length; k++) {
var word = words[k];
if (str.substr(i, word.length) == word
&& testString(words, str, i + word.length)) {
return true;
}
}
}
for (var n = 0; n < sentences.length; n++) {
var sentence = sentences[n];
if (testString(words, sentence)) {
console.log(sentence + ': Yes');
} else {
console.log(sentence + ': No');
}
}
Take the 4 words, put them into a regex.
Use that regex to split each string.
Take the length of the resulting array (subtract one for the initial length of one).
var size = 'thisis'.split(/this|is|my|dog/).length - 1
Or if your list of words is an array
var search = new RegExp(words.join('|'))
var size = 'thisis'.split(search).length - 1
Either way you are splitting up the string by the list of words you have defined.
You can sort the words by length to ensure that larger words are matched first by
words.sort(function (a, b) { return b.length - a.length })
Here is the solution for anyone interested
var input = ['this','is','a','string']; // This will work for any input, but this is a test case
var orderedInput = input.sort(function(a,b){
return b.length - a.length;
});
var inputRegex = new RegExp(orderedInput.join('|'));
// our combonation of words can be any size in an array, just doin this since prompt in js is spammy
var testStrings = ['thisis','thisisastring','thisisaduck','thisisastringg','stringg'];
var foundCombos = (regex,str) => !str.split(regex).filter(str => str.length).length;
var finalResult = testStrings.reduce((all,str)=>{
all[str] = foundCombos(inputRegex,str);
if (all[str] === true){
all[str] = 1;
}
else{
all[str] = 0;
}
return all;
},{});
console.log(finalResult);

having an issue w/ .split in a JavaScript function that gets the max # of repeating chars per word in a string

I'm writing a JavaScript function that has to take in a string argument & determine the word or words with the maximum number or repeated (or most frequent) non sequential characters and return that word or words.
The way that I went about solving this problem was to first find the maximum number of times a character was repeated per word and record that number to use later in a function to test against every word in the string (or the array of strings as I later split it); if the word met the conditions, it's pushed into an array that I return.
My maxCount function seemed to work fine on its own but when I try to make it work together with my other function to get the words with max repeated chars returned, it's not working in JS Fiddle - it keeps telling me that "string.split is not a function" - I'll admit that the way I'm using it (string.split(string[i]).length) to analyze words in the string letter by letter is a bit unconventional - I hope there's some way to salvage some of my logic to make this work in the functions that can work together to get the results that I want.
Also, I don't know if I'm using Math.max correctly/in a "legal" way, I hope so. I've tried switching my variable name to "string" thinking that would make a difference but it did not even though my arguments are of the string variety and it's a string that's being represented.
Here's a link to my Fiddle:
https://jsfiddle.net/Tamara6666/rdwxqoh6/
Here's my code:
var maxCount = function (word) {
/// var maxRepeats = 0;
var numArray = [];
var string = word;
for (var i = 0, len = string.length; i < len; i++) {
//split the word('string') into letters at the index of i
numArray.push((string.split(string[i]).length) -1);
}
var max = Math.max(...numArray);
return max;
}
///console.log(maxCount("xxxxxxxxxxxxx"));
var LetterCount = function(string){
var repeatedChars = 0;
var wordArray=[];
var stringArray = string.split(" ");
for (var i = 0; i < stringArray.length; i++){
var eachWord = stringArray[i];
var maxRepeats = maxCount(stringArray);
if (repeatedChars < maxRepeats) {
repeatedChars = maxRepeats;
wordArray = [eachWord];
}else if (repeatedChars == maxRepeats) {
wordArray.push(eachWord);
}
}
return wordArray;
};
console.log(LetterCount("I attribute my success to cats"));
//should return ["attribute", "success"]
*** I've tried to map this first function onto the array formed when I split my string at the spaces but it is just returned me an empty array (I also might not have been using map correctly in this example); I also have tried using valueOf to extract the primitive value out of the array from the first function which also didn't work. I'm not really sure what to do at this point or what angle to take- I feel if I understood more what was going wrong I could more easily go about fixing it. Any help would be much appreciated. Thanks!
You are passing an array to maxCount at line 20, while it expects a string:
var maxRepeats = maxCount(stringArray);
You should use:
var maxRepeats = maxCount(eachWord);
If you are getting split is not a function error then first make sure that your string isn't null by printing it on console. If it isn't null then confirm that its a string not an array or some other thing.

Using RegExp to loop through arrays instead of for loops (Javascript)

I have the below code which works. But would using RegExp be a more efficient code?
(Return true if the string in the first element of the array contains the letters of the string in the second element of the array.)
function mutation(arr) {
var first = arr[0].split("");
var second = arr[1].split("");
var answer = false;
for (var e = 0; e < second.length; e++) {
answer = false;
for (var i = 0; i < first.length; i++) {
if (first[i] === second[e]) {
answer = true;
}
}
}
return answer;
}
mutation(['hello', 'hey']);
Using regex to do this check as the code you've shown above:
var a = 'hello how are you';
var b ='hey you there';
var result = new RegExp(b.split(/ +/)[0].split('').join('|')).test( a.split(/ +/)[0] );
//=> true
First split(/ +/)[0] is used to get first word from both strings
Then split('') is used to get each letter from first word of 2nd input
join('|') is used to join them by | to get regex as h|e|y
new RegExp is used for construct a regex object
test is used to execute the regex
In ES6 it can be written as follows:
function mutation(lettersSource, checkedString) {
var referenceLetters = new Set(lettersSource); // get letters from string
var usedLetters = new Set(checkedString);
// create union of letters in both strings
var lettersUnion = new Set([...usedLetters, ...referenceLetters]);
return lettersUnion.size === referenceLetters.size;
}
This solution is O(m+n+k) where m and n is number of letters in strings, and k is number of used letters.
Your solution is O(m*n), quite inefficient.
In ES5 Object can be used as well as dictionary to collect only letters, but it will be slower for many possible letters (Unicode strings).
If you don't have to care about memory usage, you can create dense array using .charCodeAt to index elements in array for O(1) access to checking if elements exists (O(m+n+k)).

Check if two Strings share a common substring in JavaScript

Is there any fast way in JavaScript to find out if 2 Strings contain the same substring? e.g. I have these 2 Strings: "audi is a car" and "audiA8".
As you see the word "audi" is in both strings but we cannot find it out with a simple indexOf or RegExp, because of other characters in both strings.
The standard tool for doing this sort of thing in Bioinformatics is the BLAST program. It is used to compare two fragments of molecules (like DNA or proteins) to find where they align with each other - basically where the two strings (sometimes multi GB in size) share common substrings.
The basic algorithm is simple, just systematically break up one of the strings into pieces and compare the pieces with the other string. A simple implementation would be something like:
// Note: not fully tested, there may be bugs:
function subCompare (needle, haystack, min_substring_length) {
// Min substring length is optional, if not given or is 0 default to 1:
min_substring_length = min_substring_length || 1;
// Search possible substrings from largest to smallest:
for (var i=needle.length; i>=min_substring_length; i--) {
for (j=0; j <= (needle.length - i); j++) {
var substring = needle.substr(j,i);
var k = haystack.indexOf(substring);
if (k != -1) {
return {
found : 1,
substring : substring,
needleIndex : j,
haystackIndex : k
}
}
}
}
return {
found : 0
}
}
You can modify this algorithm to do more fancy searches like ignoring case, fuzzy matching the substring, look for multiple substrings etc. This is just the basic idea.
Take a look at the similar text function implementation here. It returns the number of matching chars in both strings.
For your example it would be:
similar_text("audi is a car", "audiA8") // -> 4
which means that strings have 4-char common substring.
Don't know about any simpler method, but this should work:
if(a.indexOf(substring) != -1 && b.indexOf(substring) != -1) { ... }
where a and b are your strings.
var a = "audi is a car";
var b = "audiA8";
var chunks = a.split(" ");
var commonsFound = 0;
for (var i = 0; i < chunks.length; i++) {
if(b.indexOf(chunks[i]) != -1) commonsFound++;
}
alert(commonsFound + " common substrings found.");

Categories