/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
var strStr = function(haystack, needle) {
var match = 0;
// Edge case: if needle is an empty string, return 0
if (needle === "") {
return 0;
}
// Get the lengths of both strings
const haystackLen = haystack.length;
const needleleLen = needle.length;
// Iterate through the haystack string
for (let i = 0; i <= haystackLen - needleleLen; i++) {
var left = 0;
while(left < needleleLen && haystack[left + i] == needle[left]) {
left ++;
}
if(left == needleleLen) {
return i;
}
}
return -1;
};
Explanation:
Edge Case: If needle is an empty string, the function immediately returns 0.
Outer Loop: The outer loop iterates over each character in haystack where there is still enough remaining length to match the needle (i <= haystackLen - needleLen).
Inner Loop: The inner loop checks if the substring of haystack starting at i matches needle. It does this by comparing characters one-by-one.
Match Found: If a match is found (j === needleLen), the starting index i is returned.
No Match: If no match is found by the end of the loop, the function returns -1.
This implementation mimics a basic substring search without using any built-in functions. The time complexity is O(n * m), where n is the length of haystack and m is the length of needle.
function threeSumClosest(nums, target) {
// Sort the array first
nums.sort((a, b) => a - b);
let closestSum = Infinity;
// Iterate through the array
for (let i = 0; i < nums.length - 2; i++) {
let left = i + 1;
let right = nums.length - 1;
// Use two pointers to find the best sum
while (left < right) {
let currentSum = nums[i] + nums[left] + nums[right];
// Update the closest sum if needed
if (Math.abs(currentSum - target) < Math.abs(closestSum - target)) {
closestSum = currentSum;
}
// Move the pointers based on the current sum
if (currentSum < target) {
left++;
} else if (currentSum > target) {
right--;
} else {
// If the exact sum is found, return immediately
return currentSum;
}
}
}
return closestSum;
}
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.
Notice that the solution set must not contain duplicate triplets.
Example 1:
Input:
nums = [-1,0,1,2,-1,-4]
Output:
[[-1,-1,2],[-1,0,1]]
Explanation:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0.
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0.
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0.
The distinct triplets are [-1,0,1] and [-1,-1,2].
Notice that the order of the output and the order of the triplets does not matter.
Sorting the Array: The array is first sorted to easily manage duplicates and use a two-pointer approach.
Iterating with a Loop: A loop iterates through the array, fixing one element (nums[i]) and then using a two-pointer approach to find the other two elements (nums[left] and nums[right]).
Avoiding Duplicates: Duplicate values are skipped using continue for the first element and while loops for the second and third elements to ensure the solution set contains only unique triplets.
Two-Pointer Approach: The sum is checked, and pointers are adjusted accordingly to find valid triplets.
Example Usage:
[-1, 0, 1, 2, -1, -4]
This would output:
[[-1, -1, 2], [-1, 0, 1]]
This solution efficiently finds all unique triplets that sum to zero in O(n^2) time complexity.
Skipping duplicates in the threeSum algorithm is crucial to ensure that the solution set contains only unique triplets. Here’s a detailed explanation of how duplicates are skipped at different stages:
1. Skipping Duplicates for the First Element (i):
When iterating through the array with the outer loop (for (let i = 0; i < nums.length - 2; i++)), the algorithm checks if the current element nums[i] is the same as the previous element nums[i - 1]. If they are the same, it means that any triplet starting with this element would already have been considered in a previous iteration, so the algorithm skips this iteration.
Code Example:
if (i > 0 && nums[i] === nums[i - 1]) continue;
Explanation:
i > 0: Ensures that we don’t check for a previous element when i is 0.
nums[i] === nums[i - 1]: If this condition is true, it means nums[i] is a duplicate of the previous element, so the loop skips to the next i using continue.
2. Skipping Duplicates for the Second and Third Elements (left and right):
After fixing the first element nums[i], the algorithm uses two pointers, left and right, to find the other two elements (nums[left] and nums[right]) that, together with nums[i], sum to zero.
Once a valid triplet is found, the algorithm moves both pointers inward but also checks for duplicates by comparing the current elements with the next ones in line. If the next element is the same as the current one, the algorithm skips the next element by advancing the pointer further.
Code Example:
// After finding a triplet while (left < right && nums[left] === nums[left + 1]) left++; while (left < right && nums[right] === nums[right - 1]) right--;
Explanation:
Left Pointer:
while (left < right && nums[left] === nums[left + 1]) left++;
This loop skips all duplicate values for nums[left] by incrementing left until it points to a new value.
Right Pointer:
while (left < right && nums[right] === nums[right - 1]) right--;
Similarly, this loop skips all duplicate values for nums[right] by decrementing right until it points to a new value.
Why This is Important:
Avoiding Redundant Triplets: Without skipping duplicates, the algorithm would include multiple instances of the same triplet in the result, which is inefficient and incorrect for this problem.
Efficiency: Skipping duplicates prevents unnecessary comparisons, speeding up the algorithm.
Example Walkthrough:
Consider the array [-1, 0, 1, 2, -1, -4]:
Sorting: The array becomes [-4, -1, -1, 0, 1, 2].
Iteration with i = 0 (nums[i] = -4):
No duplicates for nums[i], proceed with left = 1 and right = 5.
No valid triplet is found, move to the next i.
Iteration with i = 1 (nums[i] = -1):
Triplet [-1, -1, 2] is found.
Skip duplicates: left moves from index 2 to 3 because nums[2] === nums[3].
Triplet [-1, 0, 1] is found.
Iteration with i = 2 (nums[i] = -1):
Skip this iteration entirely because nums[2] === nums[1].
As a result, only unique triplets are returned: [[-1, -1, 2], [-1, 0, 1]].
/**
* @param {number[]} nums
* @return {number[][]}
*/
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
nums.sort((a, b) =>{ return a - b });
const result = [];
for(let i = 0; i < nums.length - 2; i ++) {
// Skip duplicate values for the first element of the triplet
if (i > 0 && nums[i] === nums[i - 1]) {
continue;
}
let left = i + 1;
let right = nums.length - 1;
while(left < right) {
const sum = nums[i] + nums[left] + nums[right];
if(sum === 0) {
result.push([nums[i], nums[left], nums[right]]);
// Skip duplicate values for the second and third elements of the triplet
while (left < right && nums[left] === nums[left + 1]) {
left++;
}
while (left < right && nums[right] === nums[right - 1]) {
right--;
}
left ++;
right --;
} else if(sum < 0) {
left ++;
} else {
right --;
}
}
}
return result;
};
The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)
P A H N
A P L S I I G
Y I R
And then read line by line: "PAHNAPLSIIGYIR"
Write the code that will take a string and make this conversion given a number of rows:
It’s take the first example string: “PAYPALISHIRING”
An array representation of the string would be done by pushing each character into the array.
Then if we want to iterate through this array in zig zag pattern with 3 rows, it will look like this:
Then, we have to create 3 arrays representing the 3 rows as follows:
Dots represent empty strings which we have to ignore before concatenating all 3 rows, convert them to string and this is the answer to this problem.
But how to create the loop to traverse through the zig zag pattern (figure 1) ?
first we set up `headingDown` flag to determine if we are going down or up
each time when the row reaches numRows we are flipping the value of headingDown
when headingDown is false (meaning that we are heading up) we have to do it diagonally: row — , col ++
when row become 0 we are flipping again headingDown and it becomes true again: row ++ and we keep storing values of every character we traversed in the result array.
/**
* @param {string} s
* @param {number} numRows
* @return {string}
*/
var convert = function(s, numRows) {
// if we don't have any rows or there is an empty string just return back empty string
if(s.length === 0 || numRows === 0)
return '';
// if rows are numRows is just one, we return the same string
if(numRows === 1) {
return s;
}
var l = s.length;
// put the string into single dimension array to iterate through
var arr = s.split('');
var rowsArray = [];
var row = 0;
var col = 0;
var headingDown = true; // this determines if the cursor is heading down in the same column, or up leaping to the next column (dizgonal)
// instantiate numRows arrays to store values of each row
for(var i = 0; i < numRows; i ++) {
rowsArray[i] = [];
}
// loop through each element in arr and fill rowsArray ()
for(var i = 0; i < l; i ++) {
rowsArray[row][col] = arr[i];
if(headingDown) {
row ++;
}
else {
row --;
col ++;
}
if(row == numRows -1) {
headingDown = false;
} else if(row == 0) {
headingDown = true;
}
}
// Read 2D array and assemble the string
var result = [];
for(var i = 0; i < numRows; i ++) {
for(var j = 0; j < rowsArray[i].length; j ++) {
if(typeof rowsArray[i][j] != 'undefined') {
result.push(rowsArray[i][j]);
}
}
}
return result.join('');
};
convert('PAYPALISHIRING', 3);
convert('AB', 1);
//convert('ABC', 1);
How can we optimize this ?
the above example is good to read but not a good practical example. First we don’t need two dimensional array to store characters for each row. We could replace this with string array and just keep adding characters till we reached the end of the string. This way we also don’t need to keep track of cols
/**
* @param {string} s
* @param {number} numRows
* @return {string}
*/
var convert = function(s, numRows) {
// if we don't have any rows or there is an empty string just return back empty string
if(s.length === 0 || numRows === 0)
return '';
// if rows are numRows is just one, we return the same string
if(numRows === 1) {
return s;
}
var l = s.length;
// put the string into single dimension array to iterate through
var arr = s.split('');
var left = 0;
var arrStrings = [];
var row = 0;
var col = 0;
var headingDown = true; // this determines if the cursor is heading down in the same column, or up leaping to the next column (dizgonal)
// instantiate numRows arrays to store values of each row
for(var i = 0; i < numRows; i ++) {
arrStrings[i] = '';
}
// loop through each element in arr and fill arrStrings ()
for(var i = 0; i < l; i ++) {
//arrStrings[row][col] = arr[i];
if(headingDown) {
arrStrings[row] += arr[i];
row ++;
}
else {
arrStrings[row] += arr[i];
row --;
col ++;
}
if(row == numRows -1) {
headingDown = false;
} else if(row == 0) {
headingDown = true;
}
}
var result = '';
// combine all strings and return as one
for(var i = 0; i < numRows; i ++) {
result += arrStrings[i];
}
return result;
};
Given two strings text1 and text2, return the length of their longest common subsequence. If there is no common subsequence, return 0.
A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.
For example, "ace" is a subsequence of "abcde".
A common subsequence of two strings is a subsequence that is common to both strings.
Example 1:
Input:
text1 = "abcde", text2 = "ace"
Output:
3
Explanation:
The longest common subsequence is "ace" and its length is 3.
Example 2:
Input:
text1 = "abc", text2 = "abc"
Output:
3
Explanation:
The longest common subsequence is "abc" and its length is 3.
Example 3:
Input:
text1 = "abc", text2 = "def"
Output:
0
Explanation:
There is no such common subsequence, so the result is 0.
Constraints:
1 <= text1.length, text2.length <= 1000
text1 and text2 consist of only lowercase English characters.
This problem was taken from Leetcode Longest Common Subsequence
Solution
Dynamic programming with memoization.
We create a matrix to store (memoize) the response from previous calculations so we won’t re-calculate them again.
We are starting from the first row comparing every character from column 1 with the first character from row one.
There are two cases:
either the character match, then we add 1 and add it with the value from the diagonal to the left of the cell where we are.
Longest Common Substring Step 1
the characters don’t match, then we get the MAX of the value above current cell and the value to the left of the current cell.
Longest Common Substring Step 2
Here again c int the column (first string) matches c in the row (the second string)
so we get the value of last comparison (to the left and up diagonal of the current cell) which is 1 and add 1 again since characters match.
Longest Common Substring 3
and keep going till we reach the end of both strings which is the answer.
Longest Common Substring 4
/**
* @param {string} text1
* @param {string} text2
* @return {number}
*/
var longestCommonSubsequence = function(text1, text2) {
var txt1 = text1.split('');
var txt2 = text2.split('');
txt1.unshift('0');
txt2.unshift('1');
var l1 = txt1.length;
var l2 = txt2.length;
var matrix = [];
for(var i = 0; i < l2; i ++) {
matrix[i] = new Array(l1).fill(0);
}
var maxCommon = 0;
for(var row = 0; row < l2; row ++) {
for(var col = 0; col < l1; col ++) {
var last = 0;
if(txt1[col] == txt2[row]) {
var previousDiagonalRowVal = row == 0 || col == 0 ? 0 : matrix[row - 1][col - 1];
last = 1 + previousDiagonalRowVal;
}
else {
var prevUp = row == 0 ? 0 : matrix[row - 1][col];
var prevLeft = col == 0 ? 0 : matrix[row][col - 1];
last = Math.max(prevUp, prevLeft);
}
matrix[row][col] = last;
maxCommon = last > maxCommon ? last : maxCommon;
}
}
return maxCommon;
};
var text1 = "abcde", text2 = "ace";
var r = longestCommonSubsequence(text1, text2);
console.log(">>", r);
A linked list of length n is given such that each node contains an additional random pointer, which could point to any node in the list, or null.
Construct a deep copy of the list. The deep copy should consist of exactly nbrand new nodes, where each new node has its value set to the value of its corresponding original node. Both the next and random pointer of the new nodes should point to new nodes in the copied list such that the pointers in the original list and copied list represent the same list state. None of the pointers in the new list should point to nodes in the original list.
For example, if there are two nodes X and Y in the original list, where X.random --> Y, then for the corresponding two nodes x and y in the copied list, x.random --> y.
Return the head of the copied linked list.
The linked list is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where:
val: an integer representing Node.val
random_index: the index of the node (range from 0 to n-1) that the random pointer points to, or null if it does not point to any node.
Your code will only be given the head of the original linked list.
Example 1:
Input:
head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output:
[[7,null],[13,0],[11,4],[10,2],[1,0]]
Example 2:
Input:
head = [[1,1],[2,1]]
Output:
[[1,1],[2,1]]
Example 3:
Input:
head = [[3,null],[3,0],[3,null]]
Output:
[[3,null],[3,0],[3,null]]
Constraints:
0 <= n <= 1000
-104 <= Node.val <= 104
Node.random is null or is pointing to some node in the linked list.
/**
* // Definition for a Node.
* function Node(val, next, random) {
* this.val = val;
* this.next = next;
* this.random = random;
* };
*/
/**
* @param {Node} head
* @return {Node}
*/
var copyRandomList = function(head) {
function dfsTraverse(node, visited={}) {
if(!node) {
return null;
}
// if a new node is created, return it. Otherwise you will fall into circular loops
if(node?.clone) {
return node?.clone;
}
var newNode = new Node(node?.val, null, null);
node.clone = newNode;
var next = dfsTraverse(node?.next);
var random = dfsTraverse(node?.random);
newNode.next = next;
newNode.random = random;
return newNode;
}
var result = dfsTraverse(head);
return result;
};
function Node(val, next, random) {
this.val = val;
this.next = next;
this.random = random;
}
;// [1,null],[2,0],[3,1]
var nodes = {};
nodes['1'] = new Node(1,null,null);
nodes['2'] = new Node(2,null,null);
nodes['3'] = new Node(3,null,null);
nodes['1'].next = nodes['2'];
nodes['1'].random = null;
nodes['2'].next = nodes['3'];
nodes['2'].random = nodes['1'];
nodes['3'].next = null;
nodes['3'].random = nodes['2'];
//console.log("root");
//console.log(nodes['7']);
var result = copyRandomList(nodes['1']);
console.log(result);
More elegant solution
/**
* // Definition for a Node.
* function Node(val, next, random) {
* this.val = val;
* this.next = next;
* this.random = random;
* };
*/
/**
* @param {Node} head
* @return {Node}
*/
var copyRandomList = function(head) {
let cur = head;
const copy = new Map();
// add all new nodes and values for now
while (cur) {
copy.set(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
// iterate again and point curent node to the newly created nodes using the key
while (cur) {
copy.get(cur).next = copy.get(cur.next) || null;
copy.get(cur).random = copy.get(cur.random) || null;
cur = cur.next;
}
return copy.get(head);
}
function Node(val, next, random) {
this.val = val;
this.next = next;
this.random = random;
};
// [1,null],[2,0],[3,1]
var nodes = {};
nodes['1'] = new Node(1,null,null);
nodes['2'] = new Node(2,null,null);
nodes['3'] = new Node(3,null,null);
nodes['1'].next = nodes['2'];
nodes['1'].random = null;
nodes['2'].next = nodes['3'];
nodes['2'].random = nodes['1'];
nodes['3'].next = null;
nodes['3'].random = nodes['2'];
var result = copyRandomList(nodes['1']);
console.log(result);
Given the root node of a binary search tree and two integers low and high, return the sum of values of all nodes with a value in the inclusive range [low, high].
Example 1:
Input:
root = [10,5,15,3,7,null,18], low = 7, high = 15
Output:
32
Explanation:
Nodes 7, 10, and 15 are in the range [7, 15]. 7 + 10 + 15 = 32.
Example 2:
Input:
root = [10,5,15,3,7,13,18,1,null,6], low = 6, high = 10
Output:
23
Explanation:
Nodes 6, 7, and 10 are in the range [6, 10]. 6 + 7 + 10 = 23.
Constraints:
The number of nodes in the tree is in the range [1, 2 * 104].
The graph represent connections between airports. Nodes (vertices) represent airports, and edges represent flights.
BFS search (Breadth First Search)
Breadth-first Search (BFS) starts by pushing all of the direct children to a queue (first-in, first-out). It then visits each item in queue and adds the next layer of children to the back of the queue. Since one node could be visited multiple times causing infinite loop, we have to keep track of all visited nodes.
Using JavaScript Map
// DATA
const airports = 'PHX BKK OKC JFK LAX MEX EZE HEL LOS LAP LIM'.split(' ');
const routes = [
['PHX', 'LAX'],
['PHX', 'JFK'],
['JFK', 'OKC'],
['JFK', 'HEL'],
['JFK', 'LOS'],
['MEX', 'LAX'],
['MEX', 'BKK'],
['MEX', 'LIM'],
['MEX', 'EZE'],
['LIM', 'BKK'],
];
// The graph
const adjacencyList = new Map();
// Add node
function addNode(airport) {
adjacencyList.set(airport, []);
}
// Add edge, undirected
function addEdge(origin, destination) {
adjacencyList.get(origin).push(destination);
adjacencyList.get(destination).push(origin);
}
// Create the Graph
airports.forEach(addNode);
routes.forEach(route => addEdge(route[0], route[1]));
console.log(adjacencyList);
function bfs(start, searchItem) {
const visited = new Set();
const queue = [start];
while (queue.length > 0) {
const airport = queue.shift(); // mutates the queue
const destinations = adjacencyList.get(airport);
for (const destination of destinations) {
if (destination === searchItem) {
console.log(`BFS found a route to`, searchItem)
}
if (!visited.has(destination)) {
visited.add(destination);
queue.push(destination);
}
}
}
}
bfs('PHX', 'BKK');
Using JavaScript object
// Data
var airports = {
'PHX': ['LAX', 'JFK'],
'BKK': ['MEX', 'LIM'],
'OKC': ['JFK'],
'JFK': ['LAX', 'PHX', 'OKC', 'HEL', 'LOS', 'EZE'],
'LAX': ['PHX', 'JFK', 'MEX'],
'MEX': ['LAX', 'EZE', 'BKK', 'LIM'],
'EZE': ['JFK', 'MEX'],
'HEL': ['JFK'],
'LOS': ['JFK'],
'LAP': [],
'LIM': ['MEX', 'BKK']
};
function bfs(start, endDest) {
var queue = [start];
var visited = {};
while(queue.length > 0) {
var curentNodeVal = queue.shift();
var childNodes = airports[curentNodeVal];
for(const childNode of childNodes) {
if(childNode === endDest) {
console.log("BFS found a route to :", endDest);
}
if(!visited[childNode]) {
console.log(childNode);
visited[childNode] = true;
queue.push( childNode);
}
}
}
}
bfs('PHX', 'BKK');
DFS (Depth First Search)
Depth-first Search (DFS) will go as far into the graph as possible until it reaches a node without any children, at which point it backtracks and continues the process. The algorithm can be implemented with a recursive function that keeps track of previously visited nodes. If a node has not been visited, we call the function recursively.
Using JavaScript Map
// DATA
const airports = 'PHX BKK OKC JFK LAX MEX EZE HEL LOS LAP LIM'.split(' ');
const routes = [
['PHX', 'LAX'],
['PHX', 'JFK'],
['JFK', 'OKC'],
['JFK', 'HEL'],
['JFK', 'LOS'],
['MEX', 'LAX'],
['MEX', 'BKK'],
['MEX', 'LIM'],
['MEX', 'EZE'],
['LIM', 'BKK'],
];
// The graph
const adjacencyList = new Map();
// Add node
function addNode(airport) {
adjacencyList.set(airport, []);
}
// Add edge, undirected
function addEdge(origin, destination) {
adjacencyList.get(origin).push(destination);
adjacencyList.get(destination).push(origin);
}
// Create the Graph
airports.forEach(addNode);
routes.forEach(route => addEdge(route[0], route[1]));
console.log(adjacencyList);
function dfs(start, visited = new Set()) {
console.log(start)
visited.add(start);
const destinations = adjacencyList.get(start);
for (const destination of destinations) {
if (destination === 'BKK') {
console.log(`DFS found Bangkok`)
return;
}
if (!visited.has(destination)) {
dfs(destination, visited);
}
}
}
dfs('PHX');