I'm having trouble wrapping my head around an array matching problem in JavaScript.
I have two arrays:
array1 = ["a", "b", "c", "d", "e"];
array2 = ["t", "u", "v", "w", "x"];
What I want to achieve is a cycle that works through each array and returns an array of objects that represent matched groups following the pattern:
1 to 1
1 to 1,2
1,2 to 1
1,2 to 1,2
1,2,3 to 1
1,2,3 to 1,2
1,2,3,4 to 1
1,2,3,4 to 1,2
Then once the cycle is over step up to the second character and carry on through. So for the above two arrays the result would be:
result = [
{1_1: "a;t", 1_12: "a;tu", 12_1: "ab;t", 12_12: "ab;tu", 123_1: "abc;t", 123_12: "abc;tu", 1234_1: "abcd;t", 1234_12: "abcd;tu"},
{1_1: "b;u", 1_12: "b;uv", 12_1: "bc;u", 12_12: "bc;uv", 123_1: "bcd;u", 123_12: "bcd;uv", 1234_1: "bcde;u", 1234_12: "bcde;uv"},
...and so on
];
The arrays don't have a major limit, but they will most likely be in the 5 to 12 characters long region although the outer limits in length are as short as 1 and as long as 30. If the array is is shorter than 4 then it stops when it gets to the end of the process it can. So for example:
array1 = ["a"];
array2 = ["b"];
result = [{1_1: "a;b"}];
I am assuming there is a fun game of recursion buried in this, but I don't even know how to approach this problem let alone word it in a way to make my google searches effective. Any help you could offer regarding getting started would be very appreciated.
Edit in response to comment:
What I'm looking for specifically is terms I can search to help me get started or pseudo code that would help me start the process of tackling this problem. Thanks.
Alas Permutations came too late for me. Although it looks like I kind of went in that direction. I struggled with this until I gave up and just decided to essentially brute force an answer and this is what I came up with.
I am absolutely certain that this can be drastically simplified, but on this day I didn't have the mental capacity to get there. Sometimes you just have to force it. This is what I came up with:
const matchArrays = (array1: string[], array2: string[]): string[] => {
let a1List: string[] = [];
let resultList: string[] = [];
let count: number = 4;
(array1.length < 4) && (count = array1.length);
for (let i = 0; i < array1.length; i++) {
a1List.push(array1[i]);
array1[i + 1] !== undefined && a1List.push(array1[i] + array1[i + 1]);
array1[i + 2] !== undefined && a1List.push(array1[i] + array1[i + 1] + array1[i + 2]);
array1[i + 3] !== undefined && a1List.push(array1[i] + array1[i + 1] + array1[i + 2] + array1[i + 3]);
}
for (let i = 0; i < a1List.length; i++) {
resultList.push(a1List[i] + ";" + array2[Math.floor(i / count)]);
resultList.push(a1List[i] + ";" + array2[Math.floor(i / count)] + array2[Math.floor(i / count) + 1]);
}
return resultList;
};
Thanks to anyone who stopped to read this.
Related
I'm trying to understand how to solve Leetcode Problem #740: Delete and Earn
I recently was given this problem as part of a pre-interview assessment and was unable to complete it in the allotted time. I've been working on it today to try and wrap my head around it, but I'm kinda spinning in circles at the moment. I've checked numerous resources, videos, tutorials, etc, but I'm working in vanilla JS and a lot of the guides are in C++, Python, or Typescript which I don't currently know. (I plan on learning Python and Typescript at minimum, but I'm working with my current set of knowledge for the time being). This is leading to confusion and frustration, as an accurate translation of sample python/c++ code, etc continues to elude me.
The problem is as follows:
You are given an integer array nums. You want to maximize the number of points you get by performing the following operation any number of times:
Pick any nums[i] and delete it to earn nums[i] points. Afterwards, you must delete every element equal to nums[i] - 1 and every element equal to nums[i] + 1.
Return the maximum number of points you can earn by applying the above operation some number of times.
Example 1
Input: nums = [3,4,2]
Output: 6
Explanation: You can perform the following operations:
- Delete 4 to earn 4 points. Consequently, 3 is also deleted. nums = [2].
- Delete 2 to earn 2 points. nums = [].
You earn a total of 6 points.
Example 2
Input: nums = [2,2,3,3,3,4]
Output: 9
Explanation: You can perform the following operations:
- Delete a 3 to earn 3 points. All 2's and 4's are also deleted. nums = [3,3].
- Delete a 3 again to earn 3 points. nums = [3].
- Delete a 3 once more to earn 3 points. nums = [].
You earn a total of 9 points.
What I have so far:
const deleteAndEarn = (nums) => {
if(!nums || nums.length === 0) return 0;
if(nums.length === 1) return nums[0];
if(nums.length === 2) return nums[1];
const freq = makeDict(nums);
let prevNum
let [keep, avoid] = [0, 0];
for(const num of [...Object.keys(freq)].sort()){
let max = Math.max(keep, avoid)
if(parseInt(num) - 1 !== prevNum){
[keep, avoid] = [
(freq[num] * parseInt(num)) + max,
max
]
}else{
[keep, avoid] = [
parseInt(num) * freq[num] + avoid,
max
]
}
prevNum = parseInt(num)
}
return Math.max(keep, avoid)
};
const makeDict = (nums) => {
const dict = {}
for(const num of nums){
dict[num] = !!dict[num] ? dict[num]++ : 1
}
return dict
}
Provided Python Solution
This is what I've tried to model my code off of, but I don't actually know Python syntax so I'm sure I'm missing something.
class Solution(object):
def deleteAndEarn(self, nums):
count = collections.Counter(nums)
prev = None
avoid = using = 0
for k in sorted(count):
if k - 1 != prev:
avoid, using = max(avoid, using), k * count[k] + max(avoid, using)
else:
avoid, using = max(avoid, using), k * count[k] + avoid
prev = k
return max(avoid, using)
I really don't understand at all why this code isn't working, and I've even gone as far as to run sample cases step by step. Please help me understand how to do this so I can get a job!
Many thanks
I figured it out! The problem is twofold.
Bug Number One
First, shoutout to David Eisenstat for catching the bug in my makeDict() function.
The incorrect line of code reads:
dict[num] = !!dict[num] ? dict[num]++ : 1
Whereas the correct syntax is as follows:
dict[num] = !!dict[num] ? ++dict[num] : 1
or alternatively
dict[num] = !!dict[num] ? dict[num] + 1 : 1
The issue comes from how postfix vs prefix increment operators work in Javascript.
From the MDN docs:
If used postfix, with operator after operand (for example, x++), the increment operator increments and returns the value before incrementing.
If used prefix, with operator before operand (for example, ++x), the increment operator increments and returns the value after incrementing.
Bug Number Two
The second issue comes from my initial guard clauses.
if(nums.length === 2) return nums[1];
I think this was a remnant from when I was sorting the provided array at the very start, but even then automatically selecting the last element doesn't really make any sense. I deleted this line and, combined with the adjustment to the previous makeDict() function, the code passed all the provided tests.
My working solution is provided below. Open to any suggestions as to how to improve the code for both readability, or efficiency.
Appreciate the help!
const deleteAndEarn = (nums) => {
if(!nums || nums.length === 0) return 0;
if(nums.length === 1) return nums[0];
const freq = makeDict(nums);
let prevNum
let [keep, avoid] = [0, 0];
for(const num of Object.keys(freq)){
let max = Math.max(keep, avoid)
if(parseInt(num) - 1 !== prevNum){
[keep, avoid] = [
(freq[num] * parseInt(num)) + max,
max
]
}else{
[keep, avoid] = [
parseInt(num) * freq[num] + avoid,
max
]
}
prevNum = parseInt(num)
}
return Math.max(keep, avoid)
};
const makeDict = (nums) => {
const dict = {}
for(const num of nums){
dict[num] = !!dict[num] ? ++dict[num] : 1
}
return dict
}
One bug in your existing code is that
[...Object.keys(freq)].sort()
will not sort numbers in order - see here.
Another bug is that your algorithm doesn't have any backtracking - you don't want to greedily choose 3 when given [3, 4, 4, 4].
I think the best way to approach this is to understand that it's only strings of consecutive numbers in the input that need to be considered. For example, given
[1, 2, 3, 6, 7, 8]
Separate it out into all the consecutive strings of integers:
[1, 2, 3]
[6, 7, 8]
Then decide the optimal picks for each sequence.
You can't just pick all odd numbers or all even numbers in the sequence, because that would fail to pick, eg, 1 and 4 for [1, 1, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 4]. The best approach I can see is to use a recursive function: when checking a sequence, getBestSequenceSum, starting with N, return the maximum of:
Sum of N plus getBestSequenceSum(seq.slice(2)) (skipping the next item in the sequence), OR
Sum of getBestSequenceSum(seq.slice(1)) (using the next item in the sequence)
to adequately cover all possibilities.
There may be more efficient algorithms, but this is relatively simple and intuitive.
const getBestSequenceSum = (seq) => {
if (seq.length === 0) return 0;
// Include the lowest value in the sequence, or not?
const sumOfLowestVal = seq[0].num * seq[0].count;
return Math.max(
sumOfLowestVal + getBestSequenceSum(seq.slice(2)),
getBestSequenceSum(seq.slice(1))
);
};
const deleteAndEarn = (nums) => {
nums.sort((a, b) => a - b);
let lastNum;
const sequences = [];
for (const num of nums) {
if (num !== lastNum && num !== lastNum + 1) {
// New sequence
sequences.push([]);
}
const thisSequence = sequences[sequences.length - 1];
if (num !== lastNum) {
thisSequence.push({ num, count: 0 });
}
thisSequence[thisSequence.length - 1].count++;
lastNum = num;
}
return sequences.reduce((a, seq) => a + getBestSequenceSum(seq), 0);
};
console.log(deleteAndEarn([10,8,4,2,1,3,4,8,2,9,10,4,8,5,9,1,5,1,6,8,1,1,6,7,8,9,1,7,6,8,4,5,4,1,5,9,8,6,10,6,4,3,8,4,10,8,8,10,6,4,4,4,9,6,9,10,7,1,5,3,4,4,8,1,1,2,1,4,1,1,4,9,4,7,1,5,1,10,3,5,10,3,10,2,1,10,4,1,1,4,1,2,10,9,7,10,1,2,7,5]));
The number of calculations could be reduced somewhat by changing the { num, count: 0 } objects to a single number instead, but that would be more difficult to understand when reading the code.
You could also reduce the number of calculations by caching already-optimized sequences so as not to recalculate them multiple times, but that'd make the code significantly longer.
If I have a string a12c56a1b5 then out put should be a13b5c56 as character a is repeated twice so a12 becomes a13
I have tried this:
function stringCompression (str) {
var output = '';
var count = 0;
for (var i = 0; i < str.length; i++) {
count++;
if (str[i] != str[i+1]) {
output += str[i] + count;
count = 0;
}
}
console.log(output); // but it returns `a11121c15161a111b151` instead of `a13b5c56`
}
It is happening because the code is counting the occurrence of each element and appending it, even the numbers in the string.
In this code,
for (var i = 0; i < str.length; i++) {
count++;
if (str[i] != str[i+1]) {
output += str[i] + count;
count = 0;
}
}
in first iteration i = 0, str[i] = 'a' and str[i + 1] = '1' for the given string a12c56a1b5 which are not equal hence, it will generate the output as a1 for first iteration, then a111 for second iteration since str[i] = '1' and str[i + 1] = '2' now, and so on.
We can achieve this by first separating the characters from the count. Assuming, that there would be characters from a-z and A-Z only followed by the count. We can do something like this, str.match(/[a-zA-Z]+/g) to get the characters: ["a", "c", "a", "b"] and str.match(/[0-9]+/g) to get their counts: ["12", "56", "1", "5"], put them in an object one by one and add if it already exists.
Something like this:
function stringCompression(str) {
var characters = str.match(/[a-zA-Z]+/g);
var counts = str.match(/[0-9]+/g);
var countMap = {};
for (var i = 0; i < characters.length; i++) {
if (countMap[characters[i]]) {
countMap[characters[i]] += parseInt(counts[i]);
} else {
countMap[characters[i]] = parseInt(counts[i]);
}
}
var output = Object.keys(countMap)
.map(key => key + countMap[key])
.reduce((a, b) => a + b);
console.log(output);
}
stringCompression('a12c56a1b5')
Using regex to extract word characters and numbers. Keeps an object map res to track and sum up following numbers. sorts and converts back to a string.
As an example, the for-of loop iteration flow with str=a12c56a1b5:
c='a', n='12'
res['a'] = (+n = 12) + ( (res['a'] = undefined)||0 = 0)
or ie: res['a'] = 12 + 0
c='c', n='56'
res['c'] = 56 + 0
c='a', n='1'
res['a'] = 1 + (res['a'] = 12 from iteration 1.) = 13
c='b', n='5'
res['b'] = 5 + 0
thus res = { 'a': 13, 'c': 56, 'b': 5 } after the for-of loop finishes
function stringCompression (str) {
// build object map with sums of following numbers
const res = {}
for(const [,c,n] of str.matchAll(/(\w+)(\d+)/g))
res[c] = +n + (res[c]||0)
// convert object map back to string
output = Object.entries(res)
output.sort(([a],[b])=>a<b ? -1 : a>b ? 1 : 0)
output = output.map(([a,b])=>`${a}${b}`).join('')
console.log(output); // but it returns `a11121c15161a111b151` instead of `a13b5c56`
}
stringCompression('a12c56a1b5')
[,c,n] = [1,2,3] is equivalent to c=2, n=3. It is called destructuring.
matchAll matches on a regex. It's a relatively new shorthand for calling .exec repeatedly to execute a regular expression that collects all the results that the regular expression matches on.
(\w+)(\d+) is a regex for two capture groups,
\w+ is for one or more alpha characters, \d+ is for one or more digits.
for(const [,c,n] of str.matchAll...) is equivalent to:
for each M of str.matchAll...
const c = M[1], n = M[2]`
res[c]||0 is shorthand for:
"give me res[c] if it is truthy (not undefined, null or 0), otherwise give me 0"
+n uses the unary operator + to force an implicit conversion to a number. JavaScript specs for + unary makes it convert to number, since + unary only makes sense with numbers.
It is basically the same as using Number(n) to convert a string to an number.
Conversion back to a string:
Object.entries converts an object {"key":value} to an array in the form of [ [key1, value1], [key2, value2] ]. This allows manipulating the elements of an object like an array.
.sort sorts the array. I destructured the keys to sort on the keys, so "a" "b" "c" are kept in order.
.map takes an array, and "maps" it to another array. In this case I've mapped each [key,value] to a string key+value, and then taking the final mapped array of key+value strings and joined them together to get the final output.
In case it asks you to sort it alphabetically, I added #user120242's sorting code snippet to #saheb's entire answer (in between Object.keys(countMap) and .map(...). That worked for me. I tried using #user120242's whole answer, but it did not pass all the tests since it did not add the repeated letters for longer strings. But #user120242's answer did work. It just need to be sorted alphabetically and it passed all the test cases in HackerRank. I had this question for a coding assessment (called "Better Coding Compression").
P.S. I also removed checking the capital letters from #saheb's code since that wasn't required for my coding challenge.
Here's how mine looked like:
function stringCompression(str) {
var characters = str.match(/[a-zA-Z]+/g);
var counts = str.match(/[0-9]+/g);
var countMap = {};
for (var i = 0; i < characters.length; i++) {
if (countMap[characters[i]]) {
countMap[characters[i]] += parseInt(counts[i]);
} else {
countMap[characters[i]] = parseInt(counts[i]);
}
}
var output = Object.keys(countMap)
.sort(([a],[b])=>a<b ? -1 : a>b ? 1 : 0)
.map(key => key + countMap[key])
.reduce((a, b) => a + b);
console.log(output);
}
stringCompression('a12c56a1b5')
For clarity sake, this is what I mean. I want to look for the two least numbers in an array(sorted) that will generate a particular number. The steps go thus:
Loop through the array and each time set a current value that other
numbers will be deducted from.
Keep doing that till you find the numbers that match the problem and return them.
Example. I need two numbers that when subtracted from the array will give a result of 2.
let givenArray = [1, 4, 8, 10];
The subtraction should go thus: 4 - 1 = 3(doesn't match); //continue
8 - 4 = 1(doesn't match);// continue
8 - 1 = 7(doesn't match); //continue
10 - 8 = 2(match found); //stop and return 8, 10.
NOTE: This same array may contain a 6 and 8 or 8 and 10 that will both yield 2 but 6 and 8 should be returned instead. The way the array is generated isn't of much importance.
P.S: I eventually solved it yesterday but I won't mind other ideas on how to go about it.
This solution takes the advantage of a hash table and uses a single loop approach for getting the two values out of an array to balance two values.
First, take the absolute delta of the two values of arrayA and take this for getting the values out of the greater array.
Then reduce the greater array arrayB by checking if the needed value exist and if the sum is smaller then a previously found set.
The argument for checking is build out of the absolute delta of delta and v, the actual value of the array or by taking the sum of delta and v.
The last point, and to make this all working, the actual value v is included into the hash table, for a later look up.
The result is either an array of two values, which balance the other two values or undefined, if no values are found.
var arrayA = [3, 5],
arrayB = [2, 9, 5, 4],
delta = Math.abs(arrayA[0] - arrayA[1]),
values = {},
result = arrayB.reduce((r, v) => {
function check(w) {
if (!values[w] || r && r[0] + r[1] < v + w) return;
r = [w, v];
}
check(Math.abs(delta - v));
check(delta + v);
values[v] = true;
return r;
}, undefined);
console.log(result);
I'm not sure I understood correctly, but perhaps this is what you need:
let result = arrayA[1] - arrayA[0];
let i, j;
for (i = arrayB.length - 1; i >= 1; i--) { // Set the first value
for (j = arrayB.length - 1; j >= 1; j--) { // Set the second value
if (i !== j) {
if (
arrayB[i] - arrayB[j] === result // Check substraction
|| arrayB[i] + arrayB[j] === result // Check addition
) return [arrayB[i], arrayB[j]];
}
}
}
For app-specific reasons I need to prepend exactly these two chars 'a,' (the a and one comma) at the beginning of an existing Int16Array
at the moment I tried with this code but it does not seem to work correctly:
function convertFloat32ToInt16(buffer) {
var prefix = 'a,',
prefixLength = prefix.length / 2, // divided by 2 because we deal with 16 bits, not 8 bits
bufferLength = buffer.length,
totalLength = prefixLength + bufferLength,
arr = new Int16Array(totalLength),
i
for (i = 0; i < prefixLength; i = i + 2) {
arr[i] = prefix.charCodeAt(i) + prefix.charCodeAt(i + 1)
}
for (i = prefixLength; i < totalLength; i++) {
arr[i] = Math.min(1, buffer[i - prefixLength]) * 0x7FFF // convert to 16 bit
}
return arr.buffer
}
Any suggestions how I can do it better and fix the above code?
Many thanks!
Why use an Int16Array if you need to store random characters in it? You're asking for problems doing this.
Why not just use a regular array? Replace your definition of arr with arr = [], and replace references to buffer with arr
In any case, you'll need to use a different data structure if you want to store random characters. You could always make your return line something like this:
return {buffer: arr.buffer, type: prefix}
So this is the question that is given.
You are in a room with a circle of 100 chairs. The chairs are numbered sequentially from 1 to 100.
At some point in time, the person in chair #1 will be asked to leave. The person in chair #2 will be skipped, and the person in chair #3 will be asked to leave. This pattern of skipping one person and asking the next to leave will keep going around the circle until there is one person left, the survivor.
And this is the answer I came up with. I believe this is the right answer, I've done it on paper about 10 times as well and came up with 74 every time.
Is this a trick question or something? Because I'm not sure what to do from here.
Here is the jsfiddle http://jsfiddle.net/cQUaH/
var console = {
log : function(s) {
document.body.innerHTML += s + "<br>";
}
};
var chairArr = [];
for (var i = 1; i <= 100; i++){
chairArr.push(i);
}
var j = 2;
while(chairArr.length > 1) {
console.log('removing ' + chairArr[j]);
chairArr.splice(j, 1);
j++;
if(j >= chairArr.length) {
console.log('--- Finished pass');
console.log('--- Array state:');
console.log(chairArr);
j = (j == chairArr.length) ? 0 : 1;
}
}
console.log('--- Final result: ' + chairArr);
//result 74
With a minor change in indices, you have the Josephus problem. In the traditional formulation, person 1 kills person 2, 3 kills 4, etc. To convert to that form, kill off person 1, as your problem states, and then renumber people 2-100 by subtracting 1, giving people 1-99.
A good treatment of the Josephus problem, including an account of its origin in the Jewish Revolt of 70-73 CE, is in Concrete Mathematics, 2nd edition, by Graham, Knuth, and Patashnik, Section 1.3. Both Wikipedia and Wolfram MathWorld have articles on the problem, Wikipedia even includes the original description by Josephus in The Jewish War.
The book gives a mildly complicated recursion for the solution, and a simpler algorithm. If the number of people is n, and n = 2^l + m where l is as large as possible, then the answer is 2m+1. So, since 99 = 2^6 + 35, the solution is 2*35 + 1 = 71. But you need to reverse the renumbering, so the real answer is 72.
As far as your programming problem, however, why don't you take as your basic operation Remove the first person in the circle and move the second person to the end. So, with 5 people, [1,2,3,4,5], you remove the first getting [2,3,4,5]and moving the new first element to the end getting [3,4,5,2].
var killAndRotate = function(array) { // say [1,2,3,4,5]
var dead = array.shift(), // dead = 1, array = [2,3,4,5]
skipped = array.shift(); // skipped = 2, array = [3,4,5]
array.push(skipped); // array = [3,4,5,2]
}
And then the main loop becomes:
while (chairArray.length > 1) {
killAndRotate(chairArray);
}
alert(chairArray[0]); // or console.log, or return.
// In turn, array is:
// [1,2,3,4,5]
// [3,4,5,2]
// [5,2,4]
// [4,2]
// [2] and we alert, log, or return 2.
Added
The easy way to find that result for the original Josephus problem is to see that:
If there are 2^l people, then in the first pass all the even-numbered people are killed, so the first person remains alive.
1 2 3 4 5 6 7 8
X X X X
Now there are 2^(l - 1) people. Again, the first person survives:
1 2 3 4 5 6 7 8
X X X X
X X
Repeat the process; the first person survives each pass, and so is the last survivor.
Now, suppose there are m extra people with m < 2^l. Here, l = 3 and m = 5. Kill the first m people to die.
1 2 3 4 5 6 7 8 9 10 11 12 13
X X X X X Y
Now, there are 2^l people left, and person 2 m + 1 = 11 is the first in line. So he survives.
One should also point out that adding a new index variable and splicing can lead to programmer error. Since you only need to remove from the front and add to the back, use the basic methods of arrays.
It seems to me the answer is 72. When you realize that rather than removing numbers you can skip them, the code becomes very short and straight-forward.
var chairArr = [];
for (var i = 1; i <= 100; i++)
chairArr.push(i);
for (i = 1; i < chairArr.length-2; i = i + 2)
chairArr.push(chairArr[i]);
console.log('--- Final result: ' + chairArr[i]);
What have you described here is the Josephus problem, and can be solved using dynamic programming:
function josephus(n, k)
{
if (n == 1) {
return 1;
} else {
return ((josephus(n-1, k) + k - 1) % n) + 1;
}
}
alert(josephus(100, 2));
Source: Wikipedia
The n denotes the number of chairs and k indicates every kth person leaving.
The result here is 73.
Update
Unfortunately, I didn't read the problem properly. The above code solves a slightly different problem; instead of killing off the first person in round one, the second person is killed instead. Being a survivor hinges on details :)
Solving your code problem is rather simple, start with the first person instead of the third in the first round.
var chairArr = [];
for (var i = 1; i <= 100; i++){
chairArr.push(i);
}
var j = 0;
while (chairArr.length > 1) {
chairArr.splice(j, 1);
j = (j + 1) % n;
}
You don't need an iteration to find the result, there is a formula that can be use to obtain the final chair:
function findChair (input) {
return (input - Math.pow(2, Math.floor(Math.log2(input)))) * 2 || (input === 1 ? 0 : input)
}
And for the original Josephus problem, which you kill the even numbers instead, the formula can be simplified:
function findChair (input) {
return (input - Math.pow(2, Math.floor(Math.log2(input)))) * 2 + 1
}
The cool thing about the original problem, is that you can work with binary. For example:
100 = 1100100
Take the first '1' and place it to the last:
1001001 = 73