1. Trang chủ
  2. » Công Nghệ Thông Tin

Leetcode clean code handbook 50 common interview questions

100 7 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Leetcode Clean Code Handbook 50 Common Interview Questions
Chuyên ngành Computer Science
Thể loại Handbook
Năm xuất bản 2014
Định dạng
Số trang 100
Dung lượng 1,61 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Cấu trúc

  • CHAPTER 0: FOREWORD (5)
  • CHAPTER 1: ARRAY/STRING (6)
    • 1. T WO S UM (6)
    • 2. T WO S UM II – I NPUT ARRAY IS SORTED (7)
    • 3. T WO S UM III – D ATA STRUCTURE DESIGN (9)
    • 4. V ALID P ALINDROME (11)
    • 5. I MPLEMENT STRSTR () (12)
    • 6. R EVERSE W ORDS IN A S TRING (13)
    • 7. R EVERSE W ORDS IN A S TRING II (13)
    • 8. S TRING TO I NTEGER ( ATOI ) (15)
    • 9. V ALID N UMBER (17)
    • 10. L ONGEST S UBSTRING W ITHOUT R EPEATING C HARACTERS (20)
    • 11. L ONGEST S UBSTRING WITH A T M OST T WO D ISTINCT C HARACTERS (22)
    • 12. M ISSING R ANGES (24)
    • 13. L ONGEST P ALINDROMIC S UBSTRING (25)
    • 14. O NE E DIT D ISTANCE (28)
    • 15. R EAD N C HARACTERS G IVEN R EAD 4 (30)
    • 16. R EAD N C HARACTERS G IVEN R EAD 4 – C ALL MULTIPLE TIMES (31)
  • CHAPTER 2: MATH (33)
    • 17. R EVERSE I NTEGER (33)
    • 18. P LUS O NE (35)
    • 19. P ALINDROME N UMBER (36)
  • CHAPTER 3: LINKED LIST (38)
    • 20. M ERGE T WO S ORTED L ISTS (38)
    • 21. A DD T WO N UMBERS (39)
    • 22. S WAP N ODES IN P AIRS (40)
    • 23. M ERGE K S ORTED L INKED L ISTS (41)
    • 24. C OPY L IST WITH R ANDOM P OINTER (44)
  • CHAPTER 4: BINARY TREE (47)
    • 25. V ALIDATE B INARY S EARCH T REE (47)
    • 26. M AXIMUM D EPTH OF B INARY T REE (50)
    • 27. M INIMUM D EPTH OF B INARY T REE (51)
    • 28. B ALANCED B INARY T REE (53)
    • 29. C ONVERT S ORTED A RRAY TO B ALANCED B INARY S EARCH T REE (55)
    • 30. C ONVERT S ORTED L IST TO B ALANCED B INARY S EARCH T REE (56)
    • 31. B INARY T REE M AXIMUM P ATH S UM (59)
    • 32. B INARY T REE U PSIDE D OWN (61)
  • CHAPTER 5: BIT MANIPULATION (63)
    • 33. S INGLE N UMBER (63)
    • 34. S INGLE N UMBER II (65)
  • CHAPTER 6: MISC (67)
    • 35. S PIRAL M ATRIX (67)
    • 36. I NTEGER TO R OMAN (69)
    • 37. R OMAN TO I NTEGER (71)
    • 38. C LONE GRAPH (73)
  • CHAPTER 7: STACK (75)
    • 39. M IN S TACK (75)
    • 40. E VALUATE R EVERSE P OLISH N OTATION (77)
    • 41. V ALID P ARENTHESES (81)
  • CHAPTER 8: DYNAMIC PROGRAMMING (82)
    • 42. C LIMBING S TAIRS (82)
    • 43. U NIQUE P ATHS (84)
    • 44. U NIQUE P ATHS II (87)
    • 45. M AXIMUM S UM S UBARRAY (88)
    • 46. M AXIMUM P RODUCT S UBARRAY (90)
    • 47. C OINS IN A L INE (91)
  • CHAPTER 9: BINARY SEARCH (96)
    • 48. S EARCH I NSERT P OSITION (96)
    • 49. F IND M INIMUM IN S ORTED R OTATED A RRAY (98)
    • 50. F IND M INIMUM IN R OTATED S ORTED A RRAY II – WITH DUPLICATES (100)

Nội dung

Many of the algorithmic concepts tested in coding interviews are not what I usually use at work, where I am a Front End Engineer (web). Naturally, I have forgotten quite a bit about these algorithms and data structures, which I learned mostly during my freshmen and sophomore years of college. It’s stressful to have to produce (working) code in an interview, while someone scrutinizes every keystroke that you make. What’s worse is that as an interviewee, you’re encouraged to communicate your thought process out loud to the interviewer. I used to think that being able to think, code, and communicate simultaneously was an impossible feat, until I realized that most people are just not good at coding interviews when they first start out. Interviewing is a skill that you can get better at by studying, preparing, and practicing for it.

FOREWORD

First, I would like to express thank you for buying this eBook: “Clean Code Handbook:

"50 Common Interview Questions" is the ultimate guide to mastering coding interview questions by teaching you how to write clean, concise code This resource helps you develop essential coding skills, ensuring you can confidently tackle technical interviews By learning best practices for clean code, you'll improve your problem-solving abilities and increase your chances of acing your coding interviews successfully.

This eBook is written to serve as the perfect companion for LeetCode Online Judge (OJ)

Each problem features a “Code it now” link that redirects to the official OJ problem statement page, allowing you to efficiently start coding You can submit your solution directly to the OJ system and receive instant feedback on its correctness, facilitating a smooth and interactive coding experience If the link displays “Coming soon,” it indicates that the problem will be available shortly, so keep an eye out for future updates.

Each problem is labeled with Difficulty and Frequency ratings in the top right corner, with three levels: Easy, Medium, and Hard Easy problems typically involve straightforward ideas and simple implementation, making them accessible for most users Most common interview questions fall into the Easy difficulty level, ensuring they are manageable for a wide audience.

Hard problems are primarily algorithmic in nature and demand careful planning and critical thinking to solve effectively While some coding questions may be classified as hard, such instances are relatively rare, emphasizing the complexity and challenge these problems present to developers.

Interview questions are categorized into three frequency ratings: Low, Medium, and High, based on user survey data asking, "Have you met this question in a real interview?" This information provides valuable insights into the types of questions currently prevalent in actual interview settings, helping candidates prioritize their preparation efforts accordingly.

When preparing for a job interview, include a section called “Example Questions Candidate Might Ask” to showcase your interest and understanding of the role Asking thoughtful clarifying questions demonstrates your proactive approach and helps you gain deeper insights into the position and company culture These questions provide an excellent opportunity to showcase your critical thinking skills and ensure the role aligns with your career goals Incorporating well-crafted questions into your interview strategy can leave a positive impression on your interviewer and increase your chances of success.

Each question is accompanied by multiple approaches, each starting with a summary of runtime and space complexities to facilitate quick comparison Complexity analysis is typically included alongside solutions, making it easy to understand the efficiency of each approach Since analyzing runtime and space complexity is a common requirement in technical interviews, it is essential to prepare thoroughly in this area.

Learning is most effective when you solve problems independently, utilizing hints from the book when needed If you encounter difficulties, reviewing the analysis can provide valuable insights Ultimately, attempting to write the code yourself in the Online Judge reinforces understanding and enhances problem-solving skills.

Writing concise and clean code is more challenging than it appears, even for seemingly simple problems During coding interviews, if your solution exceeds 30 lines, it likely lacks the desired brevity and clarity Most effective code examples in this eBook are between 20 and 30 lines, emphasizing the importance of concise programming for clarity and efficiency.

ARRAY/STRING

T WO S UM

Code it now: https://oj.leetcode.com/problems/two-sum/ Difficulty: Easy, Frequency: High

Given an array of integers, find two numbers such that they add up to a specific target number

The twoSum function should return the indices of two numbers that add up to the target value, with the condition that index1 is less than index2 It is important to note that both indices are not zero-based, meaning they start from 1 instead of 0 This ensures accurate identification of the numbers that sum to the target, adhering to the specified indexing convention.

You may assume that each input would have exactly one solution

The brute force approach to finding two numbers that sum to a target is straightforward: iterate through each element and check if there is a corresponding value in the array that equals target minus the current element Since this process involves searching through the remaining elements for each iteration, the overall runtime complexity is O(n^2), making it less efficient for large datasets.

We could reduce the runtime complexity of looking up a value to O(1) using a hash map that maps a value to its index public int[] twoSum(int[] numbers, int target) {

Map map = new HashMap(); for (int i = 0; i < numbers.length; i++) { int x = numbers[i]; if (map.containsKey(target - x)) { return new int[] { map.get(target - x) + 1, i + 1 };

} throw new IllegalArgumentException("No two sum solution");

What if the given input is already sorted in ascending order? See Question [2 Two Sum

II – Input array is sorted]

T WO S UM II – I NPUT ARRAY IS SORTED

Code it now: Coming soon! Difficulty: Medium, Frequency: N/A

Similar to Question [1 Two Sum], except that the input array is already sorted in ascending order

While applying a hash table approach is possible, it requires additional O(n) space, making it less space-efficient Instead, leveraging the fact that the input is already sorted allows for more optimized solutions without extra space, improving overall efficiency.

O ( n log n ) runtime, O (1) space – Binary search:

To efficiently find two elements that sum to a target value, binary search can be used to check if the complement (target – x) exists in the sorted array within O(log n) time This approach ensures that for each element, a binary search is performed, resulting in an overall time complexity of O(n log n) Implementing a two-sum algorithm with binary search optimizes performance when working with sorted data arrays.

// Assume input is already sorted for (int i = 0; i < numbers.length; i++) { int j = bsearch(numbers, target - numbers[i], i + 1); if (j != -1) { return new int[] { i + 1, j + 1 };

} throw new IllegalArgumentException("No two sum solution");

} private int bsearch(int[] A, int key, int start) { int L = start, R = A.length - 1; while (L < R) { int M = (L + R) / 2; if (A[M] < key) {

When evaluating two elements, A i and A j, their sum can only fall into three possible cases: if A i + A j exceeds the target, decreasing j is the best move since increasing i would only raise the sum further; if A i + A j is less than the target, increasing i helps because decreasing j would lower the sum; and if A i + A j equals the target, we have found the correct pair.

7 public int[] twoSum(int[] numbers, int target) {

// Assume input is already sorted int i = 0, j = numbers.length - 1; while (i < j) { int sum = numbers[i] + numbers[j]; if (sum < target) { i++;

} throw new IllegalArgumentException("No two sum solution");

T WO S UM III – D ATA STRUCTURE DESIGN

Code it now: Coming soon! Difficulty: Easy, Frequency: N/A

Create a TwoSum class that efficiently supports two essential operations: add() and find() The add(input) method inserts a number into an internal data structure, enabling dynamic data management The find(value) method checks whether any pair of numbers within the data structure sums up to the specified value Implementing these functions helps in developing powerful algorithms for pair sum problems, making your code suitable for real-time applications like finance, e-commerce, and data analysis Optimizing the TwoSum class for quick addition and retrieval of pairs ensures efficient performance, critical for solving common coding interview questions and enhancing your problem-solving skills.

For example, add(1); add(3); add(5); find(4)  true; find(7)  false

Solution: add – O ( n ) runtime, find – O (1) runtime, O ( n 2 ) space – Store pair sums in hash table:

Storing all possible pair sums in a hash table requires approximately O(n²) extra space, with an additional O(n) space needed for the list of added numbers During each add operation, the list is traversed to generate new pair sums, which are then inserted into the hash table The find operation is highly efficient, involving a single hash table lookup that operates in constant O(1) time.

This method is useful if the number of find operations far exceeds the number of add operations add – O (log n ) runtime, find – O ( n ) runtime, O ( n ) space – Binary search + Two pointers:

To efficiently maintain a sorted array of numbers, each insertion operation requires O(log n) time using a modified binary search to insert elements at the correct position For search operations within the sorted array, a two pointers approach can be effectively applied to quickly locate desired elements or ranges Implementing these optimized strategies ensures fast and reliable performance for managing sorted data structures.

O(n) runtime add – O (1) runtime, find – O ( n ) runtime, O ( n ) space – Store input in hash table:

Using a hash table to store each input provides a simpler and more efficient solution for the pair sum problem By iterating through the hash table, you can determine if a pair sum exists in O(n) time, making the process faster and more optimal It's important to handle duplicates properly to ensure accurate results, especially when the input contains repeated elements This approach streamlines the search process and improves overall performance for checking pair sums.

9 public class TwoSum { private Map table = new HashMap(); public void add(int input) { int count = table.containsKey(input) ? table.get(input) : 0; table.put(input, count + 1);

} public boolean find(int val) { for (Map.Entry entry : table.entrySet()) { int num = entry.getKey(); int y = val - num; if (y == num) {

// For duplicates, ensure there are at least two individual numbers if (entry.getValue() >= 2) return true;

} else if (table.containsKey(y)) { return true;

V ALID P ALINDROME

Code it now: https://oj.leetcode.com/problems/valid-palindrome/ Difficulty: Easy, Frequency: Medium

Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases

"A man, a plan, a canal: Panama" is a palindrome

"race a car" is not a palindrome

Example Questions Candidate Might Ask:

Q: What about an empty string? Is it a valid palindrome?

A: For the purpose of this problem, we define empty string as valid palindrome

To check if a string is a palindrome, use two pointers—one starting at the beginning (head) and the other at the end (tail) Move these pointers towards each other, skipping non-alphanumeric characters, until they meet This approach efficiently verifies the string's symmetry while ignoring irrelevant characters Incorporating this method ensures accurate palindrome validation in an optimized and straightforward manner.

When a string contains only non-alphanumeric characters, it is considered a valid palindrome because the empty string, which contains no characters, is also a valid palindrome The core logic of checking for palindromes involves iterating from both ends of the string, skipping non-letter or digit characters, and comparing characters in a case-insensitive manner This approach ensures that strings with various characters are accurately evaluated for palindrome properties, aligning with common programming practices for palindrome detection.

!= Character.toLowerCase(s.charAt(j))) { return false;

I MPLEMENT STRSTR ()

Code it now: https://oj.leetcode.com/problems/implement-strstr/ Difficulty: Easy, Frequency: High

Implement strstr() Returns the index of the first occurrence of needle in haystack, or –1 if needle is not part of haystack.

O ( nm ) runtime, O (1) space – Brute force:

Efficient string search algorithms like Rabin-Karp, KMP, and Boyer-Moore are well-known but typically studied in advanced algorithms courses However, for practical purposes such as coding interviews, the most straightforward approach is often sufficient—namely, the brute force method This direct technique involves checking all possible starting points in the text to find the pattern, making it a simple and reliable solution for quick implementation.

The brute force method for string matching is simple to implement It involves scanning the haystack from the first position and comparing each character to the needle If a mismatch occurs, the process restarts from the next position in the haystack This straightforward approach ensures that every possible position is checked for a potential match.

Assume that n = length of haystack and m = length of needle, then the runtime complexity is O(nm)

When implementing substring search algorithms, consider scenarios such as when either the needle or haystack is empty; if the needle is empty, always return 0, whereas if the haystack is empty, return -1 unless the needle is also empty, in which case 0 is returned If the length of the needle exceeds that of the haystack, the search should immediately return -1, indicating no match is possible Additionally, special attention is needed when the needle is located at the very end of the haystack, as in the case of "aaaba" and "ba," to prevent off-by-one errors Furthermore, in cases where the needle appears multiple times within the haystack, such as "mississippi" with the needle "ss," ensure the algorithm accurately identifies all occurrences to provide correct results.

To effectively find the first occurrence of the substring "issi" within a string, the algorithm should return index 2 as the initial match When analyzing string matching, consider two lengthy strings of equal length: the haystack consisting of repeated "a"s and the needle being almost identical but ending with a "b" It's crucial to perform no more than n character comparisons, where n is the length of the strings, to prevent exceeding time limits during online judging or coding challenges Optimizing string search algorithms to operate within this comparison constraint ensures efficiency and avoids timeout errors in competitive programming environments.

This implementation provides a clean solution for substring search without using extensive conditional statements It iterates through the 'haystack' string, checking for the 'needle' pattern efficiently If the entire 'needle' is found within the 'haystack', it returns the starting index; otherwise, it returns -1 when the search reaches the end of the 'haystack' without a match This approach simplifies the process by avoiding complex conditional logic, making it an effective method for string pattern matching in Java.

R EVERSE W ORDS IN A S TRING

Code it now: https://oj.leetcode.com/problems/reverse-words-in-a-string/ Difficulty: Medium, Frequency: High

Given an input string s , reverse the string word by word

For example, given s = "the sky is blue", return "blue is sky the"

Example Questions Candidate Might Ask:

A: A sequence of non-space characters constitutes a word

Q: Does tab or newline character count as space characters?

A: Assume the input does not contain any tabs or newline characters

Q: Could the input string contain leading or trailing spaces?

A: Yes However, your reversed string should not contain leading or trailing spaces

Q: How about multiple spaces between two words?

A: Reduce them to a single space in the reversed string

A straightforward method to reverse the words in a string involves a two-pass approach First, split the string by spaces to create an array of individual words Then, perform a second pass to extract and assemble the words in reverse order This simple technique efficiently reverses the sequence of words while maintaining proper structure.

To improve efficiency, we can process the string in a single pass by iterating in reverse order Keep track of each word’s start and end positions as you go When you reach the beginning of a word, append it to the result This method optimizes performance by reducing unnecessary iterations and simplifies the reversal process.

StringBuilder reversed = new StringBuilder(); int j = s.length(); for (int i = s.length() - 1; i >= 0; i ) { if (s.charAt(i) == ' ') { j = i;

} else if (i == 0 || s.charAt(i - 1) == ' ') { if (reversed.length() != 0) { reversed.append(' ');

If the input string has no leading or trailing spaces and words are separated by a single space, is it possible to perform in-place modifications without allocating extra space? This efficiency is often asked in coding interviews and algorithm design, highlighting the importance of optimizing memory usage when manipulating strings.

R EVERSE W ORDS IN A S TRING II

7 Reverse Words in a String II

Code it now: Coming soon! Difficulty: Medium, Frequency: N/A

Similar to Question [6 Reverse Words in a String], but with the following constraints:

“The input string does not contain leading or trailing spaces and the words are always separated by a single space.”

Could you do it in-place without allocating extra space?

O ( n ) runtime, O (1) space – In-place reverse:

Let us indicate the i th word by w i and its reversal as w i ′ Notice that when you reverse a word twice, you get back the original word That is, ( w i ′)′ = w i

The input string is w 1 w 2 … w n If we reverse the entire string, it becomes w n ′ … w 2 ′ w 1 ′

Reversing each individual word in a string results in the order of words being inverted This process can be achieved by first reversing each word separately, then reversing the entire string, to achieve the desired output Implementing this in code involves iterating through the character array, reversing words when a space or the end of the array is encountered The method 'reverseWords' showcases how to reverse each word in place within a character array efficiently, ensuring optimal performance This technique is essential for text manipulation tasks such as reversing word order while maintaining word integrity, and is widely applicable in string processing algorithms.

} private void reverse(char[] s, int begin, int end) { for (int i = 0; i < (end - begin) / 2; i++) { char temp = s[begin + i]; s[begin + i] = s[end - i - 1]; s[end - i - 1] = temp;

Implement the two-pass solution without using the library’s split function

Rotate an array to the right by k steps in-place without allocating extra space For instance, with k = 3, the array [0, 1, 2, 3, 4, 5, 6] is rotated to [4, 5, 6, 0, 1, 2, 3]

S TRING TO I NTEGER ( ATOI )

Code it now: https://oj.leetcode.com/problems/string-to-integer-atoi/ Difficulty: Easy, Frequency: High

Implement atoi to convert a string to an integer

The atoi function begins by ignoring any leading whitespace characters until it encounters the first non-whitespace character It then checks for an optional plus or minus sign before processing a sequence of numerical digits These digits are interpreted as a numerical value, which the function returns.

The string can contain additional characters after those that form the integral number, which are ignored and have no effect on the behavior of this function

If the initial sequence of non-whitespace characters in the string is not a valid integer or if no such sequence exists—due to the string being empty or containing only whitespace—no conversion will be performed.

When a valid conversion cannot be performed, a zero value is returned If the converted value exceeds the range of representable integers, the function returns the maximum value of 2,147,483,647 or the minimum value of –2,147,483,648 These behaviors ensure proper handling of invalid inputs and overflow situations in integer conversions.

The core challenge in this problem is handling overflow A straightforward solution is to store the number as a string, allowing evaluation at each step to detect any overflow occurrences Additionally, there are alternative methods to detect overflow that depend on understanding the specific behaviors of programming languages or operating systems.

A desirable solution does not rely on assumptions about the inner workings of the language It constructs the number step by step by appending digits through multiplication and addition, ensuring safe operations at each stage If the current number exceeds 214748364, it is certain that an additional digit will cause an overflow When the current number is exactly 214748364, appending a digit of 8 or higher will lead to overflow Additionally, it is crucial to consider edge cases like the smallest 32-bit integer, -2147483648, to ensure robust handling of all input scenarios.

15 private static final int maxDiv10 = Integer.MAX_VALUE / 10; public int atoi(String str) { int i = 0, n = str.length(); while (i < n && Character.isWhitespace(str.charAt(i))) i++; int sign = 1; if (i < n && str.charAt(i) == '+') { i++;

} else if (i < n && str.charAt(i) == '-') { sign = -1; i++;

} int num = 0; while (i < n && Character.isDigit(str.charAt(i))) { int digit = Character.getNumericValue(str.charAt(i)); if (num > maxDiv10 || num == maxDiv10 && digit >= 8) { return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;

V ALID N UMBER

Code it now: https://oj.leetcode.com/problems/valid-number/ Difficulty: Easy, Frequency: Low

Validate if a given string is numeric

Example Questions Candidate Might Ask:

Q: How to account for whitespaces in the string?

A: When deciding if a string is numeric, ignore both leading and trailing whitespaces

Q: Should I ignore spaces in between numbers – such as “1 1”?

A: No, only ignore leading and trailing whitespaces “1 1” is not numeric

Q: If the string contains additional characters after a number, is it considered valid?

A: No If the string contains any non-numeric characters (excluding whitespaces and decimal point), it is not numeric

Q: Is it valid if a plus or minus sign appear before the number?

A: Yes “+1” and “-1” are both numeric

Q: Should I consider only numbers in decimal? How about numbers in other bases such as hexadecimal (0xFF)?

A: Only consider decimal numbers “0xFF” is not numeric

Q: Should I consider exponent such as “1e10” as numeric?

A: No But feel free to work on the challenge that takes exponent into consideration (The Online Judge problem does take exponent into account.)

This problem closely resembles Question 8, "String to Integer (atoi)," and involves handling numerous edge cases Breaking the problem into smaller, manageable components makes it easier to address each aspect individually, ensuring accurate and efficient solutions.

A string could be divided into these four substrings in the order from left to right: s1 Leading whitespaces (optional) s2 Plus (+) or minus (–) sign (optional) s3 Number s4 Optional trailing whitespaces (optional)

To determine if s3 is a valid number, we first ignore s1, s2, and s4 and focus solely on s3 Recognizing that a valid number can be either a whole number or a decimal, we assess each case accordingly For whole numbers, the validation is straightforward: check if s3 contains only digits This approach ensures accurate identification of numeric strings, whether integers or decimals, aligning with standard number validation practices.

On the other hand, a decimal number could be further divided into three parts: a Integer part b Decimal point c Fractional part

A valid number consists of an integer part and/or a fractional part, both composed solely of digits For example, in the number “3.64,” the integer part is “3,” and the fractional part is “64.” While either part can be omitted, at least one must be present; a single dot “.” is invalid, whereas “1.” and “.1” are acceptable formats Understanding these formatting rules ensures correct validation of numeric inputs in your applications.

“1.0” are all valid Please note that “1.” is valid because it implies “1.0”

The core logic for determining if a string represents a numeric value is straightforward to implement in code From lines 6 to 17, the method systematically checks for leading whitespace, optional signs ('+' or '-'), and digit characters This process involves iterating through the string, skipping whitespace, handling optional sign characters, and verifying the presence of digits to confirm numeric validity Implementing such logic ensures accurate validation of whether the string 's3' is numeric, adhering to best coding practices for parsing numeric strings in software development.

} if (i < n && s.charAt(i) == '.') { i++; while (i < n && Character.isDigit(s.charAt(i))) { i++; isNumeric = true;

} while (i < n && Character.isWhitespace(s.charAt(i))) i++; return isNumeric && i == n;

A number may include an optional exponent part, indicated by the character ‘e’ followed by a whole number (exponent) For example, “1e10” is recognized as a valid numeric value To handle this, modify your existing code to accommodate numbers with optional exponent notation, ensuring it correctly processes both standard and exponential formats for improved accuracy and flexibility in numeric validation.

This article demonstrates how to extend a previous solution to validate whether a string is a valid number The provided code snippet highlights the key logic, including trimming whitespace, handling optional signs, and checking for digit sequences Implementing these steps ensures accurate parsing of numeric strings in Java Properly managing whitespace, signs, and digit validation is essential for robust number validation functions in programming.

} if (i < n && s.charAt(i) == '.') { i++; while (i < n && Character.isDigit(s.charAt(i))) { i++; isNumeric = true;

} if (isNumeric && i < n && s.charAt(i) == 'e') { i++; isNumeric = false; if (i < n && (s.charAt(i) == '+' || s.charAt(i) == '-')) i++; while (i < n && Character.isDigit(s.charAt(i))) { i++; isNumeric = true;

} while (i < n && Character.isWhitespace(s.charAt(i))) i++; return isNumeric && i == n;

L ONGEST S UBSTRING W ITHOUT R EPEATING C HARACTERS

Code it now: Difficulty: Medium, Frequency: Medium https://oj.leetcode.com/problems/longest-substring-without-repeating-characters/

Finding the length of the longest substring without repeating characters in a given string is a common programming challenge For example, in the string “abcabcbb,” the longest substring without repeating characters is “abc,” with a length of 3 Similarly, in the string “bbbbb,” the longest substring is “b,” which has a length of 1 This problem helps improve understanding of string manipulation and efficient algorithms for managing consecutive characters. -Master substring challenges effortlessly with AI-powered coding—stay in flow and optimize string algorithms like a pro [Learn more](https://pollinations.ai/redirect/windsurf)

To efficiently determine if a character exists within a substring, utilize a simple lookup table that records characters already encountered It's important to clarify with your interviewer whether the string may include characters beyond the lowercase 'a' to 'z', such as digits, uppercase letters, ASCII characters, or Unicode sets, to ensure your approach accommodates all possible character types.

When you encounter a repeated character in a string, such as the second appearance of ‘c’ in “abcdcedf,” it prompts you to identify and handle duplicates effectively Recognizing repeated characters is crucial in tasks like removing duplicates, detecting substrings, or optimizing search algorithms Understanding how to process repeated characters enhances your ability to manipulate strings efficiently in programming and data analysis.

When a repeated character is identified at index j, it indicates that the current substring, excluding the repeated character, is a potential maximum length, so the maximum length should be updated if necessary This occurrence also confirms that the repeated character previously appeared at an earlier index i, where i is less than j Recognizing repeated characters and updating the maximum substring length accordingly is essential for solving such problems efficiently.

To optimize substring analysis, you should recognize that all substrings beginning at or before index i will be smaller than the current maximum, allowing you to safely proceed to search for the next substring starting precisely at index i + 1 This approach ensures efficient iteration and accurate identification of maximum substrings in your dataset.

To efficiently track the current substring, use two indices to mark its head and tail positions Since both indices, i and j, traverse the string at most n steps each, the overall process requires a maximum of 2n steps Therefore, the algorithm's runtime complexity is O(n), ensuring optimal performance for string processing tasks.

Although an array is allocated, the space complexity remains constant at O(1) because the size of the array does not increase with the input string length This means that, regardless of how long the string is, the memory usage stays fixed, ensuring efficient space management.

20 public int lengthOfLongestSubstring(String s) { boolean[] exist = new boolean[256]; int i = 0, maxLen = 0; for (int j = 0; j < s.length(); j++) { while (exist[s.charAt(j)]) { exist[s.charAt(i)] = false; i++;

} exist[s.charAt(j)] = true; maxLen = Math.max(j - i + 1, maxLen);

If the character set includes Unicode characters beyond the ASCII range, we can enhance the previous solution by replacing the boolean array with a Set This allows us to efficiently handle a broader range of characters, ensuring accurate detection of unique characters in the string Using a Set improves the flexibility and scalability of the algorithm when working with diverse character encodings.

The initial solution for finding the longest substring without repeating characters requires up to 2n steps, but it can be optimized to only n steps This optimization involves replacing the use of a table to check character existence with a character-to-index mapping, allowing for immediate skipping of repeated characters Implementing this approach results in a more efficient algorithm for calculating the length of the longest substring without duplicates.

Arrays.fill(charMap, -1); int i = 0, maxLen = 0; for (int j = 0; j < s.length(); j++) { if (charMap[s.charAt(j)] >= i) { i = charMap[s.charAt(j)] + 1;

} charMap[s.charAt(j)] = j; maxLen = Math.max(j - i + 1, maxLen);

L ONGEST S UBSTRING WITH A T M OST T WO D ISTINCT C HARACTERS

Code it now: Coming soon! Difficulty: Hard, Frequency: N/A

Given a string S, find the length of the longest substring T that contains at most two distinct characters

T is "ece" which its length is 3

First, we could simplify the problem by assuming that S contains two or more distinct characters, which means T must contain exactly two distinct characters

The brute force approach to analyze substrings has a time complexity of O(n^3), where n is the length of the string S This method involves generating every possible substring and inserting their characters into a set to count the number of distinct characters To optimize this process, the complexity can be reduced to O(n^2) by reusing the same set when expanding substrings, significantly improving efficiency.

To effectively solve the problem, maintain a sliding window that always contains at most two distinct characters, ensuring the invariant is preserved When adding a new character that causes the invariant to break, adjust the starting pointer of the window to restore it For example, starting with the substring “abba,” adding a 'c' disrupts the invariant, requiring us to readjust the window The key is to identify the correct starting point to resize the window and satisfy the invariant again, enabling efficient computation of the longest substring with at most two distinct characters.

Let’s look at another example where S = “abaac” We found our first window “abaa” When we add ‘c’ to the window, the next sliding window should be “aac”

This method iterates through the string with a time complexity of O(n), making it efficient for processing large datasets It utilizes three pointers—i, j, and k—to track the start of the current substring, the position of the second distinct character, and the current position in the iteration The algorithm updates the maximum length of the longest substring containing at most two distinct characters as it progresses It skips repeated characters to optimize performance and adjusts the start index when a third distinct character is encountered, ensuring the substring adheres to the problem constraints.

} return Math.max(s.length() - i, maxLen);

Although the above method works fine, it could not be easily generalized to the case where T contains at most k distinct characters

To effectively find the longest substring with at most two distinct characters, we utilize a sliding window approach while maintaining a character count array This array tracks the frequency of each character within the current window, ensuring the window satisfies the invariant of containing no more than two distinct characters As we expand the window by moving the right pointer, we update the count and the number of distinct characters When the number exceeds two, the left pointer moves forward, decrementing character counts and updating the number of distinct characters accordingly to maintain the window's validity This method ensures an efficient search for the longest valid substring.

M ISSING R ANGES

Code it now: Coming soon! Difficulty: Medium, Frequency: N/A

Given a sorted integer array where the range of elements are [0, 99] inclusive, return its missing ranges

Example Questions Candidate Might Ask:

Q: What if the given array is empty?

A: Then you should return [“0->99”] as those ranges are missing

Q: What if the given array contains all elements from the ranges?

A: Return an empty list, which means no range is missing

Comparing the gaps between neighboring elements and outputting their range is straightforward in concept, but involves several edge cases Special considerations include handling the first and last elements, which lack previous or next neighbors, and dealing with empty arrays, where the expected output should be the range "0->99" Ensuring comprehensive handling of these scenarios is essential for accurate and reliable performance.

As it turns out, if we could add two “artificial” elements, –1 before the first element and

100 after the last element, we could avoid all the above pesky cases

To effectively identify missing ranges within an array, start by listing out specific test cases to ensure comprehensive testing It's important to design your solution so that it can be extended beyond the fixed range [0, 99], accommodating any arbitrary range [start, end] Implementing this flexibility allows your method to find missing ranges efficiently regardless of the specified interval, ensuring adaptability and robustness in various scenarios.

List ranges = new ArrayList(); int prev = start - 1; for (int i = 0; i = 2) { ranges.add(getRange(prev + 1, curr - 1));

} private String getRange(int from, int to) { return (from == to) ? String.valueOf(from) : from + "->" + to;

L ONGEST P ALINDROMIC S UBSTRING

Code it now: https://oj.leetcode.com/problems/longest-palindromic-substring/ Difficulty: Medium, Frequency: Medium

Given a string S, find the longest palindromic substring in S You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring

First, make sure you understand what a palindrome means A palindrome is a string which reads the same in both directions For example, “aba” is a palindome, “abc” is not

Some people will be tempted to come up with a quick solution, which is unfortunately flawed (however can be corrected easily):

Reverse S and become S’ Find the longest common substring between S and S’, which must also be the longest palindromic substring

This seemed to work, let’s see some examples below

The longest common substring between S and S’ is “aba”, which is the answer

Let’s try another example: S = “abacdfgdcaba”, S’ = “abacdgfdcaba”

The longest common substring between S and S’ is “abacd” Clearly, this is not a valid palindrome

The longest common substring method encounters limitations when a reversed copy of a non-palindromic substring appears elsewhere in the string To improve accuracy, each time a longest common substring candidate is identified, its indices are compared to those of the reversed substring If the indices match, the algorithm updates the longest palindrome found; otherwise, it skips to the next candidate This approach ensures a more reliable identification of palindromic substrings in the string.

This gives us an O(n 2 ) DP solution which uses O(n 2 ) space (could be improved to use

O(n) space) Please read more about Longest Common Substring here

The obvious brute force solution is to pick all possible starting and ending positions for a substring, and verify if it is a palindrome There are a total of such substrings

(excluding the trivial solution where a character itself is a palindrome)

Since verifying each substring takes O(n) time, the run time complexity is O(n 3 )

To improve over the brute force solution from a DP approach, first think how we can avoid unnecessary re-computation in 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

Define P[ i, j ] ← true iff the substring S i … S j is a palindrome, otherwise false

This yields a straight forward DP solution, which we first initialize the one and two letters palindromes, and work our way up finding all three letters palindromes, and so on…

This gives us a runtime complexity of O(n 2 ) and uses O(n 2 ) space to store the table

Could you improve the above space complexity further and how?

In fact, we could solve it in O(n 2 ) time using only constant space

We observe that a palindrome mirrors around its center Therefore, a palindrome can be expanded from its center, and there are only 2n – 1 such centers

In palindrome detection, the reason there are 2n – 1 centers instead of n is that the center of a palindrome can be positioned between two characters This allows for the identification of even-length palindromes, such as "abba," where the center lies between the two 'b's Recognizing both character-centered and between-character centers is essential for accurately finding all palindromic substrings in a string.

Expanding a palindrome around its center can be achieved in O(n) time, resulting in an overall time complexity of O(n²) The implemented method involves iterating through each character in the string, expanding around both single and double centers to find the longest palindromic substring For each position, it calculates the maximum length of palindromes centered at that point, updating the start and end indices accordingly to track the longest palindrome found This approach ensures all possible centers are examined efficiently to identify the longest palindromic substring within the string.

} private int expandAroundCenter(String s, int left, int right) { int L = left, R = right; while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {

Manacher's algorithm is an advanced O(n) solution for finding palindromes efficiently, explained in detail here Although it is a complex and non-trivial algorithm, understanding it can be highly rewarding and enjoyable Don't worry if you can't implement it in a quick coding session; learning and exploring this algorithm will deepen your understanding of string processing techniques.

O NE E DIT D ISTANCE

Code it now: Coming soon! Difficulty: Medium, Frequency: N/A

Given two strings S and T, determine if they are both one edit distance apart

1 If | n – m | is greater than 1, we know immediately both are not one-edit distance apart

2 It might help if you consider these cases separately, m == n and m ≠ n

3 Assume that m is always ≤ n, which greatly simplifies the conditional statements

If m > n, we could just simply swap S and T

4 If m == n, it becomes finding if there is exactly one modified operation If m ≠ n, you do not have to consider the delete operation Just consider the insert operation in T

Let us assume m = length of S, n = length of T

Although this problem is solvable by directly applying the famous Edit distance dynamic programming algorithm with runtime complexity of O(mn) and space complexity of

O(mn) (could be optimized to O(min(m,n)), it is far from desirable as there exists a simpler and more efficient one-pass algorithm

O ( n ) runtime, O (1) space – Simple one-pass:

For the case where m is equal to n, it becomes finding if there is exactly one modified character Now let’s assume m ≤ n (If m > n we could just swap them)

Assume X represents the one-edit character There are three one-edit distance operations that could be applied to S i Modify operation – Modify a character to X in S

T = “abXde” ii Insert operation – X was inserted before a character in S

T = “abcXde” iii Append operation – X was appended at the end of S

We make a first pass over S and T concurrently and stop at the first non-matching character between S and T

1 If S matches all characters in T, then check if there is an extra character at the end of T (Modify operation)

2 If | n – m | == 1, that means we must skip this non-matching character only in T and make sure the remaining characters between S and T are exactly matching (Insert operation)

3 If | n – m | == 0, then we skip both non-matching characters in S and T and make sure the remaining characters between S and T are exactly matching (Append operation) public boolean isOneEditDistance(String s, String t) { int m = s.length(), n = t.length(); if (m > n) return isOneEditDistance(t, s); if (n - m > 1) return false; int i = 0, shift = n - m; while (i < m && s.charAt(i) == t.charAt(i)) i++; if (i == m) return shift > 0; if (shift == 0) i++; while (i < m && s.charAt(i) == t.charAt(i + shift)) i++; return i == m;

R EAD N C HARACTERS G IVEN R EAD 4

Code it now: Coming soon! Difficulty: Easy, Frequency: N/A

The API: int read4(char *buf) reads 4 characters at a time from a file

The return value is the actual number of characters read For example, it returns 3 if there is only 3 characters left in the file

By using the read4 API, implement the function int read(char *buf, int n) that reads n characters from the file

Note: The read function will only be called once for each test case

This coding question appears simple but involves tricky edge cases to consider When read4 returns fewer than 4, it indicates that the end of the file has been reached However, if read4 returns exactly 4, it could mean that the last four bytes of the file have been read Understanding these nuances is crucial for handling file reading operations accurately in coding tasks.

To make sure that the buffer is not copied more than n bytes, copy the remaining bytes (n – readBytes) or the number of bytes read, whichever is smaller

/* The read4 API is defined in the parent class Reader4 int read4(char[] buf); */ public class Solution extends Reader4 {

* @param n Maximum number of characters to read

* @return The number of characters read

The `read` function efficiently reads characters into a buffer by utilizing the `read4` method, which retrieves up to four characters at a time It continues reading in a loop until the requested number of characters (`n`) are obtained or the end of the file (EOF) is reached The method tracks the total number of bytes read and updates the EOF condition when fewer than four characters are returned by `read4`, ensuring accurate reading of the input stream This implementation optimizes character reading by handling partial reads and preventing buffer overflows, adhering to best practices for efficient stream processing.

System.arraycopy(buffer /* src */, 0 /* srcPos */, buf /* dest */, readBytes /* destPos */, bytes /* length */); readBytes += bytes;

What if read could be called multiple times? See Question [16 Read N Characters Given Read4 – Call multiple times]

R EAD N C HARACTERS G IVEN R EAD 4 – C ALL MULTIPLE TIMES

Code it now: Coming soon! Difficulty: Hard, Frequency: N/A

Similar to Question [15 Read N Characters Given Read4], but the read function may be called multiple times

This makes the problem a lot more complicated, because it can be called multiple times and involves storing states

To efficiently manage data reading, we define key class member variables: a buffer, an offset, and bufsize The buffer is a fixed-size array of four elements used to temporarily store data returned by read4; any unused characters in the buffer can be utilized in subsequent read calls The offset tracks the starting position within the buffer for the next read operation, especially when partial reads occur due to byte-size constraints The bufsize indicates the actual amount of data stored in the buffer; if bufsize is greater than zero, there is leftover partial data from the previous read that should be processed first, whereas a bufsize of zero signifies that the buffer is empty and ready for new data retrieval.

This problem is a very good coding exercise Coding it correctly is extremely tricky due to the amount of edge cases to consider

/* The read4 API is defined in the parent class Reader4 int read4(char[] buf); */ public class Solution extends Reader4 { private char[] buffer = new char[4]; int offset = 0, bufsize = 0;

* @param n Maximum number of characters to read

* @return The number of characters read

The provided code snippet demonstrates a method for reading characters into a buffer, designed to handle partial reads efficiently It initializes variables to track the number of bytes read and the end-of-file (EOF) status, then continues reading in a loop until the desired number of characters is obtained or EOF is reached The method employs a buffer size check and uses a helper function, `read4()`, to read up to four characters at a time, ensuring optimal data retrieval It manages edge cases where fewer than four characters remain, marking EOF appropriately to prevent unnecessary reads Overall, this implementation illustrates an effective approach to reading variable amounts of data from a source while adhering to buffer capacity constraints. -Boost your coding efficiency with AI-driven tools like Windsurf that read and manage data buffers smartly—[Learn more](https://pollinations.ai/redirect/windsurf)

System.arraycopy(buffer /* src */, offset /* srcPos */, buf /* dest */, readBytes /* destPos */, bytes /* length */); offset = (offset + bytes) % 4; bufsize = sz - bytes; readBytes += bytes;

MATH

R EVERSE I NTEGER

Code it now: https://oj.leetcode.com/problems/reverse-integer/ Difficulty: Easy, Frequency: High

Reverse digits of an integer For example: x = 123, return 321

Example Questions Candidate Might Ask:

A: For input x = –123, you should return –321

Q: What if the integer’s last digit is 0? For example, x = 10, 100, …

A: Ignore the leading 0 digits of the reversed integer 10 and 100 are both reversed as 1

Q: What if the reversed integer overflows? For example, input x = 1000000003

A: In this case, your function should return 0

Let’s start with a simple implementation We do not need to handle negative integers separately, because the modulus operator works for negative integers as well (e.g., –43 %

10 = –3) public int reverse(int x) { int ret = 0; while (x != 0) { ret = ret * 10 + x % 10; x /= 10;

There is a flaw in the above code – the reversed integer could overflow/underflow Take x = 1000000003 for example To check for overflow/underflow, we could check if ret >

When reversing an integer, it is crucial to check if the intermediate value exceeds 214748364 before multiplying by 10 Specifically, if the current reversed number (ret) is less than -214,748,364, multiplying by 10 could cause an overflow However, if ret equals 214,748,364, overflow is prevented because the last digit of the reversed number is guaranteed to be 1 due to input constraints Proper handling of these edge cases ensures the reversal process respects integer boundaries and avoids overflow errors.

33 public int reverse(int x) { int ret = 0; while (x != 0) {

// handle overflow/underflow if (Math.abs(ret) > 214748364) { return 0;

P LUS O NE

Code it now: https://oj.leetcode.com/problems/plus-one/ Difficulty: Easy, Frequency: High

Given a number represented as an array of digits, plus one to the number

Example Questions Candidate Might Ask:

Q: Could the number be negative?

A: No Assume it is a non-negative number

Q: How are the digits ordered in the list? For example, is the number 12 represented by [1,2] or [2,1]?

A: The digits are stored such that the most significant digit is at the head of the list

Q: Could the number contain leading zeros, such as [0,0,1]?

To increment a number represented as a list of digits, start from the least significant digit and simulate by adding one If the digit is less than nine, simply add one and return the updated number However, if the digit is nine, change it to zero and carry over the one to the next digit to the left Continue this process until you either encounter a digit less than nine or complete the entire list, in which case you'll need to add a new digit at the beginning This approach efficiently handles incrementing numbers, including those with multiple trailing nines.

When adding one to a digit of 9, the digit resets to 0 and a carry of one is propagated to the next left digit This recursive process continues, with the addition being carried over to each digit until it reaches the most significant digit.

When incrementing a number represented as a list of digits, it’s essential to handle the edge case where every digit is 9 In this scenario, adding one requires transforming the number into a new form to accommodate the carry-over The algorithm iterates from the last digit to the first, checking if each digit is less than 9; if so, it increments that digit and returns immediately However, if all digits are 9, the method must update the entire number to a new value, typically by adding a leading 1 and resetting all other digits to zero Properly managing this edge case ensures the function accurately performs the "+1" operation across all possible inputs, maintaining robust and reliable code.

When all digits are 9, we perform a specific operation by appending a 0 and changing the most significant digit to 1 This approach is chosen over inserting 1 at the front of the list because, in an ArrayList implementation, appending a digit is significantly more efficient than inserting at the beginning, which would require shifting all existing elements.

P ALINDROME N UMBER

Code it now: https://oj.leetcode.com/problems/palindrome-number/ Difficulty: Easy, Frequency: Medium

Determine whether an integer is a palindrome Do this without extra space

Example Questions Candidate Might Ask:

Q: Does negative integer such as –1 qualify as a palindrome?

A: For the purpose of discussion here, we define negative integers as non-palindrome

Representing an integer as a string is an intuitive method for manipulation, but it violates the restriction of not using extra space since it requires allocating additional characters for storage Although this approach works, it defeats the purpose of space constraints by using extra memory proportional to the number of digits Many interview problems impose strict space restrictions, making it essential to find solutions that do not rely on converting integers to strings Navigating these constraints demonstrates a deeper understanding of memory-efficient algorithms for integer reversal tasks.

Reversing a number is an effective method to determine if it is a palindrome If a number is identical to its reversed version, then it qualifies as a palindrome number To reverse a number, you can use a simple algorithm: initialize a variable to store the reversed number, then repeatedly extract the last digit of the original number and build the reversed number by multiplying the current reverse by 10 and adding the extracted digit This process continues until the original number is reduced to zero, resulting in the reversed number, which can then be compared to the original to verify if it is a palindrome.

When reversing a number, it's important to consider the possibility of overflow, which can lead to unpredictable behavior across different programming languages In Java, an overflow causes the number to wrap around, while in C or C++, the behavior is undefined, potentially leading to errors Therefore, managing potential overflow is a crucial aspect of implementing a reliable number reversal algorithm.

To prevent overflow issues, you can store and return results using a larger data type such as long long instead of int However, it's important to be aware that the availability of larger data types varies across programming languages, and not all languages support such types.

To develop a more efficient and versatile solution, it is essential to begin comparing digits either from the middle outward or from both ends inward These two approaches—expanding from the center or comparing from the two ends—are fundamental strategies for optimizing palindrome checking algorithms, ensuring better performance and broader applicability.

Comparing digits from both ends simplifies the process of identifying palindromes Start by examining the first and last digits; if they differ, the number isn't a palindrome If they match, remove these outer digits and repeat the comparison Continue this process until no digits remain, confirming that the number is a palindrome if all corresponding digits match throughout.

Extracting and removing the last digit of a number is straightforward, but handling the first digit in a generic manner requires careful consideration Developing a solution for this, such as checking for a palindrome number, involves thoughtful algorithm design For instance, determining if an integer is a palindrome can be achieved by iteratively comparing the first and last digits after appropriate manipulations Understanding these techniques enhances problem-solving skills for numeric operations in programming.

} while (x != 0) { int l = x / div; int r = x % 10; if (l != r) return false; x = (x % div) / 10; div /= 100;

LINKED LIST

M ERGE T WO S ORTED L ISTS

Code it now: https://oj.leetcode.com/problems/merge-two-sorted-lists/ Difficulty: Easy, Frequency: Medium

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

To simplify list initialization, we insert a dummy head before the new list, which helps avoid handling special cases like setting the new list’s head Using a dummy head ensures that the head of the new list can be easily returned as the next node after the dummy, streamlining the insertion process This approach is a common best practice in linked list manipulation, making code cleaner and more efficient.

Using a dummy head simplifies code implementation and serves as a valuable tool in technical interviews For practical examples of dummy head usage, refer to questions like "Add Two Numbers," "Swap Nodes in Pairs," and "Merge K Sorted Linked Lists." Implementing linked list operations such as merging two sorted lists becomes more straightforward with a dummy head, enhancing both code clarity and efficiency.

ListNode p = dummyHead; while (l1 != null && l2 != null) { if (l1.val < l2.val) { p.next = l1; l1 = l1.next;

} if (l1 != null) p.next = l1; if (l2 != null) p.next = l2; return dummyHead.next;

A DD T WO N UMBERS

Code it now: https://oj.leetcode.com/problems/add-two-numbers/ Difficulty: Medium, Frequency: High

Given two linked lists representing non-negative numbers with digits stored in reverse order, the task is to add these two numbers Each node contains a single digit, and the goal is to return the sum as a new linked list in the same reverse order format This process involves traversing both lists, summing corresponding digits, managing carry-over values, and constructing the resulting linked list to represent the combined total accurately.

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

Take extra caution of the following cases:

- When one list is longer than the other

- The sum could have an extra carry of one at the end, which is easy to forget (e.g.,

(9  9) + (1) = (0  0  1)) public ListNode addTwoNumbers(ListNode l1, ListNode l2) {

This code snippet demonstrates how to add two numbers represented by linked lists It initializes pointers for both lists (`p` and `q`) and uses a dummy head node (`curr`) to facilitate the result list construction The algorithm iterates through both linked lists, summing corresponding digits along with any carryover At each step, it calculates the digit to store and updates the carry value New nodes are appended to the result linked list, and the pointers advance to the next nodes When the loop concludes, it handles remaining carry to ensure the final sum is accurate This approach efficiently adds two numbers stored as linked lists, handling different lengths and carryover seamlessly.

} if (carry > 0) { curr.next = new ListNode(carry);

S WAP N ODES IN P AIRS

Code it now: https://oj.leetcode.com/problems/swap-nodes-in-pairs/ Difficulty: Medium, Frequency: Medium

Given a linked list, swap every two adjacent nodes and return its head

Given 1  2  3  4, you should return the list as 2  1  4  3

Your algorithm should use only constant space You may not modify the values in the list, only nodes itself can be changed

Example Questions Candidate Might Ask:

Q: What if the number of nodes in the linked list has only odd number of nodes?

A: The last node should not be swapped

Let’s assume p, q, r are the current, next, and next’s next node

We could swap the nodes pairwise by adjusting where it’s pointing next: q.next = p; p.next = r;

The above operations transform the list from { p  q  r  s } to { q  p  r  s }

If the next pair of nodes exists, we should remember to connect p’s next to s Therefore, we should record the current node before advancing to the next pair of nodes

To determine the new list's head, check if the list contains two or more elements These additional conditional statements can be avoided by inserting a dummy head node at the front of the list Using a dummy node simplifies the process of manipulating linked lists during operations like swapping pairs, making the code more efficient and easier to maintain.

ListNode dummy = new ListNode(0); dummy.next = head;

ListNode prev = dummy; while (p != null && p.next != null) {

ListNode q = p.next, r = p.next.next; prev.next = q; q.next = p; p.next = r; prev = p; p = r;

M ERGE K S ORTED L INKED L ISTS

Code it now: https://oj.leetcode.com/problems/merge-k-sorted-lists/ Difficulty: Hard, Frequency: High

Merge k sorted linked lists and return it as one sorted list Analyze and describe its complexity

O ( nk 2 ) runtime, O (1) space – Brute force:

The brute force approach is to merge a list one by one For example, if the lists = [l1, l2, l3, l4], we first merge l1 and l2, then merge the result with l3, and finally l4

To analyze its time complexity, assume there are k lists, each containing n elements The algorithm performs a total of k – 1 merge operations, with the initial merge combining two lists of size n, resulting in up to 2n comparisons Subsequent merges involve larger lists, with sizes increasing by n after each operation—for example, the second merge combines lists of sizes 2n and n, and so on The total number of comparisons across all merges sums up to 2n + 3n + 4n + + kn, which simplifies to O(nk²), indicating quadratic time growth relative to the number of lists and list sizes.

O ( nk log k ) runtime, O ( k ) space – Heap:

To efficiently merge k sorted lists, we can utilize a min heap of size k, initially populated with the smallest element from each list As we extract nodes from the heap, we insert the next node from the corresponding list into the heap to maintain the size Since each insert operation costs O(log k) and there are a total of nk elements across all lists, the overall runtime complexity of this approach is O(nk log k).

Ignoring the extra space that is used to store the output list, we only use extra space of

41 private static final Comparator listComparator = new Comparator() {

@Override public int compare(ListNode x, ListNode y) { return x.val - y.val;

}; public ListNode mergeKLists(List lists) { if (lists.isEmpty()) return null;

Queue queue = new PriorityQueue(lists.size(), listComparator); for (ListNode node : lists) { if (node != null) { queue.add(node);

ListNode p = dummyHead; while (!queue.isEmpty()) {

ListNode node = queue.poll(); p.next = node; p = p.next; if (node.next != null) { queue.add(node.next);

O ( nk log k ) runtime, O (1) space – Divide and conquer using two way merge:

Merge sort utilizes a divide and conquer strategy to efficiently sort lists Specifically, we can apply the "merge two sorted lists" algorithm, as discussed in Question 20, to combine sorted sublists into a single sorted list This approach ensures optimal performance and simplifies the merging process within the merge sort framework.

Basically, the algorithm merges two lists at a time, so the number of lists reduces from:

Similarly, the size of the lists increases from (Note that the lists could subdivide itself at most log(k) times):

Therefore, the runtime complexity is:

Since we are implementing this divide and conquer algorithm iteratively, the space complexity is constant at O(1), yay!

To efficiently merge multiple sorted linked lists, implement a function that handles an empty list by returning null immediately Use a while loop to repeatedly merge pairs of lists from both ends towards the center, progressively reducing the total number of lists During each iteration, merge two lists at a time using a helper function, updating the list of merged results accordingly Continue this process until only one merged linked list remains, ensuring a structured and optimized approach to combine k sorted lists into a single sorted linked list.

} private ListNode merge2Lists(ListNode l1, ListNode l2) {

ListNode p = dummyHead; while (l1 != null && l2 != null) { if (l1.val < l2.val) { p.next = l1; l1 = l1.next;

} if (l1 != null) p.next = l1; if (l2 != null) p.next = l2; return dummyHead.next;

C OPY L IST WITH R ANDOM P OINTER

Code it now: https://oj.leetcode.com/problems/copy-list-with-random-pointer/ Difficulty: Hard, Frequency: High

A linked list is given such that each node contains an additional random pointer that could point to any node in the list or null

Return a deep copy of the list

Cloning a linked list without an additional random pointer is easy to solve The trickier part however, is to clone the random list node structure

To begin, it is essential to assign an index to each node in the linked list For example, node 0's random pointer references node 1, while node 1's random pointer points back to node 0 Additionally, node 2's random pointer references itself, node 2 Properly labeling nodes with indices is a key step for accurately duplicating complex linked lists with random pointers.

To rebuild the structure effectively, we start by assuming the existing connections, such as node 0 pointing to node 1 When creating a cloned version of the graph, it's essential to connect node 0' to node 1' to preserve the original relationships This process involves iterating through the list of nodes to establish the correct links each time, which may impact the efficiency of the reconstruction Properly linking cloned nodes ensures the integrity of the replicated structure and facilitates accurate traversal and manipulation of the graph.

O(n), making the overall time complexity O(n 2 )

To effectively represent connections in a linked structure, we can create a map that links each original node to its corresponding index, facilitating the cloning process Once this map is established, duplicating the structure becomes straightforward; for example, if node 0’s random pointer points to node 1, we simply connect the corresponding cloned nodes However, a challenge arises because connecting nodes requires traversing the cloned list to locate the target node, resulting in an O(n) complexity for each connection.

Efficient mapping is essential for quickly locating nodes during the cloning process By building a map of indices to cloned nodes, we simplify the connection of random nodes This approach reduces the complexity of linking random pointers to O(1), ensuring faster and more optimal duplicate node creation.

Initially, our process involved using two separate maps However, a closer analysis revealed that these could be combined into a single map by directly mapping each original node to its corresponding random node Streamlining this approach simplifies the process and improves efficiency in handling node relationships.

Figure 1: Linked list that has both next and random pointers

44 public RandomListNode copyRandomList(RandomListNode head) {

Map map = new HashMap();

RandomListNode q = dummy; while (p != null) { q.next = new RandomListNode(p.label); map.put(p, q.next); p = p.next; q = q.next;

} p = head; q = dummy; while (p != null) { q.next.random = map.get(p.random); p = p.next; q = q.next;

O ( n ) runtime, O (1) space – Modify original structure:

The algorithm currently employs extra space of O(n), but is it possible to eliminate this requirement? To do so, we need to modify the original data structure directly One effective approach is to update the next pointer of each original node to point to its corresponding copy, removing the need for additional memory.

Now, assume we have the above configuration we could assign the random node pointers of each copy easily with the following code: node.next.random = node.random.next;

To duplicate a linked list with random pointers, follow three key steps First, create a copy of each original node and insert these copies between existing nodes in an alternating pattern Second, assign random pointers to each node copy, ensuring they point to the correct nodes Lastly, restore the linked list to its original structure by separating the copied nodes from the originals This approach efficiently clones complex linked lists while maintaining their original connections.

We have achieved O(n) runtime complexity with using only constant extra space

45 public RandomListNode copyRandomList(RandomListNode head) {

RandomListNode copy = new RandomListNode(p.label); p.next = copy; copy.next = next; p = next;

} p = head; while (p != null) { p.next.random = (p.random != null) ? p.random.next : null; p = p.next.next;

RandomListNode headCopy = (p != null) ? p.next : null; while (p != null) {

RandomListNode copy = p.next; p.next = copy.next; p = p.next; copy.next = (p != null) ? p.next : null;

BINARY TREE

V ALIDATE B INARY S EARCH T REE

Code it now: https://oj.leetcode.com/problems/validate-binary-search-tree/ Difficulty: Medium, Frequency: High

Given a binary tree, determine if it is a valid Binary Search Tree (BST)

Understanding the difference between a Binary Tree and a Binary Search Tree (BST) is essential; a binary tree is a data structure where each node can have at most two children, whereas a BST is a specialized binary tree with the added property that the left child contains values less than the parent node, and the right child contains values greater than or equal to the parent Recognizing these distinctions helps in optimizing data organization and search operations in computer science.

- The left subtree of a node contains only nodes with keys less than the node’s key

- The right subtree of a node contains only nodes with keys greater than the node’s key

- Both the left and right subtrees must also be binary search trees

Understanding the definition of a Binary Search Tree (BST) is essential, as some may mistakenly believe that verifying a BST involves checking if each node's left child has a value less than the parent and the right child has a value greater than the parent The correct approach requires ensuring that for every node, all nodes in the left subtree are less than the node’s value, and all nodes in the right subtree are greater, not just immediate children An incorrect algorithm might assume that if each node’s immediate children satisfy these conditions, the entire tree is a BST, which is not accurate Proper validation of a BST involves verifying this property recursively for all nodes throughout the tree, adhering to the official definition and avoiding common misconceptions.

It sounds correct and convincing, but look at this counter example below: A sample tree that we name it as binary tree (1)

It’s obvious that this is not a valid BST, since (6) could never be on the right of (10)

O ( n 2 ) runtime, O ( n ) stack space – Brute force:

Based on BST’s definition, we can easily devise a brute force solution:

To determine if a binary tree is a valid binary search tree (BST), verify that for each node with value k, all nodes in its left subtree contain values less than k, while all nodes in its right subtree contain values greater than k If every node satisfies this property throughout the tree, then the tree qualifies as a BST.

Below is the brute force code that is inefficient:

47 public boolean isValidBST(TreeNode root) { if (root == null) return true; return isSubtreeLessThan(root.left, root.val)

&& isSubtreeGreaterThan(root.right, root.val)

&& isValidBST(root.left) && isValidBST(root.right);

} private boolean isSubtreeLessThan(TreeNode p, int val) { if (p == null) return true; return p.val < val

} private boolean isSubtreeGreaterThan(TreeNode p, int val) { if (p == null) return true; return p.val > val

The worst case runtime complexity is O(n 2 ) for the brute force algorithm, when the tree degenerates into a linked list with n nodes

O ( n ) runtime, O ( n ) stack space – Top-down recursion:

A more efficient solution involves avoiding the need to examine all nodes of both subtrees in every pass Instead, by passing down the low and high limits from the parent node to its children, we can effectively prune the search space This approach streamlines the process, improves performance, and ensures a more optimized traversal of the binary tree.

When traversing a binary tree from node 10 to its right node 15, we can confirm that the right node’s value falls between 10 and positive infinity As we continue from node 15 to its left node 6, we know the left node’s value must be between 10 and 15 Since node 6 does not meet this condition, it can be quickly identified as an invalid BST, ensuring efficient validation of binary search tree properties.

} private boolean valid(TreeNode p, int low, int high) { if (p == null) return true; return p.val > low && p.val < high

This algorithm runs in O(n) time, where n is the number of nodes of the binary tree

If the tree contains the smallest or largest integer values, the existing code may not function correctly To address this issue, we can use null to represent infinity, ensuring accurate handling of boundary cases This approach effectively fixes the problem and improves the robustness of the algorithm.

48 public boolean isValidBST(TreeNode root) { return valid(root, null, null);

} private boolean valid(TreeNode p, Integer low, Integer high) { if (p == null) return true; return (low == null || p.val > low) && (high == null || p.val < high)

O ( n ) runtime, O ( n ) stack space – In-order traversal:

Performing an in-order traversal of the binary tree is an effective approach to verify if the tree is a valid Binary Search Tree (BST) During traversal, ensuring that each previous node's value is less than the current node's value confirms the tree's strict monotonic increasing order This method has a time complexity of O(n), making it efficient for large trees The implementation involves a private variable to keep track of the previous node, and the root node is checked recursively to validate the BST property.

} private boolean isMonotonicIncreasing(TreeNode p) { if (p == null) return true; if (isMonotonicIncreasing(p.left)) { if (prev != null && p.val end) return null; int mid = (start + end) / 2;

TreeNode leftChild = sortedListToBST(start, mid-1);

TreeNode parent = new TreeNode(list.val); parent.left = leftChild; list = list.next; parent.right = sortedListToBST(mid+1, end); return parent;

} public TreeNode sortedListToBST(ListNode head) { int n = 0;

ListNode p = head; while (p != null) { p = p.next; n++;

B INARY T REE M AXIMUM P ATH S UM

Code it now: https://oj.leetcode.com/problems/binary-tree-maximum-path-sum/ Difficulty: Hard, Frequency: Medium

Given a binary tree, find the maximum path sum

The path may start and end at any node in the tree

For example, given the below binary tree,

The highlighted path yields the maximum sum 10

Example Questions Candidate Might Ask:

Q: What if the tree is empty?

A: Assume the tree is non-empty

Q: How about a tree that contains only a single node?

A: Then the maximum path sum starts and ends at the same node

Q: What if every node contains negative value?

A: Then you should return the single node value that is the least negative

Q: Does the maximum path have to go through the root node?

A: Not necessarily For example, the below tree yield 6 as the maximum path sum and does not go through root

Anytime when you found that doing top down approach uses a lot of repeated calculation, bottom up approach usually is able to be more efficient

Implement the bottom-up approach to find the maximum path sum in a binary tree, considering four potential cases at each node: the maximum path passing through the node connecting the left and right subtrees, the maximum path extending from the node to the left subtree, the maximum path extending from the node to the right subtree, and the node's value alone This method ensures an efficient calculation by evaluating all possible paths at every node, ultimately determining the highest possible sum across the tree Applying this strategy allows for comprehensive analysis of all path configurations, leading to an accurate and optimized solution for maximum path sum problems.

To determine the maximum path sum in a binary tree, we need to consider returning the highest sum that passes through a node and connects to either its left or right subtree If this maximum sum is negative, we should return 0 to exclude that subtree from the parent's maximum path, simplifying the implementation The solution involves maintaining a global maximum sum variable, typically initialized to the smallest possible integer, and updating it during traversal A common approach is to use a recursive function to explore each node, update the maximum path sum, and ensure the algorithm handles negative values efficiently, resulting in an optimized binary tree maximum path sum calculation.

The findMax method recursively traverses a binary tree to determine the maximum path sum It handles null nodes by returning zero, ensuring proper base cases for recursion The method calculates the maximum contribution from the left and right subtrees, updating the global maxSum with the highest sum found at each node It also considers the current node's value combined with the greater of its left or right contribution, returning this value if positive to propagate potential maximum sums upward This approach efficiently finds the maximum path sum in a binary tree by exploring all possible paths and maintaining a global maximum throughout the traversal.

B INARY T REE U PSIDE D OWN

Code it now: Coming soon! Difficulty: Medium, Frequency: N/A

To invert a binary tree where all right nodes are either leaf nodes with a sibling or null, you need to flip the tree upside down so that the original right nodes become left leaf nodes This transformation involves reassigning parent and child relationships to create a new tree structure rooted at the original leftmost node The process returns the new root of the inverted binary tree, resulting in a configuration where the left children take precedence, and the original right nodes are repositioned as left leaves Applying this algorithm effectively reconstructs the binary tree with the specified structure, ensuring that all right nodes are now leaf nodes with siblings or null.

At each node you want to assign: p.left = parent.right; p.right = parent;

When reassigning the left and right children of the current node, it's crucial to proceed with caution Additionally, you must create copies of both the parent node and the parent's right child to prevent losing references This is important because the current node will become the parent node in the next iteration Implementing this carefully ensures the correct transformation of the binary tree upside down, preserving all necessary node connections throughout the process.

TreeNode p = root, parent = null, parentRight = null; while (p != null) {

TreeNode left = p.left; p.left = parentRight; parentRight = p.right; p.right = parent; parent = p; p = left;

The above code is actually very similar to the algorithm in reversing a linked list

While the top-down approach appears concise, it involves subtle complexities and hidden traps that require careful handling Conversely, the bottom-up recursive approach simplifies the process by reassigning bottom-level nodes first, avoiding unnecessary copies and overwrites Since the new root is always the left-most leaf node, we begin reassignments there Implementing this method involves initiating a depth-first search from the bottom, as demonstrated in the function `UpsideDownBinaryTree`, which calls `dfsBottomUp(root, null)` to perform the transformation efficiently.

} private TreeNode dfsBottomUp(TreeNode p, TreeNode parent) { if (p == null) return parent;

TreeNode root = dfsBottomUp(p.left, p); p.left = (parent == null) ? parent : parent.right; p.right = parent; return root;

BIT MANIPULATION

MISC

STACK

DYNAMIC PROGRAMMING

BINARY SEARCH

Ngày đăng: 01/08/2023, 22:48