简体   繁体   中英

Two-sum Leetcode explanation, Hashmap, Javascript

Im just wondering who can explain the algorithm of this solution step by step. I dont know how hashmap works. Can you also give a basic examples using a hashmap for me to understand this algorithm. Thank you!

var twoSum = function(nums, target) {
  let hash = {};

  for(let i = 0; i < nums.length; i++) {
    const n = nums[i];
    if(hash[target - n] !== undefined) {
      return [hash[target - n], i];
    }
    hash[n] = i;
  }
  return [];
}

Your code takes an array of numbers and a target number/sum. It then returns the indexes in the array for two numbers which add up to the target number/sum.

Consider an array of numbers such as [1, 2, 3] and a target of 5 . Your task is to find the two numbers in this array which add to 5 . One way you can approach this problem is by looping over each number in your array and asking yourself "Is there a number (which I have already seen in my array) which I can add to the current number to get my target sum?".

Well, if we loop over the example array of [1, 2, 3] we first start at index 0 with the number 1 . Currently, there are no numbers which we have already seen that we can add with 1 to get our target of 5 as we haven't looped over any numbers yet.

So, so far, we have met the number 1 , which was at index 0 . This is stored in the hashmap (ie object) as {'1': 0} . Where the key is the number and the value ( 0 ) is the index it was seen at. The purpose of the object is to store the numbers we have seen and the indexes they appear at.

Next, the loop continues to index 1, with the current number being 2 . We can now ask ourselves the question: Is there a number which I have already seen in my array that I can add to my current number of 2 to get the target sum of 5 . The amount needed to add to the current number to get to the target can be obtained by doing target-currentNumber . In this case, we are currently on 2 , so we need to add 3 to get to our target sum of 5. Using the hashmap/object, we can check if we have already seen the number 3 . To do this, we can try and access the object 3 key by doing obj[target-currentNumber] . Currently, our object only has the key of '1' , so when we try and access the 3 key you'll get undefined . This means we haven't seen the number 3 yet, so, as of now, there isn't anything we can add to 2 to get our target sum.

So now our object/hashmap looks like {'1': 0, '2': 1} , as we have seen the number 1 which was at index 0 , and we have seen the number 2 which was at index 1 .

Finally, we reach the last number in your array which is at index 2. Index 2 of the array holds the number 3 . Now again, we ask ourselves the question: Is there a number we have already seen which we can add to 3 (our current number) to get the target sum?. The number we need to add to 3 to get our target number of 5 is 2 (obtained by doing target-currentNumber ). We can now check our object to see if we have already seen a number 2 in the array. To do so we can use obj[target-currentNumber] to get the value stored at the key 2 , which stores the index of 1. This means that the number 2 does exist in the array, and so we can add it to 3 to reach our target. Since the value was in the object, we can now return our findings. That being the index of where the seen number occurred, and the index of the current number.

In general, the object is used to keep track of all the previously seen numbers in your array and keep a value of the index at which the number was seen at.

Here is an example of running your code. It returns [1, 2] , as the numbers at indexes 1 and 2 can be added together to give the target sum of 5 :

 const twoSum = function(nums, target) { const hash = {}; // Stores seen numbers: {seenNumber: indexItOccurred} for (let i = 0; i < nums.length; i++) { // loop through all numbers const n = nums[i]; // grab the current number `n`. if (hash[target - n] !== undefined) { // check if the number we need to add to `n` to reach our target has been seen: return [hash[target - n], i]; // grab the index of the seen number, and the index of the current number } hash[n] = i; // update our hash to include the. number we just saw along with its index. } return []; // If no numbers add up to equal the `target`, we can return an empty array } console.log(twoSum([1, 2, 3], 5)); // [1, 2]

A solution like this might seem over-engineered. You might be wondering why you can't just look at one number in the array, and then look at all the other numbers and see if you come across a number that adds up to equal the target . A solution like that would work perfectly fine, however, it's not very efficient. If you had N numbers in your array, in the worst case (where no two numbers add up to equal your target ) you would need to loop through all of these N numbers - that means you would do N iterations. However, for each iteration where you look at a singular number, you would then need to look at each other number using a inner loop. This would mean that for each iteration of your outer loop you would do N iterations of your inner loop. This would result in you doing N*N or N 2 work (O(N 2 ) work). Unlike this approach, the solution described in the first half of this answer only needs to do N iterations over the entire array. Using the object, we can find whether or not a number is in the object in constant (O(1)) time, which means that the total work for the above algorithm is only O(N).

For further information about how objects work, you can read about bracket notation and other property accessor methods here .


EDIT: As you asked for further examples/usages of objects/hashmaps here are some examples.

Some simple use cases for an object is to store key-value pairs. To really simplify it, you can think of an object/hashmap as being an array, however, instead of indexes (so numbers), you can have "named" indexes. For example, you could have an array that looks like so:

//                 0      1   2   3
const person = ["James", "A", 18, 3];

Above, we have a person array which holds information about a person . At index 0 we have the name of the person, at index 1 we have the last name initial, at index 2 we have the age of the person and at index 3 we have the number of family members that person has. This way of representing a single person isn't very friendly, as you have to remember what information each index holds. It's not always easy to guess, especially if they hold numbers. So, instead, we can represent a single person using an object. This essentially allows us to name our indexes (these named indexes are known as keys ). So using an array above, we can do something like so to represent our person as an object:

const person = {
  name: "James",
  surname_initial: "A",
  age: 18,
  familyMembers: 3
}

Now to access the data held at name , you can use bracket notation ( person["name"] gives "James") or dot notation ( person.name also gives "James") to get the value of "James" . Doing this allows you to clearly define what each piece of data is.

The good thing about objects is that they can only keep unique keys. If you try and set a key , for instance person["age"] = 30 , then you'll update the age key to have a value of 30 . It won't create 2 keys with the name of age , instead, it will update the value at the key age to the new value of 30 . So, objects can be good for dealing with things such as grouping or finding unique values.

Another use case for objects could be to keep a count of the frequency of items in an array. For example, if you had the array ['a', 'b', 'a', 'a', 'b', 'c'] , and you were asked to find how many 'a' s, 'b' s and 'c' s appear in the array, you can use an object for this. The main idea is to loop over your array and check your object to see if the current item is already a key in your object. If it is, then you can increment the counter which it holds, if it isn't in your object you can set a new key to be the current item, with a value set to 1 , to indicate that so far you have only seen one of that item. This can be achieved like so:

 const arr = ['a', 'b', 'a', 'a', 'b', 'c']; const freq = {}; for(let i = 0; i < arr.length; i++) { const currentItem = arr[i]; if(freq[currentItem]) { // if currentItem is a key in the freq object freq[currentItem] = freq[currentItem] + 1; // update the currentItems counter value to be incremented } else { // if the currentItem is not a key in the freq object freq[currentItem] = 1; // set a new key to be the value of `currentItem`, and initialize its counter to `1`. } } console.log(freq); // Output the freq object to see frequency.

function twoSum(numbers, target) {
  for (let i = 0; i < numbers.length; i++) {
  for (let j = i + 1; j < numbers.length; j++) {
    if (numbers[i] + numbers[j] === target) {
      return [numbers.indexOf(numbers[i]), numbers.lastIndexOf(numbers[j])];
    }
  }
}
}

const nums = [1,5,9];

const twoSum = (nums, target) => {// O(n)

let map = {}; //O(n)
for (let i = 0 ; i < nums.length ; i++){
    var value = nums[i];
    var complementPair = target - value;

    if (map[complementPair] !== undefined){
        return [map[complementPair],i];
    }
    map[value] = i;

}

}

console.log(twoSum(array1, 10));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM