LeetCode Freedom Flashcards
Getting into Google
@242. Valid Anagram
Given two strings s and t, return true if t is an anagram of s, and false otherwise.
An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.
Example 1:
Input: s = “anagram”, t = “nagaram”
Output: true
1 <= s.length, t.length <= 5 * 10^4
s and t consist of lowercase English letters.
- Count with hashmap. Substract counts when traverse second string. All value of hashmap should eventually be 0.
- Use byte - ‘a’ to create an indexed array indicating the frequencies. Compare the two arrays.
func isAnagram(s string, t string) bool { if len(s) != len(t) { return false } var sArr, tArr [26]int for i := 0; i < len(s); i++ { // index in sArr is s[i] - 'a' sArr[s[i] - 'a']++ // index in tArr is t[i] - 'a' tArr[t[i] - 'a']++ } return sArr == tArr }
@424. Longest Repeating Character Replacement
You are given a string s and an integer k. You can choose any character of the string and change it to any other uppercase English character. You can perform this operation at most k times.
Return the length of the longest substring containing the same letter you can get after performing the above operations.
Example 1:
Input: s = “ABAB”, k = 2
Output: 4
Explanation: Replace the two ‘A’s with two ‘B’s or vice versa.
1 <= s.length <= 10^5
s consists of only uppercase English letters.
0 <= k <= s.length
Sliding window. (WindowSize - Most frequent Element count in window) = Characters that need to be replaced. If Characters that need to be replaced is greater than k, move left pointer of window.
@11. Container with most water
You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]).
Find two lines that together with the x-axis form a container, such that the container contains the most water.
Return the maximum amount of water a container can store.
Notice that you may not slant the container.
Example1:
Input: height = [1,8,6,2,5,4,8,3,7]
Output: 49
Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
Two Pointers. Compare left height and right height. Move the lower height one step forward. Compare the previous stored maxArea with the new area.
The logic for moving the pointers:
Scenario A: Moving the one with higher height.
1. We move to a higher height => Doesn’t matter, the height of the area is still determined by the lower one, since we decreased (right - left), the area is smaller.
2. We move to an equal height => Doesn’t matter, the area is smaller due to decreased (right - left).
3. Lower height => Not considerable, we always get a lower height.
Scenario B: Moving the one with the lower height. 1. We move to an equal height => Doesn't matter, the area is smaller due to decreased (right - left). 2. We move to a lower height => Not considerable, we always get a lower height. 3. We move to a higher height => Wwe have a `chance` of getting a greater area, but we don't know more about it since (right - left) is also an issue With all the scenarios given, moving the lower height one step forward and compare with the current max area is the solution.
func maxArea(height []int) int { area := func(left, right, leftHeight, rightHeight int) int { // The area is determined by the bottleneck from the lower between leftHeight and rightHeight return (right - left) * min(leftHeight, rightHeight) } i, j := 0, len(height) - 1 // How do we update our left and right pointers? // Minimum height is the bottleneck, we are trying to find a better bottleneck. // For example, left height = 1, right height = 7 // We now assume that right height is good, and we want to optimize the answer by finding a better left height, move left height and compare again. var maxArea int for i < j { maxArea = max(maxArea, area(i, j, height[i], height[j])) if height[i] < height[j] { i++ } else { j-- } } return maxArea } func max(a, b int) int { if a > b { return a } return b } func min(a, b int) int { if a < b { return a } return b }
@Permutation in String
Given two strings s1 and s2, return true if s2 contains a permutation of s1, or false otherwise.
In other words, return true if one of s1’s permutations is the substring of s2.
Example 1:
Input: s1 = “ab”, s2 = “eidbaooo”
Output: true
Explanation: s2 contains one permutation of s1 (“ba”).
1 <= s1.length, s2.length <= 10^4
s1 and s2 consist of lowercase English letters.
- Use byte - ‘a’ to create an indexed array indicating the frequencies. Array is comparable in Go. Compare the two arrays.
func checkInclusion(s1 string, s2 string) bool { windowSize := len(s1) if len(s2) < windowSize { return false } // Create two arrays to compare. var s1Arr [26]int for i := range s1 { // All lowercases. rune 'a' is 97. rune 'b' is 98. 98-97 means adding one to bucket 1 in the array. s1Arr[s1[i] - 'a']++ } var s2Arr [26]int var prevHeadIndex int for r := range s2 { // Remove head from s2Arr. prevHeadIndex = r - windowSize if prevHeadIndex >= 0 { s2Arr[s2[prevHeadIndex] - 'a']-- } // Add current new element to s2Arr. r is the new tail index. s2Arr[s2[r] - 'a']++ if s1Arr == s2Arr { return true } } return false }
- Create a hashmap that helps check if the window is permutation of s1.
- TLE. Sort string and check if the window is permutation of s1.
@155. Min Stack
Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.
Implement the MinStack class:
- MinStack() initializes the stack object.
- void push(int val) pushes the element val onto the stack.
- void pop() removes the element on the top of the stack.
- int top() gets the top element of the stack.
- int getMin() retrieves the minimum element in the stack.
You must implement a solution within O(1) time for each function
-2^31 <= val <= 2^31 - 1
Methods pop, top and getMin operations will always be called on non-empty stacks.
At most 3 * 10^4 calls will be made to push, pop, top, and getMin.
Create a normal stack and a monotonic decreasing stack that keeps track of the minimum values. The minimum value is always the rightmost value of the monotonic decreasing stack.
@191. Number of 1 Bits.
Write a function that takes the binary representation of a positive integer and returns the number of set bits it has (also known as the Hamming weight).
Example 1:
Input: n = 11
Output: 3
Explanation:
The input binary string 1011 has a total of three set bits.
Loop the following: Compare unit digits with 1 (0001)using &. Return value will be either 1 ( true ) or 0 ( false ). If result is 1, add one to count.
func hammingWeight(n int) int { var ones int // AND with 1. for n != 0 { if n & 1 == 1 { ones++ } n >>= 1 } return ones }
& and»_space;
@150. Evaluate Reverse Polish Notation
You are given an array of strings tokens that represents an arithmetic expression in a Reverse Polish Notation.
Evaluate the expression. Return an integer that represents the value of the expression.
Example 1:
Input: tokens = [“2”,”1”,”+”,”3”,”*”]
Output: 9
Explanation: ((2 + 1) * 3) = 9
- The valid operators are ‘+’, ‘-‘, ‘*’, and ‘/’.
- Each operand may be an integer or another expression.
- The division between two integers always truncates toward zero.
- There will not be any division by zero.
- The input represents a valid arithmetic expression in a reverse polish notation.
- The answer and all the intermediate calculations can be represented in a 32-bit integer.
- 1 <= tokens.length <= 10^4
- tokens[i] is either an operator: “+”, “-“, “*”, or “/”, or an integer in the range [-200, 200].
Create a stack that supports Pop().
Do the following recursively until the left and right value of the first operator is generated:
Pop() last token ( Head ) from stack.
If the token is an Operator, find left and right recursively.
Ex: [4, 3, *, 13, 5, /, +]
stack = [4, 3, *, 13, 5, /, +]
first op = +, stack = [4, 3, *, 13, 5, /]
left = eval([4, 3, *, 13, 5, /])
second op = /
left = 5, right = 13, return 2, stack = [4, 3, *]
right = eval([4, 3, 8])
third op = *
left = 4, right = 3, return 12, stack = []
firstOp = +, left = 2, right = 12, return 14.
@22 Generate Parentheses
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
Example 1:
Input: n = 3
Output: [”((()))”,”(()())”,”(())()”,”()(())”,”()()()”]
1 <= n <= 8
Backtracking. Keep track of the open and closing parentheses counts.
* Only add Open parentheses if openCount < n
* Only add a closing parentheses if closeCount < openCount
* Valid if openCount == closeCount == n, return
func generateParenthesis(n int) []string { open, close := "(", ")" openCount, closeCount := 1, 0 result := make([]string, 0) // Declare the function var generate func(openCount, closeCount int, currStr string) // Define the function generate = func(openCount, closeCount int, currStr string) { if openCount > n { // End the recursive call when openCount exceeds n: Too many Open Brackets. return } if closeCount > openCount { // End the recursive call when closeCount exceeds openCount: Too many Close Brackets. return } // Append to result (this is a shared slice) when we reach a valid string. if openCount == n && closeCount == n { result = append(result, currStr) return } generate(openCount+1, closeCount, fmt.Sprintf("%s%s",currStr, open)) generate(openCount, closeCount+1, fmt.Sprintf("%s%s",currStr, close)) } // Start generating strings from '('. generate(openCount, closeCount, "(") return result }
Backtracking is used when we need to check all the possible conditions and also want to cut the check early off. It is basically a depth first search with conditions.
@739 Daily Temperature
Given an array of integers temperatures represents the daily temperatures, return an array answer such that answer[i] is the number of days you have to wait after the ith day to get a warmer temperature. If there is no future day for which this is possible, keep answer[i] == 0 instead.
Example 1:
Input: temperatures = [73,74,75,71,69,72,76,73]
Output: [1,1,4,2,1,1,0,0]
Example 2:
Input: temperatures = [30,40,50,60]
Output: [1,1,1,0]
Create a stack that stores the index of days that haven’t met it’s condition. Compare the head ( temperatures[s.head()] ) with todays temperature. If the teperature is greater than the head, pop the head from the stack (meaning that it reaches condition and doesn’t have to be compared again). The days would be today - the day it was stored in the stack (todayIdx - storedIdx).
func dailyTemperatures(temperatures []int) []int { result := make([]int, len(temperatures)) s := make(stack, 0) for idx, t := range temperatures { for s.length() > 0 && t > temperatures[s.head()] { poppedIdx := s.pop() result[poppedIdx] = idx - poppedIdx } // Add the current number to stack s.push(idx) } return result } type stack []int func (s *stack) push(num int) { *s = append(*s, num) } func (s *stack) pop() int { if s.length() > 0 { poppedIndex := (*s)[len(*s) - 1] *s = (*s)[:len(*s) - 1] return poppedIndex } return -1 } func (s *stack) length() int { return len(*s) } func (s *stack) head () int { return (*s)[len(*s) - 1] }
Stack is crazy.
@853 Car Fleet
There are n cars going to the same destination along a one-lane road. The destination is target miles away.
You are given two integer array position and speed, both of length n, where position[i] is the position of the ith car and speed[i] is the speed of the ith car (in miles per hour).
A car can never pass another car ahead of it, but it can catch up to it and drive bumper to bumper at the same speed. The faster car will slow down to match the slower car’s speed. The distance between these two cars is ignored (i.e., they are assumed to have the same position).
A car fleet is some non-empty set of cars driving at the same position and same speed. Note that a single car is also a car fleet.
If a car catches up to a car fleet right at the destination point, it will still be considered as one car fleet.
Return the number of car fleets that will arrive at the destination.
Example 1:
Input: target = 12, position = [10,8,0,5,3], speed = [2,4,1,1,3]
Output: 3
Explanation:
The cars starting at 10 (speed 2) and 8 (speed 4) become a fleet, meeting each other at 12.
The car starting at 0 does not catch up to any other car, so it is a fleet by itself.
The cars starting at 5 (speed 1) and 3 (speed 3) become a fleet, meeting each other at 6. The fleet moves at speed 1 until it reaches target.
Note that no other cars meet these fleets before the destination, so the answer is 3.
n == position.length == speed.length
1 <= n <= 10^5
0 < target <= 10^6
0 <= position[i] < target
All the values of position are unique.
0 < speed[i] <= 10^6
Sort the car by position. The ones that are closer to the target are the dominating ones. If a second car reaches the target faster than the dominating one, it joins the fleet. Thus create a monotonic increasing stack that stores the time to destination for dominating cars. Only update when the time to the destination for a second car is greater than the head of the stack (the current dominating car), meaning that the car is slower and cannot catch up, this itself is a new fleet.
(position, speed, timeToTarget)
cars =
(10, 2, 1) sort (0,1,12)
(8, 4, 1) (3,3, 3)
(0, 1, 12) (5,1,7)
(5, 1, 7) (8,4,1)
(3, 3, 3) (10,2,1)
- 10, stack [1]
- 8, stack[1]
- 5, stack[1,7]
- 3, stack[1,7]
- 0, stack[1, 7, 12]
(0,1)–(3,3)–(5,1)—(8,4)–(10, 2)—12>
dom dom dom
func carFleet(target int, position []int, speed []int) int { // fleets is a stack that stores the dominating fleet throughout the process. // It is a monotonically increasing stack. f := make(fleets, 0) c := cars{ pos: position, speed: speed, } // Sort the cars by position. The ones that are closer to the target are the dominators to the previous one. sort.Sort(c) c.timeToDest = make([]float64, len(c.pos)) for idx := range c.pos { c.timeToDest[idx] = float64(target - c.pos[idx]) / float64(c.speed[idx]) } for i := len(c.timeToDest) - 1; i >= 0; i-- { // Only push the time to the stack when the stack is empty or the time to the destination is greater than the previous one (head) // We ignore the time to target that is faster than the dominating one => Meaning that it joins the fleet ahead. if len(f) == 0 || c.timeToDest[i] > f.head() { f.push(c.timeToDest[i]) } } return len(f) } type cars struct { pos []int speed []int timeToDest []float64 } func (c cars) Len() int { return len(c.pos) } func (c cars) Less(i, j int) bool { return c.pos[i] < c.pos[j] } func (c cars) Swap(i, j int) { c.pos[i], c.pos[j] = c.pos[j], c.pos[i] c.speed[i], c.speed[j] = c.speed[j], c.speed[i] } type fleets []float64 func (f *fleets) push(timeToDest float64) { *f = append(*f, timeToDest) } func (f *fleets) head() float64 { if len(*f) > 0 { return (*f)[len(*f)-1] } return -1.0 }
@74 Search a 2D Matrix
You are given an m x n integer matrix matrix with the following two properties:
Each row is sorted in non-decreasing order.
The first integer of each row is greater than the last integer of the previous row.
Given an integer target, return true if target is in matrix or false otherwise.
You must write a solution in O(log(m * n)) time complexity.
Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
Output: true
m == matrix.length
n == matrix[i].length
1 <= m, n <= 100
-10^4 <= matrix[i][j], target <= 10^4
Binary search the row first. Then Binary search the number within the target row. Check edge cases first (Out of bound and greater than the head of the tail row.). If the target is greater or equal to the head of the midRow, move headRow to mid, else, move the tailRow below mid(cuz it is not encluded).
@704 Binary Search
Given an array of integers nums which is sorted in ascending order, and an integer target, write a function to search target in nums. If target exists, then return its index. Otherwise, return -1.
You must write an algorithm with O(log n) runtime complexity.
1 <= nums.length <= 10^4
-10^4 < nums[i], target < 10^4
All the integers in nums are unique.
nums is sorted in ascending order.
Binary search. Take special care for the boundary. Always use inclusive for both ends. ‘<=’ is for arrays that only has one element.
func search(nums []int, target int) int { // Head and tail all inclusive. // Everything left of head is smaller than target, everything right of tail is greater or equal to the target. head, tail := 0, len(nums) - 1 var mid int // '<=' is for arrays that only has one element. for head <= tail { mid = (head + tail) / 2 switch { case nums[mid] == target: return mid case nums[mid] > target: tail = mid - 1 case nums[mid] < target: head = mid + 1 } } return -1 }
@875 Koko Eating Bananas
Koko loves to eat bananas. There are n piles of bananas, the ith pile has piles[i] bananas. The guards have gone and will come back in h hours.
Koko can decide her bananas-per-hour eating speed of k. Each hour, she chooses some pile of bananas and eats k bananas from that pile. If the pile has less than k bananas, she eats all of them instead and will not eat any more bananas during this hour.
Koko likes to eat slowly but still wants to finish eating all the bananas before the guards return.
Return the minimum integer k such that she can eat all the bananas within h hours.
Example 1:
Input: piles = [3,6,7,11], h = 8
Output: 4
Example 2:
Input: piles = [30,11,23,4,20], h = 5
Output: 30
Binary search. Search for all possible value util the lowest k is found. k starts from 1 and will never exceed the maximum value in piles.
func minEatingSpeed(piles []int, h int) int { // k starts from 1 an will never exceed the maximum value in piles. head, tail := 1, maxInPiles(piles) // O(n) var mid, n int for head < tail { // O(n^2) mid = (head + tail) / 2 n = need(piles, mid) // O(n) switch { case n > h: head = mid + 1 case n <= h: // Always looking for a smaller k, we might have a smaller one on the left. tail = mid } } return tail } func maxInPiles(piles []int) int { maximum := piles[0] for i := range piles { maximum = max(maximum, piles[i]) } return maximum } func need(piles []int, possibleK int) int { var n int for i := range piles { n += int(math.Ceil(float64(piles[i])/ float64(possibleK))) } return n }
Take care of the bounds. It’s tricky.
@2 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.
Example 1:
Input: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
Output: [8,9,9,9,0,0,0,1]
The number of nodes in each linked list is in the range [1, 100].
0 <= Node.val <= 9
It is guaranteed that the list represents a number that does not have leading zeros.
Create a new node that traverses when the nodes are adding.
In 10 based system there will be addOns, which will always be 1.
The value of the node will be either
* When l1 != nil && l2 != nil, value = l1.Val + l2.Val + addOn
* When l1 == nil && l2 != nil, value = l2.Val + addOn
* When l1 != nil && l2 == nil, value = l1.Val + addOn
* When both is empty, it’s out of the loop.
After it is out of the loop, if the addOn is not zero, create a new node with value 1 (When the value of the last node is greater than 10, 要進位)
@138 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.
Ex1:
Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]
0 <= n <= 1000
-104 <= Node.val <= 10^4
Node.random is null or is pointing to some node in the linked list.
Since we cannot point to the orginal one, we have to have the address of the new nodes to point to. Create a hashmap that stores the relationship between the old and new (a <-> a’). Use the relations to get temp.Random’ from temp.Random.
13 (temp) -> 13’ (newNode)
|
v
7 (temp.Random)
and since we know who 7 points to (7’), we can know who 13’ should point to, which is 7’, fetched by 7.
func copyRandomList(head *Node) *Node { relations := make(map[*Node]*Node) dummy := new(Node) nNode := dummy temp := head for temp != nil { // Create newNode newNode := &Node{Val: temp.Val} relations[temp] = newNode // Assign to the next node nNode.Next = newNode // Jump to next node. nNode = nNode.Next temp = temp.Next } temp = head nNode = dummy.Next for temp != nil { nNode.Random = relations[temp.Random] temp = temp.Next nNode = nNode.Next } return dummy.Next }
The relations between new and old!
@19 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.
Example1:
Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]
Example2:
Input: head = [1,2,3,4,5], n = 5
Output: [2,3,4,5]
The number of nodes in the list is sz.
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
Count the length of the list first.
If n == length, remove the head (return head.Next); else traverse till the previous one, change the next of the previous one to the next of the next node (temp.Next = temp.Next.Next).
func removeNthFromEnd(head *ListNode, n int) *ListNode { listLength := length(head) if n == listLength { return head.Next } prevIdx := listLength - n - 1 var idx int temp := head for temp != nil { if idx == prevIdx { temp.Next = temp.Next.Next } temp = temp.Next idx++ } return head } func length(head *ListNode) int { var count int temp := head for temp != nil { count++ temp = temp.Next } return count }
@141 Linked List Cycle
Given head, the head of a linked list, determine if the linked list has a cycle in it.
There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail’s next pointer is connected to. Note that pos is not passed as a parameter.
Return true if there is a cycle in the linked list. Otherwise, return false.
Example1:
Input: head = [3,2,0,-4], pos = 1
Output: true
Explanation: There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed).
The number of the nodes in the list is in the range [0, 104].
-10^5 <= Node.val <= 10^5
pos is -1 or a valid index in the linked-list.
- Use a set to check if a node already exist => Space: O(n)
func hasCycle(head *ListNode) bool { nodeSet := make(map[*ListNode]struct{}) temp := head var exist bool for temp != nil { if _, exist = nodeSet[temp]; exist { return true } else { nodeSet[temp] = struct{}{} } temp = temp.Next } return false }
- Use fast and slow pointer, if they point to the same node (since if there’s a cycle the fast pointer will catch up with the slow one from the back), has cycle.
func hasCycle(head *ListNode) bool { fast, slow := head, head for fast != nil && fast.Next != nil && slow != nil { fast = fast.Next.Next slow = slow.Next if fast == slow { return true } } return false }
@287 Find the Duplicate Number
Given an array of integers nums containing n + 1 integers where each integer is in the range [1, n] inclusive.
There is only one repeated number in nums, return this repeated number.
You must solve the problem without modifying the array nums and uses only constant extra space.
Example 1:
Input: nums = [1,3,4,2,2]
Output: 2
1 <= n <= 10^5
nums.length == n + 1
1 <= nums[i] <= n
All the integers in nums appear only once except for precisely one integer which appears two or more times.
- (Best Solution) Floyd’s Cycle Detection
func findDuplicate(nums []int) int { var slow, fast int // Find the intersection point of the two pointers for { slow = nums[slow] fast = nums[nums[fast]] if slow == fast { break } } // Find the entrance to the cycle slow = 0 for slow != fast { slow = nums[slow] fast = nums[fast] } return slow }
- Hashset: Return the number if it is already in set => Time: O(n), Space: O(n)
func findDuplicate(nums []int) int { set := make(map[int]struct{}) for i := range nums { if _, ok := set[nums[i]]; ok { return nums[i] } else { set[nums[i]] = struct{}{} } } return 0 }
- Sort and compare the adjacent elements => Time: O(nlogn), Space: O(1)
func findDuplicate(nums []int) int { sort.Ints(nums) for i := 1; i < len(nums); i ++ { if nums[i] == nums[i - 1] { return nums[i] } } return 0 }
Floyd’s Cycle Detection is the best
@146 LRU Cache
Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
Implement the LRUCache class:
LRUCache(int capacity) Initialize the LRU cache with positive size capacity.
int get(int key) Return the value of the key if the key exists, otherwise return -1.
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.
Example 1:
Input
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
Output
[null, null, null, 1, null, -1, null, -1, 3, 4]
Create a data structure that can store the timestamp of the keys.
- A queue that removes the head when full.
- A doubly linked list.
Get(): If key is in cache, update timestamp (For doubly linked List, move key to head). Else, return -1.
Put(): If key is in cache, update timestamp. If key not in cache and cache is full, remove the least used element (for a queue it is the head, for the doubly linked list it is the back), delete the last element from cache. Create a new timestamp for element.
type LRUCache struct { cache map[int]*list.Element linklist *list.List capacity int } type Obj struct { Key int Value int } func Constructor(capacity int) LRUCache { return LRUCache{ cache: make(map[int]*list.Element, capacity), linklist: list.New(), capacity: capacity, } } func (this *LRUCache) Get(key int) int { if elem, ok := this.cache[key]; ok { // Put the element to head. this.linklist.MoveToFront(elem) return elem.Value.(Obj).Value } return -1 } func (this *LRUCache) Put(key int, value int) { if elem, ok := this.cache[key]; ok { // If the key is in list, remove the old timestamp. this.linklist.Remove(elem) } else if len(this.cache) == this.capacity { // Reached Capacity // Last element of the linked list is the least used. leastUsedEle := this.linklist.Back() // Remove the last element from the list. this.linklist.Remove(leastUsedEle) // Remove the last element from the map. delete(this.cache, leastUsedEle.Value.(Obj).Key) } // Create a new obj newObj := Obj{ Key: key, Value: value, } newEle := this.linklist.PushFront(newObj) // New Obj is the recently used, put to head. this.cache[key] = newEle return }
We have list in Go Standard Library. It updates the node by directly updating the Next, and Prev pointer.
MoveToFront: O(1)
Remove:O(1)
PushFront: O(1)
@226 Invert Binary Tree
Given the root of a binary tree, invert the tree, and return its root.
Example 1:
Input: root = [4,2,7,1,3,6,9]
Output: [4,7,2,9,6,3,1]
- The number of nodes in the tree is in the range [0, 100].
- -100 <= Node.val <= 100
Rotate recursively. Observe the base case, where the recursive function should end.
Base case: When node is nil.
Rotate left and right.
func invertTree(root *TreeNode) *TreeNode { // If node is nil, return nil. Else rotate left and right. if root != nil { root.Left, root.Right = invertTree(root.Right), invertTree(root.Left) return root } return nil }
@104 Maximum Depth of Binary Tree
Given the root of a binary tree, return its maximum depth.
A binary tree’s maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: 3
The number of nodes in the tree is in the range [0, 104].
-100 <= Node.val <= 100
Add height recursively. Add 1 at every layer when node is not nil. Compare the maxDepth(node.Left) and maxDepth(node.Right) before adding it to the current height.
Base Case: When node is nil, then maxDepth(nil) = 0
func maxDepth(root *TreeNode) int { // DFS. if root == nil { return 0 } return 1 + max(maxDepth(root.Left), maxDepth(root.Right)) }
Always think of the base case first.
@543 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.
The number of nodes in the tree is in the range [1, 10^4].
-100 <= Node.val <= 100
Compare the diameter and the local diameter recursively.
Diameter = CurrMaxLeftHeight, CurrMaxRightHeight
Return max(currMaxLeftHeight, currMaxRightHeight) to the parent. The parent will use it as either it’s currMaxLeftHeight or currMaxRightHeight.
func diameterOfBinaryTree(root *TreeNode) int { var diameter int // dfs finds the maxDepth of each node and compares the local diameter with the stored diameter. // Local diameter is the leftHeight + rightHeight. // Only add 1 to the height when a leaf exist. var dfs func(node *TreeNode) int dfs = func(node *TreeNode) int { var leftHeight, rightHeight int if node.Left != nil { leftHeight = dfs(node.Left) + 1 } if node.Right != nil { rightHeight = dfs(node.Right) + 1 } diameter = max(diameter, leftHeight + rightHeight) return max(leftHeight, rightHeight) } dfs(root) return diameter }
Diameter = leftHeight + rightHeight
@110 Balanced Binary Tree
Given a binary tree, determine if it is
height-balanced.
Ex1:
Input: root = [3,9,20,null,null,15,7]
Output: true
A height-balanced binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one.
The number of nodes in the tree is in the range [0, 5000].
-10^4 <= Node.val <= 10^4
Do the following recursively until the base case: Check the height difference every layer. If height difference is smaller than 2, the node is balanced. Return the height of the subroot to the next layer, and compare again.
func isBalanced(root *TreeNode) bool { // Return height, isBalanced var dfs func(node *TreeNode) (int, bool) dfs = func(node *TreeNode) (int, bool) { if node == nil { return 0, true } leftHeight, leftIsBalanced := dfs(node.Left) rightHeight, rightIsBalanced := dfs(node.Right) if leftHeight - rightHeight >= 2 || rightHeight - leftHeight >= 2 { return -1, false } return max(leftHeight, rightHeight) + 1, leftIsBalanced && rightIsBalanced } _, rootBalanced := dfs(root) return rootBalanced }
Depth First Search
@100 Same Tree
Given the roots of two binary trees p and q, write a function to check if they are the same or not.
Two binary trees are considered the same if they are structurally identical, and the nodes have the same value.
Ex1:
Input: p = [1,2,3], q = [1,2,3]
Output: true
Ex2:
Input: p = [1,2], q = [1,null,2]
Output: false
The number of nodes in both trees is in the range [0, 100].
-10^4 <= Node.val <= 10^4
Do the following recursively until the base case: Check if the node value is the same. If the node value is the same, compare the children.
func isSameTree(p *TreeNode, q *TreeNode) bool { if p == nil && q == nil { return true } // In case one of the nodes is nil. if p == nil && q != nil { return false } if p != nil && q == nil { return false } return p.Val == q.Val && isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right) }
Depth First Search