I'm learning to analyze space complexity, but I'm confused of analyzing an array vs an object in JS. So I'd like to get some help here.
ex1. array []
int[] table = new int[26];
for (int i = 0; i < s.length(); i++) {
table[s.charAt(i) - 'a']++;
}
ex1. is from an example online, and it says the space complexity is O(1) because the table's size stays constant.
ex2. object {}
let nums[0,1,2,3,4,5], map = {};
for (let i = 0; i < nums.length; i++) {
map[ nums[i] ] = i;
}
I think ex2. uses O(n) because the map object is accessed 6 times. However, if I use the concept learned from ex1., the space complexity should be O(1)? Any ideas where I went wrong?
From the complexity analysis point of view, in ex 1, the complexity is O(1) because the array size doesn't increase. Because you are initializing the table to a fixed size of 26 (Looks like you are counting the characters in a string?).
See the below example that keeps track of counts of a alphabets in a string (Only small letters for clarity). In this case the length of array which tracks the count of alphabets never change even if the string changes its length.
function getCharacterCount(s){
const array = new Int8Array(26);
for (let i = 0; i < s.length; i++) {
array[s.charCodeAt(i) - 97]++;
}
return array;
}
Now let's change the implementation to map instead. Here the size of the map increases as and when a new character is encountered in the string.So
Theoretically speaking, the space complexity is O(n).
But in reality, we started with map with length 0 (0 keys) and it doesn't go beyond 26. If the string doesn't contain all the characters, the space taken would be much lesser than an array as in previous implementation.
function getCharacterCountMap(s){
const map = {};
for (let i = 0; i < s.length; i++) {
const charCode = s.charCodeAt(i) - 97;
if(map[charCode]){
map[charCode] = map[charCode] + 1
}else{
map[charCode] = 0;
}
}
return map;
}
function getCharacterCount(s){
const array = new Int8Array(26);
for (let i = 0; i < s.length; i++) {
array[s.charCodeAt(i) - 97]++;
}
return array;
}
function getCharacterCountMap(s){
const map = {};
for (let i = 0; i < s.length; i++) {
const charCode = s.charCodeAt(i) - 97;
if(map[charCode]){
map[charCode] = map[charCode] + 1
}else{
map[charCode] = 1;
}
}
return map;
}
console.log(getCharacterCount("abcdefabcedef"));
console.log(getCharacterCountMap("abcdefabcdef"));
Related
Edit: Actually the logic is wrong here.
I solved it using Python3 with a dictionary that updates the last index at which a letter is seen. In dynamic programming lingo, it is similar to L.I.S (longest increasing subsequence).
If anyone knows how to solve this without using a dictionary, please comment because I learned DP in school and those lessons only used arrays so it should be possible with just arrays.
Original question:
I am trying Leetcode, 3. Longest Substring Without Repeating Characters.
I can solve this in Python making a 2D table for dynamic programming.
But in JavaScript which I am sort of new to, I am getting an error.
evalmachine.<anonymous>:41
var top = T[i-1][j]
^
TypeError: Cannot read property '1' of undefined
at lengthOfLongestSubstring (evalmachine.<anonymous>:4
My code:
/**
* #param {string} s
* #return {number}
*/
var lengthOfLongestSubstring = function(s) {
//empty string
if (s.length <= 0){
return 0
}
//initialize dict
var dict = {};
//initialize 2D table T
var T = new Array(s.length)
for (var i = 0; i<s.length; i++){
T[i] = new Array(s.length);
}
//base cases are diagonals
for (var i = 0; i < T.length; i++){
for (var j=0; j<T.length; j++){
if(i==j){
T[i][j] = 1;
}
else{
T[i][j] = 0;
}
}
}
//put base case in dict
//dict[s[0]]=1
for (var i=0; i < s.length; i++){
for (var j=i+1; j<s.length; j++){
var row_char = s.charAt(i);
var col_char = s.charAt(j);
if (row_char==col_char){
T[i][j] = 1;
}
else{
//console.log("j",j,T)
var left = T[i][j-1]
console.log(left)
var top = T[i-1][j]
console.log(top)
var bigger = Math.max(left,top);
T[i][j] = bigger + 1
}
}
}
//iterate each row to get max
var high = Number.MIN_SAFE_INTEGER;
for (var i = 0; i < s.length; i++){
if(T[i][s.length-1] > high){
high = T[i][s.length-1];
}
}
return high;
};
It is letting me fill the table with 0's and base case of 1 indexing like T[i][j] but then complaining about indexing like that to get the value which I don't understand.
I looked at this: How to get value at a specific index of array In JavaScript?
But it does not really say anything different.
On the first iteration of the loop following the //put base case in dict comment i is 0.
You're then attempting to access T[i-1][j], which is the equivalent of T[-1][j].
Because T doesn't have a -1 index, T[-1] resolves to undefined, upon which you attempt to access index [j] and you get the error you're seeing.
I need to find the value of a 2-dimensional array and return its indexes. For example if my searched term is in array[i][j], then I want [i, j] returned.
Naturally, I came up with this simple solution:
function find(str){
for(let o = 0; o < array.length; o++){
for(let k = 0; k < array[o].length; k++){
if(array[o][k] == str){
return [o, k];
}
}
}
}
Now I need to use this method a couple hundred times as part of an algorithm, and it gets quite time-costy. Is there a more efficient way?
I have created a simple full example including a 'benchmark':
// setup to hide foo in an array
var array = [];
for(let i = 0; i < 100; i++){
array.push([])
for(let j = 0; j < 100; j++){
if(i == 99 && j == 99) array[i].push("foo"); // intentionally hiding the searched term at the worst-case position for the algorithm of find()
else array[i].push("bar");
}
}
// function to find foo
function find(str){
for(let o = 0; o < array.length; o++){
for(let k = 0; k < array[o].length; k++){
if(array[o][k] == str){
return [o, k];
}
}
}
}
// lets say we need to find foo 200 times
var a = window.performance.now();
for(let i = 0; i < 200; i++){
console.log(i, find("foo")); // if you're happy and you know it, tell us what you found
}
var b = window.performance.now();
// print performance result
$('body').html((b-a) + " ms");
JSfiddle for the benchmark example: http://jsfiddle.net/3t0db1cq/11/
(note: in that benchmark example I searched 'foo' 200 times, so you may ask why I don't simply cache it. In reality I will search different terms, so caching will barely improve the performance. Also I intentionally did put the searched term in the worst-case position of the array for this benchmark)
Can you help me find a better algorithm for find()? To be fair for the performance test, re-position the searched term at the worst-case position in the array for your algorithm, if you want to compare the results.
(Target for this is websites, so all common browsers should support it)
Seems to me that you are mapping a key (pair of integers) to a string value and you want to return the key for that value.
As you are using an array each search operation is always O(n^2) worse case, there's no "smart" way using that datastructure
As #Richrd said, you can build a reverse mapping from the string values to a pair of integers and search that. Easy way is to use a javascript Map() (hash map). Though you may want to look into a trie implementation for the string to integer map.
But this begs the question: if you are performing a lot of these reverse lookups then why store this data as a 2d-array of strings in the first place? You could save more time by storing this data as a map of strings to ints in the first place.
If you are familiar with SQL one way of doing this is to use sqllite. It is a very easy way to run sql in the browser.
https://www.tutorialspoint.com/html5/html5_web_sql.htm
Unfortunately it is not supported on all browsers so you would have to have a general idea of your audience.
Alternatively as long as all of your values are different you could reverse map your array and then search as much as you want with no cost. For instance:
// setup to hide foo in an array
var array = [];
for(let i = 0; i < 100; i++){
array.push([])
for(let j = 0; j < 100; j++){
if(i == 99 && j == 99) array[i].push("foo"); // intentionally hiding the searched term at the worst-case position for the algorithm of find()
else array[i].push("bar");
}
}
//Create your reverse mapped array. This only runs once at startup, but now allows you to
function buildReverse(arr) {
var reverseArr = {};
for(let o = 0; o < arr.length; o++){
for(let k = 0; k < arr[o].length; k++){
reverseArr[arr[o][k]] = [o, k];
}
}
return reverseArr
}
var reverseArr = buildReverse(array);
function find(str){
if (reverseArr[str] != undefined) {
return reverseArr[str];
// or
//return [reverseArr[str][0], reverseArr[str][1]]
//, etc...
}
return "";
}
// lets say we need to find foo 200 times
var a = window.performance.now();
for(let i = 0; i < 200; i++){
console.log(i, find("foo")); // if you're happy and you know it, tell us what you found
}
var b = window.performance.now();
// print performance result
$('body').html((b-a) + " ms");
In principle this question can be answered language-independent, but specifically I am looking for a Javascript implementation.
Are there any libraries that allow me to measure the "identicality" of two strings? More generally, are there any algorithms that do this, that I could implement (in Javascript)?
Take, as an example, the following string
Abnormal Elasticity of Single-Crystal Magnesiosiderite across the Spin
Transition in Earth’s Lower Mantle
And also consider the following, slightly adjusted string. Note the boldface parts that are different
bnormal Elasticity of Single Crystal Magnesio-Siderite across the Spin-Transition in Earths Lower Mantle.
Javascript's native equality operators won't tell you a lot about the relation between these strings. In this particular case, you could match the strings using regex, but in general that only works when you know which differences to expect. If the input strings are random, the generality of this approach breaks down quickly.
Approach... I can imagine writing an algorithm that splits up the input string in an arbitrary amount N of substrings, and then matching the target string with all those substrings, and using the amount of matches as a measurement of identicality. But this feels like an unattractive approach, and I wouldn't even want to think about how big O will depend on N.
It would seem to me that there are a lot of free parameters in such an algorithm. For example, whether case-sensitivity of characters should contribute equally/more/less to the measurement than order-preservation of characters, seems like an arbitrary choice to make by the designer, i.e.:
identicality("Abxy", "bAxy") versus identicality("Abxy", "aBxy")
Defining the requirements more specifically...
The first example is the scenario in which I could use it. I'm loading a bunch of strings (titles of academic papers), and I check whether I have them in my database. However, the source might contain typos, differences in conventions, errors, whatever, which makes matching hard. There probably is a more easy way to match titles in this specific scenario: as you can sort of expect what might go wrong, this allows you to write down some regex beast.
You can implement Hirschberg's algorithm and distinguish delete/insert operations (or alter Levenshtein).
For Hirschbers("Abxy", "bAxy") the results are:
It was 2 edit operations:
keep: 3
insert: 1
delete: 1
and for Hirschbers("Abxy", "aBxy") the results are:
It was 2 edit operations:
keep: 2
replace: 2
You can check the javascript implementation on this page.
'Optimal' String-Alignment Distance
function optimalStringAlignmentDistance(s, t) {
// Determine the "optimal" string-alignment distance between s and t
if (!s || !t) {
return 99;
}
var m = s.length;
var n = t.length;
/* For all i and j, d[i][j] holds the string-alignment distance
* between the first i characters of s and the first j characters of t.
* Note that the array has (m+1)x(n+1) values.
*/
var d = new Array();
for (var i = 0; i <= m; i++) {
d[i] = new Array();
d[i][0] = i;
}
for (var j = 0; j <= n; j++) {
d[0][j] = j;
}
// Determine substring distances
var cost = 0;
for (var j = 1; j <= n; j++) {
for (var i = 1; i <= m; i++) {
cost = (s.charAt(i-1) == t.charAt(j-1)) ? 0 : 1; // Subtract one to start at strings' index zero instead of index one
d[i][j] = Math.min(d[i][j-1] + 1, // insertion
Math.min(d[i-1][j] + 1, // deletion
d[i-1][j-1] + cost)); // substitution
if(i > 1 && j > 1 && s.charAt(i-1) == t.charAt(j-2) && s.charAt(i-2) == t.charAt(j-1)) {
d[i][j] = Math.min(d[i][j], d[i-2][j-2] + cost); // transposition
}
}
}
// Return the strings' distance
return d[m][n];
}
alert(optimalStringAlignmentDistance("Abxy", "bAxy"))
alert(optimalStringAlignmentDistance("Abxy", "aBxy"))
Damerau-Levenshtein Distance
function damerauLevenshteinDistance(s, t) {
// Determine the Damerau-Levenshtein distance between s and t
if (!s || !t) {
return 99;
}
var m = s.length;
var n = t.length;
var charDictionary = new Object();
/* For all i and j, d[i][j] holds the Damerau-Levenshtein distance
* between the first i characters of s and the first j characters of t.
* Note that the array has (m+1)x(n+1) values.
*/
var d = new Array();
for (var i = 0; i <= m; i++) {
d[i] = new Array();
d[i][0] = i;
}
for (var j = 0; j <= n; j++) {
d[0][j] = j;
}
// Populate a dictionary with the alphabet of the two strings
for (var i = 0; i < m; i++) {
charDictionary[s.charAt(i)] = 0;
}
for (var j = 0; j < n; j++) {
charDictionary[t.charAt(j)] = 0;
}
// Determine substring distances
for (var i = 1; i <= m; i++) {
var db = 0;
for (var j = 1; j <= n; j++) {
var i1 = charDictionary[t.charAt(j-1)];
var j1 = db;
var cost = 0;
if (s.charAt(i-1) == t.charAt(j-1)) { // Subtract one to start at strings' index zero instead of index one
db = j;
} else {
cost = 1;
}
d[i][j] = Math.min(d[i][j-1] + 1, // insertion
Math.min(d[i-1][j] + 1, // deletion
d[i-1][j-1] + cost)); // substitution
if(i1 > 0 && j1 > 0) {
d[i][j] = Math.min(d[i][j], d[i1-1][j1-1] + (i-i1-1) + (j-j1-1) + 1); //transposition
}
}
charDictionary[s.charAt(i-1)] = i;
}
// Return the strings' distance
return d[m][n];
}
alert(damerauLevenshteinDistance("Abxy", "aBxy"))
alert(damerauLevenshteinDistance("Abxy", "bAxy"))
Optimal String Alignment has better performance
Optimal String Alignment Distance 0.20-0.30ms
Damerau-Levenshtein Distance 0.40-0.50ms
Good day fellow Stack-ers,
I must ask your pardon if this question has been asked before or if it seems elementary (I am only a Javascript novice).
I have been doing w3c js challenges lately: Write a JavaScript function which will take an array of numbers stored and find the second lowest and second greatest numbers.
Here is my answer:
var array = [3,8,5,6,5,7,1,9];
var outputArray = [];
function arrayTrim() {
var sortedArray = array.sort();
outputArray.push(sortedArray[1],array[array.length-2]);
return outputArray;
}
arrayTrim();
and here is the answer that they have provided:
function Second_Greatest_Lowest(arr_num) {
arr_num.sort(function(x,y) {
return x-y;
});
var uniqa = [arr_num[0]];
var result = [];
for(var j=1; j < arr_num.length; j++) {
if(arr_num[j-1] !== arr_num[j]) {
uniqa.push(arr_num[j]);
}
}
result.push(uniqa[1],uniqa[uniqa.length-2]);
return result.join(',');
}
alert(Second_Greatest_Lowest([1,2,3,4,5]));
I know that the for loop runs through until the length of the input, but I don't understand the if statement nested within the for loop. It seems like a long way around to the solution.
Thank you!
Your answer does not perform correct for input such as f.e. [3,8,5,6,5,7,1,1,9]. Your proposed solution returns 1 as the second lowest number here – whereas it should actually be 3.
The solution suggested by the site takes that into account – that is what the if inside the loop is for, it checks if the current number is the same as the previous one. If that’s the case, it gets ignored. That way, every number will occur once, and that in turn allows to blindly pick the second element out of that sorted array and actually have it be the second lowest number.
It seems like a long way around to the solution
You took a short cut, that does not handle all edge cases correctly ;-)
The loop in question:
for(var j=1; j < arr_num.length; j++) {
if(arr_num[j-1] !== arr_num[j]) {
uniqa.push(arr_num[j]);
}
}
Provides some clue as to what it's doing by using a (reasonably) descriptive variable name: uniqa - or "unique array". The if statement is checking that the current element is not the same as the previous element - having sorted the array initially this works to give you a unique array - by only filling a new array if the element is indeed unique.
Thereafter the logic is the same as yours.
import java.util.Arrays;
public class BubbleWithMax_N_Min
{
public static void main(String[] agrs)
{
int temp;
int[] array = new int[5];
array[0] = 3;
array[1] = 99;
array[2] = 55;
array[3] = 2;
array[4] = 1;
System.out.println("the given array is:" + Arrays.toString(array));
for (int i = 0; i < array.length; i++)
{
System.out.println(array[i] + "");
}
for (int i = 0; i < array.length; i++)
{
for (int j = 1; j < array.length - i; j++)
{
if (array[j - 1] > array[j])
{
temp = array[j - 1];
array[j - 1] = array[j];
array[j] = temp;
}
}
}
System.out.println(" 2nd Min and 2nd Highest:");
for (int i = 0; i < 1; i++)
{
System.out.println(array[i+1]);
}
for (int i = 0; i < 1; i++)
{
int a= array.length-2;
System.out.println(array[a]);
}
}
}
I have an array of arrays for drawing a tilemap on the screen (basically an array of columns, each column is an array of tiles). I have tried to speed up the drawing process by not setting the array indexes that contain empty tiles, but it is not any faster.
var a1 = [];
a1[0] = 1;
a1[100] = 1;
a1[200] = 1;
a1[300] = 1;
var a2 = [];
for( var i = 0; i <= 300; i++ ) {
a2[i] = 1;
}
When I compared the time taken to loop through these two 100,000 times, a2 was slightly faster. When I tried using ( for var x in y ) instead, both with an array and an object, they were 4 - 12 times slower.
If looping through an object is a lot slower, and removing 99% of the array (not just from the end) is not making it any faster, is there any way one could actually make it faster?
Do not have holes in your arrays, just fill it completely (also pre-allocate to avoid dynamic resizing)
var a1 = new Array(301);
for (var i = 0; i < a1.length; ++i) a1[i] = 0;
a1[0] = 1;
a1[100] = 1;
a1[200] = 1;
a1[300] = 1;
Loop normally (never use for.in, use Object.keys if you need to iterate over keys):
for (var i = 0; i < a1.length; ++i) {
if (a1[i] !== 0) {
//Non empty
}
}