Google Top Interview Questions Flashcards

1
Q

Longest Substring Without Repeating Characters

Given a string s, find the length of the longest substring without repeating characters.

A

Sliding Window

By using HashSet as a sliding window, checking if a character in the current can be done in O(1).

Use HashSet to store the characters in current window [i, j)

Then we slide the index j to the right. If it is not in the HashSet, we slide j further. Doing so until s[j] is already in the HashSet. At this point, we found the maximum size of substrings without duplicate characters start with index i. If we do this for all i, we get our answer.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

3Sum

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.

A

Hashset

Since triplets must sum up to the target value, we can try the hash table approach from the Two Sum solution where the complement of the target is stored in a HashMap.

Time Complexity: O(n^2)
twoSum is O(n), and we call it n times.

For the main function:

  1. Sort the input array nums.
  2. Iterate through the array:
  3. If the current value is greater than zero, break from the loop. Remaining values cannot sum to zero.
  4. If the current value is the same as the one before, skip it.
  5. Otherwise, call twoSum for the current position i.

For twoSum function:

  1. For each index j > i in A:
  2. Compute complement value as -nums[i] - nums[j].
  3. If complement exists in hashset seen:
  4. We found a triplet - add it to the result res.
  5. Increment j while the next value is the same as before to avoid duplicates in the result.
  6. Add nums[j] to hashset seen

Return the result res.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Multiply Strings

Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2, also represented as a string.

Note: You must not use any built-in BigInteger library or convert the inputs to integer directly.

A

Elementary Math

We mimick using long hand multiplication from elementary school.

We take the ones place digit of the second number, then multiply it with all digits of the first number consequently going backward, and write the result. We need to remember about carry as well.

Then we take the tens place digit of the second number and multiply it with all digits of the first number. Since we used the tens place digit, we will multiply this result by 10. Then we write this result below the previous result, signifying that we will add it to the previous result later.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Unique Email Addresses

Every valid email consists of a local name and a domain name, separated by the ‘@’ sign. Besides lowercase letters, the email may contain one or more ‘.’ or ‘+’.

For example, in “alice@leetcode.com”, “alice” is the local name, and “leetcode.com” is the domain name.
If you add periods ‘.’ between some characters in the local name part of an email address, mail sent there will be forwarded to the same address without dots in the local name. Note that this rule does not apply to domain names.

For example, “alice.z@leetcode.com” and “alicez@leetcode.com” forward to the same email address.
If you add a plus ‘+’ in the local name, everything after the first plus sign will be ignored. This allows certain emails to be filtered. Note that this rule does not apply to domain names.

For example, “m.y+name@email.com” will be forwarded to “my@email.com”.
It is possible to use both of these rules at the same time.

Given an array of strings emails where we send one email to each email[i], return the number of different addresses that actually receive mails.

A

Approach 1: Linear Iteration
Intuition

We can iterate over an email from left to right, and add characters to local name until a ‘+’ occurs, then we can skip all characters until ‘@’ occurs, then we can again start appending the characters till the end of the email string to form the domain name.

Notice that per the rules, we do not need to read any characters between the first ‘+’ and ‘@’. While checking each character from left to right, after finding the first ‘+’ in the local name we can directly find the domain name by switching to a reverse iteration as there is only one ‘@’ and we will skip all characters in between ‘+’ and ‘@’.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Odd Even Jump

You are given an integer array arr. From some starting index, you can make a series of jumps. The (1st, 3rd, 5th, …) jumps in the series are called odd-numbered jumps, and the (2nd, 4th, 6th, …) jumps in the series are called even-numbered jumps. Note that the jumps are numbered, not the indices.

You may jump forward from index i to index j (with i < j) in the following way:

During odd-numbered jumps (i.e., jumps 1, 3, 5, …), you jump to the index j such that arr[i] <= arr[j] and arr[j] is the smallest possible value. If there are multiple such indices j, you can only jump to the smallest such index j.
During even-numbered jumps (i.e., jumps 2, 4, 6, …), you jump to the index j such that arr[i] >= arr[j] and arr[j] is the largest possible value. If there are multiple such indices j, you can only jump to the smallest such index j.
It may be the case that for some index i, there are no legal jumps.
A starting index is good if, starting from that index, you can reach the end of the array (index arr.length - 1) by jumping some number of times (possibly 0 or more than once).

Return the number of good starting indices.

A

This problem can be broken down into non-overlapping subproblems and solved using Dynamic Programming

First create boolean DP array.
dp[i][0] stands for you can arrive index n - 1 starting from index i at an odd step.
dp[i][1] stands for you can arrive index n - 1 starting from index i at an even step.

To quickly find the next greater or smaller number and its index: traverse the array reversely and store data into a TreeMap using the number as Key and its index as Value.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

License Key Formatting

You are given a license key represented as a string s that consists of only alphanumeric characters and dashes. The string is separated into n + 1 groups by n dashes. You are also given an integer k.

We want to reformat the string s such that each group contains exactly k characters, except for the first group, which could be shorter than k but still must contain at least one character. Furthermore, there must be a dash inserted between two groups, and you should convert all lowercase letters to uppercase.

Return the reformatted license key.

A

Scan linearly through the string backwards from left to right.
Add the dashes at the appropriate spots and store the results in a StringBuffer.
At the end reverse the string back.

    public String licenseKeyFormatting(String s, int k) {
        StringBuilder sb = new StringBuilder();
        for (int i = s.length() - 1; i >= 0; i--)
            if (s.charAt(i) != '-')
                sb.append(sb.length() % (k + 1) == k ? '-' : "").append(s.charAt(i));
        return sb.reverse().toString().toUpperCase();
    }
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Fruit Into Baskets

You are visiting a farm that has a single row of fruit trees arranged from left to right. The trees are represented by an integer array fruits where fruits[i] is the type of fruit the ith tree produces.

You want to collect as much fruit as possible. However, the owner has some strict rules that you must follow:

You only have two baskets, and each basket can only hold a single type of fruit. There is no limit on the amount of fruit each basket can hold.
Starting from any tree of your choice, you must pick exactly one fruit from every tree (including the start tree) while moving to the right. The picked fruits must fit in one of your baskets.
Once you reach a tree with fruit that cannot fit in your baskets, you must stop.
Given the integer array fruits, return the maximum number of fruits you can pick.

A

Solve with sliding window using a left and right pointer.

Maintain a HashMap counter which count the number of element between the two pointers.

public int totalFruit(int[] tree) {
    Map count = new HashMap<>();
    int i = 0, j;
    for (j = 0; j < tree.length; ++j) {
        count.put(tree[j], count.getOrDefault(tree[j], 0) + 1);
        if (count.size() > 2) {
            count.put(tree[i], count.get(tree[i]) - 1);
            count.remove(tree[i++], 0);
        }
    }
    return j - i;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Container With Most Water

Given n non-negative integers a1, a2, …, an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of the line i is at (i, ai) and (i, 0). Find two lines, which, together with the x-axis forms a container, such that the container contains the most water.

Notice that you may not slant the container.

A

Two Pointer Approach

The intuition behind this approach is that the area formed between the lines will always be limited by the height of the shorter line. Further, the farther the lines, the more will be the area obtained.

We take two pointers, one at the beginning and one at the end of the array constituting the length of the lines. Further, we maintain a variable, maxArea, to store the maximum area obtained till now. At every step, we find out the area formed between them, update maxArea and move the pointer pointing to the shorter line towards the other end by one step.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Next Permutation

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such an arrangement is not possible, it must rearrange it as the lowest possible order (i.e., sorted in ascending order).

The replacement must be in place and use only constant extra memory.

A

Single Pass Approach

We observe that for any given sequence that is in descending order, no next larger permutation is possible. For example, no next permutation is possible for the following array:

[9, 5, 4, 3, 1]

Scan through the numbers from right to left, when a descending number is found mark it’s index and then start going back to the right until we find the number just larger than it. Swap it in place.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Rotate Image

You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).

You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

A

Reverse on Diagonal and then Reverse Left to Right
Intuition

The most elegant solution for rotating the matrix is to firstly reverse the matrix around the main diagonal, and then reverse it from left to right. These operations are called transpose and reflect in linear algebra.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Jump Game

You are given an integer array nums. You are initially positioned at the array’s first index, and each element in the array represents your maximum jump length at that position.

Return true if you can reach the last index, or false otherwise.

A

Dynamic Programming Top-down

Top-down Dynamic Programming can be thought of as optimized backtracking. It relies on the observation that once we determine that a certain index is good / bad, this result will never change. This means that we can store the result and not need to recompute it every time.

Therefore, for each position in the array, we remember whether the index is good or bad. Let’s call this array memo and let its values be either one of: GOOD, BAD, UNKNOWN. This technique is called memoization[3].

An example of a memoization table for input array nums = [2, 4, 2, 1, 0, 2, 0] can be seen in the diagram below. We write G for a GOOD position and B for a BAD one. We can see that we cannot start from indices 2, 3 or 4 and eventually reach last index (6), but we can do that from indices 0, 1, 5 and (trivially) 6.

Steps

  1. Initially, all elements of the memo table are UNKNOWN, except for the last one, which is (trivially) GOOD (it can reach itself)
  2. Modify the backtracking algorithm such that the recursive step first checks if the index is known (GOOD / BAD)If it is known then return True / FalseOtherwise perform the backtracking steps as before
  3. Once we determine the value of the current index, we store it in the memo table
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Plus One

You are given a large integer represented as an integer array digits, where each digits[i] is the ith digit of the integer. The digits are ordered from most significant to least significant in left-to-right order. The large integer does not contain any leading 0’s.

Increment the large integer by one and return the resulting array of digits.

A

“Plus One” is a subset of the problem set “Add Number”, which shares the same solution pattern.

All these problems could be solved in linear time, and the question here is how to solve it without using the addition operation or how to solve it in constant space complexity.

The choice of algorithm should be based on the format of input. Here we list a few examples:

Integers

Usually the addition operation is not allowed for such a case. Use Bit Manipulation Approach. Here is an example: Add Binary.

Strings

Use bit by bit computation. Note, sometimes it might not be feasible to come up a solution with the constant space for languages with immutable strings, e.g. for Java and Python. Here is an example: Add Binary.

Linked Lists

Sentinel Head + Schoolbook Addition with Carry. Here is an example: Plus One Linked List.

Arrays (also the current problem)

Schoolbook addition with carry.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Minimum Window Substring

Given two strings s and t of lengths m and n respectively, return the minimum window substring of s such that every character in t (including duplicates) is included in the window. If there is no such substring, return the empty string “”.

The testcases will be generated such that the answer is unique.

A substring is a contiguous sequence of characters within the string.

A

Sliding Window
Intuition

The question asks us to return the minimum window from the string SS which has all the characters of the string TT. Let us call a window desirable if it has all the characters from TT.

We can use a simple sliding window approach to solve this problem.

In any sliding window based problem we have two pointers. One rightright pointer whose job is to expand the current window and then we have the leftleft pointer whose job is to contract a given window. At any point in time only one of these pointers move and the other one remains fixed.

The solution is pretty intuitive. We keep expanding the window by moving the right pointer. When the window has all the desired characters, we contract (if possible) and save the smallest window till now.

The answer is the smallest desirable window.

For eg. S = “ABAACBAB” T = “ABC”. Then our answer window is “ACB” and shown below is one of the possible desirable windows.

Algorithm

  1. We start with two pointers, left and right initially pointing to the first element of the string SS.
  2. We use the right pointer to expand the window until we get a desirable window i.e. a window that contains all of the characters of TT.
  3. Once we have a window with all the characters, we can move the left pointer ahead one by one. If the window is still a desirable one we keep on updating the minimum window size.
  4. If the window is not desirable any more, we repeat step \; 2step2 onwards.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Read N Characters Given Read4 II - Call multiple times

Given a file and assume that you can only read the file using a given method read4, implement a method read to read n characters. Your method read may be called multiple times.

Method read4:

The API read4 reads four consecutive characters from file, then writes those characters into the buffer array buf4.

The return value is the number of actual characters read.

Note that read4() has its own file pointer, much like FILE *fp in C.

A
private int buffPtr = 0;
    private int buffCnt = 0;
    private char[] buff = new char[4];
    public int read(char[] buf, int n) {
        int ptr = 0;
        while (ptr < n) {
            if (buffPtr == 0) {
                buffCnt = read4(buff);
            }
            if (buffCnt == 0) break;
            while (ptr < n && buffPtr < buffCnt) {
                buf[ptr++] = buff[buffPtr++];
            }
            if (buffPtr >= buffCnt) buffPtr = 0;
        }
        return ptr;
    }

I used buffer pointer (buffPtr) and buffer Counter (buffCnt) to store the data received in previous calls. In the while loop, if buffPtr reaches current buffCnt, it will be set as zero to be ready to read new data.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Longest Substring with At Most Two Distinct Characters

Given a string s, return the length of the longest substring that contains at most two distinct characters.

A

Sliding Window
Intuition

Sliding window + HashMap

To solve the problem in one pass let’s use here sliding window approach with two set pointers left and right serving as the window boundaries.

The idea is to set both pointers in the position 0 and then move right pointer to the right while the window contains not more than two distinct characters. If at some point we’ve got 3 distinct characters, let’s move left pointer to keep not more than 2 distinct characters in the window.

  1. Return N if the string length N is smaller than 3.
  2. Set both set pointers in the beginning of the string left = 0 and right = 0 and init max substring length max_len = 2.
  3. While right pointer is less than N:
    a) If hashmap contains less than 3 distinct characters, add the current character s[right] in the hashmap and move right pointer to the right.
    b) If hashmap contains 3 distinct characters, remove the leftmost character from the hashmap and move the left pointer so that sliding window contains again 2 distinct characters only.
    c) Update max_len.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Missing Ranges

You are given an inclusive range [lower, upper] and a sorted unique integer array nums, where all elements are in the inclusive range.

A number x is considered missing if x is in the range [lower, upper] and x is not in nums.

Return the smallest sorted list of ranges that cover every missing number exactly. That is, no element of nums is in any of the ranges, and each missing number is in one of the ranges.

Each range [a,b] in the list should be output as:

“a->b” if a != b
“a” if a == b

Example 1:

Input: nums = [0,1,3,50,75], lower = 0, upper = 99
Output: ["2","4->49","51->74","76->99"]
Explanation: The ranges are:
[2,2] --> "2"
[4,49] --> "4->49"
[51,74] --> "51->74"
[76,99] --> "76->99"
A

Linear Scan
Intuition and Algorithm

Since the input array, nums, is sorted ascendingly and all the elements in it are within the given [lower, upper] bounds, we can simply check consecutive elements to see if they differ by one or not. If they don’t, then we have found a missing range.

Time complexity : O(N)
Space complexity : O(1)

class Solution {
    public List findMissingRanges(int[] nums, int lower, int upper) {
        List result = new ArrayList<>();
        int prev = lower - 1;
        for (int i = 0; i <= nums.length; i++) {
            int curr = (i < nums.length) ? nums[i] : upper + 1;
            if (prev + 1 <= curr - 1) {
                result.add(formatRange(prev + 1, curr - 1));
            }
            prev = curr;
        }
        return result;
    }
    // formats range in the requested format
    private String formatRange(int lower, int upper) {
        if (lower == upper) {
            return String.valueOf(lower);
        }
        return lower + "->" + upper;
    }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Next Closest Time

Given a time represented in the format “HH:MM”, form the next closest time by reusing the current digits. There is no limit on how many times a digit can be reused.

You may assume the given input string is always valid. For example, “01:34”, “12:09” are all valid. “1:34”, “12:9” are all invalid.

Example 1:

Input: time = “19:34”
Output: “19:39”
Explanation: The next closest time choosing from digits 1, 9, 3, 4, is 19:39, which occurs 5 minutes later.
It is not 19:33, because this occurs 23 hours and 59 minutes later.

A

This approach here is trying to find next digit for each position in “HH:MM” from right to left. If the next digit is greater than current digit, return directly and keep other digits unchanged.
Here is the steps: (e.g. “17:38”)

Retrieve all four digits from given string and sort them in ascending order, “17:38” -> digits[] {‘1’, ‘3’, ‘7’, ‘8’}

Apply findNext() from the right most digit to left most digit, try to find next greater digit from digits[] (if exist) which is suitable for current position, otherwise return the minimum digit (digits[0]):

“HH:M_”: there is no upperLimit for this position (0-9). Just pick the next different digit in the sequence. In the example above, ‘8’ is already the greatest one, so we change it into the smallest one (digits[0] i.e. ‘1’) and move to next step: “17:38” -> “17:31”

“HH:_M”: the upperLimit is ‘5’ (00~59). The next different digit for ‘3’ is ‘7’, which is greater than ‘5’, so we should omit it and try next. Similarly, ‘8’ is beyond the limit, so we should choose next, i.e. ‘1’: “17:38” -> “17:11”

“H_:MM”: the upperLimit depends on result[0]. If result[0] == ‘2’, then it should be no more than ‘3’; else no upperLimit (0-9). Here we have result[0] = ‘1’, so we could choose any digit we want. The next digit for ‘7’ is ‘8’, so we change it and return the result directly. “17:38” -> “18:11”

“_H:MM”: the upperLimit is ‘2’ (00~23). e.g. “03:33” -> “00:00”

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Expressive Words

Sometimes people repeat letters to represent extra feeling. For example:

“hello” -> “heeellooo”
“hi” -> “hiiii”
In these strings like “heeellooo”, we have groups of adjacent letters that are all the same: “h”, “eee”, “ll”, “ooo”.

You are given a string s and an array of query strings words. A query word is stretchy if it can be made to be equal to s by any number of applications of the following extension operation: choose a group consisting of characters c, and add some number of characters c to the group so that the size of the group is three or more.

For example, starting with “hello”, we could do an extension on the group “o” to get “hellooo”, but we cannot get “helloo” since the group “oo” has a size less than three. Also, we could do another extension like “ll” -> “lllll” to get “helllllooo”. If s = “helllllooo”, then the query word “hello” would be stretchy because of these two extension operations: query = “hello” -> “hellooo” -> “helllllooo” = s.
Return the number of query strings that are stretchy.

Example 1:

Input: s = “heeellooo”, words = [“hello”, “hi”, “helo”]
Output: 1
Explanation:
We can extend “e” and “o” in the word “hello” to get “heeellooo”.
We can’t extend “helo” to get “heeellooo” because the group “ll” is not size 3 or more.

A

Loop through all words. check(string S, string W) checks if W is stretchy to S.

In check function, use two pointer:

If S[i] == W[j], i++, j++
If S[i - 2] == S[i - 1] == S[i] or S[i - 1] == S[i] == S[i + 1], i++
return false

    public int expressiveWords(String S, String[] words) {
        int res = 0;
        for (String W : words) if (check(S, W)) res++;
        return res;
    }
    public boolean check(String S, String W) {
        int n = S.length(), m = W.length(), j = 0;
        for (int i = 0; i < n; i++)
            if (j < m && S.charAt(i) == W.charAt(j)) j++;
            else if (i > 1 && S.charAt(i) == S.charAt(i - 1) && S.charAt(i - 1) == S.charAt(i - 2));
            else if (0 < i && i < n - 1 && S.charAt(i - 1) == S.charAt(i) && S.charAt(i) == S.charAt(i + 1));
            else return false;
        return j == m;
    }
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

Find And Replace in String
You are given a 0-indexed string s that you must perform k replacement operations on. The replacement operations are given as three 0-indexed parallel arrays, indices, sources, and targets, all of length k.

To complete the ith replacement operation:

Check if the substring sources[i] occurs at index indices[i] in the original string s.
If it does not occur, do nothing.
Otherwise if it does occur, replace that substring with targets[i].
For example, if s = “abcd”, indices[i] = 0, sources[i] = “ab”, and targets[i] = “eee”, then the result of this replacement will be “eeecd”.

All replacement operations must occur simultaneously, meaning the replacement operations should not affect the indexing of each other. The testcases will be generated such that the replacements will not overlap.

For example, a testcase with s = “abc”, indices = [0, 1], and sources = [“ab”,”bc”] will not be generated because the “ab” and “bc” replacements overlap.
Return the resulting string after performing all replacement operations on s.

A substring is a contiguous sequence of characters in a string.

A

Intuition:
Verify equality of string and change it if necessary.
The only thing we need take attention is that the string form sources and targets can be different.
Instead of record the length changement, I sort indexes and do it from right to left.
(Since indexes.length <= 100 is really small)
In this way, the different length won’t bother us anymore.

Explanation:
Descending indexes[] with tracking its index.
Verify equality of subring in S source and source.
Replace S if necessary.

public String findReplaceString(String S, int[] indexes, String[] sources, String[] targets) {
    List sorted = new ArrayList<>();
    for (int i = 0 ; i < indexes.length; i++) sorted.add(new int[]{indexes[i], i});
    Collections.sort(sorted, Comparator.comparing(i -> -i[0]));
    for (int[] ind: sorted) {
        int i = ind[0], j = ind[1];
        String s = sources[j], t = targets[j];
        if (S.substring(i, i + s.length()).equals(s)) S = S.substring(0, i) + t + S.substring(i + s.length());
    }
    return S;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Maximize Distance to Closest Person

Solution
You are given an array representing a row of seats where seats[i] = 1 represents a person sitting in the ith seat, and seats[i] = 0 represents that the ith seat is empty (0-indexed).

There is at least one empty seat, and at least one person sitting.

Alex wants to sit in the seat such that the distance between him and the closest person to him is maximized.

Return that maximum distance to the closest person.

A

Next Array [Accepted]

Intuition

Let left[i] be the distance from seat i to the closest person sitting to the left of i. Similarly, let right[i] be the distance to the closest person sitting to the right of i. This is motivated by the idea that the closest person in seat i sits a distance min(left[i], right[i]) away.

Algorithm

To construct left[i], notice it is either left[i-1] + 1 if the seat is empty, or 0 if it is full. right[i] is constructed in a similar way.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

Valid Parentheses

Given a string s containing just the characters ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[’ and ‘]’, determine if the input string is valid.

An input string is valid if:

Open brackets must be closed by the same type of brackets.
Open brackets must be closed in the correct order.

A

Use Stacks

Intuition

Imagine you are writing a small compiler for your college project and one of the tasks (or say sub-tasks) for the compiler would be to detect if the parenthesis are in place or not.

The algorithm we will look at in this article can be then used to process all the parenthesis in the program your compiler is compiling and checking if all the parenthesis are in place. This makes checking if a given string of parenthesis is valid or not, an important programming problem.

The expressions that we will deal with in this problem can consist of three different type of parenthesis:

(),
{} and
[]

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

Merge k Sorted Lists

You are given an array of k linked-lists lists, each linked-list is sorted in ascending order.

Merge all the linked-lists into one sorted linked-list and return it.

A

Compare one by one using a Priority Queue

Algorithm

Compare every k nodes (head of every linked list) and get the node with the smallest value.

Extend the final sorted linked list with the selected nodes.

Use a Priority Queue

Almost the same as the one above but optimize the comparison process by priority queue. You can refer here for more information about it.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

Trapping Rain Water

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining.

A

Dynamic Programming

Intuition

In brute force, we iterate over the left and right parts again and again just to find the highest bar size up to that index. But, this could be stored. Voila, dynamic programming.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

Kth Largest Element in an Array

Given an integer array nums and an integer k, return the kth largest element in the array.

Note that it is the kth largest element in the sorted order, not the kth distinct element.

A

Min Heap (PriorityQueue)

The idea is to init a heap “the smallest element first”, and add all elements from the array into this heap one by one keeping the size of the heap always less or equal to k. That would results in a heap containing k largest elements of the array.

The head of this heap is the answer, i.e. the kth largest element of the array.

The time complexity of adding an element in a heap of size k is O(log k), and we do it N times that means O(N log k) time complexity for the algorithm.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
Q

Meeting Rooms II

Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required.

Example 1:

Input: intervals = [[0,30],[5,10],[15,20]]
Output: 2

A

Priority Queues

We can’t really process the given meetings in any random order. The most basic way of processing the meetings is in increasing order of their start times and this is the order we will follow. After all, it makes sense to allocate a room to the meeting that is scheduled for 9 a.m. in the morning before you worry about the 5 p.m. meeting, right?

Algorithm

  1. Sort the given meetings by their start time.
  2. Initialize a new min-heap and add the first meeting’s ending time to the heap. We simply need to keep track of the ending times as that tells us when a meeting room will get free.
  3. For every meeting room check if the minimum element of the heap i.e. the room at the top of the heap is free or not.
  4. If the room is free, then we extract the topmost element and add it back with the ending time of the current meeting we are processing.
  5. If not, then we allocate a new room and add it to the heap.
  6. After processing all the meetings, the size of the heap will tell us the number of rooms allocated. This will be the minimum number of rooms needed to accommodate all the meetings.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
26
Q

Backspace String Compare

(Editor’s Choice: Frequently asked in a Google phone interview)

Given two strings s and t, return true if they are equal when both are typed into empty text editors. ‘#’ means a backspace character.

Note that after backspacing an empty text, the text will continue empty.

A

Build String using a Stack

Intuition

Let’s individually build the result of each string (build(S) and build(T)), then compare if they are equal.

Algorithm

To build the result of a string build(S), we’ll use a stack based approach, simulating the result of each keystroke.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
27
Q

Minimum Cost to Hire K Workers

There are n workers. You are given two integer arrays quality and wage where quality[i] is the quality of the ith worker and wage[i] is the minimum wage expectation for the ith worker.

We want to hire exactly k workers to form a paid group. To hire a group of k workers, we must pay them according to the following rules:

  1. Every worker in the paid group should be paid in the ratio of their quality compared to other workers in the paid group.
  2. Every worker in the paid group must be paid at least their minimum wage expectation.

Given the integer k, return the least amount of money needed to form a paid group satisfying the above conditions. Answers within 10 ^(-5) of the actual answer will be accepted.

A

Heap

Intuition

At least one worker is paid their minimum wage expectation.

Additionally, every worker has some minimum ratio of dollars to quality that they demand. For example, if wage[0] = 100 and quality[0] = 20, then the ratio for worker 0 is 5.0.

The key insight is to iterate over the ratio. Let’s say we hire workers with a ratio R or lower. Then, we would want to know the K workers with the lowest quality, and the sum of that quality. We can use a heap to maintain these variables.

Algorithm

Maintain a max heap of quality. (We’re using a minheap, with negative values.) We’ll also maintain sumq, the sum of this heap.

For each worker in order of ratio, we know all currently considered workers have lower ratio. (This worker will be the ‘captain’, as described in Approach #1.) We calculate the candidate answer as this ratio times the sum of the smallest K workers in quality.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
28
Q

K Closest Points to Origin

Given an array of points where points[i] = [xi, yi] represents a point on the X-Y plane and an integer k, return the k closest points to the origin (0, 0).

The distance between two points on the X-Y plane is the Euclidean distance (i.e., √(x1 - x2)^2 + (y1 - y2)^2).

You may return the answer in any order. The answer is guaranteed to be unique (except for the order that it is in).

A

This is a very classical problem, so-called K-th problem.
Here I will share some summaries and some classical solutions to this kind of problem.

The very naive and simple solution is sorting the all points by their distance to the origin point directly, then get the top k closest points. We can use the sort function and the code is very short.

Theoretically, the time complexity is O(NlogN), practically, the real time it takes on leetcode is 104ms.

The advantages of this solution are short, intuitive and easy to implement.
The disadvatages of this solution are not very efficient and have to know all of the points previously, and it is unable to deal with real-time(online) case, it is an off-line solution.

The short code shows as follows:

public int[][] kClosest(int[][] points, int K) {
Arrays.sort(points, (p1, p2) -> p1[0] * p1[0] + p1[1] * p1[1] - p2[0] * p2[0] - p2[1] * p2[1]);
return Arrays.copyOfRange(points, 0, K);
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
29
Q

Add Two Numbers

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

A

Elementary Math

Intuition

Keep track of the carry using a variable and simulate digits-by-digits sum starting from the head of list, which contains the least-significant digit.

Algorithm

Just like how you would sum two numbers on a piece of paper, we begin by summing the least-significant digits, which is the head of l1 and l2. Since each digit is in the range of 0…9, summing two digits may “overflow”. For example 5+7=12. In this case, we set the current digit to 22 and bring over the carry=1 to the next iteration. carry must be either 0 or 1 because the largest possible sum of two digits (including the carry) is 9 + 9 + 1 = 19

The pseudocode is as following:

1) Initialize current node to dummy head of the returning list.
2) Initialize carry to 0.
3) Initialize p and q to head of l1 and l2 respectively.
4) Loop through lists l1 and l2 until you reach both ends.
a) Set x to node p’s value. If p has reached the end of l1, set to 0.
b) Set y to node q’s value. If q has reached the end of l2, set to 0.
c) Set sum = x + y + carry.
d) Update carry = sum / 10.
e) Create a new node with the digit value of (sum mod 10) and set it to current node’s next, then advance
current node to next.
f) Advance both p and q.
5) Check if carry=1, if so append a new node with digit 11 to the returning list.
6) Return dummy head’s next node.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
30
Q

Remove Nth Node From End of List

Given the head of a linked list, remove the nth node from the end of the list and return its head.

A

Two pass algorithm

Intuition

We notice that the problem could be simply reduced to another one : Remove the (L−n+1) th node from the beginning in the list , where L is the list length. This problem is easy to solve once we found list length L.

Algorithm

First we will add an auxiliary “dummy” node, which points to the list head. The “dummy” node is used to simplify some corner cases such as a list with only one node, or removing the head of the list. On the first pass, we find the list length L. Then we set a pointer to the dummy node and start to move it through the list till it comes to the (L−n) th node. We relink next pointer of the(L−n) th node to the (L−n+2) th node and we are done.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
31
Q

Merge Two Sorted Lists

Merge two sorted linked lists and return it as a sorted list. The list should be made by splicing together the nodes of the first two lists.

A

Iteration

Intuition

We can achieve the same idea via iteration by assuming that l1 is entirely less than l2 and processing the elements one-by-one, inserting elements of l2 in the necessary places in l1.

Algorithm

First, we set up a false “prehead” node that allows us to easily return the head of the merged list later. We also maintain a prev pointer, which points to the current node for which we are considering adjusting its next pointer. Then, we do the following until at least one of l1 and l2 points to null: if the value at l1 is less than or equal to the value at l2, then we connect l1 to the previous node and increment l1. Otherwise, we do the same, but for l2. Then, regardless of which list we connected, we increment prev to keep it one step behind one of our list heads.

After the loop terminates, at most one of l1 and l2 is non-null. Therefore (because the input lists were in sorted order), if either list is non-null, it contains only elements greater than all of the previously-merged elements. This means that we can simply connect the non-null list to the merged list and return it.

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // maintain an unchanging reference to node ahead of the return node.
        ListNode prehead = new ListNode(-1);
        ListNode prev = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }
        // At least one of l1 and l2 can still have nodes at this point, so connect
        // the non-null list to the end of the merged list.
        prev.next = l1 == null ? l2 : l1;
    return prehead.next;
} }
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
32
Q

Copy List with Random Pointer

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 n brand 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.

A

Recursive
Intuition

The basic idea behind the recursive solution is to consider the linked list like a graph. Every node of the Linked List has 2 pointers (edges in a graph). Since, random pointers add the randomness to the structure we might visit the same node again leading to cycles.

All we do in this approach is to just traverse the graph and clone it. Cloning essentially means creating a new node for every unseen node you encounter. The traversal part will happen recursively in a depth first manner. Note that we have to keep track of nodes already processed because, as pointed out earlier, we can have cycles because of the random pointers.

Algorithm

  1. Start traversing the graph from head node.
  2. If we already have a cloned copy of the current node in the visited dictionary, we use the cloned node reference.
  3. If we don’t have a cloned copy in the visited dictionary, we create a new node and add it to the visited dictionary. visited_dictionary[current_node] = cloned_node_for_current_node.
  4. We then make two recursive calls, one using the random pointer and the other using next pointer. The diagram from step 1, shows random and next pointers in red and blue color respectively. Essentially we are making recursive calls for the children of the current node. In this implementation, the children are the nodes pointed by the random and the next pointers.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
33
Q

Binary Tree Maximum Path Sum

A path in a binary tree is a sequence of nodes where each pair of adjacent nodes in the sequence has an edge connecting them. A node can only appear in the sequence at most once. Note that the path does not need to pass through the root.

The path sum of a path is the sum of the node’s values in the path.

Given the root of a binary tree, return the maximum path sum of any non-empty path.

A

Recursion
Intuition

First of all, let’s simplify the problem and implement a function max_gain(node) which takes a node as an argument and computes a maximum contribution that this node and one/zero of its subtrees could add.

That means one needs to modify the above function and to check at each step what is better : to continue the current path or to start a new path with the current node as a highest node in this new path.

Algorithm

Now everything is ready to write down an algorithm.

1) Initiate max_sum as the smallest possible integer and call max_gain(node = root).
2) Implement max_gain(node) with a check to continue the old path/to start a new path:
a) Base case : if node is null, the max gain is 0.
b) Call max_gain recursively for the node children to compute max gain from the left and right subtrees : left_gain = max(max_gain(node.left), 0) and
right_gain = max(max_gain(node.right), 0).
c) Now check to continue the old path or to start a new path. To start a new path would cost price_newpath = node.val + left_gain + right_gain. Update max_sum if it’s better to start a new path.
d) For the recursion return the max gain the node and one/zero of its subtrees could add to the current path : node.val + max(left_gain, right_gain).

class Solution {
  int max_sum = Integer.MIN_VALUE;

public int max_gain(TreeNode node) {
if (node == null) return 0;

    // max sum on the left and right sub-trees of node
    int left_gain = Math.max(max_gain(node.left), 0);
    int right_gain = Math.max(max_gain(node.right), 0);
    // the price to start a new path where `node` is a highest node
    int price_newpath = node.val + left_gain + right_gain;
    // update max_sum if it's better to start a new path
    max_sum = Math.max(max_sum, price_newpath);
    // for recursion :
    // return the max gain if continue the same path
    return node.val + Math.max(left_gain, right_gain);
  }
  public int maxPathSum(TreeNode root) {
    max_gain(root);
    return max_sum;
  }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
34
Q

Word Ladder

A transformation sequence from word beginWord to word endWord using a dictionary wordList is a sequence of words beginWord -> s1 -> s2 -> … -> sk such that:

Every adjacent pair of words differs by a single letter.
Every si for 1 <= i <= k is in wordList. Note that beginWord does not need to be in wordList.
sk == endWord
Given two words, beginWord and endWord, and a dictionary wordList, return the number of words in the shortest transformation sequence from beginWord to endWord, or 0 if no such sequence exists.

A

Breadth First Search

Intuition

Start from beginWord and search the endWord using BFS.

Algorithm

1) Do the pre-processing on the given wordList and find all the possible generic/intermediate states. Save these intermediate states in a dictionary with key as the intermediate word and value as the list of words which have the same intermediate word.
2) Push a tuple containing the beginWord and 1 in a queue. The 1 represents the level number of a node. We have to return the level of the endNode as that would represent the shortest sequence/distance from the beginWord.
3) To prevent cycles, use a visited dictionary.
4) While the queue has elements, get the front element of the queue. Let’s call this word as current_word.
5) Find all the generic transformations of the current_word and find out if any of these transformations is also a transformation of other words in the word list. This is achieved by checking the all_combo_dict.
6) The list of words we get from all_combo_dict are all the words which have a common intermediate state with the current_word. These new set of words will be the adjacent nodes/words to current_word and hence added to the queue.
7) Hence, for each word in this list of intermediate words, append (word, level + 1) into the queue where level is the level for the current_word.
8) Eventually if you reach the desired word, its level would represent the shortest transformation sequence length.

Termination condition for standard BFS is finding the end word.

35
Q

Number of Islands

Given an m x n 2D binary grid grid which represents a map of ‘1’s (land) and ‘0’s (water), return the number of islands.

An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

A

DFS
Intuition

Treat the 2d grid map as an undirected graph and there is an edge between two horizontally or vertically adjacent nodes of value ‘1’.

Algorithm

Linear scan the 2d grid map, if a node contains a ‘1’, then it is a root node that triggers a Depth First Search. During DFS, every visited node should be set as ‘0’ to mark as visited node. Count the number of root nodes that trigger DFS, this number would be the number of islands since each DFS starting at some root identifies an island.

36
Q

Course Schedule II

Solution
There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai.

For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1.
Return the ordering of courses you should take to finish all courses. If there are many valid answers, return any of them. If it is impossible to finish all courses, return an empty array.

A

Using Node Indegree (topological sort)

Intuition

This approach is much easier to think about intuitively as will be clear from the following point/fact about topological ordering.

The first node in the topological ordering will be the node that doesn’t have any incoming edges. Essentially, any node that has an in-degree of 0 can start the topologically sorted order. If there are multiple such nodes, their relative order doesn’t matter and they can appear in any order.

Our current algorithm is based on this idea. We first process all the nodes/course with 0 in-degree implying no prerequisite courses required. If we remove all these courses from the graph, along with their outgoing edges, we can find out the courses/nodes that should be processed next. These would again be the nodes with 0 in-degree. We can continuously do this until all the courses have been accounted for.

Algorithm

1) Initialize a queue, Q to keep a track of all the nodes in the graph with 0 in-degree.
2) Iterate over all the edges in the input and create an adjacency list and also a map of node v/s in-degree.
3) Add all the nodes with 0 in-degree to Q.
4) The following steps are to be done until the Q becomes empty.
a) Pop a node from the Q. Let’s call this node, N.
b) For all the neighbors of this node, N, reduce their in-degree by 1. If any of the nodes’ in-degree reaches 0, add it to the Q.
c) Add the node N to the list maintaining topologically sorted order.
d) Continue from step 4a.

37
Q

Count Complete Tree Nodes

(Editor’s Choice: Frequently asked in Google phone interview)

Given the root of a complete binary tree, return the number of the nodes in the tree.

According to Wikipedia, every level, except possibly the last, is completely filled in a complete binary tree, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h.

Design an algorithm that runs in less than O(n) time complexity.

A

Linear Time

Intuition

This problem is quite popular at Google during the last year. The naive solution here is a linear time one-liner which counts nodes recursively one by one.

Implementation

class Solution {
  public int countNodes(TreeNode root) {
    return root != null ? 1 + countNodes(root.right) + countNodes(root.left) : 0;
  }
}
38
Q

Longest Increasing Path in a Matrix

Given an m x n integers matrix, return the length of the longest increasing path in matrix.

From each cell, you can either move in four directions: left, right, up, or down. You may not move diagonally or move outside the boundary (i.e., wrap-around is not allowed).

A

(DFS + Memoization)

Intuition

Cache the results for the recursion so that any subproblem will be calculated only once.

Algorithm

From previous analysis, we know that there are many duplicate calculations in the naive approach.

One optimization is that we can use a set to prevent the repeat visit in one DFS search. This optimization will reduce the time complexity for each DFS to O(mn)and the total algorithm to O(m^2n^2)

Here, we will introduce more powerful optimization, Memoization.

In computing, memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

In our problem, we recursively call dfs(x, y) for many times. But if we already know all the results for the four adjacent cells, we only need constant time. During our search if the result for a cell is not calculated, we calculate and cache it; otherwise, we get it from the cache directly.

// DFS + Memoization Solution
// Accepted and Recommended
public class Solution {
    private static final int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    private int m, n;
public int longestIncreasingPath(int[][] matrix) {
    if (matrix.length == 0) return 0;
    m = matrix.length; n = matrix[0].length;
    int[][] cache = new int[m][n];
    int ans = 0;
    for (int i = 0; i < m; ++i)
        for (int j = 0; j < n; ++j)
            ans = Math.max(ans, dfs(matrix, i, j, cache));
    return ans;
}

private int dfs(int[][] matrix, int i, int j, int[][] cache) {
    if (cache[i][j] != 0) return cache[i][j];
    for (int[] d : dirs) {
        int x = i + d[0], y = j + d[1];
        if (0 <= x && x < m && 0 <= y && y < n && matrix[x][y] > matrix[i][j])
            cache[i][j] = Math.max(cache[i][j], dfs(matrix, x, y, cache));
    }
    return ++cache[i][j];
} }
39
Q

Decode String

Given an encoded string, return its decoded string.

The encoding rule is: k[encoded_string], where the encoded_string inside the square brackets is being repeated exactly k times. Note that k is guaranteed to be a positive integer.

You may assume that the input string is always valid; No extra white spaces, square brackets are well-formed, etc.

Furthermore, you may assume that the original data does not contain any digits and that digits are only for those repeat numbers, k. For example, there won’t be input like 3a or 2[4].

A

Using Stack

Intuition

We have to decode the result in a particular pattern. We know that the input is always valid. The pattern begins with a number k, followed by opening braces [, followed by string. Post that, there could be one of the following cases :

There is another nested pattern k[string k[string]]
There is a closing bracket k[string]
Since we have to start decoding the innermost pattern first, continue iterating over the string s, pushing each character to the stack until it is not a closing bracket ]. Once we encounter the closing bracket ], we must start decoding the pattern.

As we know that stack follows the Last In First Out (LIFO) Principle, the top of the stack would have the data we must decode.

Algorithm

The input can contain an alphabet (a-z), digit (0-9), opening braces [ or closing braces ]. Start traversing string s and process each character based on the following rules:

Case 1) Current character is not a closing bracket ].

Push the current character to stack.

Case 2) Current character is a closing bracket ].

Start decoding the last traversed string by popping the string decodedString and number k from the top of the stack.

  • Pop from the stack while the next character is not an opening bracket [ and append each character (a-z) to the decodedString.
  • Pop opening bracket [ from the stack.
  • Pop from the stack while the next character is a digit (0-9) and build the number k.

Now that we have k and decodedString , decode the pattern k[decodedString] by pushing the decodedString to stack k times.

Once the entire string is traversed, pop the result from stack and return.

40
Q

Evaluate Division

You are given an array of variable pairs equations and an array of real numbers values, where equations[i] = [Ai, Bi] and values[i] represent the equation Ai / Bi = values[i]. Each Ai or Bi is a string that represents a single variable.

You are also given some queries, where queries[j] = [Cj, Dj] represents the jth query where you must find the answer for Cj / Dj = ?.

Return the answers to all queries. If a single answer cannot be determined, return -1.0.

Note: The input is always valid. You may assume that evaluating the queries will not result in division by zero and that there is no contradiction.

A

Overview

As revealed by the hints, the problem can be solved with two important data structures, namely Graph and Union-Find.

In the following sections, we will explain how to solve the problem respectively with regards to the data structures.

Path Search in Graph

Algorithm

As one can see, we just transform the problem into a path searching problem in a graph.

More precisely, we can reinterpret the problem as “given two nodes, we are asked to check if there exists a path between them. If so, we should return the cumulative products along the path as the result.

Given the above problem statement, it seems intuitive that one could apply the backtracking algorithm, or sometimes people might call it DFS (Depth-First Search).

Essentially, we can break down the algorithm into two steps overall:

Step 1). we build the graph out of the list of input equations.

Each equation corresponds to two edges in the graph.

Step 2). once the graph is built, we then can evaluate the query one by one.

The evaluation of the query is done via searching the path between the given two variables.

Other than the above searching operation, we need to handle two exceptional cases as follows:

Case 1): if either of the nodes does not exist in the graph, i.e. the variables did not appear in any of the input equations, then we can assert that no path exists.

Case 2): if the origin and the destination are the same node, i.e. a / a, we can assume that there exists an invisible self-loop path for each node and the result is one.

41
Q

Evaluate Division

(Editor’s Choice: Frequently asked in Google onsite interview)

You are given an array of variable pairs equations and an array of real numbers values, where equations[i] = [Ai, Bi] and values[i] represent the equation Ai / Bi = values[i]. Each Ai or Bi is a string that represents a single variable.

You are also given some queries, where queries[j] = [Cj, Dj] represents the jth query where you must find the answer for Cj / Dj = ?.

Return the answers to all queries. If a single answer cannot be determined, return -1.0.

Note: The input is always valid. You may assume that evaluating the queries will not result in division by zero and that there is no contradiction.

A

Overview

As revealed by the hints, the problem can be solved with two important data structures, namely Graph and Union-Find.

In the following sections, we will explain how to solve the problem respectively with regards to the data structures.

Path Search in Graph

Algorithm

As one can see, we just transform the problem into a path searching problem in a graph.

More precisely, we can reinterpret the problem as “given two nodes, we are asked to check if there exists a path between them. If so, we should return the cumulative products along the path as the result.

Given the above problem statement, it seems intuitive that one could apply the backtracking algorithm, or sometimes people might call it DFS (Depth-First Search).

Essentially, we can break down the algorithm into two steps overall:

Step 1). we build the graph out of the list of input equations.

Each equation corresponds to two edges in the graph.

Step 2). once the graph is built, we then can evaluate the query one by one.

The evaluation of the query is done via searching the path between the given two variables.

Other than the above searching operation, we need to handle two exceptional cases as follows:

Case 1): if either of the nodes does not exist in the graph, i.e. the variables did not appear in any of the input equations, then we can assert that no path exists.

Case 2): if the origin and the destination are the same node, i.e. a / a, we can assume that there exists an invisible self-loop path for each node and the result is one.

42
Q

Diameter of Binary Tree

Given the root of a binary tree, return the length of the diameter of the tree.

The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or may not pass through the root.

The length of a path between two nodes is represented by the number of edges between them.

A

Depth-first Search

Intuition

The key observation to make is: the longest path has to be between two leaf nodes. We can prove this with contradiction. Imagine that we have found the longest path, and it is not between two leaf nodes. We can extend that path by 1, by adding the child node of one of the end nodes (as at least one must have a child, given that they aren’t both leaves). This contradicts the fact that our path is the longest path. Therefore, the longest path must be between two leaf nodes.

Moreover, we know that in a tree, nodes are only connected with their parent node and 2 children. Therefore we know that the longest path in the tree would consist of a node, its longest left branch, and its longest right branch. So, our algorithm to solve this problem will find the node where the sum of its longest left and right branches is maximized. This would hint at us to apply Depth-first search (DFS) to count each node’s branch lengths, because it would allow us to dive deep into the leaves first, and then start counting the edges upwards.

DFS is a widely-used graph traversal algorithm. If you are not familiar with it, feel free to visit our Explore Cards where you will see different ways to traverse a binary tree with DFS including preorder, inorder, postorder

Let’s try to be more specific about how to apply DFS to this question. To count the lengths of each node’s left and right branches, we can implement a recursion function longestPath which takes a TreeNode as input and returns the longest path from it to the leaf node. It will recursively visit children nodes and retrieve the longest paths from them to the leaf first, and then add 1 to the longer one before returning it as the longest path.

In the midst of DFS, we also need to take the following two cases into account:

  1. the current node’s both left and right branches might be a part of the longest path;
  2. one of the current node’s left/right branches might be a part of the longest path.

Algorithm

1) Initalize an integer variable diameter to keep track of the longest path we find from the DFS.

2) Implement a recursive function longestPath which takes a TreeNode as input. It should recursively explore the entire tree rooted at the given node. Once it’s finished, it should return the longest path out of its left and right branches:
a) if node is None, we have reached the end of the tree, hence we should return 0;
b) we want to recursively explore node’s children, so we call longestPath again with node’s left and right children. In return, we get the longest path of its left and right children leftPath and rightPath;
c) if leftPath plus rightPath is longer than the current longest diameter found, then we need to update diameter;
d) finally, we return the longer one of leftPath and rightPath. Remember to add 1 as the edge connecting it with its parent.

3) Call longestPath with root.

class Solution {
    private int diameter;
    public int diameterOfBinaryTree(TreeNode root) {
        diameter = 0;
        longestPath(root);
        return diameter;
    }
    private int longestPath(TreeNode node){
        if(node == null) return 0;
        // recursively find the longest path in
        // both left child and right child
        int leftPath = longestPath(node.left);
        int rightPath = longestPath(node.right);
        // update the diameter if left_path plus right_path is larger
        diameter = Math.max(diameter, leftPath + rightPath);
        // return the longest one between left_path and right_path;
        // remember to add 1 for the path connecting the node and its parent
        return Math.max(leftPath, rightPath) + 1;
    }
}
43
Q

Cracking the Safe

There is a safe protected by a password. The password is a sequence of n digits where each digit can be in the range [0, k - 1].

The safe has a peculiar way of checking the password. When you enter in a sequence, it checks the most recent n digits that were entered each time you type a digit.

For example, the correct password is “345” and you enter in “012345”:
After typing 0, the most recent 3 digits is “0”, which is incorrect.
After typing 1, the most recent 3 digits is “01”, which is incorrect.
After typing 2, the most recent 3 digits is “012”, which is incorrect.
After typing 3, the most recent 3 digits is “123”, which is incorrect.
After typing 4, the most recent 3 digits is “234”, which is incorrect.
After typing 5, the most recent 3 digits is “345”, which is correct and the safe unlocks.
Return any string of minimum length that will unlock the safe at some point of entering it.

A

DFS with memoization

class Solution {
    public String crackSafe(int n, int k) {
        StringBuilder sb = new StringBuilder();
        int total = (int) (Math.pow(k, n));
        for (int i = 0; i < n; i++) sb.append('0');
    Set visited = new HashSet<>();
    visited.add(sb.toString());

    dfs(sb, total, visited, n, k);

    return sb.toString();
}

private boolean dfs(StringBuilder sb, int goal, Set visited, int n, int k) {
    if (visited.size() == goal) return true;
    String prev = sb.substring(sb.length() - n + 1, sb.length());
    for (int i = 0; i < k; i++) {
        String next = prev + i;
        if (!visited.contains(next)) {
            visited.add(next);
            sb.append(i);
            if (dfs(sb, goal, visited, n, k)) return true;
            else {
                visited.remove(next);
                sb.delete(sb.length() - 1, sb.length());
            }
        }
    }
    return false;
}

}

44
Q

Robot Room Cleaner

(Editor’s choice: Frequently asked in a Google onsite interview)

You are controlling a robot that is located somewhere in a room. The room is modeled as an m x n binary grid where 0 represents a wall and 1 represents an empty slot.

The robot starts at an unknown location in the root that is guaranteed to be empty, and you do not have access to the grid, but you can move the robot using the given API Robot.

You are tasked to use the robot to clean the entire room (i.e., clean every empty cell in the room). The robot with the four given APIs can move forward, turn left, or turn right. Each turn is 90 degrees.

When the robot tries to move into a wall cell, its bumper sensor detects the obstacle, and it stays on the current cell.

Design an algorithm to clean the entire room using the following APIs:

interface Robot {
  // returns true if next cell is open and robot moves into the cell.
  // returns false if next cell is obstacle and robot stays on the current cell.
  boolean move();
  // Robot will stay on the same cell after calling turnLeft/turnRight.
  // Each turn will be 90 degrees.
  void turnLeft();
  void turnRight();
  // Clean the current cell.
  void clean();
}

Note that the initial direction of the robot will be facing up. You can assume all four edges of the grid are all surrounded by a wall.

A

Spiral Backtracking

Concepts to use

Let’s use here two programming concepts.

The first one is called constrained programming.

That basically means to put restrictions after each robot move. Robot moves, and the cell is marked as visited. That propagates constraints and helps to reduce the number of combinations to consider.

Intuition

This solution is based on the same idea as maze solving algorithm called right-hand rule. Go forward, cleaning and marking all the cells on the way as visited. At the obstacle turn right, again go forward, etc. Always turn right at the obstacles and then go forward. Consider already visited cells as virtual obstacles.

What do do if after the right turn there is an obstacle just in front ?

Turn right again.

How to explore the alternative paths from the cell ?

Go back to that cell and then turn right from your last explored direction.

When to stop ?

Stop when you explored all possible paths, i.e. all 4 directions (up, right, down, and left) for each visited cell.

Algorithm

Time to write down the algorithm for the backtrack function backtrack(cell = (0, 0), direction = 0).

1) Mark the cell as visited and clean it up.
2) Explore 4 directions : up, right, down, and left (the order is important since the idea is always to turn right) :
a) Check the next cell in the chosen direction :
i) If it’s not visited yet and there is no obtacles :

   Move forward.

   Explore next cells backtrack(new_cell, new_direction).

   Backtrack, i.e. go back to the previous cell.

ii) Turn right because now there is an obstacle (or a virtual obstacle) just in front.
45
Q

Most Stones Removed with Same Row or Column

(Editor’s choice: Frequently asked in Google onsite interview(

On a 2D plane, we place n stones at some integer coordinate points. Each coordinate point may have at most one stone.

A stone can be removed if it shares either the same row or the same column as another stone that has not been removed.

Given an array stones of length n where stones[i] = [xi, yi] represents the location of the ith stone, return the largest possible number of stones that can be removed.

A

Problem:
we can remove a stone if and only if,
there is another stone in the same column OR row.
We try to remove as many as stones as possible.

One sentence to solve:
Connected stones can be reduced to 1 stone,
the maximum stones can be removed = stones number - islands number.
so just count the number of “islands”.

  1. Connected stones
    Two stones are connected if they are in the same row or same col.
    Connected stones will build a connected graph.
    It’s obvious that in one connected graph,
    we can’t remove all stones.

We have to have one stone left.
An intuition is that, in the best strategy, we can remove until 1 stone.

I guess you may reach this step when solving the problem.
But the important question is, how?

  1. A good strategy
    In fact, the proof is really straightforward.
    You probably apply a DFS, from one stone to next connected stone.
    You can remove stones in reversed order.
    In this way, all stones can be removed but the stone that you start your DFS.

One more step of explanation:
In the view of DFS, a graph is explored in the structure of a tree.
As we discussed previously,
a tree can be removed in topological order,
from leaves to root.

  1. Count the number of islands
    We call a connected graph as an island.
    One island must have at least one stone left.
    The maximum stones can be removed = stones number - islands number

The whole problem is transferred to:
What is the number of islands?

You can show all your skills on a DFS implementation,
and solve this problem as a normal one.

  1. Unify index
    Struggle between rows and cols?
    You may duplicate your codes when you try to the same thing on rows and cols.
    In fact, no logical difference between col index and rows index.

An easy trick is that, add 10000 to col index.
So we use 0 ~ 9999 for row index and 10000 ~ 19999 for col.

  1. Search on the index, not the points
    When we search on points,
    we alternately change our view on a row and on a col.

We think:
a row index, connect two stones on this row
a col index, connect two stones on this col.

In another view:
A stone, connect a row index and col.

Have this idea in mind, the solution can be much simpler.
The number of islands of points,
is the same as the number of islands of indexes.

  1. Union-Find
    I use union find to solve this problem.
    As I mentioned, the elements are not the points, but the indexes.
for each point, union two indexes.
return points number - union number
Copy a template of union-find,
write 2 lines above,
you can solve this problem in several minutes.
Map f = new HashMap<>();
int islands = 0;

public int removeStones(int[][] stones) {
    for (int i = 0; i < stones.length; ++i)
        union(stones[i][0], ~stones[i][1]);
    return stones.length - islands;
}
    public int find(int x) {
        if (f.putIfAbsent(x, x) == null)
            islands++;
        if (x != f.get(x))
            f.put(x, find(f.get(x)));
        return f.get(x);
    }
    public void union(int x, int y) {
        x = find(x);
        y = find(y);
        if (x != y) {
            f.put(x, y);
            islands--;
        }
    }
46
Q

Flip Equivalent Binary Trees

Solution
For a binary tree T, we can define a flip operation as follows: choose any node, and swap the left and right child subtrees.

A binary tree X is flip equivalent to a binary tree Y if and only if we can make X equal to Y after some number of flip operations.

Given the roots of two binary trees root1 and root2, return true if the two trees are flip equivalent or false otherwise.

A

Recursion

Intuition

If root1 and root2 have the same root value, then we only need to check if their children are equal (up to ordering.)

Algorithm

There are 3 cases:

1) If root1 or root2 is null, then they are equivalent if and only if they are both null.
2) Else, if root1 and root2 have different values, they aren’t equivalent.
3) Else, let’s check whether the children of root1 are equivalent to the children of root2. There are two different ways to pair these children.

47
Q

Word Squares

Given an array of unique strings words, return all the word squares you can build from words. The same word from words can be used multiple times. You can return the answer in any order.

A sequence of strings forms a valid word square if the kth row and column read the same string, where 0 <= k < max(numRows, numColumns).

For example, the word sequence [“ball”,”area”,”lead”,”lady”] forms a word square because each word reads the same both horizontally and vertically.

A

Backtracking

Given a list of words, we are asked to find a combination of words upon with we could construct a word square. The backbone of the algorithm to solve the above problem could be surprisingly simple.

The idea is that we construct the word square row by row from top to down. At each row, we simply do trial and error, i.e. we try with one word, if it does not meet the constraint then we try another one.

As one might notice, the above idea of the algorithm is actually known as backtracking, which is often associated with recursion and DFS (Depth-First Search) as well.

Optimization: Backtracking with Trie

48
Q

Strobogrammatic Number II

Given an integer n, return all the strobogrammatic numbers that are of length n. You may return the answer in any order.

A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down).

A

Recursion

public List findStrobogrammatic(int n) {
    return helper(n, n);
}
List helper(int n, int m) {
    if (n == 0) return new ArrayList(Arrays.asList(""));
    if (n == 1) return new ArrayList(Arrays.asList("0", "1", "8"));
List list = helper(n - 2, m);

List res = new ArrayList();

for (int i = 0; i < list.size(); i++) {
    String s = list.get(i);

    if (n != m) res.add("0" + s + "0");

    res.add("1" + s + "1");
    res.add("6" + s + "9");
    res.add("8" + s + "8");
    res.add("9" + s + "6");
}

return res; }
49
Q

Word Search II

Solution
Given an m x n board of characters and a list of strings words, return all words on the board.

Each word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.

A

Backtracking with Trie

Intuition

The problem is actually a simplified crossword puzzle game, where the word solutions have been given on the board embedded with some noise letters. All we need to to do is to cross them out.

Intuitively, in order to cross out all potential words, the overall strategy would be to iterate the cell one by one, and from each cell we walk along its neighbors in four potential directions to find matched words. While wandering around the board, we would stop the exploration when we know it would not lead to the discovery of new words.

Algorithm

The overall workflow of the algorithm is intuitive, which consists of a loop over each cell in the board and a recursive function call starting from the cell. Here is the skeleton of the algorithm.

1) We build a Trie out of the words in the dictionary, which would be used for the matching process later.
2) Starting from each cell, we start the backtracking exploration (i.e. backtracking(cell)), if there exists any word in the dictionary that starts with the letter in the cell.
3) During the recursive function call backtracking(cell), we explore the neighbor cells (i.e. neighborCell) around the current cell for the next recursive call backtracking(neighborCell). At each call, we check if the sequence of letters that we traverse so far matches any word in the dictionary, with the help of the Trie data structure that we built at the beginning.

50
Q

Android Unlock Patterns

Android devices have a special lock screen with a 3 x 3 grid of dots. Users can set an “unlock pattern” by connecting the dots in a specific sequence, forming a series of joined line segments where each segment’s endpoints are two consecutive dots in the sequence. A sequence of k dots is a valid unlock pattern if both of the following are true:

All the dots in the sequence are distinct.
If the line segment connecting two consecutive dots in the sequence passes through the center of any other dot, the other dot must have previously appeared in the sequence. No jumps through the center non-selected dots are allowed.
For example, connecting dots 2 and 9 without dots 5 or 6 appearing beforehand is valid because the line from dot 2 to dot 9 does not pass through the center of either dot 5 or 6.
However, connecting dots 1 and 3 without dot 2 appearing beforehand is invalid because the line from dot 1 to dot 3 passes through the center of dot 2.

A

Recursion: DFS

The general idea is DFS all the possible combinations from 1 to 9 and skip invalid moves along the way.

We can check invalid moves by using a jumping table. e.g. If a move requires a jump and the key that it is crossing is not visited, then the move is invalid. Furthermore, we can utilize symmetry to reduce runtime, in this case it reduced from ~120ms to ~70ms.

private int[][] jumps;
private boolean[] visited;

public int numberOfPatterns(int m, int n) {
    jumps = new int[10][10];
    jumps[1][3] = jumps[3][1] = 2;
    jumps[4][6] = jumps[6][4] = 5;
    jumps[7][9] = jumps[9][7] = 8;
    jumps[1][7] = jumps[7][1] = 4;
    jumps[2][8] = jumps[8][2] = 5;
    jumps[3][9] = jumps[9][3] = 6;
	jumps[1][9] = jumps[9][1] = jumps[3][7] = jumps[7][3] = 5;
    visited = new boolean[10];
    int count = 0;
	count += DFS(1, 1, 0, m, n) * 4; // 1, 3, 7, 9 are symmetrical
	count += DFS(2, 1, 0, m, n) * 4; // 2, 4, 6, 8 are symmetrical
	count += DFS(5, 1, 0, m, n);
	return count;
}

private int DFS(int num, int len, int count, int m, int n) {
if (len >= m) count++; // only count if moves are larger than m
len++;
if (len > n) return count;
visited[num] = true;
for (int next = 1; next <= 9; next++) {
int jump = jumps[num][next];
if (!visited[next] && (jump == 0 || visited[jump])) {
count = DFS(next, len, count, m, n);
}
}
visited[num] = false; // backtracking
return count;
}

51
Q

Letter Combinations of a Phone Number

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order.

A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.

A

Backtracking

Overview

One of the first things you should always do is look at the constraints. Quite often, you can figure out what sort of approach needs to be taken simply from looking at the input size. In an interview, asking your interviewer about the constraints will also show your attention to detail - on top of giving you information.

In this particular problem, the length of the input is extremely small, 0 <= digits.length <= 4. With such small input sizes, we can safely assume that a brute force solution in which we generate all combinations of letters will be accepted.

Whenever you have a problem where you need to generate all combinations/permutations of some group of letters/numbers, the first thought you should have is backtracking. If you’re new to backtracking, check out our backtracking explore card. Backtracking algorithms can often keep the space complexity linear with the input size.

Intuition

There aren’t any smart tricks needed for this problem - the hard part is just figuring out how to correctly generate all possible combinations, and to do this using a standard backtracking algorithm template. Let’s break down the problem, by starting with an input that is only 1-digit long, for example digits = “2”. This example is trivial - just generate all letters that correspond with digit = “2”, which would be [“a”, “b”, “c”].

What if instead we had a 2-digit long input, digits = “23”? Imagine taking each letter of digit = “2” as a starting point. That is, lock the first letter in, and solve all the possible combinations that start with that letter. If our first letter will always be “a”, then the problem is trivial again - it’s the 1-digit case, and all we have to do is generate all the letters corresponding with digit = “3”, and add that to “a”, to get [“ad”, “ae”,”af”]. This was easy because we ignored the first letter, and said it will always be “a”. But we know how to generate all the first letters too - it’s the 1-digit case which we already solved to be [“a”, “b”, “c”].

As you can see, solving the 1-digit case is trivial, and solving the 2-digit case is just solving the 1-digit case twice. The same reasoning can be extended to n digits. For the 3-digit case, solve the 2-digit case to generate all combinations of the first 2 letters, and then solve the 1-digit case for the final digit. Now that we know how to solve the 3-digit case, to solve the 4-digit case, solve the 3-digit case for all combinations of the first 3 letters, and then solve the 1-digit case for the final digit. We could extend this to infinity, but, don’t worry, for this problem we’re finished after 4.

52
Q

Generate Parentheses

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

A

Backtracking

Intuition and Algorithm

Instead of adding ‘(‘ or ‘)’ every time as in Approach 1, let’s only add them when we know it will remain a valid sequence. We can do this by keeping track of the number of opening and closing brackets we have placed so far.

We can start an opening bracket if we still have one (of n) left to place. And we can start a closing bracket if it would not exceed the number of opening brackets.

53
Q

Median of Two Sorted Arrays

Given two sorted arrays nums1 and nums2 of size m and n respectively, return the median of the two sorted arrays.

The overall run time complexity should be O(log (m+n)).

A

This problem is notoriously hard to implement due to all the corner cases. Most implementations consider odd-lengthed and even-lengthed arrays as two different cases and treat them separately. As a matter of fact, with a little mind twist. These two cases can be combined as one, leading to a very simple solution where (almost) no special treatment is needed.

First, let’s see the concept of ‘MEDIAN’ in a slightly unconventional way. That is:

“if we cut the sorted array to two halves of EQUAL LENGTHS, then
median is the AVERAGE OF Max(lower_half) and Min(upper_half), i.e. the
two numbers immediately next to the cut”.

54
Q

Find First and Last Position of Element in Sorted Array

Given an array of integers nums sorted in non-decreasing order, find the starting and ending position of a given target value.

If target is not found in the array, return [-1, -1].

You must write an algorithm with O(log n) runtime complexity.

A

Binary Search

Overview

Let’s briefly look at a brute-force way of solving this problem. Given a target element, we can simply do a linear scan over the entire array to find the first and the last position. The first occurrence will be the first time when we encounter this target. Thereafter, we continue to scan elements until we find one that is greater than the target or until we reach the end of the array. This will help us determine the last position of the target.

The downside of this approach is that it doesn’t take advantage of the sorted nature of the array. This linear scan approach has a time complexity of O(N)O(N) because there are NN elements in the array. That doesn’t sound too bad, right? Well, it does if we compare it to an approach with logarithmic time complexity. We’ll look at a binary search-based approach to solve this problem which will take advantage of the sorted nature of the array.

Two Binary Searches

Instead of using a linear-scan approach to find the boundaries once the target has been found, let’s use two binary searches to find the first and last position of the target. We can make a small tweak to the checks we perform on the middle element.

55
Q

Merge Intervals

Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.

A

Sorting

Intuition

If we sort the intervals by their start value, then each set of intervals that can be merged will appear as a contiguous “run” in the sorted list.

Algorithm

First, we sort the list as described. Then, we insert the first interval into our merged list and continue considering each interval in turn as follows: If the current interval begins after the previous interval ends, then they do not overlap and we can append the current interval to merged. Otherwise, they do overlap, and we merge them by updating the end of the previous interval if it is less than the end of the current interval.

56
Q

Insert Interval

You are given an array of non-overlapping intervals intervals where intervals[i] = [starti, endi] represent the start and the end of the ith interval and intervals is sorted in ascending order by starti. You are also given an interval newInterval = [start, end] that represents the start and end of another interval.

Insert newInterval into intervals such that intervals is still sorted in ascending order by starti and intervals still does not have any overlapping intervals (merge overlapping intervals if necessary).

Return intervals after the insertion.

A

Greedy

Greedy algorithms

Greedy problems usually look like “Find minimum number of something to do something” or “Find maximum number of something to fit in some conditions”, and typically propose an unsorted input.

The idea of greedy algorithm is to pick the locally optimal move at each step, that will lead to the globally optimal solution.

57
Q

Valid Anagram

Given two strings s and t, return true if t is an anagram of s, and false otherwise.

A

Hash Table

Algorithm

To examine if t is a rearrangement of s, we can count occurrences of each letter in the two strings and compare them. Since both s and t contain only letters from a−z, a simple counter table of size 26 is suffice.

Do we need two counter tables for comparison? Actually no, because we could increment the counter for each letter in s and decrement the counter for each letter in t, then check if the counter reaches back to zero.

58
Q

Count of Smaller Numbers After Self

You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].

A

Merge Sort

Intuition

Tweak a standard merge sort algorithm. The smaller elements on the right of a number will jump from its right to its left during the sorting process.

If we can record the numbers of those elements during sorting, then the problem is solved.

59
Q

Peak Index in a Mountain Array

Let’s call an array arr a mountain if the following properties hold:

1) arr.length >= 3
2) There exists some i with 0 < i < arr.length - 1 such that:
a) arr[0] < arr[1] < … arr[i-1] < arr[i]
b) arr[i] > arr[i+1] > … > arr[arr.length - 1]

Given an integer array arr that is guaranteed to be a mountain, return any i such that arr[0] < arr[1] < … arr[i - 1] < arr[i] > arr[i + 1] > … > arr[arr.length - 1].

A

Linear Scan

Intuition and Algorithm

The mountain increases until it doesn’t. The point at which it stops increasing is the peak.

Optimization: Binary Search

60
Q

Longest Palindromic Substring

Given a string s, return the longest palindromic substring in s.

A

Dynamic Programming

To improve over the brute force solution, we first observe how we can avoid unnecessary re-computation while validating palindromes. Consider the case “ababa”. If we already knew that “bab” is a palindrome, it is obvious that “ababa” must be a palindrome since the two left and right end letters are the same.

61
Q

Maximum Subarray

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

A subarray is a contiguous part of an array.

A

Dynamic Programming, Kadane’s Algorithm

Intuition

Whenever you see a question that asks for the maximum or minimum of something, consider Dynamic Programming as a possibility. The difficult part of this problem is figuring out when a negative number is “worth” keeping in a subarray. This question in particular is a popular problem that can be solved using an algorithm called Kadane’s Algorithm. If you’re good at problem solving though, it’s quite likely you’ll be able to come up with the algorithm on your own. This algorithm also has a very greedy-like intuition behind it.

Algorithm

1) Initialize 2 integer variables. Set both of them equal to the first value in the array.

a) currentSubarray will keep the running count of the current subarray we are focusing on.
b) maxSubarray will be our final return value. Continuously update it whenever we find a bigger subarray.

2) Iterate through the array, starting with the 2nd element (as we used the first element to initialize our variables). For each number, add it to the currentSubarray we are building. If currentSubarray becomes negative, we know it isn’t worth keeping, so throw it away. Remember to update maxSubarray every time we find a new maximum.
3) Return maxSubarray.

62
Q

Best Time to Buy and Sell Stock

You are given an array prices where prices[i] is the price of a given stock on the ith day.

You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock.

Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0.

A

One Pass

The points of interest are the peaks and valleys in the given graph. We need to find the largest peak following the smallest valley. We can maintain two variables - minprice and maxprofit corresponding to the smallest valley and maximum profit (maximum difference between selling price and minprice) obtained so far respectively.

public class Solution {
    public int maxProfit(int prices[]) {
        int minprice = Integer.MAX_VALUE;
        int maxprofit = 0;
        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < minprice)
                minprice = prices[i];
            else if (prices[i] - minprice > maxprofit)
                maxprofit = prices[i] - minprice;
        }
        return maxprofit;
    }
}
63
Q

Maximum Product Subarray

Given an integer array nums, find a contiguous non-empty subarray within the array that has the largest product, and return the product.

It is guaranteed that the answer will fit in a 32-bit integer.

A subarray is a contiguous subsequence of the array.

A

Dynamic Programming

Intuition

Rather than looking for every possible subarray to get the largest product, we can scan the array and solve smaller subproblems.

Let’s see this problem as a problem of getting the highest combo chain. The way combo chains work is that they build on top of the previous combo chains that you have acquired. The simplest case is when the numbers in nums are all positive numbers. In that case, you would only need to keep on multiplying the accumulated result to get a bigger and bigger combo chain as you progress.

However, two things can disrupt your combo chain:

1) Zeros
2) Negative numbers

Zeros will reset your combo chain. A high score which you have achieved will be recorded in placeholder result. You will have to restart your combo chain after zero. If you encounter another combo chain which is higher than the recorded high score in result, you just need to update the result.

Negative numbers are a little bit tricky. A single negative number can flip the largest combo chain to a very small number. This may sound like your combo chain has been completely disrupted but if you encounter another negative number, your combo chain can be saved. Unlike zero, you still have a hope of saving your combo chain as long as you have another negative number in nums (Think of this second negative number as an antidote for the poison that you just consumed). However, if you encounter a zero while you are looking your another negative number to save your combo chain, you lose the hope of saving that combo chain.

64
Q

Coin Change

You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money.

Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

You may assume that you have an infinite number of each kind of coin.

A

Dynamic programming

We note that this problem has an optimal substructure property, which is the key piece in solving any Dynamic Programming problems. In other words, the optimal solution can be constructed from optimal solutions of its subproblems. How to split the problem into subproblems?

Algorithm

The idea of the algorithm is to build the solution of the problem from top to bottom. It applies the idea described above. It use backtracking and cut the partial solutions in the recursive tree, which doesn’t lead to a viable solution. Тhis happens when we try to make a change of a coin with a value greater than the amount S. To improve time complexity we should store the solutions of the already calculated subproblems in a table.

65
Q

Split Array Largest Sum

Given an array nums which consists of non-negative integers and an integer m, you can split the array into m non-empty continuous subarrays.

Write an algorithm to minimize the largest sum among these m subarrays.

A

Dynamic Programming

Intuition

The problem satisfies the non-aftereffect property. We can try to use dynamic programming to solve it.

The non-aftereffect property means, once the state of a certain stage is determined, it is not affected by the state in the future. In this problem, if we get the largest subarray sum for splitting nums[0..i] into j parts, this value will not be affected by how we split the remaining part of nums.

66
Q

LRU Cache

Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.

Implement the LRUCache class:

1) LRUCache(int capacity) Initialize the LRU cache with positive size capacity.
2) int get(int key) Return the value of the key if the key exists, otherwise return -1.
3) void put(int key, int value) Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key.

The functions get and put must each run in O(1) average time complexity.

A

Ordered dictionary: LinkedHashMap

Intuition

We’re asked to implement the structure which provides the following operations in O(1) time :

Get the key / Check if the key exists

Put the key

Delete the first added key

The first two operations in O(1) time are provided by the standard hashmap, and the last one - by linked list.

There is a structure called ordered dictionary, it combines behind both hashmap and linked list. In Python this structure is called OrderedDict and in Java LinkedHashMap.

67
Q

Min Stack

Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.

Implement the MinStack class:

1) MinStack() initializes the stack object.
2) void push(int val) pushes the element val onto the stack.
3) void pop() removes the element on the top of the stack.
4) int top() gets the top element of the stack.
5) int getMin() retrieves the minimum element in the stack.

A

Stack of Value/Minimum Pairs

Intuition

An invariant is something that is always true or consistent. You should always be on the lookout for useful invariants when problem-solving in math and computer science.

Recall that with a Stack, we only ever add (push) and remove (pop) numbers from the top. Therefore, an important invariant of a Stack is that when a new number, which we’ll call x, is placed on a Stack, the numbers below it will not change for as long as number x remains on the Stack. Numbers could come and go above x for the duration of x’s presence, but never below.

So, whenever number x is the top of the Stack, the minimum will always be the same, as it’s simply the minimum out of x and all the numbers below it.

Therefore, in addition to putting a number on an underlying Stack inside our MinStack, we could also put its corresponding minimum value alongside it. Then whenever that particular number is at the top of the underlying Stack, the getTop(…) operation of MinStack is as simple as retrieving its corresponding minimum value.

68
Q

Serialize and Deserialize Binary Tree

Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

A

Depth First Search (DFS)

Intuition

The serialization of a Binary Search Tree is essentially to encode its values and more importantly its structure. One can traverse the tree to accomplish the above task. And it is well know that we have two general strategies to do so:

Breadth First Search (BFS)

We scan through the tree level by level, following the order of height, from top to bottom. The nodes on higher level would be visited before the ones with lower levels.

Depth First Search (DFS)

In this strategy, we adopt the depth as the priority, so that one would start from a root and reach all the way down to certain leaf, and then back to root to reach another branch.

The DFS strategy can further be distinguished as preorder, inorder, and postorder depending on the relative order among the root node, left node and right node.

In this task, however, the DFS strategy is more adapted for our needs, since the linkage among the adjacent nodes is naturally encoded in the order, which is rather helpful for the later task of deserialization.

Therefore, in this solution, we demonstrate an example with the preorder DFS strategy.

69
Q

Logger Rate Limiter

Design a logger system that receives a stream of messages along with their timestamps. Each unique message should only be printed at most every 10 seconds (i.e. a message printed at timestamp t will prevent other identical messages from being printed until timestamp t + 10).

All messages will come in chronological order. Several messages may arrive at the same timestamp.

Implement the Logger class:

1) Logger() Initializes the logger object.
2) bool shouldPrintMessage(int timestamp, string message) Returns true if the message should be printed in the given timestamp, otherwise returns false.

A

Hashtable

Intuition

One could combine the queue and set data structure into a hashtable or dictionary, which gives us the capacity of keeping all unique messages as of queue as well as the capacity to quickly evaluate the duplication of messages as of set.

The idea is that we keep a hashtable/dictionary with the message as key, and its timestamp as the value. The hashtable keeps all the unique messages along with the latest timestamp that the message was printed.

Algorithm

1) We initialize a hashtable/dictionary to keep the messages along with the timestamp.
2) At the arrival of a new message, the message is eligible to be printed with either of the two conditions as follows:

case 1). we have never seen the message before.

case 2). we have seen the message before, and it was printed more than 10 seconds ago.

3) In both of the above cases, we would then update the entry that is associated with the message in the hashtable, with the latest timestamp.

70
Q

RandomizedSet

Implement the RandomizedSet class:

1) RandomizedSet() Initializes the RandomizedSet object.
2) bool insert(int val) Inserts an item val into the set if not present. Returns true if the item was not present, false otherwise.
3) bool remove(int val) Removes an item val from the set if present. Returns true if the item was present, false otherwise.
4) int getRandom() Returns a random element from the current set of elements (it’s guaranteed that at least one element exists when this method is called). Each element must have the same probability of being returned.

You must implement the functions of the class such that each function works in average O(1) time complexity.

A

HashMap + ArrayList

Insert:

Add value -> its index into dictionary, average O(1) time.

Append value to array list, average O(1) time as well.

Delete:

Retrieve an index of element to delete from the hashmap.

Move the last element to the place of the element to delete, O(1) time.

Pop the last element out, O(1) time.

GetRandom

GetRandom could be implemented in O(1) time with the help of standard random.choice in Python and Random object in Java.

71
Q

Design Search Autocomplete System

Design a search autocomplete system for a search engine. Users may input a sentence (at least one word and end with a special character ‘#’).

You are given a string array sentences and an integer array times both of length n where sentences[i] is a previously typed sentence and times[i] is the corresponding number of times the sentence was typed. For each input character except ‘#’, return the top 3 historical hot sentences that have the same prefix as the part of the sentence already typed.

Here are the specific rules:

1) The hot degree for a sentence is defined as the number of times a user typed the exactly same sentence before.
2) The returned top 3 hot sentences should be sorted by hot degree (The first is the hottest one). If several sentences have the same hot degree, use ASCII-code order (smaller one appears first).
3) If less than 3 hot sentences exist, return as many as you can.
4) When the input is a special character, it means the sentence ends, and in this case, you need to return an empty list.

Implement the AutocompleteSystem class:

1) AutocompleteSystem(String[] sentences, int[] times) Initializes the object with the sentences and times arrays.
2) List input(char c) This indicates that the user typed the character c.
a) Returns an empty array [] if c == ‘#’ and stores the inputted sentence in the system.
b) Returns the top 3 historical hot sentences that have the same prefix as the part of the sentence already typed. If there are fewer than 3 matches, return them all.

A

Trie and PriorityQueue

Only thing more than a normal Trie is added a map of sentence to count in each of the Trie node to facilitate process of getting top 3 results.

72
Q

Reverse Integer

Given a signed 32-bit integer x, return x with its digits reversed. If reversing x causes the value to go outside the signed 32-bit integer range [-2^31, 2^31 - 1], then return 0.

Assume the environment does not allow you to store 64-bit integers (signed or unsigned).

A

Pop and Push Digits & Check before Overflow

Intuition

We can build up the reverse integer one digit at a time. While doing so, we can check beforehand whether or not appending another digit would cause overflow.

Algorithm

Reversing an integer can be done similarly to reversing a string.

We want to repeatedly “pop” the last digit off of x and “push” it to the back of the rev. In the end, rev will be the reverse of the x.

To “pop” and “push” digits without the help of some auxiliary stack/array, we can use math.

//pop operation:
pop = x % 10;
x /= 10;
//push operation:
temp = rev * 10 + pop;
rev = temp;
73
Q

Candy

There are n children standing in a line. Each child is assigned a rating value given in the integer array ratings.

You are giving candies to these children subjected to the following requirements:

1) Each child must have at least one candy.
2) Children with a higher rating get more candies than their neighbors.

Return the minimum number of candies you need to have to distribute the candies to the children.

A

Using two arrays

Algorithm

public class Solution {
    public int candy(int[] ratings) {
        int sum = 0;
        int[] left2right = new int[ratings.length];
        int[] right2left = new int[ratings.length];
        Arrays.fill(left2right, 1);
        Arrays.fill(right2left, 1);
        for (int i = 1; i < ratings.length; i++) {
            if (ratings[i] > ratings[i - 1]) {
                left2right[i] = left2right[i - 1] + 1;
            }
        }
        for (int i = ratings.length - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1]) {
                right2left[i] = right2left[i + 1] + 1;
            }
        }
        for (int i = 0; i < ratings.length; i++) {
            sum += Math.max(left2right[i], right2left[i]);
        }
        return sum;
    }
}
74
Q

Isomorphic Strings

Given two strings s and t, determine if they are isomorphic.

Two strings s and t are isomorphic if the characters in s can be replaced to get t.

All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character, but a character may map to itself.

A

Character Mapping with Dictionary

Intuition

The first solution is based on the approach indicated in the problem statement itself. We will process both of the strings from left to right. At each step, we take one character at a time from the two strings and compare them. There are three cases we need to handle here:

1) If the characters don’t have a mapping, we add one in the dictionary and move on.
2) The characters already have a mapping in the dictionary. If that is the case, then we’re good to go.
3) The final case is when a mapping already exists for one of the characters but it doesn’t map to the other character at hand. In this case, we can safely conclude that the given strings are not isomorphic and we can return.

If at this point you’re ready to move on to the algorithm, take a step back and think about the correctness of this solution. The above three cases only care about one-way-mapping i.e. mapping characters from the first string to the second one only. Don’t we need the mapping from the other side as well?

75
Q

Strobogrammatic Number

Given a string num which represents an integer, return true if num is a strobogrammatic number.

A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down).

A

Overview
Given a number represented as a string, we need to return true if it is strobogrammatic and false if it is not. Determining whether or not a number is strobogrammatic requires looking at whether or not it stays the same when rotated by 180 degrees.

We aren’t told very much about which digits count as strobogrammatic. While some people have found it frustrating to not be told everything, this level of uncertainty is intentionally typical of real interviews. A big part of solving this problem is clarifying the requirements, and there is a great way of doing that here on LeetCode, which we’ll talk about.

Rotating a number by 180 degrees

A good first step is to think carefully about what it means to rotate a number by 180 degrees. Some people find it easy to rotate numbers in their head, whereas others struggle to do so. If you’re in the latter group, write some numbers down on a scrap of paper and rotate the paper.

76
Q

Bulls and Cows

You are playing the Bulls and Cows game with your friend.

You write down a secret number and ask your friend to guess what the number is. When your friend makes a guess, you provide a hint with the following info:

The number of “bulls”, which are digits in the guess that are in the correct position.
The number of “cows”, which are digits in the guess that are in your secret number but are located in the wrong position. Specifically, the non-bull digits in the guess that could be rearranged such that they become bulls.
Given the secret number secret and your friend’s guess guess, return the hint for your friend’s guess.

The hint should be formatted as “xAyB”, where x is the number of bulls and y is the number of cows. Note that both secret and guess may contain duplicate digits.

A

HashMap: Two Passes

Algorithm

Initialize the number of bulls and cows to zero.

Initialize the hashmap character -> its frequency for the string secret. This hashmap could be later used during the iteration over the string guess to keep the available characters.

It’s time to iterate over the string guess.

If the current character ch of the string guess is in the string secret: if ch in h, then there could be two situations.

The corresponding characters of two strings match: ch == secret[idx].

Then it’s time to update the bulls: bulls += 1.

The update of the cows is needed if the count for the current character in the hashmap is negative or equal to zero. That means that before it was already used for cows, and the cows counter should be decreased: cows -= int(h[ch] <= 0).

The corresponding characters of two strings don’t match: ch != secret[idx]. Then increase the cows counter: cows += int(h[ch] > 0).

In both cases, one has to update hashmap, marking the current character as used: h[ch] -= 1.

Return the number of bulls and cows.

77
Q

Range Sum Query 2D - Mutable

Given a 2D matrix matrix, handle multiple queries of the following types:

Update the value of a cell in matrix.
Calculate the sum of the elements of matrix inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).
Implement the NumMatrix class:

NumMatrix(int[][] matrix) Initializes the object with the integer matrix matrix.
void update(int row, int col, int val) Updates the value of matrix[row][col] to be val.
int sumRegion(int row1, int col1, int row2, int col2) Returns the sum of the elements of matrix inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).
A

Using Binary Indexed Tree

Intuition

The problem differs than the 2D Immutable version as there is an additional update method that changes the values present inside the matrix, hence it’s not even a good choice to cache or pre-compute all the results. There is a special data structure called Binary Indexed Tree that we will use to solve this problem efficiently which is based on the idea of storing the partial sums. We then use these partial sums to obtain the overall sum in the sumRegion method. The way we store these partial sums in a Binary Indexed Tree (BIT), a.k.a. Fenwick Tree, allows us to achieve both the update and query to be done in logarithmic time.

78
Q

My Calendar II

You are implementing a program to use as your calendar. We can add a new event if adding the event will not cause a triple booking.

A triple booking happens when three events have some non-empty intersection (i.e., some moment is common to all the three events.).

The event can be represented as a pair of integers start and end that represents a booking on the half-open interval [start, end), the range of real numbers x such that start <= x < end.

Implement the MyCalendarTwo class:

MyCalendarTwo() Initializes the calendar object.
boolean book(int start, int end) Returns true if the event can be added to the calendar successfully without causing a triple booking. Otherwise, return false and do not add the event to the calendar.
A

Boundary Count

Intuition and Algorithm

When booking a new event [start, end), count delta[start]++ and delta[end]–. When processing the values of delta in sorted order of their keys, the running sum active is the number of events open at that time. If the sum is 3 or more, that time is (at least) triple booked.

79
Q

Jewels and Stones

You’re given strings jewels representing the types of stones that are jewels, and stones representing the stones you have. Each character in stones is a type of stone you have. You want to know how many of the stones you have are also jewels.

Letters are case sensitive, so “a” is considered a different type of stone from “A”.

A

Hash Set

Intuition and Algorithm

For each stone, check whether it matches any of the jewels. We can check efficiently with a Hash Set.

class Solution {
    public int numJewelsInStones(String J, String S) {
        Set Jset = new HashSet();
        for (char j: J.toCharArray())
            Jset.add(j);
        int ans = 0;
        for (char s: S.toCharArray())
            if (Jset.contains(s))
                ans++;
        return ans;
    }
}
80
Q

Swap Adjacent in LR String

In a string composed of ‘L’, ‘R’, and ‘X’ characters, like “RXXLRXRXL”, a move consists of either replacing one occurrence of “XL” with “LX”, or replacing one occurrence of “RX” with “XR”. Given the starting string start and the ending string end, return True if and only if there exists a sequence of moves to transform one string to the other.

A

public boolean canTransform(String start, String end) {
int r = 0, l = 0;
for (int i = 0; i< start.length(); i++){
if (start.charAt(i) == ‘R’){ r++; l = 0;}
if (end.charAt(i) == ‘R’) { r–; l = 0;}
if (end.charAt(i) == ‘L’) { l++; r = 0;}
if (start.charAt(i) == ‘L’) { l–; r = 0;}
if (l < 0 || r < 0) return false;
}

if (l != 0 || r != 0) return false;
return true; }
81
Q

Guess the Word

This is an interactive problem.

You are given an array of unique strings wordlist where wordlist[i] is 6 letters long, and one word in this list is chosen as secret.

You may call Master.guess(word) to guess a word. The guessed word should have type string and must be from the original list with 6 lowercase letters.

This function returns an integer type, representing the number of exact matches (value and position) of your guess to the secret word. Also, if your guess is not in the given wordlist, it will return -1 instead.

For each test case, you have exactly 10 guesses to guess the word. At the end of any number of calls, if you have made 10 or fewer calls to Master.guess and at least one of these guesses was secret, then you pass the test case.

A

Minimax

Now we want to try a better solution.
Generally, we will get 0 matches from the master.guess.
As a result, the size of wordlist reduces slowly.

Recall some math here, the possiblity that get 0 matched is:
(25/26) ^ 6 = 79.03%

That is to say, if we make a blind guess,
we have about 80% chance to get 0 matched with the secret word.

To simplify the model,
we’re going to assume that,
we will always run into the worst case (get 0 matched).

In this case,
we have 80% chance to eliminate the candidate word
as well as its close words which have at least 1 match.

Additionally, in order to delete a max part of words,
we select a candidate who has a big “family”,
(that is, the fewest 0 matched with other words.)
We want to guess a word that can minimum our worst outcome.

So we compare each two words and count their matches.
For each word, we note how many word of 0 matches it gets.
Then we guess the word with minimum words of 0 matches.

In this solution, we apply a minimax idea.
We minimize our worst case,
The worst case is max(the number of words with x matches),
and we assume it equal to “the number of words with 0 matches”

Time complexity O(N^2)
Space complexity O(N)

public void findSecretWord(String[] wordlist, Master master) {
    for (int i = 0, x = 0; i < 10 && x < 6; ++i) {
        HashMap count = new HashMap<>();
        for (String w1 : wordlist)
            for (String w2 : wordlist)
                if (match(w1, w2) == 0)
                    count.put(w1, count.getOrDefault(w1 , 0) + 1);
        String guess = "";
        int min0 = 100;
        for (String w : wordlist)
            if (count.getOrDefault(w, 0) < min0) {
                guess = w;
                min0 = count.getOrDefault(w, 0);
            }
        x = master.guess(guess);
        List wordlist2 = new ArrayList();
        for (String w : wordlist)
            if (match(guess, w) == x)
                wordlist2.add(w);
        wordlist = wordlist2.toArray(new String[0]);
    }
}
82
Q

Minimum Area Rectangle

You are given an array of points in the X-Y plane points where points[i] = [xi, yi].

Return the minimum area of a rectangle formed from these points, with sides parallel to the X and Y axes. If there is not any such rectangle, return 0.

A

HashMap: O(n^2)

class Solution {
    public int minAreaRect(int[][] points) {
        Map> map = new HashMap<>();
        for (int[] p : points) {
            if (!map.containsKey(p[0])) {
                map.put(p[0], new HashSet<>());
            }
            map.get(p[0]).add(p[1]);
        }
        int min = Integer.MAX_VALUE;
        for (int[] p1 : points) {
            for (int[] p2 : points) {
                if (p1[0] == p2[0] || p1[1] == p2[1]) { // if have the same x or y
                    continue;
                }
                if (map.get(p1[0]).contains(p2[1]) && map.get(p2[0]).contains(p1[1])) { // find other two points
                    min = Math.min(min, Math.abs(p1[0] - p2[0]) * Math.abs(p1[1] - p2[1]));
                }
            }
        }
        return min == Integer.MAX_VALUE ? 0 : min;
    }
}
83
Q

Unique Email Addresses

Every valid email consists of a local name and a domain name, separated by the ‘@’ sign. Besides lowercase letters, the email may contain one or more ‘.’ or ‘+’.

For example, in “alice@leetcode.com”, “alice” is the local name, and “leetcode.com” is the domain name.
If you add periods ‘.’ between some characters in the local name part of an email address, mail sent there will be forwarded to the same address without dots in the local name. Note that this rule does not apply to domain names.

For example, “alice.z@leetcode.com” and “alicez@leetcode.com” forward to the same email address.
If you add a plus ‘+’ in the local name, everything after the first plus sign will be ignored. This allows certain emails to be filtered. Note that this rule does not apply to domain names.

For example, “m.y+name@email.com” will be forwarded to “my@email.com”.
It is possible to use both of these rules at the same time.

Given an array of strings emails where we send one email to each email[i], return the number of different addresses that actually receive mails.

A

Linear Iteration

Intuition

We can iterate over an email from left to right, and add characters to local name until a ‘+’ occurs, then we can skip all characters until ‘@’ occurs, then we can again start appending the characters till the end of the email string to form the domain name.

Notice that per the rules, we do not need to read any characters between the first ‘+’ and ‘@’. While checking each character from left to right, after finding the first ‘+’ in the local name we can directly find the domain name by switching to a reverse iteration as there is only one ‘@’ and we will skip all characters in between ‘+’ and ‘@’.

84
Q

Given an integer array nums, design an algorithm to randomly shuffle the array. All permutations of the array should be equally likely as a result of the shuffling.

Implement the Solution class:

Solution(int[] nums) Initializes the object with the integer array nums.
int[] reset() Resets the array to its original configuration and returns it.
int[] shuffle() Returns a random shuffling of the array.

A
public class Solution {
    private int[] nums;
    private Random random;
    public Solution(int[] nums) {
        this.nums = nums;
        random = new Random();
    }
    /** Resets the array to its original configuration and return it. */
    public int[] reset() {
        return nums;
    }
    /** Returns a random shuffling of the array. */
    public int[] shuffle() {
        if(nums == null) return null;
        int[] a = nums.clone();
        for(int j = 1; j < a.length; j++) {
            int i = random.nextInt(j + 1);
            swap(a, i, j);
        }
        return a;
    }
    private void swap(int[] a, int i, int j) {
        int t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
}