So I'm following a algorithms course and it asked to implement the following function:
Write a function called findLongestSubstring, which accepts a string and returns the length of the longest substring with all distinct characters.
findLongestSubstring('') // 0
findLongestSubstring('bbbbbb') // 1
findLongestSubstring('longestsubstring') // 8
And said it has to have a time complexity of o(n).
So I could not come up with a o(n) solution, but a supposed (n^2)... my naive solution:
function findLongestSubstring(str: string): number {
const array = str.split("");
let maxLength = 0;
for (let i = 0; i < array.length; i++) {
const letterArray = [array[i]];
for (let j = i + 1; j < array.length; j++) {
if (letterArray.find(e => e === array[j])) {
if (letterArray.length > maxLength) maxLength = letterArray.length;
break;
} else {
letterArray.push(array[j])
if (j === array.length - 1) {
if (letterArray.length > maxLength) maxLength = letterArray.length;
break;
}
}
}
}
return maxLength;
}
Then I went to check his solution:
function findLongestSubstring(str: string) {
let longest = 0;
let seen = {};
let start = 0;
for (let i = 0; i < str.length; i++) {
let char = str[i];
if (seen[char]) {
start = Math.max(start, seen[char]);
}
// index - beginning of substring + 1 (to include current in count)
longest = Math.max(longest, i - start + 1);
// store the index of the next char so as to not double count
seen[char] = i + 1;
}
return longest;
}
So I do agree that my solution which has a nested for loop appears to be O(n2) and his solution o(n). But when I tested both algorithms with a really big string to my surprise my algorithm was acctually faster and i have NO idea why... can someone enlight me?
The way Im testing:
test("really big string to test bigO", () => {
const alphabet = "abcdefghijklmnopqrstuvwxyz"
const randomLettersArray = Array.from({ length: 100000000 }, () => alphabet[Math.floor(Math.random() * alphabet.length)]);
const randomBigString = randomLettersArray.join("")
const start = Date.now();
findLongestSubstring(randomBigString);
const end = Date.now();
console.log(`Execution time: ${end - start} ms`);
})
Your nested loop solution looks like it could take O(n2) time, but it's actually limited by the longest possible valid substring, and that is limited by the size of the alphabet.
Your test uses a small alphabet -- only 26 characters. Your inner loop has a maximum of 26 iterations in this case, and that just might be faster than the other algorithm in some environments.
If you were to use a much larger alphabet -- say 10000 characters or so, then your algorithm would be much slower, but the other one would not slow down much at all.
Let us condider all longest subtrings (meeting the distinctness condition) that end at the successive positions:
l -> l
lo -> lo
lon -> lon
long -> long
longe -> longe
longes -> longes
longest -> longest
longests -> ts
longestsu -> tsu
longestsub -> tsub
longestsubs -> ubs
longestsubst -> ubst
longestsubstr -> usbtr
longestsubstri -> ubstri
longestsubstrin -> ubstrin
longestsubstring -> ubstring
You will notice that the index of the first character never descreases. Every time one appends a new character, the starting index either does not change, or it moves past the position of the same character in the substring.
So the following loop could do:
the substring is empty
for all ending positions in the string:
append the new character to the substring
move the starting position past the same character in the substring, if any
But we are not done yet: if the new character is found in the substring, the starting cursor will stop there; but if it is not found, we need to traverse the whole substring for nothing, and this can make the complexity quadratic.
So we need an extra device to tell us if the substring contains the new character without traversing the substring. As the alphabet has a finite size, we can do with an array of booleans which will act as counters. Initially all booleans are zero; when a character enters/exits the substring, its entry in the array is set/reset.
Hence the whole search can be performed in linear time. Of course the solution is the length of the longest string found during this process.
all booleans are initially reset
the substring is empty
for all ending positions in the string:
append the new character to the substring
if the boolean for this character is set:
move the starting position past the same character in the substring,
while resetting the booleans of all characters met
set the boolean for this character
update the longest length so far
For my assignment I am supposed to be able to take user inputted text and reverse it. My code works for the most part, but my inverted string starts with NaN. I'm pretty sure this is because my loop does an initial run before user input is collected. I tried to fix this with an if statement:
if (typeof length == 'number') {
//myLoop
}
This only made things worse however. I need to find a work around so that my inverted string does not start with NaN.
var userName = prompt("Enter your first and last name in lower case");
var length = userName.length;
var reverse;
for (var i = length; i >= 0; i--) {
reverse += userName[i];
}
I have tried enclosing my loop within the if statement, and placing my if statement inside the loop; neither works.
Firstly, you need to start reverse with an empty string. Secondly, you have to bear in mind that the index pointer on a string is zero-based, so the last character's position is always one less than the length of the string.
Thus,
var userName = prompt("Enter your first and last name in lower case");
var len = userName.length;
var reverse = "";
for (var i = len - 1; i >= 0; i--) {
reverse += userName[i];
}
console.log(reverse);
Your loop is incorrect. You're starting at an index equal to length, when actually the index range is 0 to length-1
In JS, you can reverse a string in a single line of code.
var userName = prompt("Enter your first and last name in lower case");
var reverse = userName.split('').reverse().join('')
console.log(reverse);
My purpose is to punch multiple strings into a single (shortest) string that will contain all the character of each string in a forward direction. The question is not specific to any language, but more into the algorithm part. (probably will implement it in a node server, so tagging nodejs/javascript).
So, to explain the problem:
Let's consider I have few strings
["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"]
The Resultant string should be something like:
"sjmachppoalidveonrk"
jack: sjmachppoalidveonrk
apple: sjmachppoalidveonrk
solid: sjmachppoalidveonrk
====================================>>>> all in the forward direction
These all are manual evaluation and the output may not 100% perfect in the example.
So, the point is all the letters of each string have to exist in the output in
FORWARD DIRECTION (here the actual problem belongs), and possibly the server will send the final strings and numbers like 27594 will be generated and passed to extract the token, in the required end. If I have to punch it in a minimal possible string it would have much easier (That case only unique chars are enough). But in this case there are some points:
Letters can be present multiple time, though I have to reuse any
letter if possible, eg: for solid and hold o > l > d can be
reused as forward direction but for apple (a > p) and spark
(p > a) we have to repeat a as in one case it appears before p
for apple, and after p for sparks so either we need to repeat
a or p. Even, we cannot do p > a > p as it will not cover both the case
because we need two p after a for apple
We directly have no option to place a single p and use the same
index twice in a time of extract, we need multiple p with no option
left as the input string contains that
I am (not) sure, that there is multiple outputs possible for a set of
strings. but the concern is it should be minimal in length,
the combination doesn't matter if its cover all the tokens in a forward direction. all (or one ) outputs of minimal possible length
need to trace.
Adding this point as an EDIT to this post. After reading the comments and knowing that it's already an existing
problem is known as shortest common supersequence problem we can
define that the resultant string will be the shortest possible
string from which we can re generate any input string by simply
removing some (0 to N) chars, this is same as all inputs can be found in a forward direction in the resultant string.
I have tried, by starting with an arbitrary string, and then made an analysis of next string and splitting all the letters, and place them accordingly, but after some times, it seems that current string letters can be placed in a better way, If the last string's (or a previous string's) letters were placed according to the current string. But again that string was analysed and placed based on something (multiple) what was processed, and placing something in the favor of something that is not processed seems difficult because to that we need to process that. Or might me maintaining a tree of all processed/unprocessed tree will help, building the building the final string? Any better way than it, it seems a brute force?
Note: I know there are a lot of other transformation possible, please try not to suggest anything else to use, we are doing a bit research on it.
I came up with a somewhat brute force method. This way finds the optimal way to combine 2 words then does it for each element in the array.
This strategy works by trying finding the best possible way to combine 2 words together. It is considered the best by having the fewest letters. Each word is fed into an ever growing "merged" word. Each time a new word is added the existing word is searched for a matching character which exists in the word to be merged. Once one is found both are split into 2 sets and attempted to be joined (using the rules at hand, no need 2 add if letter already exists ect..). The strategy generally yields good results.
The join_word method takes 2 words you wish to join, the first parameter is considered to be the word you wish to place the other into. It then searches for the best way to split into and word into 2 separate parts to merge together, it does this by looking for any shared common characters. This is where the splits_on_letter method comes in.
The splits_on_letter method takes a word and a letter which you wish to split on, then returns a 2d array of all the possible left and right sides of splitting on that character. For example splits_on_letter('boom', 'o') would return [["b","oom"],["bo","om"],["boo","m"]], this is all the combinations of how we could use the letter o as a split point.
The sort() at the beginning is to attempt to place like elements together. The order in which you merge the elements generally effects the results length. One approach I tried was to sort them based upon how many common letters they used (with their peers), however the results were varying. However in all my tests I had maybe 5 or 6 different word sets to test with, its possible with a larger, more varying word arrays you might find different results.
Output is
spmjhooarckpplivden
var words = ["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"];
var result = minify_words(words);
document.write(result);
function minify_words(words) {
// Theres a good sorting method somewhere which can place this in an optimal order for combining them,
// hoever after quite a few attempts i couldnt get better than just a regular sort... so just use that
words = words.sort();
/*
Joins 2 words together ensuring each word has all its letters in the result left to right
*/
function join_word(into, word) {
var best = null;
// straight brute force each word down. Try to run a split on each letter and
for(var i=0;i<word.length;i++) {
var letter = word[i];
// split our 2 words into 2 segments on that pivot letter
var intoPartsArr = splits_on_letter(into, letter);
var wordPartsArr = splits_on_letter(word, letter);
for(var p1=0;p1<intoPartsArr.length;p1++) {
for(var p2=0;p2<wordPartsArr.length;p2++) {
var intoParts = intoPartsArr[p1], wordParts = wordPartsArr[p2];
// merge left and right and push them together
var result = add_letters(intoParts[0], wordParts[0]) + add_letters(intoParts[1], wordParts[1]);
if(!best || result.length <= best.length) {
best = result;
}
}
}
}
// its possible that there is no best, just tack the words together at that point
return best || (into + word);
}
/*
Splits a word at the index of the provided letter
*/
function splits_on_letter(word, letter) {
var ix, result = [], offset = 0;;
while((ix = word.indexOf(letter, offset)) !== -1) {
result.push([word.substring(0, ix), word.substring(ix, word.length)]);
offset = ix+1;
}
result.push([word.substring(0, offset), word.substring(offset, word.length)]);
return result;
}
/*
Adds letters to the word given our set of rules. Adds them starting left to right, will only add if the letter isnt found
*/
function add_letters(word, addl) {
var rIx = 0;
for (var i = 0; i < addl.length; i++) {
var foundIndex = word.indexOf(addl[i], rIx);
if (foundIndex == -1) {
word = word.substring(0, rIx) + addl[i] + word.substring(rIx, word.length);
rIx += addl[i].length;
} else {
rIx = foundIndex + addl[i].length;
}
}
return word;
}
// For each of our words, merge them together
var joinedWords = words[0];
for (var i = 1; i < words.length; i++) {
joinedWords = join_word(joinedWords, words[i]);
}
return joinedWords;
}
A first try, not really optimized (183% shorter):
function getShort(arr){
var perfect="";
//iterate the array
arr.forEach(function(string){
//iterate over the characters in the array
string.split("").reduce(function(pos,char){
var n=perfect.indexOf(char,pos+1);//check if theres already a possible char
if(n<0){
//if its not existing, simply add it behind the current
perfect=perfect.substr(0,pos+1)+char+perfect.substr(pos+1);
return pos+1;
}
return n;//continue with that char
},-1);
})
return perfect;
}
In action
This can be improved trough simply running the upper code with some variants of the array (200% improvement):
var s=["jack",...];
var perfect=null;
for(var i=0;i<s.length;i++){
//shift
s.push(s.shift());
var result=getShort(s);
if(!perfect || result.length<perfect.length) perfect=result;
}
In action
Thats quite close to the minimum number of characters ive estimated ( 244% minimization might be possible in the best case)
Ive also wrote a function to get the minimal number of chars and one to check if a certain word fails, you can find them here
I have used the idea of Dynamic programming to first generate the shortest possible string in forward direction as stated in OP. Then I have combined the result obtained in the previous step to send as a parameter along with the next String in the list. Below is the working code in java. Hope this would help to reach the most optimal solution, in case my solution is identified to be non optimal. Please feel free to report any countercases for the below code:
public String shortestPossibleString(String a, String b){
int[][] dp = new int[a.length()+1][b.length()+1];
//form the dynamic table consisting of
//length of shortest substring till that points
for(int i=0;i<=a.length();i++){
for(int j=0;j<=b.length();j++){
if(i == 0)
dp[i][j] = j;
else if(j == 0)
dp[i][j] = i;
else if(a.charAt(i-1) == b.charAt(j-1))
dp[i][j] = 1+dp[i-1][j-1];
else
dp[i][j] = 1+Math.min(dp[i-1][j],dp[i][j-1]);
}
}
//Backtrack from here to find the shortest substring
char[] sQ = new char[dp[a.length()][b.length()]];
int s = dp[a.length()][b.length()]-1;
int i=a.length(), j=b.length();
while(i!=0 && j!=0){
// If current character in a and b are same, then
// current character is part of shortest supersequence
if(a.charAt(i-1) == b.charAt(j-1)){
sQ[s] = a.charAt(i-1);
i--;
j--;
s--;
}
else {
// If current character in a and b are different
if(dp[i-1][j] > dp[i][j-1]){
sQ[s] = b.charAt(j-1);
j--;
s--;
}
else{
sQ[s] = a.charAt(i-1);
i--;
s--;
}
}
}
// If b reaches its end, put remaining characters
// of a in the result string
while(i!=0){
sQ[s] = a.charAt(i-1);
i--;
s--;
}
// If a reaches its end, put remaining characters
// of b in the result string
while(j!=0){
sQ[s] = b.charAt(j-1);
j--;
s--;
}
return String.valueOf(sQ);
}
public void getCombinedString(String... values){
String sSQ = shortestPossibleString(values[0],values[1]);
for(int i=2;i<values.length;i++){
sSQ = shortestPossibleString(values[i],sSQ);
}
System.out.println(sSQ);
}
Driver program:
e.getCombinedString("jack", "apple", "maven", "hold",
"solid", "mark", "moon", "poor", "spark", "live");
Output:
jmapphsolivecparkonidr
Worst case time complexity of the above solution would be O(product of length of all input strings) when all strings have all characters distinct and not even a single character matches between any pair of strings.
Here is an optimal solution based on dynamic programming in JavaScript, but it can only get through solid on my computer before it runs out of memory. It differs from #CodeHunter's solution in that it keeps the entire set of optimal solutions after each added string, not just one of them. You can see that the number of optimal solutions grows exponentially; even after solid there are already 518,640 optimal solutions.
const STRINGS = ["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"]
function map(set, f) {
const result = new Set
for (const o of set) result.add(f(o))
return result
}
function addAll(set, other) {
for (const o of other) set.add(o)
return set
}
function shortest(set) { //set is assumed non-empty
let minLength
let minMatching
for (const s of set) {
if (!minLength || s.length < minLength) {
minLength = s.length
minMatching = new Set([s])
}
else if (s.length === minLength) minMatching.add(s)
}
return minMatching
}
class ZipCache {
constructor() {
this.cache = new Map
}
get(str1, str2) {
const cached1 = this.cache.get(str1)
if (!cached1) return undefined
return cached1.get(str2)
}
set(str1, str2, zipped) {
let cached1 = this.cache.get(str1)
if (!cached1) {
cached1 = new Map
this.cache.set(str1, cached1)
}
cached1.set(str2, zipped)
}
}
const zipCache = new ZipCache
function zip(str1, str2) {
const cached = zipCache.get(str1, str2)
if (cached) return cached
if (!str1) { //str1 is empty, so only choice is str2
const result = new Set([str2])
zipCache.set(str1, str2, result)
return result
}
if (!str2) { //str2 is empty, so only choice is str1
const result = new Set([str1])
zipCache.set(str1, str2, result)
return result
}
//Both strings start with same letter
//so optimal solution must start with this letter
if (str1[0] === str2[0]) {
const zipped = zip(str1.substring(1), str2.substring(1))
const result = map(zipped, s => str1[0] + s)
zipCache.set(str1, str2, result)
return result
}
//Either do str1[0] + zip(str1[1:], str2)
//or str2[0] + zip(str1, str2[1:])
const zip1 = zip(str1.substring(1), str2)
const zip2 = zip(str1, str2.substring(1))
const test1 = map(zip1, s => str1[0] + s)
const test2 = map(zip2, s => str2[0] + s)
const result = shortest(addAll(test1, test2))
zipCache.set(str1, str2, result)
return result
}
let cumulative = new Set([''])
for (const string of STRINGS) {
console.log(string)
const newCumulative = new Set
for (const test of cumulative) {
addAll(newCumulative, zip(test, string))
}
cumulative = shortest(newCumulative)
console.log(cumulative.size)
}
console.log(cumulative) //never reached
I'm totally not a Math whiz kid here, but have put together a function with the great help of StackOverflow (and a lot of trial and error) that generates a random serial number from a Formula, group of Letters/Numbers, and array (so as to not duplicate values).
So, my current formula is as follows:
$.extend({
generateSerial: function(formula, chrs, checks) {
var formula = formula && formula != "" ? formula : 'XXX-XXX-XXX-XXX-XXX', // Default Formula to use, should change to what's most commonly used!
chrs = chrs && chrs != "" ? chrs : "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", // Default characters to randomize, if not defined!
len = (formula.match(/X/g) || []).length,
indices = [],
rand;
// Get all "-" char indexes
for(var i=0; i < formula.length; i++) {
if (formula[i] === "-") indices.push(i);
}
do {
rand = Array(len).join().split(',').map(function() {
return chrs.charAt(Math.floor(Math.random() * chrs.length));
}).join('');
// Rebuild string!
if (indices && indices.length > 0)
{
for(var x=0; x < indices.length; x++)
rand = rand.insert(indices[x], '-');
}
} while (checks && $.inArray(rand, checks) !== -1);
return rand;
}
});
Ok, so, what I need to be able to do is to find total possible values and make sure that it is possible to generate a unique serial number before actually doing so.
For example:
var num = $.generateSerial('XX', 'AB', new Array('AB', 'BA', 'AA', 'BB'));
This will cause the code to do an infinite loop, since there are no more possibilties here, other than the ones being excluded from the extension. So this will cause browser to crash. What I need to be able to do here is to be able to get the number of possible unique values here and if it is greater than 0, continue, otherwise, don't continue, maybe an alert for an error would be fine.
Also, keep in mind, could also do this in a loop so as to not repeat serials already generated:
var currSerials = [];
for (var x = 0; x < 5; x++)
{
var output = $.generateSerial('XXX-XXX-XXX', '0123456789', currSerials);
currSerials.push(output);
}
But the important thing here, is how to get total possible unique values from within the generateSerial function itself? We have the length, characters, and exclusions array also in here (checks). This would seem more like a math question, and I'm not expert in Math. Could use some help here.
Thanks guys :)
Here is a jsFiddle of it working nicely because there are more possible choices than 16: http://jsfiddle.net/qpw66bwb/1/
And here is a jsFiddle of the problem I am facing: Just click the "Generate Serials" button to see the problem (it continuously loops, never finishes), it wants to create 16 serials, but 16 possible choices are not even possible with 2 characters and only using A and B characters: http://jsfiddle.net/qpw66bwb/2/
I need to catch the loop here and exit out of it, if it is not able to generate a random number somehow. But how?
The number of possible serials is len * chrs.length, assuming all the characters in chrs are different. The serial contains len characters to fill in randomly, and chrs.length is the number of possible characters in each position of that.
var number = 342345820139586830203845861938475676
var output = []
var sum = 0;
while (number) {
output.push(number % 10);
number = Math.floor(number/10);
}
output = output.reverse();
function addTerms () {
for (i = 0; i < output.length; i=i+2) {
var term = Math.pow(output[i], output[i+1]);
sum += term;
}
return sum;
}
document.write(output);
document.write("<br>");
document.write(addTerms());
I am trying to take that large number and split it into its digits. Then, find the sum of the the first digit raised to the power of the 2nd, 3rd digit raiseed to the 4th, 5th raised to the 6th and so on. for some reason, my array is returning weird digits, causing my sum to be off. the correct answer is 2517052. Thanks
You're running into precision issues within JavaScript. Just evaluate the current value of number before you start doing anything, and the results may surprise you:
>>> var number = 342345820139586830203845861938475676; number;
3.423458201395868e+35
See also: What is JavaScript's highest integer value that a Number can go to without losing precision?
To resolve your issue, I'd store your input number as an array (or maybe even a string), then pull the digits off of that.
This will solve your calculation with the expected result of 2517052:
var number = "342345820139586830203845861938475676";
var sum = 0;
for(var i=0; i<number.length; i=i+2){
sum += Math.pow(number.charAt(i), number.charAt(i+1));
}
sum;
JavaScript stores numbers in floating point format (commonly double). double can store precisely only 15 digits.
You can use string to store this large number.
As mentioned, this is a problem with numeric precision. It applies to all programming languages that use native numeric formats. Your problem works fine if you use a string instead
var number = '342345820139586830203845861938475676'
var digits = number.split('')
var total = 0
while (digits.length > 1) {
var [n, power] = digits.splice(0, 2)
total += Math.pow(n, power)
}
(the result is 2517052, byt the way!)
Cast the number as a string and then iterate through it doing your math.
var number = "342345820139586830203845861938475676";//number definition
var X = 0;//some iterator
var numberAtX = 0 + number.charAt(X);//number access
The greatest integer supported by Javascript is 9007199254740992. So that only your output is weird.
For Reference go through the link http://ecma262-5.com/ELS5_HTML.htm#Section_8.5
[edit] adjusted the answer based on Borodins comment.
Mmm, I think the result should be 2517052. I'd say this does the same:
var numbers = '342345820139586830203845861938475676'.split('')
,num = numbers.splice(0,2)
,result = Math.pow(num[0],num[1]);
while ( (num = numbers.splice(0,2)) && num.length ){
result += Math.pow(num[0],num[1]);
}
console.log(result); //=> 2517052
The array methods map and reduce are supported in modern browsers,
and could be worth defining in older browsers. This is a good opportunity,
if you haven't used them before.
If you are going to make an array of a string anyway,
match pairs of digits instead of splitting to single digits.
This example takes numbers or strings.
function sumPower(s){
return String(s).match(/\d{2}/g).map(function(itm){
return Math.pow(itm.charAt(0), itm.charAt(1));
}).reduce(function(a, b){
return a+b;
});
}
sumPower('342345820139586830203845861938475676');
alert(sumPower(s))
/*
returned value:(Number)
2517052
*/