Maximum Length of Repeated Subarray (leetcode) - javascript

I'm looking at this leetcode question, and am having an issue completing the naive approach. I was able to come to an optimal solution here. But I'm not sure what's wrong with my naive attempt.
The question is as follows:
Given two integer arrays A and B, return the maximum length of an
subarray that appears in both arrays.
Example:
Input: A: [1,2,3,2,1] B: [3,2,1,4,7]
Output: 3
Explanation: The repeated subarray with maximum length is [3, 2, 1].
Here is my current code:
var findLength = function(a, b) {
if (a.length === 0 || b.length === 0) {
return 0;
}
let aWithoutFinalNumber = a.slice(0, a.length - 1);
let bWithoutFinalNumber = b.slice(0, b.length - 1);
let aFinalNumber = a[a.length - 1];
let bFinalNumber = b[b.length - 1];
// matching numbers
if(aFinalNumber === bFinalNumber) {
return 1 + findLength(aWithoutFinalNumber, bWithoutFinalNumber);
} else { // mismatch. Compete to find the maximum length.
return Math.max(findLength(a, bWithoutFinalNumber), findLength(aWithoutFinalNumber, b));
}
};
My solution passes several test cases, but fails on cases like a: [0,1,1,1,1] b: [1,0,1,0,1]. Any insight on my mistake would be appreciated!

The problem comes from the way you calculate the max length when the last elements match. here is a minimal example:
var findLength = function(a, b) {
if (a.length === 0 || b.length === 0) {
return 0;
}
let aWithoutFinalNumber = a.slice(0, a.length - 1);
let bWithoutFinalNumber = b.slice(0, b.length - 1);
let aFinalNumber = a[a.length - 1];
let bFinalNumber = b[b.length - 1];
// matching numbers
if(aFinalNumber === bFinalNumber) {
return 1 + findLength(aWithoutFinalNumber, bWithoutFinalNumber); //< -- problem here
} else { // mismatch. Compete to find the maximum length.
return Math.max(findLength(a, bWithoutFinalNumber), findLength(aWithoutFinalNumber, b));
}
};
console.log(findLength([1, 0, 2, 1], [1, 0, 3, 1]));
If there is any match you add 1 to the max length but that's not necessarily true if there is a mismatch later. Here is a shortened version what happens with illustration for easier understanding:
[1, 0, 2, 1]
^-------|
[1, 0, 3, 1] | -- match, max length +1
^-------|
______
[1, 0, 2, 1]
^----------|
[1, 0, 3, 1] | -- mismatch, max length +0
^----------|
______
[1, 0, 2, 1]
^-------------|
[1, 0, 3, 1] | -- match, max length +1
^-------------|
______
[1, 0, 2, 1]
^----------------|
[1, 0, 3, 1] | -- match, max length +1
^----------------|
When you total all the matches, you get 3 however, the count should have been reset when there was a mismatch.
One simple change that can be done to the algorithm to avoid this problem is to pass the current count as an argument to the function. This way, you can control when the count needs to be reset:
var findLength = function(a, b, maxSoFar = 0) { //<-- default count of zero
if (a.length === 0 || b.length === 0) {
return maxSoFar; //<-- return the count
}
let aWithoutFinalNumber = a.slice(0, a.length - 1);
let bWithoutFinalNumber = b.slice(0, b.length - 1);
let aFinalNumber = a[a.length - 1];
let bFinalNumber = b[b.length - 1];
// matching numbers
if(aFinalNumber === bFinalNumber) {
const newMax = maxSoFar + 1; //<-- increment the count
return Math.max(newMax, findLength(aWithoutFinalNumber, bWithoutFinalNumber, newMax)); //<-- return the newMax in case the next is a mismatch
} else { // mismatch. Compete to find the maximum length.
return Math.max(findLength(a, bWithoutFinalNumber), findLength(aWithoutFinalNumber, b)); //<-- reset the count
}
};
console.log(findLength([1, 0, 2, 1], [1, 0, 3, 1]));
console.log(findLength([1, 2, 3, 2, 1], [3, 2, 1, 4, 7]));
console.log(findLength([0, 1, 1, 1, 1], [1, 0, 1, 0, 1]));

You could use nested loop and when same element occur in both arrays you could start incrementing both of those indexes until elements in both arrays are the same. The result that gives you the largest set of elements will be the returned result.
function maxSub(a, b) {
let result = null;
function findAll(i, j) {
const res = [];
if (a[i] !== b[j] || a[i] == undefined || b[j] == undefined) {
return res;
}
return res.concat(a[i], ...findAll(i + 1, j + 1))
}
a.forEach((e, i) => {
b.forEach((c, j) => {
if (e == c) {
const sub = findAll(i, j);
if (!result || sub.length > result.length) {
result = sub
}
}
})
})
return result;
}
console.log(maxSub([0, 1, 1, 1, 1], [1, 0, 1, 0, 1]))
console.log(maxSub([1, 2, 3, 2, 1], [3, 2, 1, 4, 7]))

you can use dp with a table as well. i tried the other code, and it gave errors in cases like: [0,0,0,0,1,0,0] and [0,0,0,0,0,1,0].
Here is a python code for the same.
def findLength(a, b):
if len(a)==0 or len(b)==0:
return 0
n=len(a)
m=len(b)
dp=[[0 for _ in range(n+1)] for _ in range(m+1)]
maxSoFar=0
for i in range(1,m+1):
for j in range(1,n+1):
if a[j-1]==b[i-1]:
dp[i][j]=dp[i-1][j-1]+1
maxSoFar=max(maxSoFar,dp[i][j])
else:
dp[i][j]=0
return maxSoFar

Related

How do i return new array with removing one of the elements based on condition [duplicate]

I have a number array [2, 1, 3, 4, 5, 1] and want to remove the smallest number in the list. But somehow my IF statement gets skipped.
I checked and by itself "numbers[i + 1]" and "numbers[i]" do work, but "numbers[i + 1] < numbers[i]" doesn't...
function removeSmallest(numbers) {
var smallestNumberKEY = 0;
for (i = 0; i <= numbers.lenths; i++) {
if (numbers[i + 1] < numbers[i]) {
smallestNumberKEY = i + 1;
}
}
numbers.splice(smallestNumberKEY, 1);
return numbers;
}
document.write(removeSmallest([2, 1, 3, 4, 5, 1]));
You have a typo in your code, array doesn't have lenths property
function removeSmallest(numbers) {
var smallestNumberKEY = 0;
for (var i = 0; i < numbers.length - 1; i++) {
if (numbers[i + 1] < numbers[i]) {
smallestNumberKEY = i + 1;
numbers.splice(smallestNumberKEY, 1);
}
}
return numbers;
}
document.write(removeSmallest([2, 1, 3, 4, 5, 1]));
But your algorithm wont work for another array, e.g [5, 3, 1, 4, 1], it will remove a value 3 too.
You can find the min value with Math.min function and then filter an array
function removeSmallest(arr) {
var min = Math.min(...arr);
return arr.filter(e => e != min);
}
You can use Array#filter instead
function removeSmallest(arr) {
var min = Math.min.apply(null, arr);
return arr.filter((e) => {return e != min});
}
console.log(removeSmallest([2, 1, 3, 4, 5, 1]))
Short one liner. If the smallest value exist multiple times it will only remove ONE. This may or may not be what you want.
const result = [6,1,3,1].sort().filter((_,i) => i) // result = [1,3,6]
It works by sorting and then creating a new array from the items where indeces are truthy(anything but 0)
another solution with splice and indexOf:
array = [2, 1, 3, 4, 5, 1];
function replace(arr){
arr = arr.slice(); //copy the array
arr.splice( arr.indexOf(Math.min.apply(null, arr)),1)
return arr;
}
document.write( replace(array) ,'<br> original array : ', array)
edit : making a copy of the array will avoid the original array from being modified
"Short" solution using Array.forEach and Array.splice methods:
function removeSmallest(numbers) {
var min = Math.min.apply(null, numbers);
numbers.forEach((v, k, arr) => v !== min || arr.splice(k,1));
return numbers;
}
console.log(removeSmallest([2, 1, 3, 4, 5, 1])); // [2, 3, 4, 5]
This is a proposal with a single loop of Array#reduce and without Math.min.
The algorithm sets in the first loop min with the value of the element and returns an empty array, because the actual element is the smallest value and the result set should not contain the smallest value.
The next loop can have
a value smaller than min, then assign a to min and return a copy of the original array until the previous element, because a new minimum is found and all other previous elements are greater than the actual value and belongs to the result array.
a value greater then min, then the actual value is pushed to the result set.
a value equal to min, then the vaue is skipped.
'use strict';
var removeSmallest = function () {
var min;
return function (r, a, i, aa) {
if (!i || a < min) {
min = a;
return aa.slice(0, i);
}
if (a > min) {
r.push(a);
}
return r;
}
}();
document.write('<pre>' + JSON.stringify([2, 1, 3, 2, 4, 5, 1].reduce(removeSmallest, []), 0, 4) + '</pre>');
I like this oneliner: list.filter(function(n) { return n != Math.min.apply( Math, list ) })
check it out here: https://jsfiddle.net/rz2n4rsd/1/
function remove_smallest(list) {
return list.filter(function(n) { return n != Math.min.apply( Math, list ) })
}
var list = [2, 1, 0, 4, 5, 1]
console.log(list) // [2, 1, 0, 4, 5, 1]
list = remove_smallest(list)
console.log(list) // [2, 1, 4, 5, 1]
list = remove_smallest(list)
console.log(list) // [2, 4, 5]
I had to do this but I needed a solution that did not mutate the input array numbers and ran in O(n) time. If that's what you're looking for, try this one:
const removeSmallest = (numbers) => {
const minValIndex = numbers.reduce((finalIndex, currentVal, currentIndex, array) => {
return array[currentIndex] <= array[finalIndex] ? currentIndex : finalIndex
}, 0)
return numbers.slice(0, minValIndex).concat(numbers.slice(minValIndex + 1))
}
function sumOfPaiars(ints){
var array = [];
var min = Math.min(...ints)
console.log(min)
for(var i=0;i<ints.length;i++){
if(ints[i]>min){
array.push(ints[i])
}
}
return array
}
If you only wish to remove a single instance of the smallest value (which was my use-case, not clear from the op).
arr.sort().shift()
Here is a piece of code that is work properly but is not accepted from codewars:
let numbers = [5, 3, 2, 1, 4];
numbers.sort(function numbers(a, b) {
return a - b;
});
const firstElement = numbers.shift();

Refactor Javascript to Optimize for Speed

I was able to figure out a working solution to the problem but I would like to know how I could go about refactoring this code to make it faster. Thank you.
/*
Given a sequence of integers as an array, determine whether it is
possible to obtain a strictly increasing sequence by removing no more
than one element from the array.
*/
//var array2 = [0, -2, 5, 6]; // expect true
//var array2 = [1, 1, 1, 2, 3]; // expect false
var array2 = [1, 2, 5, 5, 5] // expect false
function almostIncreasingSequence(sequence) {
let passing = false;
sequence.forEach((number, idx, array) => {
let sequence2 = sequence.filter((num, id) => id !== idx);
let answer = sequence2.every((num, idx, array) => {
return idx === sequence2.length - 1 || num < sequence2[idx + 1];
});
if (answer) {
passing = true;
}
});
return passing;
}
console.log(almostIncreasingSequence(array2));
If I'm understanding the problem right, you only need to make 1 pass through the sequence. You're looking for arrays where the difference between index (i-1) and (i) decreases only one time in the whole array.
(this code has gone through several edits to handle edge cases)
function almostIncreasingSequence(sequence) {
if(sequence.length == 1)
return true;
var countDecreases = 0;
var i = -1;
for(var index=1; index<sequence.length; index++)
{
if(sequence[index-1] > sequence[index])
{
countDecreases++;
i = index;
if(countDecreases > 1)
return false;
}
}
var canRemovePreviousElement = (i == 1 || sequence[i-2] <= sequence[i]);
var cannotRemoveSelectedElement = (i+1 < sequence.length && sequence[i-1] > sequence[i+1]);
if(canRemovePreviousElement)
return cannotRemoveSelectedElement;
return (countDecreases == 1 && !cannotRemoveSelectedElement);
}
// Testing /////////////////////////////////
var passTestCases = {
"remove index 0": [2, 0, 1, 2],
"remove index 1": [0, 1, 0, 0, 1],
"remove index (last-1)": [0, 1, -1, 2],
"remove index (last)": [0, 1, 2, -1],
"remove only element": [1]
};
var failTestCases = {
"remove index 0 or 1": [0, -1, 1, 2],
"remove any index, all increasing": [1, 2, 5, 5, 5],
"remove any index, with decrease": [1, 0],
"one decrease": [0, 1, 2, 0, 1],
"two decreases": [1, 0, 2, 1],
"no elements to remove": []
};
runTests(passTestCases, true, almostIncreasingSequence);
runTests(failTestCases, false, almostIncreasingSequence);
function runTests(testCases, expectedResult, method) {
console.log("\nShould Return " + expectedResult);
for(var key in testCases)
{
var testCase = testCases[key];
var result = method(testCase);
console.log(key + ": " + testCase + ": result " + result);
}
}
On optimizing for speed:
This method will only make one pass through the array, while the original post uses nested loops. In general, a linear algorithm will be faster than a non-linear algorithm for large data sets.
Comparing the speeds of the two algorithms with these tiny test cases gives inconclusive results.
Note that we have a conflict when a[i] <= a[i - 1] as we want a strictly increasing sequence. There are two ways to solve the conflict. One is to remove a[i] from the array, hoping that a[i + 1] won't conflict with a[i - 1]. Another is to remove a[i - 1], hoping that a[i - 2] won't conflict with a[i].
As we have the case that a[i] <= a[i - 1], it should be better to remove a[i - 1] so that our new max element in the sequence is less or equal than before, thus giving more chances to have no conflict on next element. So if we can choose between removing a[i] and a[i - 1], removing a[i - 1] would be better.
We can remove a[i - 1] when a[i - 2] does not conflict with a[i]. In the code, sequence[prev - 1] is this a[i - 2], so in the case where there is a conflict, our only choice left is removing a[i].
To avoid actually removing elements on the array, I used the little trick of just doing a continue, which makes i increase but prev stays the same, thus making the new a[i] compare against a[i - 2] as they should be now next to each other in the strictly increasing sequence.
When we don't fall into the continue, we do prev = i instead of prev += 1 because we would actually need to add 2 to prev after we got into the continue and there was no conflict between a[i] and a[i - 2]. If we added just 1 to prev, we would have prev pointing to a[i - 1], but we can't have that as its the element we "removed" from the sequence.
function isAlmostIncreasingSequence(sequence) {
var prev = 0;
var removed = false;
for (var i = 1; i < sequence.length; i++) {
if (sequence[i] <= sequence[prev]) {
if (removed == false) {
removed = true;
if (prev > 0 && sequence[i] <= sequence[prev - 1]) { // need to remove sequence[i] instead of sequence[prev]
continue;
}
} else {
return false;
}
}
prev = i;
}
return true;
}
var tests = [[0, -2, 5, 6], [1, 1, 1, 2, 3], [1, 2, 5, 5, 5], [1, 3, 2], [0, -2, 5, 6], [1, 2, 3, 4, 5, 3, 5, 6], [1, 1], [1, 2, 5, 3, 5], [1, 2, 3, 4, 3, 6]];
tests.forEach(function(arr){
console.log(arr.join(","), isAlmostIncreasingSequence(arr));
});

How to convert an array in an complex array

I have this array [2, 1, 2, 1, 1, 1, 1, 1]
I want if the sum of the values exceed four, it's make a new array in array.
I want a result like that: [[2,1],[2,1,1],[1,1,1]]
You could use Array#reduce and use it for adding the values of the last inserted array and for the whole result array.
The main part of the algorithm is this line
!i || r[r.length - 1].reduce(add, 0) + a > 4 ?
r.push([a]) :
r[r.length - 1].push(a);
In it, a check takes place, if i is zero (at start) or if the sum of the last array of the result is in sum with the actual item greater than 4, then a new array with the actual value is added. If not, then the element is pushed to the last array.
var data = [2, 1, 2, 1, 1, 1, 1, 1],
add = function (a, b) { return a + b; },
result = data.reduce(function (r, a, i) {
!i || r[r.length - 1].reduce(add, 0) + a > 4 ? r.push([a]) : r[r.length - 1].push(a);
return r;
}, []);
console.log(result);
You can loop through the array and build a new one, if the sum exceed 4 push the previous array into the result like:
var myArr = [2, 1, 2, 1, 1, 1, 1, 1];
var newArr = [];
var newInner = [];
for (var i = 0; i < myArr.length; i++) {
if (summArr(newInner) + myArr[i] > 4) {
newArr.push(newInner);
newInner = [];
}
newInner.push(myArr[i]);
if (i==myArr.length-1) newArr.push(newInner);
}
function summArr(arr) {
return arr.reduce(add, 0);
function add(a, b) {
return a + b;
}
}
Demo: https://jsfiddle.net/q0ps7960/
for simple way...
var array1 = [2, 1, 2, 1, 1, 1, 1, 1];
var tmp=[];
var output=[];
var sum=0;
for(var i=0; i<array1.length; i++){
sum +=array1[i];
if(sum<=4){
tmp.push(array1[i]);
}else{
output.push(tmp);
sum =array1[i];
tmp=[array1[i]];
}
}
output.push(tmp);
console.log(output);

Best practice when sorting an array in pure javascript, if I have to send one group to the back

If I have the following array:
[0, 1, 3, 0, 4, 2]
And I'd like to sort it ascending order, barring zeros which I need on the end:
[1, 2, 3, 4, 0, 0]
Bear in mind I don't have access to underscore or linq.js for this solution.
My current solution works, but feels quite heavy, long, and not very elegant. Here's my code:
function sortNumbers(numbers) {
var zeroNumbers = [];
var notZeroNumbers = [];
for (var i = 0; i < numbers.length; i++) {
if (numbers[i] === 0) {
zeroNumbers.push(numbers[i]);
} else {
notZeroNumbers.push(numbers[i]);
}
}
var sortedNumbers = notZeroNumbers.sort(function (a, b) {
return parseFloat(a) - parseFloat(b);
});
for (var x = 0; x < zeroNumbers.length; x++) {
sortedNumbers.push(zeroNumbers[x]);
}
return sortedNumbers;
}
Can I improve on this solution?
This is not related to this question, but I was searching for "pure sort javascript" and this is the first answer.
Because sort mutates the original array, the best practice when sorting an array is to clone it first.
const sortedArray = [...array].sort(/* optional comparison function*/)
simply try
var output = [0, 1, 3, 0, 4, 2].sort(function(a, b) {
a = a || Number.MAX_SAFE_INTEGER; //if a == 0 then it will be a falsey value and a will be assigned Number.MAX_SAFE_INTEGER
b = b || Number.MAX_SAFE_INTEGER;
return a - b;
});
console.log(output)
var arr = [0, 1, 3, 0, 4, 2, 9, 8, 7, 0];
arr.sort(function (left, right) {
return left == right ? 0 : (left === 0 ? 1 : (left < right ? -1 : 1));
});
console.log(arr)
This will always put zeroes at the end regardless of the size of the number.
Another alternative solution using Array.sort, Array.splice and Array.push functions:
var arr = [0, 1, 3, 0, 4, 2];
arr.sort();
while(arr[0] === 0) { arr.splice(0,1); arr.push(0); }
console.log(arr); // [1, 2, 3, 4, 0, 0]
You can use sort for this, which takes a closure/callback.
var sortedArray = [0, 1, 3, 0, 4, 2].sort(function(currentValue, nextValue) {
if(currentValue === 0) {
return 1;
} else {
return currentValue - nextValue;
}
});

Check if array is in monotonic sequence

I would like to return true if the given array is in monotonic sequence. This is where I am so far but this function doesn't work as it should:
var isMonotone = function (arr) {
return arr.reduce(function (previousValue, currentValue) {
return previousValue <= currentValue;
});
};
http://jsfiddle.net/marcusdei/t0L1wryy/6/
This is not homework, I want to learn JS for my career. Thanks!
You could use every, like so (just replace [1, 2, 3, 4] with arr)
[1, 2, 3, 4].every(function(e, i, a) { if (i) return e > a[i-1]; else return true; });
You'll need to adjust the > depending on whether you want to check for monotonically decreasing / increasing.
I just needed a very performant implementation of this function (ES6).
This is what I came up with:
const is_monotonically_decreasing = (array) => {
const n = array.length
let i = 1
while (i < n && array[i] - array[i - 1] < 0) {
i++
}
return i === n
}
This saves the first iteration and using callbacks (smaller stack, less memory).
The behavior can changed by adjusting array[i] - array[i - 1] < 0 accordingly.
JavaScript one-liner solution:
Sort & Join
var isMonotonic = (arr) => {
return (
arr.join`` == [...arr].sort((a, b) => a - b).join`` ||
arr.join`` == [...arr].sort((a, b) => b - a).join``
);
};
console.log(isMonotonic([8, 4, 2, 1]));
console.log(isMonotonic([8, 0, 5, 1]));
console.log(isMonotonic([7, 7, 7]));
Every
var isMonotonic = (arr) => {
return (
arr.every((v, i) => i === 0 || v <= arr[i - 1]) ||
arr.every((v, i) => i === 0 || v >= arr[i - 1])
);
};
console.log(isMonotonic([8, 4, 2, 1]));
console.log(isMonotonic([8, 0, 5, 1]));
console.log(isMonotonic([7, 7, 7]));

Categories

Resources