Coding Challenges Reference
Kip Landergren
(Updated: )
Contents
- How to Solve Problems
- Computer Science
- Language Specific Techniques
- How to Improve
- Frequently Asked Questions (FAQ)
- Additional Research
- Resources
-
leetcode
- README.md
- problems/00001-two-sum/README.md
- problems/00001-two-sum/golang/solution.go
- problems/00001-two-sum/typescript/explained-solution-01.ts
- problems/00001-two-sum/typescript/explained-solution-02-5.ts
- problems/00001-two-sum/typescript/explained-solution-02.ts
- problems/00001-two-sum/typescript/explained-solution-03.ts
- problems/00001-two-sum/typescript/explained-solution-04.ts
- problems/00001-two-sum/typescript/solution.ts
- problems/00002-add-two-numbers/README.md
- problems/00002-add-two-numbers/golang/solution.go
- problems/00002-add-two-numbers/typescript/solution.ts
- problems/00003-longest-substring-without-repeating-characters/README.md
- problems/00003-longest-substring-without-repeating-characters/typescript/solution.ts
- problems/00004-median-of-two-sorted-arrays/README.md
- problems/00004-median-of-two-sorted-arrays/typescript/solution.ts
- problems/00005-longest-palindromic-substring/README.md
- problems/00005-longest-palindromic-substring/typescript/solution.ts
- problems/00007-reverse-integer/README.md
- problems/00007-reverse-integer/typescript/solution.ts
- problems/00008-string-to-integer-atoi/README.md
- problems/00008-string-to-integer-atoi/typescript/solution.ts
- problems/00011-container-with-most-water/README.md
- problems/00011-container-with-most-water/golang/solution.go
- problems/00011-container-with-most-water/typescript/solution.ts
- problems/00013-roman-to-integer/README.md
- problems/00013-roman-to-integer/golang/solution.go
- problems/00013-roman-to-integer/typescript/solution.ts
- problems/00014-longest-common-prefix/README.md
- problems/00014-longest-common-prefix/typescript/solution.ts
- problems/00015-3sum/README.md
- problems/00015-3sum/typescript/solution.ts
- problems/00017-letter-combinations-of-a-phone-number/README.md
- problems/00017-letter-combinations-of-a-phone-number/typescript/solution.ts
- problems/00019-remove-nth-node-from-end-of-list/README.md
- problems/00019-remove-nth-node-from-end-of-list/golang/solution.go
- problems/00020-valid-parentheses/README.md
- problems/00020-valid-parentheses/golang/solution.go
- problems/00020-valid-parentheses/typescript/solution.ts
- problems/00021-merge-two-sorted-lists/README.md
- problems/00021-merge-two-sorted-lists/golang/solution.go
- problems/00022-generate-parentheses/README.md
- problems/00022-generate-parentheses/golang/solution-dynamic-programming.go
- problems/00022-generate-parentheses/golang/solution.go
- problems/00022-generate-parentheses/typescript/solution.ts
- problems/00023-merge-k-sorted-lists/README.md
- problems/00023-merge-k-sorted-lists/golang/solution-1-record-slice.go
- problems/00023-merge-k-sorted-lists/golang/solution-2-int-ptr.go
- problems/00023-merge-k-sorted-lists/golang/solution-3-recursive.go
- problems/00023-merge-k-sorted-lists/golang/solution-4-iterative.go
- problems/00026-remove-duplicates-from-sorted-array/README.md
- problems/00026-remove-duplicates-from-sorted-array/golang/solution.go
- problems/00033-search-in-rotated-sorted-array/README.md
- problems/00033-search-in-rotated-sorted-array/golang/solution.go
- problems/00033-search-in-rotated-sorted-array/typescript/solution.ts
- problems/00036-valid-sudoku/README.md
- problems/00036-valid-sudoku/golang/solution.go
- problems/00036-valid-sudoku/typescript/solution.ts
- problems/00042-trapping-rain-water/README.md
- problems/00042-trapping-rain-water/typescript/solution.ts
- problems/00049-group-anagrams/README.md
- problems/00049-group-anagrams/golang/solution.go
- problems/00049-group-anagrams/typescript/solution.ts
- problems/00051-n-queens/README.md
- problems/00051-n-queens/typescript/solution-2.ts
- problems/00051-n-queens/typescript/solution.ts
- problems/00055-jump-game/README.md
- problems/00055-jump-game/golang/solution.go
- problems/00055-jump-game/typescript/solution.ts
- problems/00056-merge-intervals/README.md
- problems/00056-merge-intervals/golang/solution.go
- problems/00056-merge-intervals/typescript/solution.ts
- problems/00059-spiral-matrix-ii/README.md
- problems/00059-spiral-matrix-ii/typescript/solution.ts
- problems/00061-rotate-list/README.md
- problems/00061-rotate-list/typescript/solution-1.ts
- problems/00061-rotate-list/typescript/solution-2.ts
- problems/00064-minimum-path-sum/README.md
- problems/00064-minimum-path-sum/golang/solution.go
- problems/00064-minimum-path-sum/typescript/solution.ts
- problems/00074-search-a-2d-matrix/README.md
- problems/00074-search-a-2d-matrix/golang/solution.go
- problems/00074-search-a-2d-matrix/typescript/solution.ts
- problems/00078-subsets/README.md
- problems/00078-subsets/typescript/solution.ts
- problems/00079-word-search/README.md
- problems/00079-word-search/golang/solution.go
- problems/00079-word-search/typescript/solution.ts
- problems/00084-largest-rectangle-in-histogram/README.md
- problems/00084-largest-rectangle-in-histogram/typescript/solution.ts
- problems/00090-subsets-ii/README.md
- problems/00090-subsets-ii/typescript/solution.ts
- problems/00101-symmetric-tree/README.md
- problems/00101-symmetric-tree/golang/solution.go
- problems/00101-symmetric-tree/typescript/solution.ts
- problems/00102-binary-tree-level-order-traversal/README.md
- problems/00102-binary-tree-level-order-traversal/golang/solution.go
- problems/00102-binary-tree-level-order-traversal/typescript/solution.ts
- problems/00104-maximum-depth-of-binary-tree/README.md
- problems/00104-maximum-depth-of-binary-tree/golang/solution.go
- problems/00104-maximum-depth-of-binary-tree/typescript/solution.ts
- problems/00120-triangle/README.md
- problems/00120-triangle/typescript/solution-1.ts
- problems/00120-triangle/typescript/solution-2.ts
- problems/00128-longest-consecutive-sequence/README.md
- problems/00128-longest-consecutive-sequence/golang/solution.go
- problems/00128-longest-consecutive-sequence/typescript/solution.ts
- problems/00130-surrounded-regions/README.md
- problems/00130-surrounded-regions/typescript/solution-1.ts
- problems/00130-surrounded-regions/typescript/solution-2.ts
- problems/00131-palindrome-partitioning/README.md
- problems/00131-palindrome-partitioning/typescript/solution.ts
- problems/00146-lru-cache/README.md
- problems/00146-lru-cache/typescript/solution-doubly-linked-list.ts
- problems/00146-lru-cache/typescript/solution-simplified-map.ts
- problems/00151-reverse-words-in-a-string/README.md
- problems/00151-reverse-words-in-a-string/typescript/solution.ts
- problems/00153-find-minimum-in-rotated-sorted-array/README.md
- problems/00153-find-minimum-in-rotated-sorted-array/golang/solution.go
- problems/00153-find-minimum-in-rotated-sorted-array/typescript/solution.ts
- problems/00155-min-stack/README.md
- problems/00155-min-stack/golang/solution.go
- problems/00155-min-stack/typescript/solution.ts
- problems/00167-two-sum-ii---input-array-is-sorted/README.md
- problems/00167-two-sum-ii---input-array-is-sorted/typescript/solution.ts
- problems/00199-binary-tree-right-side-view/README.md
- problems/00199-binary-tree-right-side-view/golang/solution.go
- problems/00199-binary-tree-right-side-view/typescript/solution.ts
- problems/00207-course-schedule/README.md
- problems/00207-course-schedule/typescript/solution.ts
- problems/00208-implement-trie-prefix-trie/README.md
- problems/00208-implement-trie-prefix-trie/typescript/solution-with-map.ts
- problems/00208-implement-trie-prefix-trie/typescript/solution-with-object.ts
- problems/00217-contains-duplicate/README.md
- problems/00217-contains-duplicate/golang/solution.go
- problems/00217-contains-duplicate/typescript/solution.ts
- problems/00226-invert-binary-tree/README.md
- problems/00226-invert-binary-tree/golang/solution.go
- problems/00226-invert-binary-tree/typescript/solution.ts
- problems/00234-palindrome-linked-list/README.md
- problems/00234-palindrome-linked-list/typescript/solution.ts
- problems/00238-product-of-array-except-self/README.md
- problems/00238-product-of-array-except-self/typescript/solution.ts
- problems/00242-valid-anagram/README.md
- problems/00242-valid-anagram/golang/solution.go
- problems/00242-valid-anagram/typescript/solution.ts
- problems/00322-coin-change/README.md
- problems/00322-coin-change/typescript/solution.ts
- problems/00347-top-k-frequent-elements/README.md
- problems/00347-top-k-frequent-elements/typescript/solution.ts
- problems/00383-ransom-note/README.md
- problems/00383-ransom-note/typescript/solution.ts
- problems/00412-fizz-buzz/README.md
- problems/00412-fizz-buzz/typescript/solution.ts
- problems/00665-non-decreasing-array/README.md
- problems/00665-non-decreasing-array/typescript/solution.ts
- problems/00704-binary-search/README.md
- problems/00704-binary-search/golang/solution.go
- problems/00704-binary-search/typescript/solution.ts
- problems/00739-daily-temperatures/README.md
- problems/00739-daily-temperatures/golang/solution-original.go
- problems/00739-daily-temperatures/golang/solution-stack.go
- problems/00739-daily-temperatures/typescript/solution.ts
- problems/00875-koko-eating-bananas/README.md
- problems/00875-koko-eating-bananas/golang/solution.go
- problems/00875-koko-eating-bananas/typescript/solution.ts
- problems/00876-middle-of-the-linked-list/README.md
- problems/00876-middle-of-the-linked-list/typescript/solution.ts
- problems/00981-time-based-key-value-store/README.md
- problems/00981-time-based-key-value-store/golang/solution-1.go
- problems/00981-time-based-key-value-store/golang/solution-2.go
- problems/00981-time-based-key-value-store/typescript/solution.ts
- problems/01337-the-k-weakest-rows-in-a-matrix/README.md
- problems/01337-the-k-weakest-rows-in-a-matrix/golang/solution.go
- problems/01337-the-k-weakest-rows-in-a-matrix/typescript/solution.ts
- problems/01342-number-of-steps-to-reduce-a-number-to-zero/README.md
- problems/01342-number-of-steps-to-reduce-a-number-to-zero/golang/solution.go
- problems/01342-number-of-steps-to-reduce-a-number-to-zero/typescript/solution.ts
- problems/01672-richest-customer-wealth/README.md
- problems/01672-richest-customer-wealth/typescript/solution.ts
- problems/02281-sum-of-total-strength-of-wizards/README.md
- problems/02281-sum-of-total-strength-of-wizards/golang/attempt-1.go
- problems/tsconfig.json
- utilities/edits/edits.go
- utilities/edits/go.mod
- utilities/leet/go.mod
- utilities/leet/leet.go
How to Solve Problems
General Strategy
- read the problem:
- use your intuition: what does the problem—and possible solution—remind you of?
- binary search?
- dfs?
- greedy?
- pick up clues / look for hints about the problem space:
- what do the examples tell you? e.g. are they sorted? are negative values used?
- what do the constraints tell you? e.g. what time complexity should you be shooting for? can you ignore scale because some parameter range is capped?
- use your intuition: what does the problem—and possible solution—remind you of?
- manually work through examples:
- physically draw / type out what you think is happening
- develop a solution strategy:
- establish the tools/topics to use (guessing is fine!)
- verbalize approach
- list assumptions
- start with a naive / brute force strategy and see where that gets you
- what to do if you are stuck:
- take one piece of information and come up with as many solution approaches as possible
- implementation:
- create your own test cases (e.g. empty array, array with 1 element, etc) to verify that you understand it
Specific Strategies for Problem Types
Arrays
Remember:
- all index-based array access is O(1)
- looping through it multiple times is still just O(n)! as long as you do not loop for every n
- should I sort it?
- should I use a sliding window / two pointer approach here?
- what information can I derive from index? from value?
Solution process:
- do the first or last values tell me anything? this may be the starting point for a dynamic programming solution
- if we sort the input does the problem still make sense? is it now easier? harder?
Binary Search
Common criteria:
- has sorted array
- must be done in O(log n)
Solution process:
- binary search does not need to be of an array—it can be of a value within a range or criteria
- visualize items on a number line and ask yourself: “what region do I want to be in?”
- recognize that binary search does not necessarily have to be about value equality as its criteria—it could be:
- whether the target is within range
- has properties relative to the mid index
- is the maximum value not exceeding some value
- or something else
- the right and left pointers only move inward. because of the criteria check on midpoint, you necessarily will not “miss” any potential value to the left of the left pointer or to the right of the right pointer. e.g. the pointers always move in one direction, and that is toward the target. they do not move outward or change direction.
Be aware of progamming languages that can overflow. Prefer equivalent:
mid = left + Math.floor((r-l)/2)
over:
mid = (r+l)/2
Style recommendation:
- add conditions for explicit cases and leave bare else clause
Linked Lists
Common criteria:
- has a linked list / list node
Solution process:
- keep track of the head in a separate variable
- keep track of the previous node
- use a while loop to iterate
Recursive Problems
Common criteria:
- non-zero options to choose at each juncture (e.g. “heads” or “tails”), creating branching paths in a tree-like execution structure
- generating permutations
Solution process:
- determine branching criteria
- determine the state necessary to fulfil that branching criteria
- create a
helper
function - take a “recursive leap of faith” that
helper
will work (before implementation) - establish base cases
- implement
helper
When implementing helper
:
- determine state variable
- find the subproblem (greenbox) of the previous function call output; this is the recursive relation
- recursive leap of faith (trust that your function will “just work” or “do what it says on the tin”), and implement remaining logic
- add base case / initial condition to ensure function will stop; this is the inductive step
Dynamic Programming Problems
Common criteria:
- a function that describes state transitions
Solution process:
- establish state
- establish transition function / transition criteria
- convert that transition function to read from a memo (can literally replace function calls with memo lookups)
- create a
helper
function - implement
helper
taking state and memo as params with body using transition function and memo - establish base cases, filling memo
Breadth First Search (BFS) / Depth First Search (DFS)
Graph Problems
Terminology:
node (vertex) | |
edge | |
graph | |
adjacency (adjacent, neighboring) | |
path | |
cycle | path that starts and ends at same node |
connected | path exists between; connectivity is transitive |
connected component | maximal (node must be included if it can be) group of nodes all connected to each other |
directed | |
undirected | |
reach (reachability) | |
strongly connected | each node can reach each other |
strongly connected component | |
weighted / unweighted | |
simple | |
connected graph | only one connected component |
complete graph | |
acyclic graph (forest) |
Storing graphs:
- adjacency matrix (2d array
adj[a][b] == true
if there is edge between
Sorting
n log n
Specific Techniques
Two Pointers
In some cases you can adjust both pointers at once. See Problem 234.
Sliding Window
OK, the idea with a sliding window is that you keep track of the start and end indices and then data about what you have seen in a lookup (e.g. with a map or set). Sliding window can be used most often with strings and arrays.
Performance Considerations
- do not pass copies of your lookup, prefer using an external (non-local to function) lookup
- do an optimization pass for every
for
loop you write—does this need to exist?
Computer Science
Algorithms
- BFS/DFS
- sorting
- binary search
- greedy
- topological sort
- union find
- lowest common ancestor
- shortest paths
- divide and conquer
- max flow
Data Structures
Queue
Type | Linear Data Structure |
Access | FIFO |
space | O(n) | O(n) |
search | O(n) | O(n) |
insert | O(1) | O(1) |
delete (dequeue) | O(1) | O(1) |
Stack
Type | Linear Data Structure |
Access | LIFO |
- array
- hashmap/hashset
- heap
- linked list
- stack
- binary search tree
- fenwick / segment tree
- queue
- trie
- minimum spanning tree
- strongly connected components
- frequency array
Math
- combinatronics and probability
- game theory
- knuth-morris-pratt
- modular arithmetic
- convex hull
- sqrt decomposition
- fast fourier transform
Programming Techniques
- recursion
- dynamic programming
- prefix sums
- brute force
- prime factorization
- string hashing
- two pointers
- bitwise operations
Time Complexity
- big o notation always cares about the bigger value
- look for the upper bound in a problem’s execution (e.g. drawing the call hierarchy)
Language Specific Techniques
Go (golang)
- make use of the zero value: prefer
var count int
tocount := 0
- if you know the length of your data beforehand, allocate your slice with that capacity
Binary Search
TypeScript
- can you improve the existing types?
- if you just need a test for presence, consider
Set
rather thanMap
- arrays are dynamically sized: you can index into them directly
How to Improve
slow brain - develop ability to create insights, which are new solutions to problems.
fast brain - use your intuition for pattern matching against old solutions: given a problem, what has worked before? will it also work here? use your gut feeling:
To develop intuition:
- look at hard problems
- read solutions
- map “problem components” (e.g. divide array into contiguous groups, future groups are independent of past groups, cost = max - sum) to “solution components” (e.g. dynamic programming, running max/sum, range max/sum, O(n3))
- include questions you should ask yourself:
- why is N so small?
- what does that allow us to do?
- what if K = 1 or K = 2 ?
- what info is relevant to DP state?
- be able to explain conclusions (e.g. “DP works because groups are independent of past groups, so past groups aren’t relevant to state”)
- learn these and create the mapping in plain english
- try to implement solution yourself, then prefer just spending more time thinking about it, then prefer implementing it from existing solution
- always about creating mental mapping of problem components to solution components
Intuition test strategy:
- read problem
- get solution ideas from reading (not solving)
- read solution to see if correct
- if correct, implement solution yourself
General learning method:
- start w/ big picture
- for all details:
- pick a detail and learn it
- understand it in context (think outside the box)
- understant it by itself (care, invent, read details)
- reinforce it (invent, practice, explain, explore (ask yourself dumb questions, change one part of problem, see if another solution also applies, question intuitive assumptions, ask “why?”))
- repeat
Get better insights:
- work on very hard problems
Convergent thinking strategy:
- pattern recognition
- finding common themes/ideas
- relating similar problems
Divergent (creative) thinking strategy:
-
take one piece of information and come up with as many solutions as possible
-
generate lots of ideas
-
filter good ones
-
apply good ones
-
reinforce idea generation
-
take a guess at the state required, then list out the values for a simple example
-
try bfs / dfs; if fails try greedy
-
for linked lists: understand that you are working with limited data
-
if you have a subarray question, it will likely involve prefix sums
-
learn monotonic stacks
-
topological sort useful for detecting cycle in directed graph; makes life easier when using dynamic programming on a directed acyclic graph; checking for directed cycles
-
trie: useful for strings and maximum pair xor stuff
Frequently Asked Questions (FAQ)
What is the difference between recursion and dynamic programming?
(working answer) dynamic programming is recursion with memoization
There is not really a hard and fast difference. I think of it as the following:
- dynamic programming breaks a problem down into discrete sub-problems that compose the solution to the current problem, often with the ability to memoize the results
- recursion, which is used in dynamic programming, generates solutions by possibly iterating over the entire space of possibilities before beginning to construct the solution
I guess too I think of DP when there is a purely mathematical function for expressing the transition from one state to another. When the transition needs to iterate over a range, I think of it more as recursive.
Additional Research
Google Doc of topic explanation. Recommends searching “Topic [X] tutorial”.
- S Tier
- BFS/DFS
- sorting
- A Tier
- binary search
- dynamic programming
- greedy
- hashmap/hashset
- heap
- linked list
- prefix sums
- stack
- topological sort
- B Tier
- binary search tree
- brute force
- fenwick / segment tree
- prime factorization
- queue
- recursion
- string hashing
- two pointers
- union find
- C Tier
- bitwise operations
- combinatronics and probability
- game theory
- knuth-morris-pratt
- lowest common ancestor
- modular arithmetic
- shortest paths
- trie
- D Tier
- convex hull
- divide and conquer
- minimum spanning tree
- sqrt decomposition
- strongly connected components
- F Tier
- fast fourier transform
- max flow
- other
- frequency array
Resources
- NeetCode
- NeetCode (YouTube) - current Googler; explains leetcode problems and other interview topics
- Colin Galen (Youtube) - competetive programmer; explains data structures, algorithms, and competetive programming concepts; computer science topics are very focused on application rather than theory
- ByteByteGo (YouTube) - general technical interview prep content
- LeetCode - technical interview problems
leetcode
README.md
# leetcode
solutions to leetcode problems
## how to solve problems
- generate problem scaffold via `leet`
- fill out `README.md`:
- include initial thoughts
- sketch / diagram the problem, and possible solutions
- attempt to solve, stopping only when:
- you have lost patience
- 90 minutes have elapsed
- on completion, fill out `README.md`
- note `comp.t` and `comp.s` and explain why
- consider adding total time spent
- add tags / search terms (e.g. “easy dynamic programming”
- update whether `[OPEN]` or `[SOLVED]`
### generate problem scaffold
```
$ ./bin/leet --language typescript --number 234 --title 'Palindrome Linked List' --url 'https://leetcode.com/problems/palindrome-linked-list/'
```
## `leet` tool
build and install:
```
$ go build -o ./bin utilities/leet/leet.go
```
problems/00001-two-sum/README.md
# [SOLVED] 1 - Two Sum
[Two Sum](https://leetcode.com/problems/two-sum/)
## notes
### first thoughts
- worst case must check every element
- array is not sorted
- numbers may be duplicated
- guaranteed only one solution
- store complement index in lookup
- confusing at first what exactly to store: `complement` or `val`?
## summary
- comp.t: O(n). worst case we must check every element once
- comp.s: O(n). our lookup stores `n` values
- trick is to think: what information am I given? what information can I derive
from that? how does that help me get the answer?
## tags
easy array
problems/00001-two-sum/golang/solution.go
package main
import "fmt"
// [2,7,11,15], target = 9
func twoSum(nums []int, target int) []int {
// we want to find the indices of which numbers add up to the target
lookup := make(map[int]int)
ind1 := -1
ind2 := -1
for i, val := range nums {
complement := target - val
if complementIndex, ok := lookup[complement]; ok {
// we have previously passed by our complement; grab it
ind1 = complementIndex
ind2 = i
break
} else {
// haven't seen it yet, store the index
lookup[val] = i
}
}
return []int{ind1, ind2}
}
func main() {
fmt.Println(twoSum([]int{2, 7, 11, 15}, 9))
}
problems/00001-two-sum/typescript/explained-solution-01.ts
function twoSum(nums: number[], target: number): number[] {
let indices = [-1, -1];
outer: for (let i = 0; i < nums.length; i++) {
for (let j = 0; j < nums.length; j++) {
if (i === j) {
/* cannot use same element twice */
continue;
}
/* we have a match */
if (nums[i] + nums[j] === target) {
indices = [i, j];
break outer;
}
}
}
return indices;
}
console.log(twoSum([2, 7, 11, 15], 9));
console.log(twoSum([3, 2, 4], 6));
console.log(twoSum([3, 3], 6));
export {};
problems/00001-two-sum/typescript/explained-solution-02-5.ts
/* uses insight from nums[j] === target - nums[i] */
/* which can be rewritten as: complement === target - nums[i] */
function twoSum(nums: number[], target: number): number[] {
let indices = [-1, -1];
outer: for (let i = 0; i < nums.length - 1; i++) {
const complement = target - nums[i];
/* search remaining for complement index */
for (let j = i + 1; j < nums.length; j++) {
/* we have a match */
if (nums[j] === complement) {
indices = [i, j];
break outer;
}
}
}
return indices;
}
console.log(twoSum([2, 7, 11, 15], 9));
console.log(twoSum([3, 2, 4], 6));
console.log(twoSum([3, 3], 6));
export {};
problems/00001-two-sum/typescript/explained-solution-02.ts
function twoSum(nums: number[], target: number): number[] {
let indices = [-1, -1];
outer: for (let i = 0; i < nums.length - 1; i++) {
for (let j = i + 1; j < nums.length; j++) {
/* we have a match */
if (nums[i] + nums[j] === target) {
indices = [i, j];
break outer;
}
}
}
return indices;
}
console.log(twoSum([2, 7, 11, 15], 9));
console.log(twoSum([3, 2, 4], 6));
console.log(twoSum([3, 3], 6));
export {};
problems/00001-two-sum/typescript/explained-solution-03.ts
/* to improve: we know necessarily that we have to read all values in worst
case, so this should clue you into using a for loop */
/* we want to know: */
/* 1) does complement exist in nums? (want O(1) check) */
/* 2) if it does, what is its index */
/* doing a second (or third) pass through `nums` is not a problem: O(2n) is still O(n) */
function twoSum(nums: number[], target: number): number[] {
let indices = [-1, -1];
/* build lookup */
const lookup = new Map<number, number>();
for (let i = 0; i < nums.length; i++) {
lookup.set(nums[i], i);
}
/* note: could use reduce, but I encourage use of for loops for their
flexibility should you need to break out / change logic quickly */
/* find indices */
for (let i = 0; i < nums.length - 1; i++) {
const complement = target - nums[i];
/* search lookup for complement index */
const maybeComplementIndex = lookup.get(complement);
if (maybeComplementIndex !== undefined && maybeComplementIndex !== i) {
/* we found it! */
indices = [i, maybeComplementIndex];
break;
}
}
return indices;
}
console.log(twoSum([2, 7, 11, 15], 9));
console.log(twoSum([3, 2, 4], 6));
console.log(twoSum([3, 3], 6));
export {};
problems/00001-two-sum/typescript/explained-solution-04.ts
/* build as you go */
function twoSum(nums: number[], target: number): number[] {
let indices = [-1, -1];
const lookup = new Map<number, number>();
/* note the change in i bounds here: we must iterate through all elements of nums */
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
const maybeComplementIndex = lookup.get(complement);
if (maybeComplementIndex === undefined) {
/* it could be some future value's complement */
lookup.set(nums[i], i);
continue;
}
/* we found it. no need to compare whether index is same because it is
impossible to occur */
indices = [maybeComplementIndex, i];
break;
}
return indices;
}
console.log(twoSum([2, 7, 11, 15], 9));
console.log(twoSum([3, 2, 4], 6));
console.log(twoSum([3, 3], 6));
export {};
problems/00001-two-sum/typescript/solution.ts
/* [2,7,11,15], target = 9 */
function twoSum(nums: number[], target: number): number[] {
const lookup = new Map<number, number>();
/* 0 start: val=2, c=7, lookup={} */
/* 0 end: val=2, c=7, lookup={2: 0} */
/* 1 start: val=7, c=2, lookup={2:0} */
for (let i = 0; i < nums.length; i++) {
const val = nums[i];
const desiredComplement = target - val;
const seenComplement = lookup.get(desiredComplement);
/* have we seen the complement that would produce target before? */
if (seenComplement === undefined) {
/* we have not seen its complement, so store the value */
lookup.set(val, i);
} else {
return [seenComplement, i];
}
}
return [-1, -1];
}
console.log(twoSum([2, 7, 11, 15], 9));
export {};
problems/00002-add-two-numbers/README.md
# [SOLVED] 2 - Add Two Numbers
[Add Two Numbers](https://leetcode.com/problems/add-two-numbers/)
## notes
### first thoughts
- no guarantee that `l1` and `l2` are the same length. no matter what though,
the ones place, tens, place, etc will always be consistent
- add the values of each, have some carry forward, and advance both until both
nil
- having trouble getting clean logic for iteration to create `sum`
## summary
- had a very hard time getting the order of operations correct for attaching the
`Next` pointer correctly
- did not correctly handle the last digit (when `carryForward` is non-zero)
- looked at solutions after submission and cleaned up for loop condition to
include `carryForward != 0`
- golang implementation at 44% cpu / 40% memory but not seeing major
implementation differences in solutions
- comp.t: O(m + n): we need to walk both `l1` and `l2`
- comp.s: O(max(m, n)): our `sum` is the length of our longest input + 1
## tags
medium linked list
problems/00002-add-two-numbers/golang/solution.go
package main
import (
"encoding/json"
"fmt"
"log"
)
type ListNode struct {
Val int
Next *ListNode
}
func (ln *ListNode) String() string {
return fmt.Sprintf("%v, %v", ln.Val, ln.Next)
}
func ListNodeFromVals(vals []int) *ListNode {
holder := &ListNode{}
head := holder
for _, val := range vals {
head.Next = &ListNode{Val: val}
head = head.Next
}
return holder.Next
}
func ListNodeFrom(s string) *ListNode {
var vals []int
err := json.Unmarshal([]byte(s), &vals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
return ListNodeFromVals(vals)
}
func ListNodesFrom(s string) []*ListNode {
var nodeVals [][]int
err := json.Unmarshal([]byte(s), &nodeVals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
var output []*ListNode
for _, nodeVal := range nodeVals {
output = append(output, ListNodeFromVals(nodeVal))
}
return output
}
// l1 = [2,4,3], l2 = [5,6,4]
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
currDigit1 := l1
currDigit2 := l2
var sum *ListNode
var prevSumDigit *ListNode
carryForward := 0
for currDigit1 != nil || currDigit2 != nil || carryForward != 0 {
// calculate it
curr1Val := 0
curr2Val := 0
if currDigit1 != nil {
curr1Val = currDigit1.Val
currDigit1 = currDigit1.Next
}
if currDigit2 != nil {
curr2Val = currDigit2.Val
currDigit2 = currDigit2.Next
}
total := curr1Val + curr2Val + carryForward
val := total % 10
carryForward = total / 10
// we have the ability to add a new link. create it.
next := &ListNode{Val: val}
// add it
if prevSumDigit == nil {
// special case: we want to keep a pointer `sum` to the head of the list,
// so point it to the first `next` we encounter
sum = next
} else {
// otherwise update the previous digits pointer to the one we are working with
prevSumDigit.Next = next
}
prevSumDigit = next
}
return sum
}
func main() {
fmt.Println(addTwoNumbers(ListNodeFrom("[2,4,3]"), ListNodeFrom("[5,6,4]")))
fmt.Println(addTwoNumbers(ListNodeFrom("[0]"), ListNodeFrom("[0]")))
fmt.Println(addTwoNumbers(ListNodeFrom("[9,9,9,9,9,9,9]"), ListNodeFrom("[9,9,9,9]")))
}
problems/00002-add-two-numbers/typescript/solution.ts
class ListNode {
val: number;
next: ListNode | null;
constructor(val?: number, next?: ListNode | null) {
this.val = val === undefined ? 0 : val;
this.next = next === undefined ? null : next;
}
}
function addTwoNumbers(
l1: ListNode | null,
l2: ListNode | null
): ListNode | null {
const sum = new ListNode();
let prev = null;
let currDigit1 = l1;
let currDigit2 = l2;
let carryForward = 0;
while (currDigit1 || currDigit2 || carryForward != 0) {
const curr1Val = currDigit1?.val ?? 0;
const curr2Val = currDigit2?.val ?? 0;
const total = curr1Val + curr2Val + carryForward;
const val = total % 10;
carryForward = Math.floor(total / 10);
if (prev === null) {
/* special case */
sum.val = val;
prev = sum;
} else {
const next = new ListNode(val);
prev.next = next;
prev = next;
}
currDigit1 = currDigit1?.next ?? null;
currDigit2 = currDigit2?.next ?? null;
}
return sum;
}
export {};
problems/00003-longest-substring-without-repeating-characters/README.md
# [SOLVED] 3 - Longest Substring Without Repeating Characters
[Longest Substring Without Repeating Characters](https://leetcode.com/problems/longest-substring-without-repeating-characters/)
## notes
## summary
## tags
medium string
problems/00003-longest-substring-without-repeating-characters/typescript/solution.ts
function lengthOfLongestSubstring(s: string): number {
if (s.length === 0) {
return 0;
}
if (s.length === 1) {
return 1;
}
/* the length of our unique substring is the distance between our windowStart and windowEnd indices */
const lookup = new Map<string, number>();
let maxLength = 0;
let windowStart = 0;
for (let windowEnd = 0; windowEnd < s.length; windowEnd++) {
const char = s[windowEnd];
const maybeStart = lookup.get(char) ?? -1;
if (windowStart <= maybeStart) {
/* we have seen it. move window_start to it + 1 */
windowStart = maybeStart + 1;
}
const subStringLength = windowEnd - windowStart + 1;
if (maxLength < subStringLength) {
maxLength = subStringLength;
}
lookup.set(char, windowEnd);
}
return maxLength;
}
export {};
problems/00004-median-of-two-sorted-arrays/README.md
# [SOLVED] 4 - Median of Two Sorted Arrays
[Median of Two Sorted Arrays](https://leetcode.com/problems/median-of-two-sorted-arrays/)
## notes
## summary
- comp.t: O(m+n)
- comp.s: O(m+n)
## tags
hard array
problems/00004-median-of-two-sorted-arrays/typescript/solution.ts
function findMedianSortedArrays(nums1: number[], nums2: number[]): number {
if (nums1.length === 0 && nums2.length === 0) {
return 0;
}
let median = 0;
let i = 0;
let j = 0;
const merged = [];
while (i < nums1.length || j < nums2.length) {
/* i = 0, 1, 1 */
/* j = 0, 0, 1 */
let n1 = nums1[i]; /* 1, 3, 3 */
let n2 = nums2[j]; /* 2, 2, u */
let added = 0;
if (n1 === undefined) {
added = n2;
j++;
} else if (n2 === undefined) {
added = n1;
i++;
} else {
added = n1 <= n2 ? n1 : n2; /* 1, 2, 3 */
if (n1 <= n2) {
i++;
} else {
j++;
}
}
merged.push(added); /* [1, 2, 3] */
}
if (merged.length % 2 === 0) {
const midIndex = merged.length / 2;
median = (merged[midIndex - 1] + merged[midIndex]) / 2;
} else {
median = merged[Math.floor(merged.length / 2)];
}
return median;
}
export {};
problems/00005-longest-palindromic-substring/README.md
# [SOLVED] 5 - Longest Palindromic Substring
[Longest Palindromic Substring](https://leetcode.com/problems/longest-palindromic-substring/)
## notes
## summary
## tags
medium array
problems/00005-longest-palindromic-substring/typescript/solution.ts
function longestPalindrome(s: string): string {
if (s.length === 1) {
return s;
}
if (s.length === 2) {
if (s[0] === s[1]) {
return s;
} else {
return s[0];
}
}
let longest = "";
/* the idea here is that a palindrome starts from a "seed" of either two
consecutive same letters (e.g. "aa") or three letters where the first and
third are the same (e.g. "aba"). we want to find all of these seeds within
the string and then, for each, "grow" the potential palindrome start and end
indices by 1 until we have determined the maximum length */
const seedIndices = [];
for (let i = 0, j = 1, k = 2; k < s.length; i++) {
j = i + 1;
k = j + 1;
const c_i = s[i];
const c_j = s[j];
const c_k = s[k];
/* case: two consecutive same (e.g. "aa") */
if (c_i === c_j) {
seedIndices.push([i, j]);
}
/* case: three consecutive where first and third are same (e.g. "aba") */
if (c_i === c_k) {
seedIndices.push([i, k]);
}
}
/* seed indices in hand, "grow" them by moving their start and end values
until we have determined longest */
for (let [start, end] of seedIndices) {
let candidateStart = start - 1;
let candidateEnd = end + 1;
let c_s = s[candidateStart];
let c_e = s[candidateEnd];
while (0 <= candidateStart && candidateEnd < s.length && c_s === c_e) {
start -= 1;
end += 1;
candidateStart = candidateStart - 1;
candidateEnd = candidateEnd + 1;
c_s = s[candidateStart];
c_e = s[candidateEnd];
}
const maybeLongest = s.slice(start, end + 1);
if (longest.length < maybeLongest.length) {
longest = maybeLongest;
}
}
/* case: we have found no palindromes, just choose the first letter */
if (longest === "") {
longest = s[0];
}
return longest;
}
export {};
problems/00007-reverse-integer/README.md
# [SOLVED] 7 - Reverse Integer
[Reverse Integer](https://leetcode.com/problems/reverse-integer/)
## notes
### first thoughts
- convert the number to a string, then try to convert to a 32 bit int and stop
if that fails
- needs to handle a negative sign
- cast to string, split into list of chars, remove negative sign if
present. this is now the value in ones, tens, etc that we need to check if
outside bounds. create bounds that are the upper bound, converted to list of
chars, with sign removed, and reversed. use this to verify that each place is
less than the other.
- I guess the logic is simpler: only check if outside of bounds if length is
equal to bound. if greater fail. if less succeed.
- need to remove leading zeros from any reversal
## summary
- annoying problem
## tags
problems/00007-reverse-integer/typescript/solution.ts
function reverse(x: number): number {
/* cheeky: convert to string, split on chars, reverse, convert to number and
check bounds */
const isPositive = 0 <= x;
const stringOriginalNum = String(Math.abs(x));
const reversedDigits = stringOriginalNum.split("").reverse();
/* remove leading zeros */
while (reversedDigits[0] === "0") {
reversedDigits.shift();
}
/* handle case where we were passed 0 */
if (reversedDigits.length === 0) {
return 0;
}
const min = String(Math.pow(2, 31)).split("");
const max = String(Math.pow(2, 31) - 1).split("");
/* want to check if transformedDigits is greater than min and less than max,
but, important: the description states: "Assume the environment does not
allow you to store 64-bit integers (signed or unsigned)." so we must compare
digit by digit rather than using Number.parseInt for the whole value */
let transformedDigits = reversedDigits;
const bound = isPositive ? max : min;
if (transformedDigits.length < bound.length) {
/* we are OK */
} else if (transformedDigits.length === bound.length) {
/* we need to compare that each new digit is less */
for (let i = 0; i < bound.length; i++) {
const tDigit = Number(transformedDigits[i]);
const mDigit = Number(bound[i]);
if (tDigit < mDigit) {
/* our transformation is less than the max, so we have no more
comparisons left */
break;
} else if (tDigit === mDigit) {
/* continue, we don't know yet if we are less */
} else {
/* our transformed is greater, so we must return 0 per instructions */
return 0;
}
}
}
/* this Number.parseInt _is_ safe within problem description because we know
it will fit within a 32-bit signed integer */
const absVal = Number.parseInt(reversedDigits.join(""));
return isPositive ? absVal : absVal * -1;
}
export {};
problems/00008-string-to-integer-atoi/README.md
# [SOLVED] 8 - String to Integer (atoi)
[String to Integer (atoi)](https://leetcode.com/problems/string-to-integer-atoi/)
## notes
- I hated implementing this lol. the test cases were tedious and super annoying.
- a bunch of solutions use `Number(x)` or `parseInt` which is no bueno
## summary
- avoid doing this
## tags
medium string
problems/00008-string-to-integer-atoi/typescript/solution.ts
const min = Math.pow(2, 31);
const max = Math.pow(2, 31) - 1;
const minDigits = String(min).split("");
const maxDigits = String(max).split("");
function myAtoi(s: string): number {
const maybeNegativeMatch = s.match(/^\s*-?\d+/);
const maybePosMatch = s.match(/^\s*\+?\d+/);
if (maybeNegativeMatch === null && maybePosMatch === null) {
return 0;
}
const maybeMatch =
maybeNegativeMatch === null ? maybePosMatch : maybeNegativeMatch;
if (maybeMatch === null) {
return 0;
}
const raw = maybeMatch[0].trim().split("");
const isNegative = raw[0] === "-";
/* clean the sign */
if (raw[0] === "-" || raw[0] === "+") {
raw.shift();
}
/* clean any leading 0s */
let c = raw[0];
while (c === "0") {
raw.shift();
c = raw[0];
}
const cleaned = raw;
if (cleaned.length === 0) {
return 0;
}
/* compare to our bounds: if less or more, clamp */
const boundDigits = isNegative ? minDigits : maxDigits;
let digit = 0;
if (cleaned.length < boundDigits.length) {
/* we good */
} else if (cleaned.length === boundDigits.length) {
/* compare */
for (let i = 0; i < cleaned.length; i++) {
const cleanedDigit = cleaned[i];
const boundDigit = boundDigits[i];
if (cleanedDigit < boundDigit) {
/* cleaned is smaller than our bound, no need to keep checking */
break;
} else if (cleanedDigit === boundDigit) {
/* continue checking */
} else {
/* clamp */
return isNegative ? -1 * min : max;
}
}
} else {
/* clamp */
return isNegative ? -1 * min : max;
}
digit = Number.parseInt(cleaned.join(""));
digit = isNegative ? -1 * digit : digit;
return digit;
}
export {};
problems/00011-container-with-most-water/README.md
# [SOLVED] 11 - Container With Most Water
[Container With Most Water](https://leetcode.com/problems/container-with-most-water/)
## notes
### first thoughts
- `n` will be at least 2, so don’t worry about weird edge cases
- naive solution: for each value, iterate all other values and calculate
area. this would be O(n^2).
- we do not want to sort our input
- feels like two pointers
- always move the smaller height pointer because we could hold more water if we
encounter a higher option.
- keep track of max area as you go
- stop when the pointers cross
## summary
- comp.t: O(n) as we look at each index exactly once
- comp.s: O(1) as we just store `max`
- problem solving trick was to realize that starting at the leftmost and
rightmost indices necessarily gives us the maximum possible width for our
potential rectangle, so we can safely move inward and not be concerned that we
have overlooked some larger width possibility.
## tags
medium array
medium two pointers
problems/00011-container-with-most-water/golang/solution.go
package main
import "fmt"
func maxArea(height []int) int {
leftIndex, rightIndex := 0, len(height)-1
max := 0
for leftIndex < rightIndex {
left, right := height[leftIndex], height[rightIndex]
width := rightIndex - leftIndex
area := min(left, right) * width
if max < area {
max = area
}
if left < right {
leftIndex++
} else {
rightIndex--
}
}
return max
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func main() {
fmt.Println(maxArea([]int{1, 1}))
fmt.Println(maxArea([]int{1, 8, 6, 2, 5, 4, 8, 3, 7}))
}
problems/00011-container-with-most-water/typescript/solution.ts
function maxArea(height: number[]): number {
let max = 0;
let start = 0;
let end = height.length - 1;
/* move start and end closer to middle, calculating height as we go */
while (start < end) {
const left = height[start];
const right = height[end];
const area = Math.min(left, right) * (end - start);
if (max < area) {
max = area;
}
if (left <= right) {
start++;
} else {
end--;
}
}
return max;
}
export {};
problems/00013-roman-to-integer/README.md
# [SOLVED] 13 - Roman to Integer
[Roman to Integer](https://leetcode.com/problems/roman-to-integer/)
## notes
### first thoughts
- store special cases in lookup
- store normal cases in other lookup
- loop through input string chars, looking ahead by one, if special case, look
up and advance by 2. otherwise lookup normal and advance by 1
- sum the numbers
## summary
- comp.t: O(n) for a string of length n. we must read all characters
- comp.s: O(1) + k we just store the sum and have a fixed size map
- after successful submission I looked at other implementations that were faster
and thought this code was clearer
## tags
easy string parsing
easy array
problems/00013-roman-to-integer/golang/solution.go
package main
import (
"fmt"
"strings"
)
func romanToInt(s string) int {
lookup := map[string]int{
// normal
"I": 1,
"V": 5,
"X": 10,
"L": 50,
"C": 100,
"D": 500,
"M": 1000,
// special cases
"IV": 4,
"IX": 9,
"XL": 40,
"XC": 90,
"CD": 400,
"CM": 900,
}
sum := 0
chars := strings.Split(s, "")
i := 0
for i < len(chars) {
j := i + 1
if j < len(chars) {
// do we have special case?
key := fmt.Sprint(chars[i], chars[j])
if val, ok := lookup[key]; ok {
sum += val
i += 2
continue
}
}
if val, ok := lookup[chars[i]]; ok {
sum += val
i++
}
}
return sum
}
func main() {
fmt.Println(romanToInt("III"))
fmt.Println(romanToInt("LVIII"))
fmt.Println(romanToInt("MCMXCIV"))
}
problems/00013-roman-to-integer/typescript/solution.ts
const lookup = new Map<string, number>();
/* normal */
lookup.set("I", 1);
lookup.set("V", 5);
lookup.set("X", 10);
lookup.set("L", 50);
lookup.set("C", 100);
lookup.set("D", 500);
lookup.set("M", 1000);
/* special cases */
lookup.set("IV", 4);
lookup.set("IX", 9);
lookup.set("XL", 40);
lookup.set("XC", 90);
lookup.set("CD", 400);
lookup.set("CM", 900);
function romanToInt(s: string): number {
/* read by two characters, processing via special case lookup */
let sum = 0;
let i = 0;
while (i < s.length) {
const maybeSpecial = lookup.get(s.slice(i, i + 2));
if (maybeSpecial !== undefined) {
sum += maybeSpecial;
i += 2;
continue;
}
const maybeBase = lookup.get(s[i]);
if (maybeBase !== undefined) {
sum += maybeBase;
i += 1;
continue;
}
throw new Error("cannot handle");
}
return sum;
}
export {};
problems/00014-longest-common-prefix/README.md
# [SOLVED] 14 - Longest Common Prefix
[Longest Common Prefix](https://leetcode.com/problems/longest-common-prefix/)
## notes
## summary
## tags
easy array
problems/00014-longest-common-prefix/typescript/solution.ts
function longestCommonPrefix(strs: string[]): string {
const lcp = [];
let i = 0;
let maybeNext = strs[0][i];
while (maybeNext !== undefined) {
for (const s of strs) {
if (s[i] === maybeNext) {
/* continue */
} else {
return lcp.join("");
}
}
lcp.push(maybeNext);
i++;
maybeNext = strs[0][i];
}
return lcp.join("");
}
export {};
problems/00015-3sum/README.md
# [SOLVED] 15 - 3Sum
[3Sum](https://leetcode.com/problems/3sum/)
## notes
## summary
## tags
medium array
medium two pointers
problems/00015-3sum/typescript/solution.ts
function threeSum(nums: number[]): number[][] {
const sortedNums = nums.sort((a, b) => a - b); /* ascending */
const triplets = [];
for (let i = 0; i < sortedNums.length; i++) {
const numI = sortedNums[i];
if (0 < i && numI === sortedNums[i - 1]) {
continue;
}
let left = i + 1;
let right = sortedNums.length - 1;
while (left < right) {
const numJ = sortedNums[left];
const numK = sortedNums[right];
const triplet = [numI, numJ, numK];
const sum = triplet.reduce((memo, n) => {
return memo + n;
}, 0);
if (sum === 0) {
triplets.push(triplet);
left++;
while (left < right && sortedNums[left] === sortedNums[left - 1]) {
left++;
}
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return triplets;
}
export {};
problems/00017-letter-combinations-of-a-phone-number/README.md
# [SOLVED] 17 - Letter Combinations of a Phone Number
[Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/)
## notes
## summary
## tags
- medium dynamic programming
- medium backtrack
- medium recursive
problems/00017-letter-combinations-of-a-phone-number/typescript/solution.ts
const digitToChar = new Map<string, string[]>();
digitToChar.set("2", ["a", "b", "c"]);
digitToChar.set("3", ["d", "e", "f"]);
digitToChar.set("4", ["g", "h", "i"]);
digitToChar.set("5", ["j", "k", "l"]);
digitToChar.set("6", ["m", "n", "o"]);
digitToChar.set("7", ["p", "q", "r", "s"]);
digitToChar.set("8", ["t", "u", "v"]);
digitToChar.set("9", ["w", "y", "x", "z"]);
function letterCombinations(digits: string): string[] {
const combinations: string[] = [];
const backtrack = ({ i, s }: { i: number; s: string }) => {
if (s.length === digits.length) {
combinations.push(s);
return;
}
const chars = digitToChar.get(digits[i]) ?? [];
for (const c of chars) {
backtrack({ i: i + 1, s: s + c });
}
};
if (0 < digits.length) {
backtrack({ i: 0, s: "" });
}
return combinations;
}
export {};
problems/00019-remove-nth-node-from-end-of-list/README.md
# [OPEN] 19 - Remove Nth Node From End of List
## notes
## first thoughts
get length of `head`, then loop through again , updating the (length - n)
ListNode's Next to (length - n + 1)
ahhh so the issue is that we do not know the length yet
## second thoughts
- operation will always be involving 3 ListNodes
- if `(length - n) == 0`, return `nil`
- store the modified ListNodes in a lookup using the current length as a
key. once you reach the end, calculate `length - n`, and look up
- store the prev and next ListNodes in a lookup using the current length as a
key
- confused about how to modify when n is equal to length
OK, so what I implemented
- walk the `ListNode`
- when the length is greater than 1, store in a lookup the current length and
the `prev` and `next` pointers
- when you have reached the end, calculate the nthFromEnd value
- look up the nthFromEnd value in the lookup, getting `prev` and `next`
- update `prev.Next` to point to `next`
- comp.t: O(n). we walk the linked list once
- comp.s: O(n). we store 2 values for every n elements, which reduces to O(n)
## summary
## tags
medium linked list
problems/00019-remove-nth-node-from-end-of-list/golang/solution.go
package main
import (
"encoding/json"
"fmt"
"log"
)
type ListNode struct {
Val int
Next *ListNode
}
func (ln *ListNode) String() string {
return fmt.Sprintf("%v, %v", ln.Val, ln.Next)
}
func ListNodeFromVals(vals []int) *ListNode {
holder := &ListNode{}
head := holder
for _, val := range vals {
head.Next = &ListNode{Val: val}
head = head.Next
}
return holder.Next
}
func ListNodeFrom(s string) *ListNode {
var vals []int
err := json.Unmarshal([]byte(s), &vals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
return ListNodeFromVals(vals)
}
func ListNodesFrom(s string) []*ListNode {
var nodeVals [][]int
err := json.Unmarshal([]byte(s), &nodeVals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
var output []*ListNode
for _, nodeVal := range nodeVals {
output = append(output, ListNodeFromVals(nodeVal))
}
return output
}
// head = [1,2,3,4,5], n = 2
// [1,2,3,4,5]
// 0,1,2,3,4
// [1,2,3->5]
// updates the (length - n) Next to point to (length - n + 1)
// challenge: do it in one pass
// [1], n = 1
// 0: prev=nil, cur:1, next: nil
func removeNthFromEnd(head *ListNode, n int) *ListNode {
if head == nil {
return head
}
if head.Next == nil {
return nil
}
lookup := make(map[int][]*ListNode)
length := 0
var prev *ListNode
prev = nil
cur := head
next := cur.Next
for cur != nil {
length += 1
fmt.Printf("before length=%d, prev=%v, cur=%v, next=%v\n", length, prev, cur, next)
if prev != nil {
lookup[length] = []*ListNode{prev, next}
}
fmt.Printf("%v\n", lookup)
if _, ok := lookup[2]; ok {
fmt.Printf("%v\n", lookup[2][0])
fmt.Printf("%v\n", lookup[2][1])
}
prev = cur
cur = next
if cur == nil {
next = nil
} else {
next = cur.Next
}
fmt.Printf("after length=%d, prev=%v, cur=%v, next=%v\n", length, prev, cur, next)
}
fmt.Printf("%v\n", lookup)
nthFromEnd := length - n + 1
fmt.Printf("%d\n", nthFromEnd)
nodes, ok := lookup[nthFromEnd]
if ok {
first := nodes[0]
second := nodes[1]
fmt.Printf("%v\n", first)
fmt.Printf("%v\n", second)
first.Next = second
}
return head
}
func printable(head *ListNode) {
var s []int
for head != nil {
s = append(s, head.Val)
head = head.Next
}
fmt.Printf("%v\n", s)
}
func main() {
output1 := removeNthFromEnd(ListNodeFrom("[1,2,3,4,5]"), 2)
printable(output1)
output2 := removeNthFromEnd(ListNodeFrom("[1]"), 1)
printable(output2)
output3 := removeNthFromEnd(ListNodeFrom("[1,2]"), 1)
printable(output3)
}
problems/00020-valid-parentheses/README.md
# [SOLVED] 20 - Valid Parentheses
[Valid Parentheses](https://leetcode.com/problems/valid-parentheses/)
## notes
### first thoughts
- keep a stack for each paren type. push any open paren on. when encounter a
closed, pop off. valid if all are empty.
- I don’t need a stack, just a count. break if it goes negative
- ugh I did not read problem fully. they need to be actually valid groupings.
## summary
- typescript implementation was done many days previous to golang
- `solution.go` comp.t: O(n) we must read every char
- `solution.go` comp.s: O(n) `record` could contain all `n` chars
## tags
easy stack
problems/00020-valid-parentheses/golang/solution.go
package main
import (
"fmt"
"strings"
)
func isValid(s string) bool {
var record []string
res := true
for _, c := range strings.Split(s, "") {
if c == "(" || c == "{" || c == "[" {
// open paren always valid move
record = append(record, c)
} else {
// closed paren has some caveats
if len(record) == 0 {
// we have not received an open, so invalid
res = false
break
}
// top must be the corresponding open paren
top := record[len(record)-1]
if top == "(" && c != ")" {
res = false
break
} else if top == "{" && c != "}" {
res = false
break
} else if top == "[" && c != "]" {
res = false
break
}
// we are good, remove the last
record = record[:len(record)-1]
}
}
return res && len(record) == 0
}
func main() {
fmt.Println(isValid("()"))
fmt.Println(isValid("()[]{}"))
fmt.Println(isValid("(]"))
fmt.Println(isValid("([)]"))
fmt.Println(isValid("["))
}
problems/00020-valid-parentheses/typescript/solution.ts
const openParenLookup = new Map<string, string>();
openParenLookup.set("(", ")");
openParenLookup.set("{", "}");
openParenLookup.set("[", "]");
const closedParenLookup = new Map<string, string>();
closedParenLookup.set(")", "(");
closedParenLookup.set("}", "{");
closedParenLookup.set("]", "[");
function isValid(s: string): boolean {
if (s.length % 2 !== 0) {
return false;
}
/* idea: read characters from string, push val if open paren, pop if close
paren, if popped value does not work we stop */
const record: string[] = [];
for (const paren of s.split("")) {
if (openParenLookup.has(paren)) {
/* we know it is an open */
record.push(paren);
continue;
}
/* it is a closed paren, lets 1) make sure we find it and 2) make sure it
matches */
const maybeMatchingOpenParen = closedParenLookup.get(paren);
if (maybeMatchingOpenParen === undefined) {
throw new Error(`unknown paren: ${paren}`);
}
const last = record.pop();
if (last === undefined) {
return false;
}
if (last === maybeMatchingOpenParen) {
continue;
}
return false;
}
return record.length === 0;
}
console.log(isValid("()"));
console.log(isValid("()[]{}"));
console.log(isValid("(]"));
console.log(isValid("]"));
console.log(isValid("(()"));
console.log(isValid("(((("));
export {};
problems/00021-merge-two-sorted-lists/README.md
# [SOLVED] 21 - Merge Two Sorted Lists
[Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/description/)
## notes
### first thoughts
- recursive
- check for which val is less. update that `.Next` to point to the result of
`mergeTwoLists`
- handle base cases of either being `nil`
- comp.t: O(n + m). we must walk each `ListNode`.
- comp.s: O(n + m). we must create a new `ListNode` of length `n + m`.
## summary
accepted solution. 100% runtime. 59% memory.
Key here was the “recursive leap of faith” to think: assuming that we can
determine which `ListNode` goes first, a function `mergeToLists` exists that
will sort whatever is remaining.
I found it helpful to think of it like:
```
determine which of the following values is less:
__
ln1: |1|->2->4
ln2: |2|->3->4
which then creates the start of our result:
res: 1
and then draw a box around the remainder of the problem:
_____
ln1: ____|2->4|
ln2: |2 ->3->4|
which fits for the args to `mergeTwoLists`
```
## tags
easy linked list
easy recursive
problems/00021-merge-two-sorted-lists/golang/solution.go
package main
import (
"encoding/json"
"fmt"
"log"
)
type ListNode struct {
Val int
Next *ListNode
}
func (ln *ListNode) String() string {
var s []int
for ln != nil {
s = append(s, ln.Val)
ln = ln.Next
}
return fmt.Sprintf("%v", s)
}
func ListNodeFromVals(vals []int) *ListNode {
holder := &ListNode{}
head := holder
for _, val := range vals {
head.Next = &ListNode{Val: val}
head = head.Next
}
return holder.Next
}
func ListNodeFrom(s string) *ListNode {
var vals []int
err := json.Unmarshal([]byte(s), &vals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
return ListNodeFromVals(vals)
}
func ListNodesFrom(s string) []*ListNode {
var nodeVals [][]int
err := json.Unmarshal([]byte(s), &nodeVals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
var output []*ListNode
for _, nodeVal := range nodeVals {
output = append(output, ListNodeFromVals(nodeVal))
}
return output
}
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
if list1 == nil {
return list2
}
if list2 == nil {
return list1
}
if list1.Val < list2.Val {
list1.Next = mergeTwoLists(list1.Next, list2)
return list1
} else {
list2.Next = mergeTwoLists(list1, list2.Next)
return list2
}
}
func printable(head *ListNode) {
var s []int
for head != nil {
s = append(s, head.Val)
head = head.Next
}
fmt.Printf("%v\n", s)
}
func main() {
printable(mergeTwoLists(ListNodeFrom("[1,2,4]"), ListNodeFrom("[1,3,4]")))
printable(mergeTwoLists(ListNodeFrom("[]"), ListNodeFrom("[]")))
printable(mergeTwoLists(ListNodeFrom("[]"), ListNodeFrom("[0]")))
}
problems/00022-generate-parentheses/README.md
# [SOLVED] 22 - Generate Parentheses
[Generate Parentheses](https://leetcode.com/problems/generate-parentheses/)
## notes
### first thoughts
- dynamic programming
- maybe state being remaining open and closed parens
- a closed paren must have a corresponding open paren
- we only have one option on our first move. how to reflect this? I keep wanting
to iterate through each of the `n` parens, but the case is handled by just
including one
```
n=1 ()
n=2
0: (
1: (), ((,
2: ()(, (((, ((),
3: ()((, ()(), (((), (()(, (())
```
### second thoughts
- recursion, not dynamic programming (we are not caching data)
- you have *two options* at each branch in the tree: either add an `(` or add an
`)`
- the ability to do one or the other is determined by however many valid options
remain
- therefore state is the current permutation `perm`, `remainingOpen`, and
`remainingClosed`
- recursive leap of faith: assume `helper` will give you a list of possible
strings
## summary
for `solution.go`:
- comp.t: O(2^n). at every character index we have 2 options (open or closed).
- comp.s: O(n). the height of the recursive tree is `2 * n`, which reduces to
O(n). this space complexity refers to the height of the stack generated from
the solution approach, not the amount of characters in each permutation or the
number of permutations.
- I missed that the length of `perm` is necessarily `2 * n` because for every
`n` open parens there must be `n` closed parens.
- key realization here was that at each index of the permutation string you have
two options: either add an `(` or add an `)`.
for `solution-dynamic-programming.go`:
added a second approach using dynamic programming with memoization that caches
each set of permutations at n. comp.t is now O(n) because we memoize the result
of the computation. now 100% for runtime.
## tags
medium recursion
medium dynamic programming
problems/00022-generate-parentheses/golang/solution-dynamic-programming.go
package main
import "fmt"
func helper(remainingOpen int, remainingClosed int, lookup map[string][]string) []string {
key := fmt.Sprintf("%d-%d", remainingOpen, remainingClosed)
permutations, ok := lookup[key]
if ok {
return permutations
}
var result []string
// add open
if 1 <= remainingOpen {
for _, perm := range helper(remainingOpen-1, remainingClosed+1, lookup) {
result = append(result, "("+perm)
}
}
// add closed
if 1 <= remainingClosed {
for _, perm := range helper(remainingOpen, remainingClosed-1, lookup) {
result = append(result, ")"+perm)
}
}
lookup[key] = result
return lookup[key]
}
func generateParenthesis(n int) []string {
lookup := make(map[string][]string)
// base cases
lookup["0-0"] = []string{}
lookup["1-0"] = []string{"()"}
lookup["0-1"] = []string{")"}
return helper(n, 0, lookup)
}
func main() {
fmt.Println(generateParenthesis(0))
fmt.Println(generateParenthesis(1))
fmt.Println(generateParenthesis(2))
fmt.Println(generateParenthesis(3))
}
problems/00022-generate-parentheses/golang/solution.go
package main
import "fmt"
// we have two options: either add an open or add a closed
func helper(perm string, remainingOpen int, remainingClosed int) []string {
var result []string
if remainingOpen == 0 && remainingClosed == 0 {
// we are done
result = append(result, perm)
} else if 0 < remainingOpen && remainingClosed == 0 {
// can only add an open
result = append(result, helper(fmt.Sprint(perm, "("), remainingOpen-1, remainingClosed+1)...)
} else if remainingOpen == 0 && 0 < remainingClosed {
// can only add a closed
result = append(result, helper(fmt.Sprint(perm, ")"), remainingOpen, remainingClosed-1)...)
} else {
// can both add an open and add a closed
result = append(result, helper(fmt.Sprint(perm, "("), remainingOpen-1, remainingClosed+1)...)
result = append(result, helper(fmt.Sprint(perm, ")"), remainingOpen, remainingClosed-1)...)
}
return result
}
func generateParenthesis(n int) []string {
return helper("", n, 0)
}
func main() {
fmt.Println(generateParenthesis(0))
fmt.Println(generateParenthesis(1))
fmt.Println(generateParenthesis(2))
fmt.Println(generateParenthesis(3))
}
problems/00022-generate-parentheses/typescript/solution.ts
function generateParenthesis(n: number): string[] {
/* I want a function that will add all the possible strings to a combos I pass in */
const helper = (
numOpenAvail: number,
numClosedAvail: number,
currentCombo: string,
combos: string[]
) => {
if (numOpenAvail === 0 && numClosedAvail === 0) {
/* we are done */
combos.push(currentCombo);
}
/* two choices: either add an open or closed */
if (0 < numOpenAvail) {
helper(numOpenAvail - 1, numClosedAvail + 1, currentCombo + "(", combos);
}
if (0 < numClosedAvail) {
helper(numOpenAvail, numClosedAvail - 1, currentCombo + ")", combos);
}
};
const output: string[] = [];
helper(n, 0, "", output);
return output;
}
console.log(generateParenthesis(3));
console.log(generateParenthesis(1));
export {};
problems/00023-merge-k-sorted-lists/README.md
# [SOLVED] 23 - Merge k Sorted Lists
[Merge k Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/)
## notes
### first thoughts
- naive solution would be to scan back and forth through the lists, building the
ListNode
- thought that I wanted to read each list once, so I figured I could stuff each
number into its own queue in a `record` slice (with the number corresponding
to the index of `record`). I added an offset based on the contraints.
- the solution was accepted and 50th percentile fast but not in the spirit of
the problem.
- I did think “each list is already sorted, so is there a way I can use that to
my advantage?”. I think this is what the example hints were trying to show:
that if list is length 1 you already have your answer. So a more in-spirit
approach is to take the first list, merge it with i + 1, then merge that
result with i+2. this walks nodes multiple times so I am surprised it is
faster.
- an optimization I could do to my current approach is to use `[]*int` and just
increment rather than append
### optimization via int pointers
- did this and got to 90th percentile. still not spirit of the problem
### recursive optimization
- got 36th percentile, which surprised me as I thought this would be more
efficient. will try iterative approach.
### iterative optimization
- got 33rd percentile, which also surprised me because this is basically the
approach that the top performing implementations did. maybe there is some
difference I am missing.
## summary
- (int pointer impl) comp.t: O(n) for the number of nodes total in k lists
- (int pointer impl) comp.s: O(1) because of constraints
## tags
problems/00023-merge-k-sorted-lists/golang/solution-1-record-slice.go
package main
import (
"encoding/json"
"fmt"
"log"
"math"
)
type ListNode struct {
Val int
Next *ListNode
}
func (ln *ListNode) String() string {
return fmt.Sprintf("%v, %v", ln.Val, ln.Next)
}
func ListNodeFromVals(vals []int) *ListNode {
holder := &ListNode{}
head := holder
for _, val := range vals {
head.Next = &ListNode{Val: val}
head = head.Next
}
return holder.Next
}
func ListNodeFrom(s string) *ListNode {
var vals []int
err := json.Unmarshal([]byte(s), &vals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
return ListNodeFromVals(vals)
}
func ListNodesFrom(s string) []*ListNode {
var nodeVals [][]int
err := json.Unmarshal([]byte(s), &nodeVals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
var output []*ListNode
for _, nodeVal := range nodeVals {
output = append(output, ListNodeFromVals(nodeVal))
}
return output
}
// original first pass solution
func mergeKLists(lists []*ListNode) *ListNode {
offset := int(math.Pow(10, 4))
record := make([][]int, offset*2)
for _, node := range lists {
for node != nil {
record[node.Val+offset] = append(record[node.Val+offset], node.Val)
node = node.Next
}
}
var tail *ListNode
for i := len(record) - 1; 0 <= i; i-- {
if len(record[i]) != 0 {
for _, val := range record[i] {
prev := &ListNode{Val: val, Next: tail}
tail = prev
}
}
}
return tail
}
func main() {
fmt.Println(mergeKLists(ListNodesFrom("[[1,4,5],[1,3,4],[2,6]]")))
fmt.Println(mergeKLists(ListNodesFrom("[]")))
fmt.Println(mergeKLists(ListNodesFrom("[[]]")))
}
problems/00023-merge-k-sorted-lists/golang/solution-2-int-ptr.go
package main
import (
"encoding/json"
"fmt"
"log"
"math"
)
type ListNode struct {
Val int
Next *ListNode
}
func (ln *ListNode) String() string {
return fmt.Sprintf("%v, %v", ln.Val, ln.Next)
}
func ListNodeFromVals(vals []int) *ListNode {
holder := &ListNode{}
head := holder
for _, val := range vals {
head.Next = &ListNode{Val: val}
head = head.Next
}
return holder.Next
}
func ListNodeFrom(s string) *ListNode {
var vals []int
err := json.Unmarshal([]byte(s), &vals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
return ListNodeFromVals(vals)
}
func ListNodesFrom(s string) []*ListNode {
var nodeVals [][]int
err := json.Unmarshal([]byte(s), &nodeVals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
var output []*ListNode
for _, nodeVal := range nodeVals {
output = append(output, ListNodeFromVals(nodeVal))
}
return output
}
// optimized *int solution
func mergeKLists(lists []*ListNode) *ListNode {
offset := int(math.Pow(10, 4))
record := make([]*int, offset*2)
for _, node := range lists {
for node != nil {
var sum *int
sum = new(int)
if record[node.Val+offset] == nil {
*sum = 1
} else {
*sum = 1 + *record[node.Val+offset]
}
record[node.Val+offset] = sum
node = node.Next
}
}
var tail *ListNode
for i := len(record) - 1; 0 <= i; i-- {
if record[i] != nil {
amt := *record[i]
for j := 0; j < amt; j++ {
prev := &ListNode{Val: i - offset, Next: tail}
tail = prev
}
}
}
return tail
}
func main() {
fmt.Println(mergeKLists(ListNodesFrom("[[1,4,5],[1,3,4],[2,6]]")))
fmt.Println(mergeKLists(ListNodesFrom("[]")))
fmt.Println(mergeKLists(ListNodesFrom("[[]]")))
}
problems/00023-merge-k-sorted-lists/golang/solution-3-recursive.go
package main
import (
"encoding/json"
"fmt"
"log"
)
type ListNode struct {
Val int
Next *ListNode
}
func (ln *ListNode) String() string {
return fmt.Sprintf("%v, %v", ln.Val, ln.Next)
}
func ListNodeFromVals(vals []int) *ListNode {
holder := &ListNode{}
head := holder
for _, val := range vals {
head.Next = &ListNode{Val: val}
head = head.Next
}
return holder.Next
}
func ListNodeFrom(s string) *ListNode {
var vals []int
err := json.Unmarshal([]byte(s), &vals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
return ListNodeFromVals(vals)
}
func ListNodesFrom(s string) []*ListNode {
var nodeVals [][]int
err := json.Unmarshal([]byte(s), &nodeVals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
var output []*ListNode
for _, nodeVal := range nodeVals {
output = append(output, ListNodeFromVals(nodeVal))
}
return output
}
// idea: reduce problem to merging two lists, and then minimize the number of
// updates by walking the primary list until the alternate has a lower value
func mergeKLists(lists []*ListNode) *ListNode {
var list1 *ListNode
var list2 *ListNode
// base cases
if len(lists) == 0 {
return nil
} else if len(lists) == 1 {
return lists[0]
} else if len(lists) == 2 {
list1 = lists[0]
list2 = lists[1]
} else {
list1 = lists[0]
list2 = mergeKLists(lists[1:])
}
if list1 == nil {
return list2
}
if list2 == nil {
return list1
}
// do the actual merging
var primary *ListNode
var alternate *ListNode
// choose primary being the one with the lower starting value
if list1.Val < list2.Val {
primary = list1
alternate = list2
} else {
primary = list2
alternate = list1
}
head := &ListNode{}
head.Next = primary
for alternate != nil {
if primary.Next == nil {
// we are done. there are no more nodes on primary and we know that
// alternate contains greater vals
primary.Next = alternate
break
}
// look ahead to our next value, should we instead insert alternate?
if primary.Next.Val <= alternate.Val {
// nope, we should just keep moving primary forward
primary = primary.Next
} else {
// yes, we should insert alternate
oldPrimaryNext := primary.Next
primary.Next = alternate
alternate = oldPrimaryNext
}
}
return head.Next
}
func main() {
fmt.Println(mergeKLists(ListNodesFrom("[[1,4,5],[1,3,4],[2,6]]")))
fmt.Println(mergeKLists(ListNodesFrom("[]")))
fmt.Println(mergeKLists(ListNodesFrom("[[]]")))
}
problems/00023-merge-k-sorted-lists/golang/solution-4-iterative.go
package main
import (
"encoding/json"
"fmt"
"log"
)
type ListNode struct {
Val int
Next *ListNode
}
func (ln *ListNode) String() string {
return fmt.Sprintf("%v, %v", ln.Val, ln.Next)
}
func ListNodeFromVals(vals []int) *ListNode {
holder := &ListNode{}
head := holder
for _, val := range vals {
head.Next = &ListNode{Val: val}
head = head.Next
}
return holder.Next
}
func ListNodeFrom(s string) *ListNode {
var vals []int
err := json.Unmarshal([]byte(s), &vals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
return ListNodeFromVals(vals)
}
func ListNodesFrom(s string) []*ListNode {
var nodeVals [][]int
err := json.Unmarshal([]byte(s), &nodeVals)
if err != nil {
log.Fatalln("trying to build ListNode:", err)
}
var output []*ListNode
for _, nodeVal := range nodeVals {
output = append(output, ListNodeFromVals(nodeVal))
}
return output
}
func merge2Lists(list1 *ListNode, list2 *ListNode) *ListNode {
if list1 == nil {
return list2
}
if list2 == nil {
return list1
}
// do the actual merging
var primary *ListNode
var alternate *ListNode
// choose primary being the one with the lower starting value
if list1.Val < list2.Val {
primary = list1
alternate = list2
} else {
primary = list2
alternate = list1
}
head := &ListNode{}
head.Next = primary
for alternate != nil {
if primary.Next == nil {
// we are done. there are no more nodes on primary and we know that
// alternate contains greater vals
primary.Next = alternate
break
}
// look ahead to our next value, should we instead insert alternate?
if primary.Next.Val <= alternate.Val {
// nope, we should just keep moving primary forward
primary = primary.Next
} else {
// yes, we should insert alternate
oldPrimaryNext := primary.Next
primary.Next = alternate
alternate = oldPrimaryNext
}
}
return head.Next
}
// idea: reduce problem to merging two lists, and then minimize the number of
// updates by walking the primary list until the alternate has a lower value
func mergeKLists(lists []*ListNode) *ListNode {
// base cases
if len(lists) == 0 {
return nil
}
if len(lists) == 1 {
return lists[0]
}
// find the first non-nil list
primaryIndex := 0
for primaryIndex < len(lists)-1 && lists[primaryIndex] == nil {
primaryIndex += 1
}
head := lists[primaryIndex]
for i := primaryIndex + 1; i < len(lists); i++ {
head = merge2Lists(head, lists[i])
}
return head
}
func main() {
fmt.Println(mergeKLists(ListNodesFrom("[[1,4,5],[1,3,4],[2,6]]")))
fmt.Println(mergeKLists(ListNodesFrom("[]")))
fmt.Println(mergeKLists(ListNodesFrom("[[]]")))
}
problems/00026-remove-duplicates-from-sorted-array/README.md
# [SOLVED] 26 - Remove Duplicates from Sorted Array
[Remove Duplicates from Sorted Array](https://leetcode.com/problems/remove-duplicates-from-sorted-array/)
## notes
### first thoughts
- keep track of the current number’s index `i`
- advance `k` steps, where `k=1` and increments by 1
- if the `i + k` number is the same as `nums[i]`, advance
- if the `i + k` number is greater than `nums[i]`, increment `i`, insert the
number into `nums[i]` and repeat
## summary
- comp.t: O(n) we must examine every element
- comp.s: O(1) we replace values in place
- I need to read the constraints better as `nums.length` will always be greater
than or equal to 1.
## tags
easy array
problems/00026-remove-duplicates-from-sorted-array/golang/solution.go
package main
import "fmt"
func removeDuplicates(nums []int) int {
k := 0
for _, num := range nums {
if nums[k] < num {
k++
nums[k] = num
}
}
return k + 1
}
func main() {
fmt.Println(removeDuplicates([]int{1}))
fmt.Println(removeDuplicates([]int{1, 1}))
fmt.Println(removeDuplicates([]int{1, 1, 2}))
fmt.Println(removeDuplicates([]int{0, 0, 1, 1, 1, 2, 2, 3, 3, 4}))
}
problems/00033-search-in-rotated-sorted-array/README.md
# [SOLVED] 33 - Search in Rotated Sorted Array
[Search in Rotated Sorted Array](https://leetcode.com/problems/search-in-rotated-sorted-array/)
## notes
### first thoughts
- ascending, distinct values
- alg must run in O(log n) (e.g. no sorting)
- smells like binary search. modified version of it.
- idea. find the pivot point, then modify search based on that.
- I think we can just implement a modified version of binary search that starts
with bounds check? e.g. check 0 and check length - 1?, then choose the middle?
- I think finding the pivot, then searching within each makes more intuitive
sense.
## summary
- comp.t: O(log n) in either implementation due to binary search
- comp.s: O(1) just storing the targetIndex
- OK, a couple things: getting the pivot search correct was difficult, not all
test cases were actually rotated, and I think the logic ends up getting more
convoluted because of all of these conditions. I submitted a working solution
and looked at the other responses and saw that they essentially break the
problem down into whether or not you are searching in the sorted left half or
the sorted right half, and just ignore the discontinuity. I will try this
implementation.
- I implemented the special-case logic and think it is both more complex and
harder to reason about.
## tags
medium array
medium binary search
medium two pointers
problems/00033-search-in-rotated-sorted-array/golang/solution.go
package main
import "fmt"
func search(nums []int, target int) int {
l, r := 0, len(nums)-1
t := -1
for l <= r {
m := l + (r-l)/2
if nums[m] == target {
t = m
break
}
if nums[l] <= nums[m] {
if nums[m] < target {
l = m + 1
} else if target < nums[l] {
l = m + 1
} else {
r = m - 1
}
} else {
if target < nums[m] {
r = m - 1
} else if nums[r] < target {
r = m - 1
} else {
l = m + 1
}
}
}
return t
}
func main() {
fmt.Println(search([]int{4, 5, 6, 7, 0, 1, 2}, 0))
fmt.Println(search([]int{4, 5, 6, 7, 0, 1, 2}, 3))
fmt.Println(search([]int{1}, 0))
}
problems/00033-search-in-rotated-sorted-array/typescript/solution.ts
function searchViaPivotPoint(nums: number[], target: number): number {
/* pivot happens when there is drop from large to small */
/* find pivotIndex */
let leftIndex = 0;
let rightIndex = nums.length - 1;
let pivotIndex = -1;
while (leftIndex < rightIndex) {
let midIndex = leftIndex + Math.floor((rightIndex - leftIndex) / 2);
if (rightIndex - leftIndex === 1 && nums[rightIndex] < nums[leftIndex]) {
pivotIndex = leftIndex;
break;
} else if (nums[leftIndex] < nums[midIndex]) {
/* we overshot */
leftIndex = midIndex;
} else {
rightIndex = midIndex;
}
}
/* console.log({ nums, target, pivotIndex, leftIndex, rightIndex }); */
if (pivotIndex === -1) {
leftIndex = 0;
rightIndex = nums.length - 1;
} else if (nums[0] <= target && target <= nums[pivotIndex]) {
leftIndex = 0;
rightIndex = pivotIndex;
} else {
leftIndex = pivotIndex + 1;
rightIndex = nums.length - 1;
}
/* normal binary search */
let targetIndex = -1;
while (leftIndex <= rightIndex) {
let midIndex = leftIndex + Math.floor((rightIndex - leftIndex) / 2);
/* console.log({ nums, target, leftIndex, rightIndex, midIndex }); */
if (nums[midIndex] === target) {
targetIndex = midIndex;
break;
} else if (nums[midIndex] < target) {
leftIndex = midIndex + 1;
} else {
rightIndex = midIndex - 1;
}
}
if (nums[leftIndex] === target) {
targetIndex = leftIndex;
}
return targetIndex;
}
function search(nums: number[], target: number): number {
let leftIndex = 0;
let rightIndex = nums.length - 1;
let targetIndex = -1;
while (leftIndex <= rightIndex) {
let midIndex = leftIndex + Math.floor((rightIndex - leftIndex) / 2);
if (nums[midIndex] === target) {
targetIndex = midIndex;
break;
}
if (nums[leftIndex] <= nums[midIndex]) {
/* mid point is in the left sorted portion of the array */
if (nums[midIndex] < target) {
/* "somewhere" to the right of midpoint */
leftIndex = midIndex + 1;
} else if (target < nums[leftIndex]) {
/* there is no way we missed it because we started left at 0, so this
is also "somewhere" to the right of mid point */
leftIndex = midIndex + 1;
} else {
/* it is in between our leftIndex and midIndex */
rightIndex = midIndex - 1;
}
} else {
/* mid point is in the right sorted portion of the array */
if (target < nums[midIndex]) {
/* target is "somewhere" to the left of the midpoint */
rightIndex = midIndex - 1;
} else if (nums[rightIndex] < target) {
/* there is no way we missed it because we started right at nums.length
- 1, so this is also "somwhere" to the left of mid point */
rightIndex = midIndex - 1;
} else {
/* it is in between our midIndex and rightIndex */
leftIndex = midIndex + 1;
}
}
}
return targetIndex;
}
console.log(search([1, 3], 1));
console.log(search([1, 3, 5], 1));
console.log(search([4, 5, 6, 7, 0, 1, 2], 3));
console.log(search([1, 3, 5, 6], 1));
console.log(search([4, 5, 6, 7, 0, 1, 2], 0));
console.log(search([1], 0));
export {};
problems/00036-valid-sudoku/README.md
# [SOLVED] 36 - Valid Suduko
[Valid Suduko](https://leetcode.com/problems/valid-sudoku/)
## notes
### first thoughts
- write a validation function that takes an `[]string` and confirms it only
encounters a non `.` key once by storing in a map
- check each row, check each column, then build each ninth section and confirm
## summary
- I wrote the typescript solution before the golang, and I honestly think I like
it better. the logic is pretty clear and we just do a single pass of all
numbers, short circuiting when we enter invalid state.
- comp.t: O(1) as we have a fixed set of sets to check
- comp.s: O(1) we use a lookup but it is of fixed size
## tags
medium array
problems/00036-valid-sudoku/golang/solution.go
package main
func isValid(row []byte) bool {
lookup := make(map[byte]bool, 9)
for _, b := range row {
if b == '.' {
continue
}
if _, ok := lookup[b]; ok {
return false
} else {
lookup[b] = true
}
}
return true
}
func isValidSudoku(board [][]byte) bool {
var setsToValidate [][]byte
// add all actual rows
setsToValidate = append(setsToValidate, board...)
// add all columns
for x := 0; x < len(board[0]); x++ {
var col []byte
for y := 0; y < len(board); y++ {
col = append(col, board[y][x])
}
setsToValidate = append(setsToValidate, col)
}
// add all ninth sections
positions := []int{0, 3, 6}
for _, py := range positions {
for _, px := range positions {
var section []byte
for x := px; x < px+3; x++ {
for y := py; y < py+3; y++ {
section = append(section, board[y][x])
}
}
setsToValidate = append(setsToValidate, section)
}
}
res := true
for _, s := range setsToValidate {
if !isValid(s) {
res = false
break
}
}
return res
}
func main() {
}
problems/00036-valid-sudoku/typescript/solution.ts
/* y=0 through y=8 */
/* x=0 through x=8 */
/* box 0 through 8 */
function isValidSudoku(board: string[][]): boolean {
const lookups = new Map<string, Set<string>>();
for (let y = 0; y < board.length; y++) {
for (let x = 0; x < board.length; x++) {
const keys = [];
keys.push(`y=${y}`);
keys.push(`x=${x}`);
if (x <= 2 && y <= 2) {
keys.push("box=0");
} else if (x <= 2 && y <= 5) {
keys.push("box=1");
} else if (x <= 2 && y <= 8) {
keys.push("box=2");
} else if (x <= 5 && y <= 2) {
keys.push("box=3");
} else if (x <= 5 && y <= 5) {
keys.push("box=4");
} else if (x <= 5 && y <= 8) {
keys.push("box=5");
} else if (x <= 8 && y <= 2) {
keys.push("box=6");
} else if (x <= 8 && y <= 5) {
keys.push("box=7");
} else if (x <= 8 && y <= 8) {
keys.push("box=8");
} else {
console.log("huh", x, y);
return false;
}
const num = board[y][x];
if (num === ".") {
continue;
}
for (const key of keys) {
const lookup = lookups.get(key);
if (lookup === undefined) {
lookups.set(key, new Set([num]));
} else {
if (lookup.has(num)) {
return false;
}
lookup.add(num);
lookups.set(key, lookup);
}
}
/* console.log({ keys, num, lookups }); */
}
}
return true;
}
console.log(
isValidSudoku([
["5", "3", ".", ".", "7", ".", ".", ".", "."],
["6", ".", ".", "1", "9", "5", ".", ".", "."],
[".", "9", "8", ".", ".", ".", ".", "6", "."],
["8", ".", ".", ".", "6", ".", ".", ".", "3"],
["4", ".", ".", "8", ".", "3", ".", ".", "1"],
["7", ".", ".", ".", "2", ".", ".", ".", "6"],
[".", "6", ".", ".", ".", ".", "2", "8", "."],
[".", ".", ".", "4", "1", "9", ".", ".", "5"],
[".", ".", ".", ".", "8", ".", ".", "7", "9"],
])
);
console.log(
isValidSudoku([
["8", "3", ".", ".", "7", ".", ".", ".", "."],
["6", ".", ".", "1", "9", "5", ".", ".", "."],
[".", "9", "8", ".", ".", ".", ".", "6", "."],
["8", ".", ".", ".", "6", ".", ".", ".", "3"],
["4", ".", ".", "8", ".", "3", ".", ".", "1"],
["7", ".", ".", ".", "2", ".", ".", ".", "6"],
[".", "6", ".", ".", ".", ".", "2", "8", "."],
[".", ".", ".", "4", "1", "9", ".", ".", "5"],
[".", ".", ".", ".", "8", ".", ".", "7", "9"],
])
);
export {};
problems/00042-trapping-rain-water/README.md
# [OPEN] 42 - Trapping Rain Water
[Trapping Rain Water](https://leetcode.com/problems/trapping-rain-water/)
## notes
- in progress
## summary
## tags
problems/00042-trapping-rain-water/typescript/solution.ts
function trap(height: number[]): number {
/* first thought: break the heights into 2d grid, then run every y value
(every row) to determine the volume of water */
/* - this would be O(n) to create the grid, then O(k * n) k is the largest
value to traverse it. */
/* make grid */
const grid: number[][] = [[]];
for (let x = 0; x < height.length; x++) {
const maxY = height[x];
for (let y = 0; y <= maxY; y++) {
/* check if row exists */
if (grid[y] === undefined) {
/* no: create */
grid[y] = [];
}
grid[y][x] = 1;
}
}
/* traverse grid by y collecting all rainwater */
let trappedRainWater = 0;
for (let y = 1; y < grid.length; y++) {
const row = grid[y];
let indexOfWall = -1;
for (let x = 0; x < row.length; x++) {
const val = grid[y][x];
if (val === 1) {
if (indexOfWall < 0) {
/* first wall we have found */
indexOfWall = x;
} else {
/* we have a previous wall */
trappedRainWater += x - (indexOfWall + 1);
indexOfWall = x;
}
}
/* console.log({ x, y, val, trappedRainWater }); */
}
}
/* console.log(grid); */
/* console.log("trapped rainwater:", trappedRainWater); */
return trappedRainWater;
}
trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]);
trap([4, 2, 0, 3, 2, 5]);
export {};
problems/00049-group-anagrams/README.md
# [SOLVED] 49 - Group Anagrams
[Group Anagrams](https://leetcode.com/problems/group-anagrams/)
## notes
### first thoughts
- sort letters, look up key in map, store in slice
- assume repeated letters need to also be repeated (e.g. “aa” and “a” are not
anagraps)
- solution will be comp.t O(n) to go through every item, then O(ni log ni) to
sort `n[i]` and comp.s O(n) to store in map
- I need to go through every string, so I am at least O(n). is there any way I
can look up character by character without having to sort the individual
words? can’t think of one
## summary
- comp.t: O(n) for every word, then O(c log c) for the number of characters
sorted.
- comp.s: O(n) storing each word in a map
## tags
medium hashing
problems/00049-group-anagrams/golang/solution.go
package main
import (
"fmt"
"sort"
"strings"
)
func groupAnagrams(strs []string) [][]string {
// build lookup of anagrams
lookup := make(map[string][]string, len(strs))
for _, word := range strs {
// make key
letters := strings.Split(word, "")
sort.Strings(letters)
key := strings.Join(letters, "")
// find any existing anagrams
anagrams, ok := lookup[key]
if !ok {
anagrams = []string{}
}
// add the new one and set
anagrams = append(anagrams, word)
lookup[key] = anagrams
}
// build output
var output [][]string
for _, anagrams := range lookup {
output = append(output, anagrams)
}
return output
}
func main() {
fmt.Println(groupAnagrams([]string{"eat", "tea", "tan", "ate", "nat", "bat"}))
fmt.Println(groupAnagrams([]string{""}))
fmt.Println(groupAnagrams([]string{"a"}))
}
problems/00049-group-anagrams/typescript/solution.ts
const createAnagramHashKey = (s: string): string => {
return s.split("").sort().join("");
};
/* time: O(n) */
/* space: O(n) */
function groupAnagrams(strs: string[]): string[][] {
const lookup = strs.reduce((memo, word) => {
const key = createAnagramHashKey(word);
const existingWords = memo.get(key);
if (existingWords === undefined) {
memo.set(key, [word]);
} else {
existingWords.push(word);
memo.set(
key,
existingWords
); /* TODO: not sure if I need to actually set this because existingWords is ref? */
}
return memo;
}, new Map<string, string[]>());
const output = [];
for (const [_key, value] of lookup) {
output.push(value);
}
return output;
}
console.log(groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"]));
console.log(groupAnagrams([""]));
console.log(groupAnagrams(["a"]));
export {};
problems/00051-n-queens/README.md
# [OPEN] 51 - N-Queens
[N-Queens](https://leetcode.com/problems/n-queens/)
## notes
### first thoughts
- `n` can be 1
- `n=2` has no solutions
- I could have one or more queens in the same position in two distinct
solutions, but not all of them.
- brute force would iterate all possible positions which would be I think
`2^(n^2)`. i.e. for every `n` you have two options queen or no queen.
- can I find a sub problem? I do not see the solution to `n=1` in the solution
to `n=2`.
- if I brute force I can restrict the problem space by reducing the valid
squares. e.g. `place(queen_positions, available_coords, remaining_queens)`
which would return `queen_positions` once `remaining_queens=0`. if it comes
back empty I know it is not possible.
### second thoughts
- what is the subproblem?
- I want helper to take a board, and number of remaining queens, and return to
me all the possible boards
- problem. I keep getting duplicate boards being inserted and I do not know
why. my intuition is that the lookup should prevent this.
- problem. I loop over every possible x and y value
## summary
## tags
problems/00051-n-queens/typescript/solution-2.ts
function solveNQueens(n: number): string[][] {
const printBoard = (board: string[][]) => {
for (const row of board) {
console.log(row.join(""));
}
};
const generateBoard = (size: number) => {
const board: string[][] = [];
for (let y = 0; y < size; y++) {
board.push(Array(size).fill("."));
}
return board;
};
const keyify = (board: string[][]): string => {
return board.map((r) => r.join("")).join("");
};
const checkQueens = (x: number, y: number, board: string[][]) => {
/* vertical */
for (let y2 = 0; y2 < board.length; y2++) {
if (board[y2][x] === "Q") {
return false;
}
}
/* north east */
for (let x2 = x + 1, y2 = y - 1; x2 < n && 0 <= y2; x2++, y2--) {
if (board[y2][x2] === "Q") {
return false;
}
}
/* south east */
for (let x2 = x + 1, y2 = y + 1; x2 < n && y2 < n; x2++, y2++) {
if (board[y2][x2] === "Q") {
return false;
}
}
/* south west */
for (let x2 = x - 1, y2 = y + 1; 0 <= x2 && y2 < n; x2--, y2++) {
if (board[y2][x2] === "Q") {
return false;
}
}
/* north west */
for (let x2 = x - 1, y2 = y - 1; 0 <= x2 && 0 <= y2; x2--, y2--) {
if (board[y2][x2] === "Q") {
return false;
}
}
return true;
};
const lookup = new Map<string, string[][]>();
const helper = (board: string[][], remainingQueens: number): string[][] => {
if (remainingQueens === 0) {
return [board.map((row) => row.join(""))];
}
if (lookup.has(keyify(board))) {
/* we have already found the solutions */
return [];
}
const boards = [];
for (let y = 0; y < board.length; y++) {
/* horizontal */
if (board[y].includes("Q")) {
continue;
}
for (let x = 0; x < board[0].length; x++) {
if (checkQueens(x, y, board)) {
/* we can place! */
const newBoard = JSON.parse(JSON.stringify(board));
newBoard[y][x] = "Q";
const newBoards = helper(newBoard, remainingQueens - 1);
/* lookup.set(keyify(newBoard), newBoards); */
boards.push(...newBoards);
}
}
}
lookup.set(keyify(board), boards);
console.log({ lookup });
return boards;
};
const board = generateBoard(n);
return helper(board, n);
}
console.log(solveNQueens(1));
console.log(solveNQueens(2));
console.log(solveNQueens(3));
console.log(solveNQueens(4));
/* console.log(solveNQueens(5));
* console.log(solveNQueens(6));
* console.log(solveNQueens(7));
* console.log(solveNQueens(8));
* console.log(solveNQueens(9)); */
export {};
problems/00051-n-queens/typescript/solution.ts
type Coord = {
x: number;
y: number;
v: string;
};
function solveNQueens(n: number): string[][] {
const generateBoard = (n: number) => {
const board: string[][] = [];
for (let y = 0; y < n; y++) {
board.push(Array(n).fill("."));
}
return board;
};
const generateCoords = (n: number) => {
const coords = new Set<string>();
for (let y = 0; y < n; y++) {
for (let x = 0; x < n; x++) {
coords.add(
JSON.stringify({
x: x,
y: y,
})
);
}
}
return coords;
};
const determineDisallowed = (coord: Coord, size: number) => {
const disallowedCoords = new Set<string>();
disallowedCoords.add(JSON.stringify(coord));
/* horizontal */
for (let x = 0; x < size; x++) {
disallowedCoords.add(JSON.stringify({ x: x, y: coord.y }));
}
/* vertical */
for (let y = 0; y < size; y++) {
disallowedCoords.add(JSON.stringify({ x: coord.x, y: y }));
}
/* north east */
for (let x = coord.x + 1, y = coord.y - 1; x < size && 0 <= y; x++, y--) {
disallowedCoords.add(JSON.stringify({ x: x, y: y }));
}
/* south east */
for (let x = coord.x + 1, y = coord.y + 1; x < size && y < size; x++, y++) {
disallowedCoords.add(JSON.stringify({ x: x, y: y }));
}
/* south west */
for (let x = coord.x - 1, y = coord.y + 1; 0 <= x && y < size; x--, y++) {
disallowedCoords.add(JSON.stringify({ x: x, y: y }));
}
/* north west */
for (let x = coord.x - 1, y = coord.y - 1; 0 <= x && 0 <= y; x--, y--) {
disallowedCoords.add(JSON.stringify({ x: x, y: y }));
}
return disallowedCoords;
};
const lookup = new Map<string, string[][]>();
const helper = (
queenCoords: Coord[],
availableCoords: Set<string>,
remainingQueens: number
) => {
const b = generateBoard(n);
for (const queenCoord of queenCoords) {
b[queenCoord.y][queenCoord.x] = "Q";
}
const stringBoard = b.map((row) => row.join(""));
const key = stringBoard.join(",");
if (lookup.has(key)) {
return;
}
if (remainingQueens === 0) {
return stringBoard;
}
if (availableCoords.size === 0) {
/* no more avail */
return;
}
const boards: string[][] = [];
for (const sCoord of availableCoords) {
const coord: Coord = JSON.parse(sCoord);
const newQueenCoords = [...queenCoords, coord];
const disallowedCoords = determineDisallowed(coord, n);
/* reduce the available coords by whatever was disallowed */
const newAvailableCoords = new Set<string>();
for (const availCoord of availableCoords) {
if (disallowedCoords.has(availCoord)) {
continue;
}
newAvailableCoords.add(availCoord);
}
const maybeBoard = helper(
newQueenCoords,
newAvailableCoords,
remainingQueens - 1
);
if (maybeBoard === undefined) {
} else {
boards.push(maybeBoard);
}
}
lookup.set(key, boards);
};
const coords = generateCoords(n);
helper([], coords, n);
const boards: string[][] = [];
for (const [_key, val] of lookup) {
for (const board of val) {
boards.push(board);
}
}
return boards;
}
console.log(solveNQueens(1));
console.log(solveNQueens(2));
console.log(solveNQueens(3));
console.log(solveNQueens(4));
console.log(solveNQueens(5));
console.log(solveNQueens(6));
console.log(solveNQueens(7));
console.log(solveNQueens(8));
console.log(solveNQueens(9));
export {};
problems/00055-jump-game/README.md
# [SOLVED] 55 - Jump Game
[Jump Game](https://leetcode.com/problems/jump-game/)
## notes
### first thoughts
- must reach the exact last index
- feels like dynamic programming: reaching the last index from `0` can be broken
down into reaching the last index from a closer index. we accumulate all of
these indices with paths to the end and if we can land on any from 0 we win.
- problem does not ask for the shortest path, so we can be happy and return true
if we have any solution
- what is our state? `indicesToEnd`. starts with one value, which is the last
index. can store in lookup for fast checking. need to check whether 0 to
`nums[i]` is in lookup.
- is there way to avoid looking up those values one by one?
- I am concerned that we have scenario where we move the goal post closer but
then can only reach the end by jumping directly to it. e.g. we cut off a
solution by moving the goal post. OK, thinking some more: this is the maximum
distance we can jump, not a fixed value. therefore if we can reach the end we
could also reach the goal post. therefore it is safe to move the target index
value.
algorithm:
- work backwards through `nums`
- check if `nums[i]` can reach. if it can, move the goal post to `i`. “can
reach” is just a distance calculation.
- we necessarily have to check every value of `n`, so this will likely be O(n)
time complexity.
## summary
- comp.t: O(n) as we necessarily have to check all values
- comp.s: O(1) as we just store `goalPost`
- not sure if this is actually dynamic programming as we are not doing any
recursive breakdown of the problem.
- another solution approach is to realize that you want to be *able* to jump as
far as possible. so you could instead iterate elements from index 0 and keep
track of the maximum distance you could make. that implies that any other
index prior to that maximum distance is also reachable. so as long as you keep
track of the maximum distance you can make you just iterate through all
elements until that maximum distance is greater than or equal to the final
position.
## tags
medium array
medium dynamic programming?
problems/00055-jump-game/golang/solution.go
package main
import "fmt"
func canJump(nums []int) bool {
goalPost := len(nums) - 1
// work backwards from the goal post, updating it to be the closer value
// every time we can make it
for i := len(nums) - 2; 0 <= i; i-- {
distanceToGoal := goalPost - i
maxJumpDistance := nums[i]
if distanceToGoal <= maxJumpDistance {
goalPost = i
}
}
return goalPost == 0
}
func main() {
fmt.Println(canJump([]int{2, 3, 1, 1, 4}))
fmt.Println(canJump([]int{3, 2, 1, 0, 4}))
}
problems/00055-jump-game/typescript/solution.ts
function canJump(nums: number[]): boolean {
let goalPost = nums.length - 1;
for (let i = nums.length - 2; 0 <= i; i--) {
const maxJumpDistance = nums[i];
const distanceToGoal = goalPost - i;
if (distanceToGoal <= maxJumpDistance) {
goalPost = i;
}
}
return goalPost === 0;
}
console.log(canJump([2, 3, 1, 1, 4]));
console.log(canJump([3, 2, 1, 0, 4]));
export {};
problems/00056-merge-intervals/README.md
# [SOLVED] 56 - Merge Intervals
[Merge Intervals](https://leetcode.com/problems/merge-intervals/)
## notes
### first thoughts
- is order guaranteed?
- idea. populate an array with 0s, then fill with 1s for each interval. then
loop the array recording when intervals start and end. this would involve a
lot of redundant population if there is a lot of overlap
- how can I do it with just the interval bounds? I care about the condition
where an interval’s start is less than or equal to another. then I take
`max(int_i_end, int_j_end)`
- lets assume I can calculate overlap correctly regardless of which is the
larger distance, what does this get me? I would still need to perform this for
every combination.
- idea. create some function `merge` which takes `intervals1` and `intervals2`
and returns the merged set. we then call this recursively starting with the
intervals in `intervals[0]` and `intervals[1]`. this still has problem of
having to loop over every interval.
- do I gain anything by having every start value? every end value?
- do I gain anything by sorting? ahhh if I sort by the start value, I know that
I only have to do one pass comparing `intervals[i]` and `intervals[i+1]`
- when sorted, overlap is just when the start of the next is less than or equal
to the end of the prev
## summary
- comp.t: O(n log n) for sort + O(n) for iteration. O(n log n)
- comp.s: O(n). sort happens in place. just need to store n elements in `merged`
- had trouble getting the logic right for creating `merged`. initially was just
combining `intervals[i]` and `intervals[j]` and realized that I needed to
“carry forward” my candidate merge, which led to the current approach.
## tags
medium array
medium sort
problems/00056-merge-intervals/golang/solution.go
package main
import (
"sort"
)
// first working implementation. left as artifact of thought process
func merge(intervals [][]int) [][]int {
sort.Slice(intervals, func(i int, j int) bool {
return intervals[i][0] <= intervals[j][0]
})
var merged [][]int
merged = append(merged, intervals[0])
// i keeps track of our position in `merged`
i := 0
for j := 1; j < len(intervals); j++ {
candidateMerge := intervals[i]
candidateEnd := candidateMerge[1]
intervalJ := intervals[j]
jStart := intervalJ[0]
jEnd := intervalJ[1]
// we know cand.start <= j.start
if jStart <= candidateEnd {
// we can merge j with cand
var mEnd int
if candidateEnd <= jEnd {
mEnd = jEnd
} else {
mEnd = candidateEnd
}
candidateMerge[1] = mEnd
} else {
// no merge,
i = j
merged = append(merged, intervalJ)
}
}
return merged
}
// alternative implementation with clearer code (slice initialization, removing
// the need for `j` by just using the last value)
func merge2(intervals [][]int) [][]int {
// sort intervals by their first values
sort.Slice(intervals, func(i int, j int) bool {
return intervals[i][0] <= intervals[j][0]
})
merged := [][]int{intervals[0]}
for i := 1; i < len(intervals); i++ {
cand := merged[len(merged)-1]
candEnd := cand[1]
start := intervals[i][0]
end := intervals[i][1]
if start <= candEnd {
// merge it. note that we do not need to adjust start value because we
// have previously set it to the minimum (base case of adding 0th element
// and then below of adding it directly).
if candEnd < end {
merged[len(merged)-1][1] = end
} else {
merged[len(merged)-1][1] = candEnd
}
} else {
// we cannot merge this interval, so add it directly
merged = append(merged, []int{start, end})
}
}
return merged
}
func main() {
merge([][]int{{1, 3}})
merge([][]int{{1, 3}, {2, 6}})
merge([][]int{{1, 3}, {2, 6}, {8, 10}, {15, 18}})
merge([][]int{{2, 6}, {8, 10}, {15, 18}, {1, 3}})
}
problems/00056-merge-intervals/typescript/solution.ts
function merge(intervals: number[][]): number[][] {
intervals.sort((a, b) => {
return a[0] - b[0];
});
const merged = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
const mergeEnd = merged[merged.length - 1][1];
const [start, end] = intervals[i];
if (start <= mergeEnd) {
/* we can merge it */
merged[merged.length - 1][1] = Math.max(mergeEnd, end);
} else {
merged.push([start, end]);
}
}
return merged;
}
export {};
problems/00059-spiral-matrix-ii/README.md
# [OPEN] 59 - Spiral Matrix II
[Spiral Matrix II](https://leetcode.com/problems/spiral-matrix-ii/description/)
## notes
## summary
## tags
problems/00059-spiral-matrix-ii/typescript/solution.ts
const printMatrix = (m: number[][]) => {
console.log("Matrix:");
for (const row of m) {
console.log(row);
}
console.log();
};
function generateMatrix(n: number): number[][] {
let minX = 0;
let maxX = n - 1;
let minY = 0;
let maxY = n - 1;
/* generate array of arrays */
const mat = new Array(n);
for (let i = 0; i < n; i++) {
mat[i] = Array(n).fill(-1);
}
console.log({ mat });
let val = 1;
/* TBD whether this is correct */
while (minX <= maxX) {
/* move right */
for (let y = minY, x = minX; x <= maxX; x++) {
mat[y][x] = val;
console.log("moving right");
console.log({ x, y, mat });
val++;
}
minY++;
console.log({ minX, maxX, minY, maxY });
/* move down */
for (let y = minY, x = maxX; y <= maxY; y++) {
mat[y][x] = val;
console.log("moving down");
console.log({ x, y, mat });
val++;
}
maxX--;
console.log({ minX, maxX, minY, maxY });
/* move left */
for (let y = maxY, x = maxX; minX <= x; x--) {
mat[y][x] = val;
console.log("moving left");
console.log({ x, y, mat });
val++;
}
maxY--;
console.log({ minX, maxX, minY, maxY });
/* move up */
for (let y = maxY, x = minX; minY <= y; y--) {
mat[y][x] = val;
console.log("moving up");
console.log({ x, y, mat });
val++;
}
minX++;
console.log({ minX, maxX, minY, maxY });
}
console.log({ n, mat });
return mat;
}
printMatrix(generateMatrix(1));
printMatrix(generateMatrix(2));
printMatrix(generateMatrix(3));
export {};
problems/00061-rotate-list/README.md
# [OPEN] 61 - Rotate List
[Rotate List](https://leetcode.com/problems/rotate-list/)
## notes
### first thoughts
- we do not know the length of the list without walking it
- `k` is (potentially) much larger than the length of the list
- we need to determine the length, by walking the list, and if `k` is less than
the length, cut, or if we get to the end and still have values of k, determine
the cut index
- while we are walking we should record the index and reference to the
node. could just stuff into an array.
### second thoughts
- ah, we need four things for non-zero `sliceIndex`:
- the original head
- the node previous to the slice index
- the node at the slice index
- the last node
- comp.t: O(n) because we must iterate all elements of the ListNode
- comp.s: O(1) because we only store four pieces of data
### solution 2
- this approach creates an array to begin with
- comp.t: O(n) because we must iterate all elements of the ListNode
- comp.s: O(n) because we store each ListNode in an array
## summary
## tags
problems/00061-rotate-list/typescript/solution-1.ts
class ListNode {
val: number;
next: ListNode | null;
constructor(val?: number, next?: ListNode | null) {
this.val = val === undefined ? 0 : val;
this.next = next === undefined ? null : next;
}
}
const arrFromListNode = (ln: ListNode | null): number[] => {
const arr: number[] = [];
while (ln !== null) {
arr.push(ln.val);
ln = ln.next;
}
return arr;
};
const listNodeFrom = (arr: number[]): ListNode | null => {
if (arr.length === 0) {
return null;
}
let holder = new ListNode(arr[0]);
const head = holder;
for (let i = 1; i < arr.length; i++) {
const next = new ListNode(arr[i]);
holder.next = next;
holder = next;
}
return head;
};
function rotateRight(head: ListNode | null, k: number): ListNode | null {
/* base cases */
if (k === 0) {
return head;
}
if (head === null) {
return head;
}
const getLength = (head: ListNode | null) => {
let length = 0;
while (head !== null) {
head = head.next;
length++;
}
return length;
};
const length = getLength(head);
if (length === 0 || length === 1) {
return head;
}
const sliceIndex = k % length;
console.log({ sliceIndex, k, length });
const originalHead = head;
let prev = originalHead;
let newHead: ListNode | null = null;
let last = originalHead;
let curr: ListNode | null = originalHead;
let count = sliceIndex - 1;
while (curr !== null) {
count--;
if (count === 0) {
prev = curr;
newHead = curr.next;
}
last = curr;
curr = curr.next;
}
console.log({ originalHead, prev, newHead, last });
prev.next = null;
last.next = originalHead;
return newHead;
}
/* console.log(arrFromListNode(rotateRight(null, 0))); */
/* console.log(arrFromListNode(rotateRight(listNodeFrom([1]), 99))); */
console.log(arrFromListNode(rotateRight(listNodeFrom([1, 2]), 1)));
console.log(arrFromListNode(rotateRight(listNodeFrom([1, 2, 3, 4, 5]), 2)));
/* console.log(arrFromListNode(rotateRight(listNodeFrom([0, 1, 2]), 4))); */
export {};
problems/00061-rotate-list/typescript/solution-2.ts
class ListNode {
val: number;
next: ListNode | null;
constructor(val?: number, next?: ListNode | null) {
this.val = val === undefined ? 0 : val;
this.next = next === undefined ? null : next;
}
}
const arrFromListNode = (ln: ListNode | null): number[] => {
const arr: number[] = [];
while (ln !== null) {
arr.push(ln.val);
ln = ln.next;
}
return arr;
};
const listNodeFrom = (arr: number[]): ListNode | null => {
if (arr.length === 0) {
return null;
}
let holder = new ListNode(arr[0]);
const head = holder;
for (let i = 1; i < arr.length; i++) {
const next = new ListNode(arr[i]);
holder.next = next;
holder = next;
}
return head;
};
function rotateRight(head: ListNode | null, k: number): ListNode | null {
/* base cases */
if (k === 0) {
return head;
}
if (head === null) {
return head;
}
const arr: ListNode[] = [];
while (head !== null) {
arr.push(head);
head = head.next;
}
if (arr.length === 1) {
return arr[0];
}
const sliceIndex = k % arr.length;
console.log({ k, sliceIndex });
const originalHead = arr[0];
let prev = arr[sliceIndex];
let newHead = arr[sliceIndex + 1];
let last = arr[arr.length - 1];
console.log({ originalHead, prev, newHead, last });
prev.next = null;
last.next = originalHead;
return newHead;
}
/* console.log(arrFromListNode(rotateRight(null, 0))); */
/* console.log(arrFromListNode(rotateRight(listNodeFrom([1]), 99))); */
console.log(arrFromListNode(rotateRight(listNodeFrom([1, 2]), 1)));
/* console.log(arrFromListNode(rotateRight(listNodeFrom([1, 2, 3, 4, 5]), 2))); */
/* console.log(arrFromListNode(rotateRight(listNodeFrom([0, 1, 2]), 4))); */
export {};
problems/00064-minimum-path-sum/README.md
# [SOLVED] 64 - Minimum Path Sum
[Minimum Path Sum](https://leetcode.com/problems/minimum-path-sum/)
## notes
### first thoughts
- feels like dynamic programming
- I have to look at all values in case there is some really small value, so
comp.t is going to be O(m + n)
- state: y, x, cost
- transition function: cost(x,y) = sum(x,y) + min(cost(x+1,y), cost(y+1, x))
- memo: `lookup[y][x]`
- base cases: `lookup[desty][destx] = sum[y][x]`
- going to work backward from end
## summary
- comp.t: O(n + m) because we need to look at all values
- comp.s: O(n * m) because we store in a lookup
- initially my notes were written assuming we’d start at the end and then work
our way back to 0,0, but I ended up implementing starting at 0,0 and working
toward the base case. I switched this on implementation because it seemed more
clear to pass the starting position and have the base case be embedded in the
lookup rather than the other way around.
- one interesting golang approach basically said “my initial condition is
val(0,0), I know that the entire y column at x=0 are only accessible via
downward movement, so I can calculate their costs, same goes for row y=0
summing across, and so I can update the grid directly, and then walk from 1,1
to the bottom right by taking the min() of either arrival from left or from
above.” so it solved for the y=0 and x=0 edges of the grid, and then used
those to walk downward. clever!
- interesting from the typescript: `Array.fill`, which I was not aware of.
## tags
problems/00064-minimum-path-sum/golang/solution.go
package main
import (
"fmt"
"math"
)
func min(a, b int) int {
if a < b {
return a
}
return b
}
func helper(grid [][]int, lookup [][]int, x, y int) int {
if x < 0 || len(grid[0]) <= x {
return math.MaxInt
}
if y < 0 || len(grid) <= y {
return math.MaxInt
}
if lookup[y][x] != -1 {
return lookup[y][x]
}
minPath := grid[y][x] + min(helper(grid, lookup, x+1, y), helper(grid, lookup, x, y+1))
lookup[y][x] = minPath
return minPath
}
func minPathSum(grid [][]int) int {
var lookup [][]int
for range grid {
row := make([]int, len(grid[0]))
for i := range row {
row[i] = -1
}
lookup = append(lookup, row)
}
destY := len(grid) - 1
destX := len(grid[0]) - 1
// initial condition
lookup[destY][destX] = grid[destY][destX]
return helper(grid, lookup, 0, 0)
}
func main() {
fmt.Println(minPathSum([][]int{{1, 3, 1}, {1, 5, 1}, {4, 2, 1}}))
fmt.Println(minPathSum([][]int{{1, 2, 3}, {4, 5, 6}}))
}
problems/00064-minimum-path-sum/typescript/solution.ts
const helper = (
grid: number[][],
lookup: number[][],
x: number,
y: number
): number => {
if (y < 0 || grid.length <= y) {
return Number.MAX_SAFE_INTEGER;
}
if (x < 0 || grid[0].length <= x) {
return Number.MAX_SAFE_INTEGER;
}
const maybeCached = lookup[y][x];
if (maybeCached !== -1) {
return maybeCached;
}
const val =
grid[y][x] +
Math.min(helper(grid, lookup, x + 1, y), helper(grid, lookup, x, y + 1));
lookup[y][x] = val;
return val;
};
function minPathSum(grid: number[][]): number {
const lookup: number[][] = [];
for (const row of grid) {
lookup.push(row.map((_v) => -1));
}
const destY = grid.length - 1;
const destX = grid[0].length - 1;
lookup[destY][destX] = grid[destY][destX];
return helper(grid, lookup, 0, 0);
}
console.log(
minPathSum([
[1, 3, 1],
[1, 5, 1],
[4, 2, 1],
])
);
console.log(
minPathSum([
[1, 2, 3],
[4, 5, 6],
])
);
export {};
problems/00074-search-a-2d-matrix/README.md
# [SOLVED] 74 - Search a 2D Matrix
[Search a 2D Matrix](https://leetcode.com/problems/search-a-2d-matrix/)
## notes
### first thoughts
### golang implementation thoughts
- smells like binary search
- ordering allows for binary search of both columns and rows
- must run in O(log(m + n)), so very likely binary search
- solution approach: bs first column to find closest without going over, then bs
row. bs’ing first column can be made easier by inspecting the first and last
values of the array. if they are bounds, that is our condition for stopping.
- solution approach: append each row and just bs it all
## summary
- typescript solution written when I was first starting solving, golang more
recently
- comp.t: O(m + n) in both due to size of search space + binary search
- comp.s: O(1) no extra storage used
## tags
medium binary search
medium array
medium matrix
problems/00074-search-a-2d-matrix/golang/solution.go
package main
// return index if found, -1 if not
func bs(nums []int, target int) int {
for l, r, m := 0, len(nums)-1, -1; l <= r; {
m = l + (r-l)/2
if nums[m] == target {
// we are done
return m
} else if target < nums[m] {
// target is to left of mid
r = m - 1
} else if nums[m] < target {
// target is to the right of mid
l = m + 1
} else {
// can't happen
}
}
return -1
}
func searchMatrix(matrix [][]int, target int) bool {
// cheeky: append all values to a single []int that we can run bs on
var all []int
for _, row := range matrix {
all = append(all, row...)
}
return bs(all, target) != -1
}
// alternative implementation demonstrating binary search using criteria
func searchMatrix2(matrix [][]int, target int) bool {
// find the y index to search within
y := -1
for l, r, m := 0, len(matrix)-1, -1; l <= r; {
m = l + (r-l)/2
if matrix[m][0] <= target && target <= matrix[m][len(matrix[m])-1] {
// our target could be within this row
y = m
break
} else if target < matrix[m][0] {
// target is lower than this row
r = m - 1
} else if matrix[m][0] < target {
// target is higher than this row
l = m + 1
} else {
// can't happen
}
}
if y == -1 {
// couldn't find a row that could contain target
return false
}
return bs(matrix[y], target) != -1
}
func main() {
}
problems/00074-search-a-2d-matrix/typescript/solution.ts
/* non-decreasing: ca nhave same value sequentially (no guarantee of
uniqueness) */
/* a consequence of rows of non-decreasing rows and first number larger than
any of the previous is that both rows and columns can be searched using binary
search */
/* idea: find the possible column first, return false if not possible. if
possible found, search row */
/* matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 */
/* state: yLow, yHigh, yMid, xLow, xHigh, xMid */
/* 0: yLow=0, yHigh=3-1 yMid=Math.floor((2-0)/2)=1 yVal=matrix[yMid][0]=10
target is less, move yHigh=yMid */
/* 1: yLow=0, yHigh=1 fails condition, check if matrix[yLow] is less than
target. if yes, x search else return false */
/* 2: xLow=0, xHigh=3 xMid=Math.floor((3-0)/2)=1 xVal[matrix[yLow][xMid]=3
target is found */
const binarySearch = (arr: number[], target: number): number => {
let index = -1;
let leftIndex = 0;
let rightIndex = arr.length - 1;
while (leftIndex <= rightIndex) {
const midIndex = Math.floor(leftIndex + (rightIndex - leftIndex) / 2);
const maybeVal = arr[midIndex];
if (maybeVal === target) {
/* found it */
index = midIndex;
break;
} else if (maybeVal < target) {
leftIndex = midIndex + 1;
} else if (target < maybeVal) {
rightIndex = midIndex - 1;
} else {
/* can't happen */
}
}
return index;
};
function searchMatrix(matrix: number[][], target: number): boolean {
let present = false;
let yLow = 0;
let yHigh = matrix.length - 1;
let mid = yLow;
while (yLow <= yHigh) {
mid = Math.floor(yLow + (yHigh - yLow) / 2);
const yValMin = matrix[mid][0];
const yValMax = matrix[mid][matrix[mid].length - 1];
if (target < yValMin) {
yHigh = mid - 1;
} else if (yValMax < target) {
yLow = mid + 1;
} else {
break;
}
}
if (yLow <= yHigh) {
console.log({ mid });
present = binarySearch(matrix[mid], target) !== -1;
}
return present;
}
console.log(
searchMatrix(
[
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 60],
],
3
)
);
console.log(
searchMatrix(
[
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 60],
],
13
)
);
console.log(searchMatrix([[1], [3]], 0));
export {};
problems/00078-subsets/README.md
# [SOLVED] 78 - Subsets
[Subsets](https://leetcode.com/problems/subsets/)
## notes
### first thoughts
- smells like dynamic programming
- likely need to sort to ensure that only unique sets are generated
```
nums=[1,2,3]
always add empty set:
sets = [[]]
do not need to sort nums because each element is unique
start at i = 0 to i < nums.length
iterate sets, adding nums[i] to each member, adding result to local_set
append all of local_set to sets
```
- do not need dynamic programming
- manually working out:
```
[1,2,3]
sets = [[]]
local_set = [[1]]
sets = [[], [1]]
local_set = [[2], [1, 2]]
sets = [[], [1], [2], [1,2]]
local_set = [[3], [1,3], [2,3], [1,2,3]]
sets = [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]
```
- comp.t: O(n)
- comp.s: O(2^n)?
## summary
## tags
problems/00078-subsets/typescript/solution.ts
function subsets(nums: number[]): number[][] {
const sets: number[][] = [[]];
for (let i = 0; i < nums.length; i++) {
const localSet: number[][] = [];
for (const s of sets) {
localSet.push([...s, nums[i]]);
}
sets.push(...localSet);
}
return sets;
}
console.log(subsets([1, 2, 3]));
console.log(subsets([0]));
export {};
problems/00079-word-search/README.md
# [SOLVED] 79 - Word Search
[Word Search](https://leetcode.com/problems/word-search/)
## notes
### first thoughts
- idea. take first character of string, iterate all cells of matrix. write
recursive algorithm to try and walk to find the remaining characters. actually
can just pass every cell to that helper.
- create helper that takes a call and can lookup whether a cell has been visited
before. helper should attempt to walk all valid neighbors and only set the
lookup if it is a valid letter in the word.
- state is going to be: lookup, y, x, and remaining characters
## summary
- comp.t: at each coord, we have 4 options, so O(4^(n * m))?
- comp.s: lookup is just O(len(word))
- the optimizations I looked at were really clever: checking whether word could
fit in matrix, checking whether letter count even made it viable, and then
reversing the word if there were more rare characters in the second half
- my initial golang implementation didn’t realize that maps are passed by
reference, then my `fresh` map function to copy it was too slow, and so I
switched to making my lookup shared across invocations of `helper` and it just
annoys me lol.
## tags
medium depth first search
medium dfs
medium recursive
medium matrix
problems/00079-word-search/golang/solution.go
package main
import "fmt"
type coord struct {
x int
y int
}
func exist(board [][]byte, word string) bool {
var helper func(board [][]byte, x int, y int, chars []byte) bool
lookup := make(map[coord]bool)
helper = func(board [][]byte, x int, y int, chars []byte) bool {
if len(chars) == 0 {
// we are done
return true
}
if x < 0 || len(board[0])-1 < x || y < 0 || len(board)-1 < y {
// out of bounds
return false
}
cell := coord{x: x, y: y}
if _, ok := lookup[cell]; ok {
// we have already been here
return false
}
res := false
if board[y][x] == chars[0] {
lookup[cell] = true
// we have a candidate. check all neighbors
res = helper(board, x+1, y, chars[1:]) ||
helper(board, x-1, y, chars[1:]) ||
helper(board, x, y+1, chars[1:]) ||
helper(board, x, y-1, chars[1:])
}
delete(lookup, cell)
return res
}
for y := 0; y < len(board); y++ {
for x := 0; x < len(board[0]); x++ {
// using []byte(word) is dangerous but assume sane input
if helper(board, x, y, []byte(word)) {
return true
}
}
}
return false
}
func main() {
fmt.Println(exist([][]byte{{'A', 'B', 'C', 'E'}, {'S', 'F', 'C', 'S'}, {'A', 'D', 'E', 'E'}}, "ABCCED"))
fmt.Println(exist([][]byte{{'A', 'B', 'C', 'E'}, {'S', 'F', 'C', 'S'}, {'A', 'D', 'E', 'E'}}, "SEE"))
fmt.Println(exist([][]byte{{'A', 'B', 'C', 'E'}, {'S', 'F', 'E', 'S'}, {'A', 'D', 'E', 'E'}}, "ABCESEEEFS"))
}
problems/00079-word-search/typescript/solution.ts
function exist(board: string[][], word: string): boolean {
/* loop through every x, y of the board */
/* find first character of word */
/* start with i = 0 */
/* keep track of the letters / cells we have visited */
/* recursively check all neighbors, incrementing i and passing lookup of
places we visited */
const seen = new Set<string>();
const helper = (i: number, x: number, y: number): boolean => {
/* base case: */
if (i === word.length) {
return true;
}
if (y < 0 || board.length <= y) {
return false;
}
if (x < 0 || board[0].length <= x) {
return false;
}
if (seen.has(JSON.stringify([x, y]))) {
return false;
}
const char = word[i];
const val = board[y][x];
seen.add(JSON.stringify([x, y]));
let result = false;
if (char === val) {
/* we can continue */
result =
helper(i + 1, x, y + 1) ||
helper(i + 1, x, y - 1) ||
helper(i + 1, x + 1, y) ||
helper(i + 1, x - 1, y);
}
seen.delete(JSON.stringify([x, y]));
return result;
};
/* we still need to iterate through all x and y */
for (let y = 0; y < board.length; y++) {
for (let x = 0; x < board[0].length; x++) {
if (helper(0, x, y)) {
return true;
} else {
/* continue */
}
}
}
return false;
}
export {};
problems/00084-largest-rectangle-in-histogram/README.md
# [OPEN] 84 - Largest Rectangle in Histogram
[Largest Rectangle in Histogram](https://leetcode.com/problems/largest-rectangle-in-histogram/)
## notes
- imported old attempt. failed with time limit exceeded.
## summary
## tags
problems/00084-largest-rectangle-in-histogram/typescript/solution.ts
function largestRectangleArea(heights: number[]): number {
let maxArea = 0;
for (let i = 0; i < heights.length; i++) {
const height = heights[i];
let startIndex = i;
for (let start = i; 0 <= start; start--) {
const maybeStartingHeight = heights[start];
if (height <= maybeStartingHeight) {
startIndex = start;
} else {
break;
}
}
let endIndex = i;
for (let end = i; end < heights.length; end++) {
const maybeEndingHeight = heights[end];
if (height <= maybeEndingHeight) {
endIndex = end;
} else {
break;
}
}
const area = height * (endIndex - startIndex + 1);
/* console.log({ height, startIndex, endIndex, area }); */
if (maxArea < area) {
maxArea = area;
}
}
return maxArea;
}
console.log(largestRectangleArea([2, 1, 5, 6, 2, 3]));
console.log(largestRectangleArea([2, 4]));
export {};
problems/00090-subsets-ii/README.md
# [SOLVED] 90 - Subsets II
[Subsets II](https://leetcode.com/problems/subsets-ii/)
## notes
### first pass thoughts
- same as subsets, but this time use a real set to guard against duplicates
- would need to sort.
- is there a better way?
## summary
- other solutions using backtracking and dfs
## tags
problems/00090-subsets-ii/typescript/solution.ts
function subsetsWithDup(nums: number[]): number[][] {
const lookup = new Set<string>([JSON.stringify([])]);
for (const num of nums) {
const localSubSets: number[][] = [];
for (const subSet of lookup) {
localSubSets.push([...JSON.parse(subSet), num]);
}
for (const localSubSet of localSubSets) {
localSubSet.sort((a, b) => {
return a - b; /* sort ascending */
});
lookup.add(JSON.stringify(localSubSet));
}
}
return [...lookup].map((s) => JSON.parse(s));
}
console.log(subsetsWithDup([1, 2, 2]));
console.log(subsetsWithDup([0]));
export {};
problems/00101-symmetric-tree/README.md
# [SOLVED] 101 - Symmetric Tree
[Symmetric Tree](https://leetcode.com/problems/symmetric-tree/)
## notes
### first thoughts
- I actually had trouble conceptualizing how to handle this. I knew I wanted to
recursively descend, but I kept wanting to use the helper without figuring out
the sub problem.
- the subproblem was how to determine if trees of depth 1 were symmetric. once I
figured this I could derive the base cases that helped with the rest of
implementation
- I do not know off hand how to do the iterative approach
## summary
- comp.t: O(n). we must compare every node
- comp.s: O(log n). the depth of the binary tree is log (n), which is the amount
of stack space we need to execute the recursive solution.
- added an iterative solution that was correct in spirit but relied a bit too
much on convoluted `reverse` usage. I did not think the iterative solution was
easy. There are other iterative approaches too that I did not implement.
## tags
easy binary tree
easy tree
easy recursive
easy iterative
problems/00101-symmetric-tree/golang/solution.go
package main
import "fmt"
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func helper(left *TreeNode, right *TreeNode) bool {
if left == nil && right == nil {
return true
} else if left == nil || right == nil {
return false
}
return left.Val == right.Val && helper(left.Left, right.Right) && helper(left.Right, right.Left)
}
func isSymmetricRec(root *TreeNode) bool {
return helper(root, root)
}
func isSymmetricIter(root *TreeNode) bool {
return false
}
func isSymmetric(root *TreeNode) bool {
return isSymmetricRec(root)
}
func main() {
fmt.Println(isSymmetric(&TreeNode{Val: 1}))
fmt.Println(isSymmetric(&TreeNode{Val: 1, Left: &TreeNode{Val: 2}, Right: &TreeNode{Val: 2}}))
fmt.Println(isSymmetric(&TreeNode{Val: 1, Left: &TreeNode{Val: 2, Left: &TreeNode{Val: 3}, Right: &TreeNode{Val: 4}}, Right: &TreeNode{Val: 2, Left: &TreeNode{Val: 4}, Right: &TreeNode{Val: 3}}}))
fmt.Println(isSymmetric(&TreeNode{Val: 1, Left: &TreeNode{Val: 2, Right: &TreeNode{Val: 3}}, Right: &TreeNode{Val: 2, Right: &TreeNode{Val: 3}}}))
}
problems/00101-symmetric-tree/typescript/solution.ts
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
this.right = right === undefined ? null : right;
}
}
const recursiveHelper = (
leftNode: TreeNode | null,
rightNode: TreeNode | null
): boolean => {
if (leftNode === null && rightNode === null) {
/* both null: equal */
return true;
} else if (leftNode === null || rightNode === null) {
/* only one is null, not equal */
return false;
}
return (
/* values are equal */
leftNode.val === rightNode.val &&
/* the outer node of each should be symmetric */
recursiveHelper(leftNode.left, rightNode.right) &&
/* as well as the inner nodes */
recursiveHelper(leftNode.right, rightNode.left)
);
};
const iterSymmetric = (root: TreeNode | null) => {
if (root === null) {
return true;
}
/* we want to compare that the left-to-right "level" of the left side is
equal to the right-toleft "level" of the right side */
let leftSide = [root.left];
let rightSide = [root.right];
let prevLeftLevel: (TreeNode | null)[] = [];
let prevRightLevel: (TreeNode | null)[] = [];
let sym = true;
while (leftSide.length !== 0 && rightSide.length !== 0 && sym) {
/* compare */
if (leftSide.length !== rightSide.length) {
return false;
}
for (let i = 0; i < leftSide.length; i++) {
const l = leftSide[i];
const r = rightSide[i];
if (l === null && r === null) {
/* no problem */
} else if (l === null || r === null) {
return false;
} else if (l.val !== r.val) {
return false;
}
}
prevLeftLevel = leftSide;
prevRightLevel = rightSide.reverse();
/* left to right */
leftSide = prevLeftLevel.flatMap((n) => {
const l = n === null ? null : n.left;
const r = n === null ? null : n.right;
return [l, r];
});
/* right to left */
rightSide = Array.from(prevRightLevel)
.reverse()
.flatMap((n) => {
const l = n === null ? null : n.left;
const r = n === null ? null : n.right;
return [r, l];
});
if (
leftSide.filter((n) => n !== null).length === 0 &&
rightSide.filter((n) => n !== null).length === 0
) {
break;
}
}
return sym;
};
function isSymmetric(root: TreeNode | null): boolean {
return recursiveHelper(root, root);
}
console.log(
iterSymmetric(
new TreeNode(
1,
new TreeNode(2, null, new TreeNode(3)),
new TreeNode(2, null, new TreeNode(3))
)
)
);
console.log(
iterSymmetric(
new TreeNode(
2,
new TreeNode(
3,
new TreeNode(4),
new TreeNode(5, new TreeNode(8), new TreeNode(9))
),
new TreeNode(
3,
new TreeNode(5, new TreeNode(9), new TreeNode(8)),
new TreeNode(4)
)
)
)
);
export {};
problems/00102-binary-tree-level-order-traversal/README.md
# [SOLVED] 102 - Binary Tree Level Order Traversal
[Binary Tree Level Order Traversal](https://leetcode.com/problems/binary-tree-level-order-traversal/)
## notes
- iterative, rather than recursive
- create a level that we flatmap each level and grab the left / right values
## summary
- comp.t: O(n) in worst case.
- comp.s: O(n) output array
- flatmap is very useful here!
- golang implementation was actually simpler. I may have made it too complicated
w/ `flatMap`
## tags
medium binary tree
medium iterative
problems/00102-binary-tree-level-order-traversal/golang/solution.go
package main
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func levelOrder(root *TreeNode) [][]int {
var output [][]int
var level []*TreeNode
if root != nil {
level = append(level, root)
}
for len(level) != 0 {
var levelVals []int
var nextLevel []*TreeNode
// find the values
for _, tn := range level {
if tn == nil {
continue
}
levelVals = append(levelVals, tn.Val)
// update the next level
if tn.Left != nil {
nextLevel = append(nextLevel, tn.Left)
}
if tn.Right != nil {
nextLevel = append(nextLevel, tn.Right)
}
}
// add them to the output
output = append(output, levelVals)
// update the level
level = nextLevel
}
return output
}
func main() {
}
problems/00102-binary-tree-level-order-traversal/typescript/solution.ts
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
this.right = right === undefined ? null : right;
}
}
function levelOrder(root: TreeNode | null): number[][] {
const output: number[][] = [];
let level = [root].flatMap((n) => {
if (n === null) {
return [];
} else {
return [n];
}
});
while (level.length !== 0) {
output.push(
level.flatMap((n) => {
if (n === null) {
return [];
} else {
return [n.val];
}
})
);
level = level.flatMap((n) => {
if (n === null) {
return [];
}
if (n.left !== null && n.right !== null) {
return [n.left, n.right];
}
if (n.left === null && n.right !== null) {
return [n.right];
}
if (n.left !== null && n.right === null) {
return [n.left];
}
return [];
});
}
return output;
}
console.log(
levelOrder(
new TreeNode(
3,
new TreeNode(9),
new TreeNode(20, new TreeNode(15), new TreeNode(7))
)
)
);
console.log(levelOrder(new TreeNode(1)));
console.log(levelOrder(null));
export {};
problems/00104-maximum-depth-of-binary-tree/README.md
# [SOLVED] 104 - Maximum Depth of Binary Tree
[Maximum Depth of Binary Tree](https://leetcode.com/problems/maximum-depth-of-binary-tree/)
## notes
### first thoughts
- recursive
- add 1 for each level, selecting the maximum depth between left and right
## summary
- comp.t: O(n) where n is the number of nodes in the tree
- comp.s: O(log n) which is the depth of the tree
## tags
easy recursive
easy binary tree
problems/00104-maximum-depth-of-binary-tree/golang/solution.go
package main
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func maxDepth(root *TreeNode) int {
if root == nil {
return 0
}
lHeight := maxDepth(root.Left)
rHeight := maxDepth(root.Right)
var max int
if lHeight < rHeight {
max = rHeight
} else {
max = lHeight
}
return 1 + max
}
func main() {
}
problems/00104-maximum-depth-of-binary-tree/typescript/solution.ts
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
this.right = right === undefined ? null : right;
}
}
function maxDepth(root: TreeNode | null): number {
if (root === null) {
return 0;
}
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
console.log(maxDepth(new TreeNode(1, null, null)));
console.log(maxDepth(new TreeNode(1, null, new TreeNode(2))));
console.log(
maxDepth(
new TreeNode(
3,
new TreeNode(9),
new TreeNode(20, new TreeNode(15), new TreeNode(17))
)
)
);
export {};
problems/00120-triangle/README.md
# [SOLVED] 120 - Triangle
[Triangle](https://leetcode.com/problems/triangle/)
## notes
### first thoughts
- feels like DP
- every branch I get two choices: `i` or `i+1`
- negative numbers are allowed
- I necessarily have to check / rule out all paths
- the last row of a triangle of length `n` will have `n` elements
- I am not sure how the runtime complexity changes with each level being one longer than the next:
- 0: 1 element. 1 path to get there
- 1: 2 element. 2 path to get there
- 2: 3 element. 4 path to get there
- 3: 4 element. 8 path to get there
- so looks like `2^(n-1)`
- state: `i, j`
- fxn: `shorted_path(i, j) = weight(i,j) + min(shorted_path(i - 1, j - 1), shorted_path(i - 1, j))`
- base case: `shortest_path(0,0) = weight(0,0)`
```
2
3 4
6 5 7
4 1 8 3
```
- OK, that appeared to work
- comp.t: `O(n)` we compute each value exactly once
- comp.s: `O(n)` we store each node’s value exactly once
## summary
- I liked the approach where you work bottom up just taking the min for the next
level.
## tags
problems/00120-triangle/typescript/solution-1.ts
function minimumTotal(triangle: number[][]): number {
let minTotal = Number.MAX_SAFE_INTEGER;
const keyify = (i: number, j: number) => {
return `${i}-${j}`;
};
const lookup = new Map<string, number>();
lookup.set(keyify(0, 0), triangle[0][0]);
const helper = (i: number, j: number): number => {
if (j < 0 || i < 0 || triangle[i].length <= j) {
/* we went out of bounds */
return Number.MAX_SAFE_INTEGER;
}
const maybeWeight = lookup.get(keyify(i, j));
if (maybeWeight !== undefined) {
return maybeWeight;
}
const sum =
triangle[i][j] + Math.min(helper(i - 1, j - 1), helper(i - 1, j));
lookup.set(keyify(i, j), sum);
return sum;
};
for (let j = 0; j < triangle[triangle.length - 1].length; j++) {
const sum = helper(triangle.length - 1, j);
if (sum < minTotal) {
minTotal = sum;
}
}
return minTotal;
}
console.log(minimumTotal([[2], [3, 4], [6, 5, 7], [4, 1, 8, 3]]));
console.log(minimumTotal([[-10]]));
export {};
problems/00120-triangle/typescript/solution-2.ts
function minimumTotal(triangle: number[][]): number {
for (let i = triangle.length - 2; 0 <= i; i--) {
for (let j = 0; j < triangle[i].length; j++) {
triangle[i][j] += Math.min(triangle[i + 1][j], triangle[i + 1][j + 1]);
}
}
return triangle[0][0];
}
console.log(minimumTotal([[2], [3, 4], [6, 5, 7], [4, 1, 8, 3]]));
console.log(minimumTotal([[-10]]));
export {};
problems/00128-longest-consecutive-sequence/README.md
# [SOLVED] 128 - Longest Consecutive Sequence
[Longest Consecutive Sequence](https://leetcode.com/problems/longest-consecutive-sequence/)
## notes
### first thoughts
- must run in O(n) time, which means we could still do multiple passes through
- idea. store the numbers encountered in a lookup. when you encounter a number,
check whether sequential values are present. if they are, count them and
compare to max.
### second thoughts
- submitted correct solution but it was slow
- investigation showed that I was doing unnecessary checks. I really only want
to start the sequence check when I know for sure I am at the bottom of it (the
smallest value).
- possibly would have realized this if I had drawn out the map and then asked
“what is performance impact of checking `prev` and `next` for each value?”
- typescript optimization: using `Set` is faster than `Map`
## summary
- comp.t: I think O(n), but for every n we potentially start walking a sequence
whose size is bounded by n.
- comp.s: O(n) for the lookup.
- intuition was correct, but I have no idea why the golang code is only 5th
percentile for performance. some solutions sort, which is invalid re: running
in O(n) time.
- re: speedup, it looks like golang is much faster when you directly access
rather than create new variables, and minimize your lookup checks. the current
version only moves forward to check a sequence if it encounters the number
that is the lowest value in that sequence.
## tags
medium array
problems/00128-longest-consecutive-sequence/golang/solution.go
package main
import "fmt"
func longestConsecutive(nums []int) int {
lookup := make(map[int]bool, len(nums))
for i := range nums {
lookup[nums[i]] = true
}
var max int
for num := range lookup {
if !lookup[num-1] {
// we have the lowest in a sequence
length := 1
for lookup[num+length] {
length++
}
if max < length {
max = length
}
}
}
return max
}
func main() {
fmt.Println(longestConsecutive([]int{100, 4, 200, 1, 3, 2}))
fmt.Println(longestConsecutive([]int{0, 3, 7, 2, 5, 8, 4, 6, 0, 1}))
}
problems/00128-longest-consecutive-sequence/typescript/solution.ts
function longestConsecutive(nums: number[]): number {
const lookup = new Set(nums);
let max = 0;
for (const num of lookup) {
if (!lookup.has(num - 1)) {
/* we are at beginning of sequence */
let length = 1;
while (lookup.has(num + length)) {
length++;
}
if (max < length) {
max = length;
}
}
}
return max;
}
console.log(longestConsecutive([100, 4, 200, 1, 3, 2]));
export {};
problems/00130-surrounded-regions/README.md
# [SOLVED] 130 - Surrounded Regions
[Surrounded Regions](https://leetcode.com/problems/surrounded-regions/)
## notes
### first thoughts
- need to check every square
- need to determine whether an area is captureable
- create lookup(i, j) = capturable
- mark all edges lookup(i, j) = false
- iterate remaining i and j
- once an O is found, start DFS for x and y
- if board[x,y] is X, back up
- if board[x,y] is O, add x and y to local subset
- check if capturable
- if present and true, we have already been here. back up
- if present and false, we have not been here. continue
- continue until board is complete
- state: `i`, `j`
- transition: `captureable(i, j) = board[i][j] === ‘O’ && (board[i+1][j] === ‘X’
|| capturable(i+1, j) && ... ) `
- note: this did not work. I kept bouncing back and forth between `i` and `j`
values I had already visited. I
### second thoughts
- iterate non-border cells
- if you encounter an `O`, walk to find every adjacent `O`, recording coordinates
- if coordinates do not contain a border position, flip to `X`
- comp.t: O(m + n)
- comp.s: O(m + n) (storing visited)
- worked, but was very slow
### third thoughts
- top performing ones did dfs from border using a queue
- implemented and got slightly better performance but not much better.
## summary
## tags
problems/00130-surrounded-regions/typescript/solution-1.ts
/**
Do not return anything, modify board in-place instead.
*/
function solve(board: string[][]): void {
console.log("init:");
console.log(board);
const keyify = (i: number, j: number) => {
return `${i},${j}`;
};
const visited = new Set<string>();
/* iterate non-boarder cells */
for (let i = 1; i < board.length - 1; i++) {
for (let j = 1; j < board[0].length - 1; j++) {
const key = keyify(i, j);
if (visited.has(key)) {
continue;
}
if (board[i][j] === "X") {
continue;
}
/* board[i][j] is "O". walk adjacent until completely filled out */
const seenOs = new Set<string>();
let borderDetected = false;
const helper = (i2: number, j2: number) => {
const key2 = keyify(i2, j2);
/* we have been here before */
if (seenOs.has(key2)) {
return;
}
/* out of bounds */
if (
i2 < 0 ||
board.length - 1 < i2 ||
j2 < 0 ||
board[0].length - 1 < j2
) {
return;
}
/* candidate! */
if (board[i2][j2] === "O") {
/* did we hit border? */
if (
i2 === 0 ||
i2 === board.length - 1 ||
j2 === 0 ||
j2 === board[0].length - 1
) {
borderDetected = true;
}
seenOs.add(key2);
helper(i2 - 1, j2);
helper(i2 + 1, j2);
helper(i2, j2 - 1);
helper(i2, j2 + 1);
return;
}
};
helper(i, j);
/* add all to visited */
for (const k of seenOs) {
visited.add(k);
if (borderDetected) {
/* cannot turn them to "X"s */
} else {
/* can safely turn them to Xs */
const [i3, j3] = k.split(",").map((s) => Number(s));
board[i3][j3] = "X";
}
}
}
}
}
const board1 = [
["X", "X", "X", "X"],
["X", "O", "O", "X"],
["X", "X", "O", "X"],
["X", "O", "X", "X"],
];
solve(board1);
console.log(board1);
const board2 = [
["O", "X", "X", "O", "X"],
["X", "O", "O", "X", "O"],
["X", "O", "X", "O", "X"],
["O", "X", "O", "O", "O"],
["X", "X", "O", "X", "O"],
];
solve(board2);
console.log(board2);
export {};
problems/00130-surrounded-regions/typescript/solution-2.ts
/**
Do not return anything, modify board in-place instead.
*/
function solve(board: string[][]): void {
const maxY = board.length - 1;
const maxX = board[0].length - 1;
const queue: number[][] = [];
/* turn every border cell to B */
for (let y = 0; y <= maxY; y++) {
if (board[y][0] === "O") {
board[y][0] = "B";
queue.push([y, 0]);
}
if (board[y][maxX] === "O") {
board[y][maxX] = "B";
queue.push([y, maxX]);
}
}
for (let x = 0; x <= maxX; x++) {
if (board[0][x] === "O") {
board[0][x] = "B";
queue.push([0, x]);
}
if (board[maxY][x] === "O") {
board[maxY][x] = "B";
queue.push([maxY, x]);
}
}
const directions = [
[0, 1],
[0, -1],
[1, 0],
[-1, 0],
];
while (queue.length !== 0) {
const coord = queue.pop();
if (coord === undefined) {
continue;
}
for (const d of directions) {
const nextY = coord[0] + d[0];
const nextX = coord[1] + d[1];
if (
0 < nextY &&
nextY < maxY &&
0 < nextX &&
nextX < maxX &&
board[nextY][nextX] === "O"
) {
queue.push([nextY, nextX]);
board[nextY][nextX] = "B";
}
}
}
for (let y = 0; y <= maxY; y++) {
for (let x = 0; x <= maxX; x++) {
if (board[y][x] === "B") {
board[y][x] = "O";
} else if (board[y][x] === "O") {
board[y][x] = "X";
} else {
/* do nothing */
}
}
}
}
const board1 = [
["X", "X", "X", "X"],
["X", "O", "O", "X"],
["X", "X", "O", "X"],
["X", "O", "X", "X"],
];
solve(board1);
console.log(board1);
const board2 = [
["O", "X", "X", "O", "X"],
["X", "O", "O", "X", "O"],
["X", "O", "X", "O", "X"],
["O", "X", "O", "O", "O"],
["X", "X", "O", "X", "O"],
];
solve(board2);
console.log(board2);
export {};
problems/00131-palindrome-partitioning/README.md
# [OPEN] 131 - Palindrome Partitioning
[Palindrome Partitioning](https://leetcode.com/problems/palindrome-partitioning/)
## notes
### first thoughts
- the word split by character will be at least one subset
- we can create a function `isPal` to run on every subset of the word
- we could eliminate word by being smart about remaining permutations not being
palindromic across their midpoints.
- I misunderstood that `s` must be partitioned such that all members are
themselves palindromes.
## summary
## tags
problems/00131-palindrome-partitioning/typescript/solution.ts
function partition(s: string): string[][] {
const isPalindrome = (word: string) => {
let result = true;
for (let i = 0; i < word.length; i++) {
const j = word.length - i;
result = result && word[i] === word[j];
}
return result;
};
const partitions = [s.split("")];
for (let i = 0; i < s.length - 1; i++) {
for (let j = 2; j <= s.length; j++) {
const maybePart = s.slice(0, j);
if (isPalindrome(maybePart)) {
partitions.push(maybePart);
}
}
}
return partitions;
}
export {};
problems/00146-lru-cache/README.md
# [SOLVED] 146 - LRU Cache
[LRU Cache](https://leetcode.com/problems/lru-cache/)
## notes
### first thoughts
- implementing `get` and `put` in O(1) means we will likely be using an array or
a map (or both).
- need to keep track of the complete set of ordered cache values. so we know
which to evict.
- thinking if an existing value is refreshed, we look it up, get its cacheOrder
and, if necessary, move it to the front of the array.
- if we are adding a new value and have to evict we: remove the last value from
the array and then delete it from the lookup.
- the `lookup` value will be like `{v: number, index: number, cacheOrder:
number}`
- problem is that deleting from an array is not strictly O(n) as items may need
to be rearranged.
- so maybe we can do it with fixed size array and... somehow keep another array
of the indices?
- `get` always moves an item to the front. so worse case we move the last item
to the front and shift all others.
- can implement a Doubly LinkedList-like structure within the map value that
points to its `prev` and `next`. when `prev` is `undefined` it is already the
most recently used. when `next` is `undefined` it is the end. we will also
need to keep track of which word is `head` and which is `tail` so that when we
fill the map we update the pointers correctly and so that when we reach
capacity we can quickly determine which to evict (`tail`)
### simplified map
- saw a solution that took advantage of `Map` preserving insertion order when
accessing `keys()` and decided to try it.
## summary
- first attempt was 100% memory but 13% compute. I don’t really know why it was
so slow. comp.t: O(1) for all. comp.s: O(k) for capacity.
- simplified map was 94% memory but 50% compute. same comp.t: O(1) for
all. comp.s: O(k) for capacity.
## tags
problems/00146-lru-cache/typescript/solution-doubly-linked-list.ts
type CacheValue = {
prevKey?: number;
nextKey?: number;
value: number;
};
class LRUCache {
lookup: Map<number, CacheValue>;
count: number;
capacity: number;
headKey?: number;
tailKey?: number;
constructor(capacity: number) {
this.lookup = new Map<number, CacheValue>();
this.count = 0;
this.capacity = capacity;
}
get(key: number): number {
console.log("get:", { key });
const maybeVal = this.lookup.get(key);
if (maybeVal === undefined) {
return -1;
}
/* we have the key already stored */
if (maybeVal.prevKey === undefined) {
/* nothing to do, we are already head */
} else {
this._stitchOutAndPromoteToHead(key, maybeVal, maybeVal.value);
}
return maybeVal.value;
}
put(key: number, value: number): void {
console.log("put:", { key, value });
const maybeVal = this.lookup.get(key);
if (maybeVal === undefined) {
/* evict if necessary */
if (this.tailKey === undefined) {
/* this is first value we have added */
this.lookup.set(key, { value: value });
this.count += 1;
this.headKey = key;
this.tailKey = key;
} else if (this.capacity === 1) {
if (this.headKey === undefined) {
throw new Error("no head key when only size 1?");
}
this.lookup.delete(this.headKey);
this.lookup.set(key, { value: value });
this.headKey = key;
this.tailKey = key;
} else if (this.count < this.capacity) {
/* we have more room to add */
/* no eviction */
/* tail stays the same */
/* find headKey cache value */
if (this.headKey === undefined) {
throw new Error("no head key?");
}
const maybeHead = this.lookup.get(this.headKey);
if (maybeHead === undefined) {
throw new Error("no head?");
}
/* update it so prev now points to `key` */
maybeHead.prevKey = key;
this.lookup.set(this.headKey, maybeHead);
/* create `key` cachevalue that points next to prev headKey */
this.lookup.set(key, { value: value, nextKey: this.headKey });
/* update the head key */
this.headKey = key;
if (this.capacity === 2) {
/* special case: update tail */
if (this.tailKey === undefined) {
throw new Error("no tail key?");
}
const maybeTail = this.lookup.get(this.tailKey);
if (maybeTail === undefined) {
throw new Error("no tail?");
}
maybeTail.prevKey = key;
this.lookup.set(this.tailKey, maybeTail);
}
/* update the count */
this.count += 1;
} else {
/* evict */
/* eviction required */
/* find tail CacheValue */
if (this.tailKey === undefined) {
throw new Error("no tail key?");
}
const maybeTail = this.lookup.get(this.tailKey);
if (maybeTail === undefined) {
throw new Error("no tail?");
}
/* find prev */
const maybeNewTailKey = maybeTail.prevKey;
if (maybeNewTailKey === undefined) {
throw new Error("no tail prev key?");
}
const newTail = this.lookup.get(maybeNewTailKey);
if (newTail === undefined) {
throw new Error("no new tail?");
}
/* update prev.next to undefined */
newTail.nextKey = undefined;
this.lookup.set(maybeNewTailKey, newTail);
/* delete tail from lookup */
this.lookup.delete(this.tailKey);
/* update tail */
this.tailKey = maybeNewTailKey;
/* update head */
if (this.headKey === undefined) {
throw new Error("no head key?");
}
const maybeHead = this.lookup.get(this.headKey);
if (maybeHead === undefined) {
throw new Error("no head?");
}
/* update old head */
maybeHead.prevKey = key;
this.lookup.set(this.headKey, maybeHead);
/* update new head */
this.lookup.set(key, { value: value, nextKey: this.headKey });
this.headKey = key;
}
} else {
this._stitchOutAndPromoteToHead(key, maybeVal, value);
}
}
_stitchOutAndPromoteToHead(
key: number,
cacheVal: CacheValue,
newValue: number
) {
const maybePrevKey = cacheVal.prevKey;
/* we already exist. arrange correctly */
if (maybePrevKey === undefined) {
/* already head */
cacheVal.value = newValue;
this.lookup.set(key, cacheVal);
return;
}
const maybePrev = this.lookup.get(maybePrevKey);
if (maybePrev === undefined) {
throw new Error("no prev?");
}
const maybeNextKey = cacheVal.nextKey;
let maybeNext = undefined;
if (maybeNextKey !== undefined) {
maybeNext = this.lookup.get(maybeNextKey);
if (maybeNext === undefined) {
throw new Error("next val exists but no next?");
}
}
if (this.headKey === undefined) {
throw new Error("no head key?");
}
const maybeHead = this.lookup.get(this.headKey);
if (maybeHead === undefined) {
throw new Error("no head?");
}
/* stitch out */
maybePrev.nextKey = maybeNextKey;
this.lookup.set(maybePrevKey, maybePrev);
if (maybeNextKey !== undefined && maybeNext !== undefined) {
maybeNext.prevKey = maybePrevKey;
this.lookup.set(maybeNextKey, maybeNext);
} else {
/* update tail */
this.tailKey = maybePrevKey;
}
/* promote to head */
cacheVal.prevKey = undefined;
cacheVal.nextKey = this.headKey;
cacheVal.value = newValue;
this.lookup.set(key, cacheVal);
maybeHead.prevKey = key;
this.lookup.set(this.headKey, maybeHead);
this.headKey = key;
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* var obj = new LRUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/
const c1 = new LRUCache(2);
c1.put(1, 1); // cache is {1=1}
console.log(c1);
c1.put(2, 2); // cache is {1=1, 2=2}
console.log(c1);
console.log(c1.get(1)); // return 1
console.log(c1);
c1.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
console.log(c1);
console.log(c1.get(2)); // returns -1 (not found)
console.log(c1);
c1.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
console.log(c1);
console.log(c1.get(1)); // return -1 (not found)
console.log(c1);
console.log(c1.get(3)); // return 3
console.log(c1);
console.log(c1.get(4)); // return 4
console.log(c1);
const c2 = new LRUCache(2);
c2.put(1, 0);
console.log(c2);
c2.put(2, 2);
console.log(c2);
const c3 = new LRUCache(2);
c3.put(2, 1);
console.log(c3);
c3.put(2, 2);
console.log(c3);
console.log(c3.get(2));
const runTest = (commands: string[], args: number[][]) => {
console.log("\n\n\n", "NEW TEST");
const c = new LRUCache(args[0][0]);
for (let i = 1; i < commands.length; i++) {
const command = commands[i];
const [k, v] = args[i];
if (command === "put") {
c.put(k, v);
} else {
c.get(k);
}
console.log(c);
}
};
const tests = [
{
commands: [
"LRUCache",
"put",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"get",
"get",
"get",
"put",
"put",
"get",
"put",
"get",
],
args: [
[10],
[7, 28],
[7, 1],
[8, 15],
[6],
[10, 27],
[8, 10],
[8],
[6, 29],
[1, 9],
[6],
[10, 7],
[1],
[2],
[13],
[8, 30],
[1, 5],
[1],
[13, 2],
[12],
],
},
{
commands: [
"LRUCache",
"put",
"put",
"put",
"put",
"put",
"get",
"put",
"get",
"get",
"put",
"get",
"put",
"put",
"put",
"get",
"put",
"get",
"get",
"get",
"get",
"put",
"put",
"get",
"get",
"get",
"put",
"put",
"get",
"put",
"get",
"put",
"get",
"get",
"get",
"put",
"put",
"put",
"get",
"put",
"get",
"get",
"put",
"put",
"get",
"put",
"put",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"put",
"put",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"get",
"get",
"get",
"put",
"get",
"get",
"put",
"put",
"put",
"put",
"get",
"put",
"put",
"put",
"put",
"get",
"get",
"get",
"put",
"put",
"put",
"get",
"put",
"put",
"put",
"get",
"put",
"put",
"put",
"get",
"get",
"get",
"put",
"put",
"put",
"put",
"get",
"put",
"put",
"put",
"put",
"put",
"put",
"put",
],
args: [
[10],
[10, 13],
[3, 17],
[6, 11],
[10, 5],
[9, 10],
[13],
[2, 19],
[2],
[3],
[5, 25],
[8],
[9, 22],
[5, 5],
[1, 30],
[11],
[9, 12],
[7],
[5],
[8],
[9],
[4, 30],
[9, 3],
[9],
[10],
[10],
[6, 14],
[3, 1],
[3],
[10, 11],
[8],
[2, 14],
[1],
[5],
[4],
[11, 4],
[12, 24],
[5, 18],
[13],
[7, 23],
[8],
[12],
[3, 27],
[2, 12],
[5],
[2, 9],
[13, 4],
[8, 18],
[1, 7],
[6],
[9, 29],
[8, 21],
[5],
[6, 30],
[1, 12],
[10],
[4, 15],
[7, 22],
[11, 26],
[8, 17],
[9, 29],
[5],
[3, 4],
[11, 30],
[12],
[4, 29],
[3],
[9],
[6],
[3, 4],
[1],
[10],
[3, 29],
[10, 28],
[1, 20],
[11, 13],
[3],
[3, 12],
[3, 8],
[10, 9],
[3, 26],
[8],
[7],
[5],
[13, 17],
[2, 27],
[11, 15],
[12],
[9, 19],
[2, 15],
[3, 16],
[1],
[12, 17],
[9, 1],
[6, 19],
[4],
[5],
[5],
[8, 1],
[11, 7],
[5, 2],
[9, 28],
[1],
[2, 2],
[7, 4],
[4, 22],
[7, 24],
[9, 26],
[13, 28],
[11, 26],
],
},
];
let count = 0;
for (const { commands, args } of tests) {
runTest(commands, args);
count++;
if (count === 2) {
break;
}
}
export {};
problems/00146-lru-cache/typescript/solution-simplified-map.ts
class LRUCache {
lookup: Map<number, number>;
capacity: number;
constructor(capacity: number) {
this.lookup = new Map<number, number>();
this.capacity = capacity;
}
get(key: number): number {
const maybeVal = this.lookup.get(key);
if (maybeVal === undefined) {
return -1;
}
/* delete and reinsert to preserve LRU insertion order */
this.lookup.delete(key);
this.lookup.set(key, maybeVal);
return maybeVal;
}
put(key: number, value: number): void {
if (this.lookup.has(key)) {
/* delete and reinsert to preserve LRU insertion order */
this.lookup.delete(key);
}
this.lookup.set(key, value);
if (this.capacity < this.lookup.size) {
/* evict */
const lruKey = this.lookup.keys().next().value;
this.lookup.delete(lruKey);
}
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* var obj = new LRUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/
const c1 = new LRUCache(2);
c1.put(1, 1); // cache is {1=1}
console.log(c1);
c1.put(2, 2); // cache is {1=1, 2=2}
console.log(c1);
console.log(c1.get(1)); // return 1
console.log(c1);
c1.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
console.log(c1);
console.log(c1.get(2)); // returns -1 (not found)
console.log(c1);
c1.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
console.log(c1);
console.log(c1.get(1)); // return -1 (not found)
console.log(c1);
console.log(c1.get(3)); // return 3
console.log(c1);
console.log(c1.get(4)); // return 4
console.log(c1);
const c2 = new LRUCache(2);
c2.put(1, 0);
console.log(c2);
c2.put(2, 2);
console.log(c2);
const c3 = new LRUCache(2);
c3.put(2, 1);
console.log(c3);
c3.put(2, 2);
console.log(c3);
console.log(c3.get(2));
const runTest = (commands: string[], args: number[][]) => {
console.log("\n\n\n", "NEW TEST");
const c = new LRUCache(args[0][0]);
for (let i = 1; i < commands.length; i++) {
const command = commands[i];
const [k, v] = args[i];
if (command === "put") {
c.put(k, v);
} else {
c.get(k);
}
console.log(c);
}
};
const tests = [
{
commands: [
"LRUCache",
"put",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"get",
"get",
"get",
"put",
"put",
"get",
"put",
"get",
],
args: [
[10],
[7, 28],
[7, 1],
[8, 15],
[6],
[10, 27],
[8, 10],
[8],
[6, 29],
[1, 9],
[6],
[10, 7],
[1],
[2],
[13],
[8, 30],
[1, 5],
[1],
[13, 2],
[12],
],
},
{
commands: [
"LRUCache",
"put",
"put",
"put",
"put",
"put",
"get",
"put",
"get",
"get",
"put",
"get",
"put",
"put",
"put",
"get",
"put",
"get",
"get",
"get",
"get",
"put",
"put",
"get",
"get",
"get",
"put",
"put",
"get",
"put",
"get",
"put",
"get",
"get",
"get",
"put",
"put",
"put",
"get",
"put",
"get",
"get",
"put",
"put",
"get",
"put",
"put",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"put",
"put",
"put",
"put",
"get",
"put",
"put",
"get",
"put",
"get",
"get",
"get",
"put",
"get",
"get",
"put",
"put",
"put",
"put",
"get",
"put",
"put",
"put",
"put",
"get",
"get",
"get",
"put",
"put",
"put",
"get",
"put",
"put",
"put",
"get",
"put",
"put",
"put",
"get",
"get",
"get",
"put",
"put",
"put",
"put",
"get",
"put",
"put",
"put",
"put",
"put",
"put",
"put",
],
args: [
[10],
[10, 13],
[3, 17],
[6, 11],
[10, 5],
[9, 10],
[13],
[2, 19],
[2],
[3],
[5, 25],
[8],
[9, 22],
[5, 5],
[1, 30],
[11],
[9, 12],
[7],
[5],
[8],
[9],
[4, 30],
[9, 3],
[9],
[10],
[10],
[6, 14],
[3, 1],
[3],
[10, 11],
[8],
[2, 14],
[1],
[5],
[4],
[11, 4],
[12, 24],
[5, 18],
[13],
[7, 23],
[8],
[12],
[3, 27],
[2, 12],
[5],
[2, 9],
[13, 4],
[8, 18],
[1, 7],
[6],
[9, 29],
[8, 21],
[5],
[6, 30],
[1, 12],
[10],
[4, 15],
[7, 22],
[11, 26],
[8, 17],
[9, 29],
[5],
[3, 4],
[11, 30],
[12],
[4, 29],
[3],
[9],
[6],
[3, 4],
[1],
[10],
[3, 29],
[10, 28],
[1, 20],
[11, 13],
[3],
[3, 12],
[3, 8],
[10, 9],
[3, 26],
[8],
[7],
[5],
[13, 17],
[2, 27],
[11, 15],
[12],
[9, 19],
[2, 15],
[3, 16],
[1],
[12, 17],
[9, 1],
[6, 19],
[4],
[5],
[5],
[8, 1],
[11, 7],
[5, 2],
[9, 28],
[1],
[2, 2],
[7, 4],
[4, 22],
[7, 24],
[9, 26],
[13, 28],
[11, 26],
],
},
];
let count = 0;
for (const { commands, args } of tests) {
runTest(commands, args);
count++;
if (count === 2) {
break;
}
}
export {};
problems/00151-reverse-words-in-a-string/README.md
# [SOLVED] 151 - Reverse Words in a String
[Reverse Words in a String](https://leetcode.com/problems/reverse-words-in-a-string/)
## notes
### first thoughts
- I am not sure the difficulty here. split on whitespace and reverse.
- added a filter because I split on the literal space character rather than some
regex for whitespace
## summary
- 89th percentile performance
## tags
problems/00151-reverse-words-in-a-string/typescript/solution.ts
function reverseWords(s: string): string {
const words = s.split(" ").filter((w) => 0 < w.length);
words.reverse();
return words.join(" ");
}
console.log(reverseWords("the sky is blue"));
console.log(reverseWords(" hello world "));
console.log(reverseWords("a good example"));
export {};
problems/00153-find-minimum-in-rotated-sorted-array/README.md
# [SOLVED] 153 - Find Minimum in Rotated Sorted Array
[Find Minimum in Rotated Sorted Array](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/)
## notes
### golang thoughts
- sorted but rotated and O(log n) requirement makes me think binary search
- idea is to find the discontinuity. if not found just return the first element
## summary
- submitted accepted solution after iterating on logic for finding pivot. other
solutions made search logic just normal binary search but not run with `l < r`
and to not move `r` by `-1` (like I ended up doing).
- comp.t: O(log n) due to binary search
- comp.s: O(1) due to not using additional space
## tags
medium array
medium binary search
problems/00153-find-minimum-in-rotated-sorted-array/golang/solution.go
package main
import "fmt"
func findMin(nums []int) int {
pivotIndex := -1
for l, m, r := 0, -1, len(nums)-1; l <= r; {
m = l + (r-l)/2
if m+1 < len(nums) && nums[m+1] < nums[m] {
pivotIndex = m
break
}
// m is on left side of pivot
if nums[l] < nums[m] && nums[r] < nums[m] {
l = m + 1
} else if nums[m] < nums[l] && nums[m] < nums[r] {
r = m
} else {
// couldn't find it
break
}
}
if pivotIndex == -1 {
return nums[0]
}
return nums[pivotIndex+1]
}
func main() {
fmt.Println("answer:", findMin([]int{3, 4, 5, 1, 2}) == 1)
fmt.Println("answer:", findMin([]int{4, 5, 6, 7, 0, 1, 2}) == 0)
fmt.Println("answer:", findMin([]int{11, 13, 15, 17}) == 11)
fmt.Println("answer:", findMin([]int{2, 3, 4, 5, 1}) == 1)
fmt.Println("answer:", findMin([]int{4, 5, 1, 2, 3}) == 1)
}
problems/00153-find-minimum-in-rotated-sorted-array/typescript/solution.ts
/* nums is unique, so no [0,0,0] */
/* thoughts: */
/* rotate array until first and last are descending, then binary search */
/* trying to find where n[i] and n[i+1] (wrapped) are descending */
/* modified binary search where if right value is found smaller than left
value, means that that discontinuity is between them. we are always trying to
keep left value greater than right value. as soon as it is less, we are done */
/* [1] length 1 => n[0] minimum */
/* [2,1] length 2 => n[0] or n[1] minimum */
/* [2,3,1] length 3 => */
/* [3,4,5,1,2] */
/* 0: l=3, r=2, m=5 */
/* 1: l=1, r=2, m=1 */
/* [4,5,6,7,0,1,2] */
/* 0: l=4, r=2, m=7 */
/* 1: l=0, r=2, m=1 */
/* [11,13,15,17] */
/* 0: l=11, r=17, m=13 */
/* [2,1] */
/* 0: l=2, r=1, m=2 */
/* 1: l=1, r=1 */
/* [3,1,2] */
/* 0: l=3, r=2, m=1 */
/* 1: l=3, r=1, m=3 */
function findMin(nums: number[]): number {
let leftIndex = 0;
let rightIndex = nums.length - 1;
let left = nums[leftIndex];
let right = nums[rightIndex];
while (right < left && leftIndex <= rightIndex) {
const midIndex = leftIndex + Math.floor((rightIndex - leftIndex) / 2);
const mid = nums[midIndex];
if (left <= mid) {
leftIndex = midIndex + 1;
} else {
/* move only by `midIndex`, rather than `midIndex-1`, because we need to
compare the difference between the two, rather than find a single value
*/
rightIndex = midIndex;
}
left = nums[leftIndex];
right = nums[rightIndex];
}
return left;
}
console.log(findMin([2, 1]));
console.log(findMin([3, 1, 2]));
export {};
problems/00155-min-stack/README.md
# [SOLVED] 155 - Min Stack
[Min Stack](https://leetcode.com/problems/min-stack/)
## notes
### first thoughts
- to achieve O(1) operation likely need to use lookup or array
- can use two stacks one for the backing stack and the other for the `getMin`
call
## summary
- comp.t: O(1) for all operations
- comp.s: O(n) need to store 2x how ever many n elements are pushed
- saw an alternative optimized way that used a length and single `[][2]int`
where the val was stored in `0` and the min in `1`. this would reduce
operations required in half.
## tags
medium stack
medium array
problems/00155-min-stack/golang/solution.go
package main
type MinStack struct {
stack []int
mins []int
}
func Constructor() MinStack {
return MinStack{[]int{}, []int{}}
}
func (this *MinStack) Push(val int) {
this.stack = append(this.stack, val)
if len(this.mins)-1 < 0 || val < this.mins[len(this.mins)-1] {
this.mins = append(this.mins, val)
} else {
this.mins = append(this.mins, this.mins[len(this.mins)-1])
}
}
func (this *MinStack) Pop() {
this.mins = this.mins[:len(this.mins)-1]
this.stack = this.stack[:len(this.stack)-1]
}
func (this *MinStack) Top() int {
return this.stack[len(this.stack)-1]
}
func (this *MinStack) GetMin() int {
return this.mins[len(this.mins)-1]
}
/**
* Your MinStack object will be instantiated and called as such:
* obj := Constructor();
* obj.Push(val);
* obj.Pop();
* param_3 := obj.Top();
* param_4 := obj.GetMin();
*/
func main() {
}
problems/00155-min-stack/typescript/solution.ts
class MinStack {
stack: number[];
mins: number[];
constructor() {
this.stack = [];
this.mins = [];
}
push(val: number): void {
this.stack.push(val);
if (this.mins.length === 0) {
/* we have no min yet */
this.mins.push(val);
} else if (val < this.mins[this.mins.length - 1]) {
/* this val is less than our current min */
this.mins.push(val);
} else {
/* our current minimum is also the minimum after pushing val */
this.mins.push(this.mins[this.mins.length - 1]);
}
}
pop(): void {
this.stack.pop();
this.mins.pop();
}
top(): number {
if (this.stack.length === 0) {
return -1;
}
return this.stack[this.stack.length - 1];
}
getMin(): number {
if (this.mins.length === 0) {
return -1;
}
return this.mins[this.mins.length - 1];
}
}
/**
* Your MinStack object will be instantiated and called as such:
* var obj = new MinStack()
* obj.push(val)
* obj.pop()
* var param_3 = obj.top()
* var param_4 = obj.getMin()
*/
export {};
problems/00167-two-sum-ii---input-array-is-sorted/README.md
# [SOLVED] 167 - Two Sum II - Input Array is Sorted
[Two Sum II - Input Array is Sorted](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/)
## notes
## summary
## tags
medium array
medium two pointers
problems/00167-two-sum-ii---input-array-is-sorted/typescript/solution.ts
/* Your solution must use only constant extra space. */
function twoSum(numbers: number[], target: number): number[] {
let leftPointer = 0;
let rightPointer = numbers.length - 1;
while (leftPointer < rightPointer) {
const left = numbers[leftPointer];
const right = numbers[rightPointer];
const sum = left + right;
if (sum === target) {
return [leftPointer + 1, rightPointer + 1];
} else if (sum < target) {
leftPointer++;
} else {
rightPointer--;
}
}
return [-1, -1];
}
export {};
problems/00199-binary-tree-right-side-view/README.md
# [SOLVED] 199 - Binary Tree Right Side View
[Binary Tree Right Side View](https://leetcode.com/problems/binary-tree-right-side-view/)
## notes
### first thoughts
- did not understand the problem until rereading “values of the nodes *you can
see*”. so this means that we could return values that are on the “left” side
of the tree but are only visible because of `null` right side values.
- first thought is to create an array for each “level” of the binary tree, and
then just return the last values from it.
- another thought is to walk the tree depth first from right to left, keeping
track of whether or not we have seen a node at the specified level. we prefer
the first node we encounter for each level.
- the tree is not necessarily balanced, so we could have to visit every node of
the tree, meaning comp.t is O(n)
- let’s do both solution approaches
- recursive: `helper` should go as deep as possible, preferring right side, and
return an array of values seen at each level. it should take a value level
which is used to set the first value it encounters in the array and increment
it on depth.
- iter: should keep a list of each level and flatmap over the roots to figure
out what the next course is.
## summary
- recursive. comp.t: O(n) we must visit every node
- recursive. comp.s (alg stack): O(n) in worst case, we must recursively dive
down
- iter. comp.t: O(n) we must visit every node
- iter. comp.s (data structure): O(n) in worst case storing values in `output`
- iter. had implementation bug where adding the values to `level` via `flatMap`
needed to be in order that ensured `[l,r]`, then `[r]`, then `[l]` were
preferentially added.
- typescript solution, even with iter approach was only at 39% percentile. my
guess is that the use of `flatMap` is not as fast as if I had just iterated
directly
- golang 50th percentile for performance and am not sure the faster go solutions
are really doing anything that fancy to learn from. I find them harder to
read.
## tags
medium recursion
medium binary tree
problems/00199-binary-tree-right-side-view/golang/solution.go
package main
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func rightSideView(root *TreeNode) []int {
var output []int
var level []*TreeNode
if root != nil {
level = append(level, root)
}
for len(level) != 0 {
// grab the rightmost val and add to output
output = append(output, level[len(level)-1].Val)
// build next level
var nextLevel []*TreeNode
for _, tn := range level {
if tn.Left != nil {
nextLevel = append(nextLevel, tn.Left)
}
if tn.Right != nil {
nextLevel = append(nextLevel, tn.Right)
}
}
level = nextLevel
}
return output
}
func main() {
}
problems/00199-binary-tree-right-side-view/typescript/solution.ts
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
this.right = right === undefined ? null : right;
}
}
const helperRecursive = (
node: TreeNode | null,
currentDepth: number,
records: number[]
): number[] => {
console.log({ node, currentDepth, records });
if (node === null) {
return records;
}
if (records[currentDepth] === undefined) {
/* first time we have encountered it. set it */
records[currentDepth] = node.val;
}
/* start descending preferring right side */
let updatedRecords = helperRecursive(node.right, currentDepth + 1, records);
updatedRecords = helperRecursive(node.left, currentDepth + 1, updatedRecords);
return updatedRecords;
};
function rightSideView(root: TreeNode | null): number[] {
return helperRecursive(root, 0, []);
}
function rightSideViewIter(root: TreeNode | null): number[] {
/* goal with the iter approah is to keep track of each level */
const output: number[] = [];
let level: TreeNode[] = [root].flatMap((n) => {
if (n === null) {
return [];
} else {
return [n];
}
});
while (level.length !== 0) {
const rightMost = level[level.length - 1];
output.push(rightMost.val);
level = level.flatMap((n) => {
if (n.left === null && n.right === null) {
return [];
}
if (n.left !== null && n.right !== null) {
return [n.left, n.right];
}
if (n.right !== null) {
return [n.right];
}
if (n.left !== null) {
return [n.left];
}
return [];
});
}
return output;
}
console.log(rightSideView(null));
console.log(rightSideView(new TreeNode(1, null, new TreeNode(3))));
console.log(
rightSideView(
new TreeNode(
1,
new TreeNode(2, null, new TreeNode(5)),
new TreeNode(3, null, new TreeNode(4))
)
)
);
console.log(rightSideViewIter(null));
console.log(rightSideViewIter(new TreeNode(1, null, new TreeNode(3))));
console.log(
rightSideViewIter(
new TreeNode(
1,
new TreeNode(2, null, new TreeNode(5)),
new TreeNode(3, null, new TreeNode(4))
)
)
);
export {};
problems/00207-course-schedule/README.md
# [OPEN] 207 - Course Schedule
[Course Schedule](https://leetcode.com/problems/course-schedule/)
## notes
### first thoughts
- I do not fully understand if the prerequisites refer to the `numCourses`
values specifically or just a label for any course?
- anyway my approach will be to create a lookup of `a` to all `b_i...j` and then
check two things
- whether we have visited a course before (cycle)
- whether the count of walking the graph exceeds `numCourses`
## summary
## tags
problems/00207-course-schedule/typescript/solution.ts
function canFinish(numCourses: number, prerequisites: number[][]): boolean {
/* create req lookup */
const lookup = new Map<number, number[]>();
for (const [a, b] of prerequisites) {
const reqs = lookup.get(a) ?? [];
reqs.push(b);
lookup.set(a, reqs);
}
/* walk graph */
const visited = new Set<number>();
const helper = (requirement: number, coursesRemaining: number): boolean => {
/* no more courses we can take */
if (coursesRemaining < 0) {
return false;
}
const requirements = lookup.get(requirement);
/* no requirements left */
if (requirements === undefined) {
return true;
}
/* we have been here before: cycle! */
if (visited.has(requirement)) {
return false;
}
visited.add(requirement);
/* can we also do all the requirements? */
let result = true;
for (const r of requirements) {
result = result && helper(r, coursesRemaining--);
}
return result;
};
return helper(numCourses - 1, numCourses);
}
console.log(canFinish(2, [[1, 0]]));
console.log(
canFinish(2, [
[1, 0],
[0, 1],
])
);
export {};
problems/00208-implement-trie-prefix-trie/README.md
# [SOLVED] 208 - Implement Trie (Prefix Trie)
[Implement Trie (Prefix Trie)](https://leetcode.com/problems/implement-trie-prefix-tree/)
## notes
### first thoughts
- can create using one map for word check and then a graph of maps for prefix
search.
- thought initially that I could use a tree of tries, but wikipedia says that
the root node is always the empty character.
- likely going to be kind of heavyweight creating a map for each character
## summary
- `solution-with-map.ts` was 37th percentile
- `solution-with-object.ts` was 80th percentile
## tags
problems/00208-implement-trie-prefix-trie/typescript/solution-with-map.ts
type TNode = {
word: string;
next: Map<string, TNode>;
};
class Trie {
node: TNode;
constructor() {
this.node = { word: "", next: new Map<string, TNode>() };
}
insert(word: string): void {
const chars = word.split("");
let node = this.node;
for (const c of chars) {
let nextNode = node.next.get(c) ?? {
word: "",
next: new Map<string, TNode>(),
};
node.next.set(c, nextNode);
node = nextNode;
}
node.word = word;
}
search(word: string): boolean {
const chars = word.split("");
let node = this.node;
for (const c of chars) {
let nextNode = node.next.get(c);
if (nextNode === undefined) {
break;
} else {
node = nextNode;
}
}
return node.word === word;
}
startsWith(prefix: string): boolean {
const chars = prefix.split("");
let present = true;
let node = this.node;
for (const c of chars) {
let nextNode = node.next.get(c);
if (nextNode === undefined) {
present = false;
break;
} else {
node = nextNode;
}
}
return present;
}
}
const trie = new Trie();
console.log(trie.insert("apple"));
console.log(trie.search("apple")); // return True
console.log(trie.search("app")); // return False
console.log(trie.startsWith("app")); // return True
console.log(trie.insert("app"));
console.log(trie.search("app")); // return True
/**
* Your Trie object will be instantiated and called as such:
* var obj = new Trie()
* obj.insert(word)
* var param_2 = obj.search(word)
* var param_3 = obj.startsWith(prefix)
*/
export {};
problems/00208-implement-trie-prefix-trie/typescript/solution-with-object.ts
type TNode = {
word: string;
children: { [key: string]: TNode };
};
class Trie {
node: TNode;
constructor() {
this.node = { word: "", children: {} };
}
insert(word: string): void {
const chars = word.split("");
let node = this.node;
for (const c of chars) {
let nextNode = node.children[c] ?? {
word: "",
children: {},
};
node.children[c] = nextNode;
node = nextNode;
}
node.word = word;
}
search(word: string): boolean {
const chars = word.split("");
let node = this.node;
for (const c of chars) {
let nextNode = node.children[c];
if (nextNode === undefined) {
break;
} else {
node = nextNode;
}
}
return node.word === word;
}
startsWith(prefix: string): boolean {
const chars = prefix.split("");
let present = true;
let node = this.node;
for (const c of chars) {
let nextNode = node.children[c];
if (nextNode === undefined) {
present = false;
break;
} else {
node = nextNode;
}
}
return present;
}
}
const trie = new Trie();
console.log(trie.insert("apple"));
console.log(trie.search("apple")); // return True
console.log(trie.search("app")); // return False
console.log(trie.startsWith("app")); // return True
console.log(trie.insert("app"));
console.log(trie.search("app")); // return True
/**
* Your Trie object will be instantiated and called as such:
* var obj = new Trie()
* obj.insert(word)
* var param_2 = obj.search(word)
* var param_3 = obj.startsWith(prefix)
*/
export {};
problems/00217-contains-duplicate/README.md
# [SOLVED] 217 - Contains Duplicate
[Contains Duplicate](https://leetcode.com/problems/contains-duplicate/)
## notes
### first thoughts
- could sort and loop through checking if neighbors are same
- could create lookup of seen numbers
## summary
- comp.t: O(n) must iterate all elements
- comp.s: O(n) storing elements in lookup
## tags
easy array
problems/00217-contains-duplicate/golang/solution.go
package main
func containsDuplicate(nums []int) bool {
lookup := make(map[int]bool, len(nums))
var output bool
for _, num := range nums {
if _, ok := lookup[num]; ok {
output = true
break
} else {
lookup[num] = true
}
}
return output
}
func main() {
}
problems/00217-contains-duplicate/typescript/solution.ts
/* time: O(n) */
/* space: O(n) */
function containsDuplicate(nums: number[]): boolean {
/* cheeky */
return new Set(nums).size !== nums.length;
}
/* time: O(n) but short circuits */
/* space: O(n) but short circuits */
function containsDuplicateAlt(nums: number[]): boolean {
const lookup = new Set<number>();
for (let i = 0; i < nums.length; i++) {
const num = nums[i];
if (lookup.has(num)) {
return true;
}
lookup.add(num);
}
return false;
}
console.log("orig:");
console.log(containsDuplicate([1, 2, 3, 1]));
console.log(containsDuplicate([1, 2, 3, 4]));
console.log(containsDuplicate([1, 1, 1, 3, 3, 4, 3, 2, 4, 2]));
console.log("alt:");
console.log(containsDuplicateAlt([1, 2, 3, 1]));
console.log(containsDuplicateAlt([1, 2, 3, 4]));
console.log(containsDuplicateAlt([1, 1, 1, 3, 3, 4, 3, 2, 4, 2]));
export {};
problems/00226-invert-binary-tree/README.md
# [SOLVED] 226 - Invert Binary Tree
[Invert Binary Tree](https://leetcode.com/problems/invert-binary-tree/)
## notes
### first thoughts
- recursive, binary tree
- implementation should just swap right and left
## summary
- comp.t: O(n) must visit every node of the tree
- comp.s: O(log n), but could be O(n) in worst case for unbalanced tree.
- implemented correct solution. looked at other solutions and saw an alternative
approach that I implemented. noted some clarity differences but it was more
performant.
## tags
easy binary tree
easy recursive
problems/00226-invert-binary-tree/golang/solution.go
package main
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func invertTree(root *TreeNode) *TreeNode {
if root == nil {
return root
}
oldLeft := root.Left
root.Left = invertTree(root.Right)
root.Right = invertTree(oldLeft)
return root
}
// alternative implementation that eliminates multiple returns and temporary
// variable. I personally always feel weird calling recursive functions that
// return something and not actually doing anything with them, so even though
// this is performance improvement I do not think it improves readability.
func invertTreeAlt(root *TreeNode) *TreeNode {
if root != nil {
root.Left, root.Right = root.Right, root.Left
invertTree(root.Left)
invertTree(root.Right)
}
return root
}
func main() {
}
problems/00226-invert-binary-tree/typescript/solution.ts
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
this.right = right === undefined ? null : right;
}
}
/* first thoughts: */
/* recursion */
/* base case: swap left and right */
function invertTree(root: TreeNode | null): TreeNode | null {
if (root === null) {
return null;
}
const left = root.left;
const right = root.right;
root.left = invertTree(right);
root.right = invertTree(left);
return root;
}
export {};
problems/00234-palindrome-linked-list/README.md
# [SOLVED] 234 - Palindrome Linked List
[Palindrome Linked List](https://leetcode.com/problems/palindrome-linked-list/)
## notes
## summary
## tags
easy linked list
problems/00234-palindrome-linked-list/typescript/solution.ts
class ListNode {
val: number;
next: ListNode | null;
constructor(val?: number, next?: ListNode | null) {
this.val = val === undefined ? 0 : val;
this.next = next === undefined ? null : next;
}
}
function isPalindrome(head: ListNode | null): boolean {
if (head === null) {
return false;
}
// better idea: build two arrays and compare at end
let ln: ListNode | null = head;
let arr1: number[] = [];
let arr2: number[] = [];
while (ln !== null) {
arr1.push(ln.val);
arr2.unshift(ln.val);
ln = ln.next;
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
export {};
problems/00238-product-of-array-except-self/README.md
# [SOLVED] 238 - Product of Array Except Self
[Product of Array Except Self](https://leetcode.com/problems/product-of-array-except-self/)
## notes
### first thoughts
- cannot use division
- noticed that for `nums[i]` the value is the product of `nums[0]` to
`nums[i-1]` multiplied by the product of `nums[i+1]` to `nums[nums.length-1]`
- solution approach will be to create two arrays: one containing the product of
`nums[i]` from left to right and the other from right to left
- algorithm will be to multiply the `ltr[i-1]` and `rtl[i+1]` values
## summary
- comp.t: O(n): we loop through `nums` 3 times, so O(3n), but this reduces to O(n)
- comp.s: O(n): we create three arrays of size `n`
## tags
medium array
problems/00238-product-of-array-except-self/typescript/solution.ts
function productExceptSelf(nums: number[]): number[] {
const ltr: number[] = [];
for (let i = 0; i < nums.length; i++) {
const val = nums[i];
if (i === 0) {
ltr[i] = val;
} else {
ltr[i] = ltr[i - 1] * val;
}
}
const rtl: number[] = [];
for (let i = nums.length - 1; 0 <= i; i--) {
const val = nums[i];
if (i === nums.length - 1) {
rtl[i] = val;
} else {
rtl[i] = rtl[i + 1] * val;
}
}
/* optimization: could remove this and perform the multiplication during each
iteration of `ltr` and `rtl` */
const result: number[] = [];
for (let i = 0; i < nums.length; i++) {
const ltrVal = ltr[i - 1] ?? 1;
const rtlVal = rtl[i + 1] ?? 1;
result[i] = ltrVal * rtlVal;
}
return result;
}
productExceptSelf([1, 2, 3, 4]);
productExceptSelf([-1, 1, 0, -3, 3]);
export {};
problems/00242-valid-anagram/README.md
# [SOLVED] 242 - Valid Anagram
[Valid Anagram](https://leetcode.com/problems/valid-anagram/)
## notes
- sort each string and then compare equality
## summary
- comp.t: O(sc log sc) + O(tc log tc) where sc and tc are the number of
characters in s and t
- comp.s: O(sc + tc) a list for each
- highly optimized take advantage of `s` and `t` are only composed of lowercase
English letters and create two `[26]int{}` that are populated via single
passes through each string and then compared index by index. smart!
- typescript implementation takes different approach: it creates a lookup for
each string and then compares the two. this avoids the sort.
## tags
easy string
problems/00242-valid-anagram/golang/solution.go
package main
import (
"sort"
"strings"
)
func isAnagram(s string, t string) bool {
sc := strings.Split(s, "")
sort.Strings(sc)
tc := strings.Split(t, "")
sort.Strings(tc)
return strings.Join(sc, "") == strings.Join(tc, "")
}
func main() {
}
problems/00242-valid-anagram/typescript/solution.ts
function isAnagram(s: string, t: string): boolean {
if (s.length !== t.length) {
/* no way that different lengths can be anagram */
return false;
}
const createLookup = (str: string): Map<string, number> => {
return str.split("").reduce((memo, c) => {
const numOccurrences = memo.get(c);
if (numOccurrences === undefined) {
memo.set(c, 1);
} else {
memo.set(c, numOccurrences + 1);
}
return memo;
}, new Map<string, number>());
};
const sLookup = createLookup(s);
const tLookup = createLookup(t);
if (sLookup.size !== tLookup.size) {
return false;
}
let isAnagram = true;
for (const [c, count] of sLookup) {
isAnagram = isAnagram && tLookup.get(c) === count;
if (!isAnagram) {
break;
}
}
return isAnagram;
}
console.log(isAnagram("anagram", "nagaram"));
console.log(isAnagram("rat", "car"));
export {};
problems/00322-coin-change/README.md
# [OPEN] 322 - Coin Change
[Coin Change](https://leetcode.com/problems/coin-change/)
## notes
### first thoughts
- divide sum by largest
- prompted / hinted that there are combinations that just dividing by largest
will not work
- dynamic programming
## summary
## tags
problems/00322-coin-change/typescript/solution.ts
/* below is attempt to get version working that returns the list of
permutations. I am trying to improve upon what I did w/ dphil */
const coins = (
remainingAmount: number,
currentPermutation: number[],
denoms: number[],
lookup: Map<number, number[]>
): number[] => {
console.log({ remainingAmount, currentPermutation, denoms, lookup });
/* no more options */
const foo = currentPermutation[currentPermutation.length - 1] ?? 0;
if (remainingAmount - foo === 0) {
return currentPermutation;
}
/* cannot make change */
if (remainingAmount < 0 || denoms.length === 0) {
return [];
}
/* have we already calculated the minimum for this amt before? */
const maybeRemaining = lookup.get(remainingAmount);
if (maybeRemaining !== undefined) {
if (maybeRemaining.length === 0) {
/* we cannot make it */
return [];
} else {
currentPermutation.push(...maybeRemaining);
return currentPermutation;
}
}
let min = Number.MAX_SAFE_INTEGER;
let choice: number[] = [];
for (const d of denoms) {
const newAmount = remainingAmount - d;
const option = coins(
newAmount,
[d],
denoms.filter((d) => d <= newAmount),
lookup
);
if (option.length !== 0 && option.length < min) {
choice = Array.from(option);
choice.unshift(...currentPermutation);
min = choice.length;
}
}
lookup.set(remainingAmount, Array.from(choice));
return choice;
};
function fewestCoinsNeeded(amount: number, denominations: number[]): number[] {
return coins(
amount,
[],
denominations.filter((d) => d <= amount),
new Map<number, number[]>()
);
}
/* console.log(fewestCoinsNeeded(7, [2, 3]));
* console.log(fewestCoinsNeeded(1, [1, 5, 10, 25, 50]));
* console.log(fewestCoinsNeeded(2, [1, 5, 10, 25, 50])); */
console.log(fewestCoinsNeeded(5, [1, 5, 10, 25, 50]));
/* console.log(fewestCoinsNeeded(147, [1, 5, 10, 25, 50])); */
export {};
problems/00347-top-k-frequent-elements/README.md
# [SOLVED] 347 - Top K Frequent Elements
[Top K Frequent Elements](https://leetcode.com/problems/top-k-frequent-elements/)
## notes
## summary
- comp.t O(n + n + n log n), O(n log n)
- comp.s O(n + n + n), O(n)
- should look at other solutions and revisit for improved performance
## tags
medium array
medium sort
problems/00347-top-k-frequent-elements/typescript/solution.ts
/* note: can improve nlogn with bst of the k elements to be nlogk */
function topKFrequent(nums: number[], k: number): number[] {
/* thought: keep count of numbers in lookup, then iterate the lookup values
(counts), sort them, and slice k */
const countLookup = nums.reduce((memo, num) => {
const count = memo.get(num);
if (count === undefined) {
memo.set(num, 1);
} else {
memo.set(num, count + 1);
}
return memo;
}, new Map<number, number>());
const stats = [];
for (const [num, count] of countLookup) {
stats.push({ num: num, count: count });
}
return stats
.sort((a, b) => b.count - a.count)
.map((s) => s.num)
.slice(0, k);
}
console.log(topKFrequent([1, 1, 1, 2, 2, 3], 2));
console.log(topKFrequent([1], 1));
export {};
problems/00383-ransom-note/README.md
# [SOLVED] 383 - Ransom Note
[Ransom Note](https://leetcode.com/problems/ransom-note/)
## notes
### first thoughts
- convert magazine into a set and remove when used in ransom note
- set needs to have counts of duplicate letters
- split the strings, sort them, group them into sub arrays, make a lookup out of
magazine by letter, and then determine if there is enough by comparing the sub
arrays
- I want to only read ransom note once, and then search magazine
- if I search for each character after iterating through ransome note, that
would be quadratic n * n log(n)
- convert both to maps of counts and then compare
## summary
## tags
easy string
problems/00383-ransom-note/typescript/solution.ts
function canConstruct(ransomNote: string, magazine: string): boolean {
// lets get base cases out of the way
if (magazine.length < ransomNote.length) {
return false;
}
const ransomLookup = ransomNote.split("").reduce((memo, char) => {
const num = memo.get(char);
if (num === undefined) {
memo.set(char, 1);
} else {
memo.set(char, num + 1);
}
return memo;
}, new Map<string, number>());
const magazineLookup = magazine.split("").reduce((memo, char) => {
const num = memo.get(char);
if (num === undefined) {
memo.set(char, 1);
} else {
memo.set(char, num + 1);
}
return memo;
}, new Map<string, number>());
// we only care if we can support ransom note creation
for (const [char, ransomCount] of ransomLookup.entries()) {
const maybeMagCount = magazineLookup.get(char);
if (maybeMagCount === undefined || maybeMagCount < ransomCount) {
return false;
}
}
return true;
}
export {};
problems/00412-fizz-buzz/README.md
# [SOLVED] 412 - Fizz Buzz
[Fizz Buzz](https://leetcode.com/problems/fizz-buzz/)
## notes
## summary
## tags
easy
problems/00412-fizz-buzz/typescript/solution.ts
function fizzBuzz(n: number): string[] {
const answer: string[] = [];
for (let i = 1; i <= n; i++) {
if (i % 3 === 0 && i % 5 === 0) {
answer.push("FizzBuzz");
} else if (i % 3 === 0) {
answer.push("Fizz");
} else if (i % 5 === 0) {
answer.push("Buzz");
} else {
answer.push(String(i));
}
}
return answer;
}
export {};
problems/00665-non-decreasing-array/README.md
# [OPEN] 665 - Non-decreasing Array
[Non-decreasing Array](https://leetcode.com/problems/non-decreasing-array/)
## notes
- array of length 1 is true
- array of length 2 is also true
- incorrect initial thought: array of length 3 is true if there is _only one_ x[i+1] < x[i]
- array of length 3 is false if you find non-decreasing and try changing either the first or second value and test
## summary
## tags
problems/00665-non-decreasing-array/typescript/solution.ts
const isSequentiallyNonDecreasing = (nums: number[], i: number): boolean => {
const curr = nums[i];
const next = nums[i + 1];
console.log({ curr, next });
return curr <= next;
};
const isNonDecreasing = (nums: number[]): boolean => {
for (let i = 0; i <= nums.length - 2; i++) {
if (isSequentiallyNonDecreasing(nums, i)) {
/* do nothing */
} else {
return false;
}
}
return true;
};
function checkPossibility(nums: number[]): boolean {
if (nums.length === 1) {
return true;
}
if (nums.length === 2) {
return true;
}
let result = false;
if (isNonDecreasing(nums)) {
result = true;
} else {
/* we found a non decreasing sequence, try modifying positions */
let f_i = 0;
for (let i = 0; i <= nums.length - 2; i++) {
if (isSequentiallyNonDecreasing(nums, i)) {
/* do nothing */
} else {
f_i = i;
break;
}
}
/* scenario: nums[f_i - 1] is out of bounds; try changing to nums[f_i + 1] */
/* scenario: nums[f_i - 1] exists; try changing to nums[f_i - 1] */
/* scenario: change nums[f_i + 1] to nums[f_i] */
if (f_i - 1 < 0) {
const attempt1 = [...nums];
attempt1[f_i] = attempt1[f_i + 1];
result = isNonDecreasing(attempt1);
} else {
const attempt2 = [...nums];
attempt2[f_i] = attempt2[f_i - 1];
if (isNonDecreasing(attempt2)) {
result = true;
} else {
const attempt3 = [...nums];
attempt3[f_i + 1] = attempt3[f_i];
if (isNonDecreasing(attempt3)) {
result = true;
}
}
}
}
return result;
}
console.log(checkPossibility([4, 2, 3]));
console.log(checkPossibility([4, 2, 1]));
console.log(checkPossibility([3, 4, 2, 3]));
export {};
problems/00704-binary-search/README.md
# [SOLVED] 704 - Binary Search
[Binary Search](https://leetcode.com/problems/binary-search/)
## notes
### first thoughts
- binary search due to sorted array
## summary
- comp.t: O(log n) due to halving problem space at each `midIndex` change
- comp.s: O(1) just returning index
- know binary search cold
- visualize items on a number line and asking yourself “what region do I want to
be in?”
## tags
easy array
easy binary search
problems/00704-binary-search/golang/solution.go
package main
import "fmt"
func search(nums []int, target int) int {
for l, r, m := 0, len(nums)-1, -1; l <= r; {
m = l + (r-l)/2
if nums[m] == target {
return m
} else if nums[m] < target {
l = m + 1
} else if target < nums[m] {
r = m - 1
} else {
// can't happen
}
}
return -1
}
func main() {
fmt.Println(search([]int{-1, 0, 3, 5, 9, 12}, 9))
fmt.Println(search([]int{-1, 0, 3, 5, 9, 12}, 2))
}
problems/00704-binary-search/typescript/solution.ts
function search(nums: number[], target: number): number {
let index = -1;
let leftIndex = 0;
let rightIndex = nums.length - 1;
while (leftIndex <= rightIndex) {
let midIndex = Math.floor(leftIndex + (rightIndex - leftIndex) / 2);
const maybeTarget = nums[midIndex];
if (maybeTarget < target) {
/* shift the left */
leftIndex = midIndex + 1;
} else if (target < maybeTarget) {
/* shift the right */
rightIndex = midIndex - 1;
} else {
/* found it */
index = midIndex;
break;
}
}
return index;
}
export {};
problems/00739-daily-temperatures/README.md
# [SOLVED] 739 - Daily Temperatures
[Daily Temperatures](https://leetcode.com/problems/daily-temperatures/)
## notes
### first thoughts
- last value will always be 0 as there are no more days ahead
- once we have passed a value and set its value it is no longer useful to us.
- idea. sort the temperatures, store a record of temp and value. for every
value, find the temperature, then search that temperature + remaining for the
smallest index.
- idea. when working backward from end, can we use the min days until greater
temperature to determine future temperatures that will potentially be warmer?
- so you basically work backwards, check if n+1 is greater, if yes, you are done
just put 1. otherwise look up the result array and see how many days you have
to go before getting a warmer temperature than that. you know that this is
necessarily the next largest temperature, otherwise you’d have a smaller
number.
- smells kind of like dynamic programming because you can start at the end and
work your way to beginning. the last value will always be 0.
## summary
- implemented accepted solution but had bug caught in test cases for condition
entering while loop.
- also had unnecessary index check that I removed.
- comp.t: O(n) to check every temperature, and for each temperature `i` need to
perform worst case `n - i` comparison operations (e.g. `temperature` contains
a high value followed by much smaller but successively increasing values. I am
not sure how this should be amortized.
- comp.s: O(n) storing `answer`
### for version `solution-stack.go`
- comp.t: O(n) and I think may be slightly better performance because you go
through `temperatures` once O(n) and go through at most `n` values in `stack`,
so this would be 2 * O(n). not clear to me which is better.
- comp.s: O(n) storing `answer` and `stack`
## tags
medium array
medium stack
problems/00739-daily-temperatures/golang/solution-original.go
package main
import "fmt"
func dailyTemperatures(temperatures []int) []int {
// initialize our answers to 0s
answer := make([]int, len(temperatures))
for i := range answer {
answer[i] = 0
}
// we know the last value will always be zero, so working backward from the
// second to last index, calculate the future closest warmest day using
// information we have already accumulated.
for i := len(temperatures) - 2; 0 <= i; i-- {
todaysTemp := temperatures[i]
// try tomorrow as first guess for next warmest day
candidateNextWarmestIndex := i + 1
candidateNextWarmest := temperatures[candidateNextWarmestIndex]
// if it isn't that is OK: we have already calculated how many days away
// _that_ value is from the next warmest. use that information to jump
// forward and see if that value is warmer than today
for candidateNextWarmest <= todaysTemp {
daysUntilNextWarmest := answer[candidateNextWarmestIndex]
if daysUntilNextWarmest == 0 {
// we are done, cannot find a warmer temperature
break
}
// jump to this next warmer temperature. this should never jump beyond
// the bounds
candidateNextWarmestIndex += daysUntilNextWarmest
candidateNextWarmest = temperatures[candidateNextWarmestIndex]
}
// we have looped through to find our best candidate. if it is warmer than
// todays temp, set it. otherwise leave the default 0
if todaysTemp < candidateNextWarmest {
answer[i] = candidateNextWarmestIndex - i
}
}
return answer
}
func main() {
fmt.Println(dailyTemperatures([]int{73, 74, 75, 71, 69, 72, 76, 73}))
fmt.Println(dailyTemperatures([]int{30, 40, 50, 60}))
fmt.Println(dailyTemperatures([]int{30, 60, 90}))
fmt.Println(dailyTemperatures([]int{89, 62, 70, 58, 47, 47, 46, 76, 100, 70}))
}
problems/00739-daily-temperatures/golang/solution-stack.go
package main
import "fmt"
type record struct {
temp int
idx int
}
func dailyTemperatures(temperatures []int) []int {
answer := make([]int, len(temperatures))
var stack []record
for i := 0; i < len(temperatures); i++ {
for 0 < len(stack) && stack[len(stack)-1].temp < temperatures[i] {
// we found a candidate
record := stack[len(stack)-1]
answer[record.idx] = i - record.idx
// remove it from the stack
stack = stack[:len(stack)-1]
}
stack = append(stack, record{temperatures[i], i})
}
return answer
}
func main() {
fmt.Println(dailyTemperatures([]int{73, 74, 75, 71, 69, 72, 76, 73}))
fmt.Println(dailyTemperatures([]int{30, 40, 50, 60}))
fmt.Println(dailyTemperatures([]int{30, 60, 90}))
fmt.Println(dailyTemperatures([]int{89, 62, 70, 58, 47, 47, 46, 76, 100, 70}))
}
problems/00739-daily-temperatures/typescript/solution.ts
function dailyTemperatures(temperatures: number[]): number[] {
const answer = Array(temperatures.length).fill(0);
for (let i = temperatures.length - 2; 0 <= i; i--) {
const todaysTemp = temperatures[i];
let candidateTemperatureIndex = i + 1;
let candidateWarmerDayTemp = temperatures[candidateTemperatureIndex];
while (candidateWarmerDayTemp <= todaysTemp) {
const hopsUntilNextWarmerDay = answer[candidateTemperatureIndex];
if (hopsUntilNextWarmerDay === 0) {
/* no more warmer we are done */
break;
}
candidateTemperatureIndex += hopsUntilNextWarmerDay;
candidateWarmerDayTemp = temperatures[candidateTemperatureIndex];
}
if (todaysTemp < candidateWarmerDayTemp) {
answer[i] = candidateTemperatureIndex - i;
}
}
return answer;
}
console.log(dailyTemperatures([73, 74, 75, 71, 69, 72, 76, 73]));
console.log(dailyTemperatures([30, 40, 50, 60]));
console.log(dailyTemperatures([30, 60, 90]));
export {};
problems/00875-koko-eating-bananas/README.md
# [SOLVED] 875 - Koko Eating Bananas
[Koko Eating Bananas](https://leetcode.com/problems/koko-eating-bananas/)
## notes
### typescript first attempt
- could not solve, had to look up answer, did not understand utility of binary
search
### golang implementation
- we need to determine `k`, the min eating speed
- we are told that `h` will always be greater than or equal to `piles.length`
- naive solution: try values of `k` starting at 1 until we have finished before
`h`
- that is kind of the same as saying `k=[1,2,...,m]` where our upper bound `m`
is `max(k_1, ..., k_i)`. if we find the max, we could then binary search
values of k until we get the minimum `k` such that there are 0 left over
bananas
- is there a smarter way though? sort the piles, `h-len(piles)` is the amount of
“extra” cycles we have to eat more...I feel like there is a mathematical
approach to do this minimization but I do not know it. going to try the binary
search approach.
- calculation is: `num(piles) = sum(remainder piles[i] / k), stopping when h is
less than zero`
- got memory exceeded error for creating `possibleK` of values rather than just
using possible values of k directly.
- got time limit exceeded. needed to improve `bananasRemaining`
- got accepted answer, but still slow.
- optimization: no need to sort, just get max
- not seeing more optimization differences between this and top answers
## summary
- comp.t: assume `n = len(piles)` and `k = max(piles[i])`. we need O(log k) for
finding optimal k, then for each of those perform O(n) operations to check the
remaining bananas. so total I think is O(n log k).
- comp.s: O(1) no additional space
## tags
medium array
medium binary search
problems/00875-koko-eating-bananas/golang/solution.go
package main
import (
"fmt"
)
func bananasRemaining(piles []int, h int, k int) int {
var remaining int
// had to make this faster
for _, amt := range piles {
if h < 0 {
remaining += amt
continue
}
// integer division ceiling
neededH := 1 + (amt-1)/k
var iter int
if neededH <= h {
// we can perform it
iter = neededH
} else {
iter = h
}
h -= iter
amt -= iter * k
if 0 < amt {
remaining += amt
}
}
return remaining
}
func minEatingSpeed(piles []int, h int) int {
var max int
for _, size := range piles {
if max < size {
max = size
}
}
var k int
for l, m, r := 1, -1, max; l <= r; {
m = l + (r-l)/2
if bananasRemaining(piles, h, m) <= 0 {
// we have a candidate, try a smaller value of k
k = m
r = m - 1
} else {
// we are not eating fast enough
l = m + 1
}
}
return k
}
func main() {
fmt.Println(minEatingSpeed([]int{3, 6, 7, 11}, 8))
fmt.Println(minEatingSpeed([]int{30, 11, 23, 4, 20}, 5))
fmt.Println(minEatingSpeed([]int{30, 11, 23, 4, 20}, 6))
// fmt.Println(bananasRemaining([]int{30, 11, 23, 4, 20}, 5, 26))
// fmt.Println(bananasRemaining([]int{30, 11, 23, 4, 20}, 5, 27))
// fmt.Println(bananasRemaining([]int{30, 11, 23, 4, 20}, 5, 28))
// fmt.Println(bananasRemaining([]int{30, 11, 23, 4, 20}, 5, 29))
// fmt.Println(bananasRemaining([]int{30, 11, 23, 4, 20}, 5, 30))
}
problems/00875-koko-eating-bananas/typescript/solution.ts
/* piles = [3,6,7,11], h = 8 */
/* k: 1...max(piles) */
function minEatingSpeed(piles: number[], h: number): number {
if (piles.length === 1) {
return Math.ceil(piles[0] / h);
}
let max = -1;
for (let i = 0; i < piles.length; i++) {
const maybeMax = piles[i];
if (max < maybeMax) {
max = maybeMax;
}
}
/* idea: loop through piles to figure out how many hours it takes for a given k */
const hoursPerCandidate = (candidate: number) => {
return piles.reduce((memo, numBananas) => {
memo += Math.ceil(numBananas / candidate);
return memo;
}, 0);
};
let left = 1;
let right = max;
let k = max;
while (left <= right) {
const maybeK = left + Math.floor((right - left) / 2);
let maybeHours = hoursPerCandidate(maybeK);
if (maybeHours <= h) {
k = Math.min(k, maybeK);
right = maybeK - 1;
} else if (h < maybeHours) {
left = maybeK + 1;
} else {
/* can't happen */
}
}
return k;
}
console.log(minEatingSpeed([3, 6, 7, 11], 8));
console.log(minEatingSpeed([30, 11, 23, 4, 20], 5));
console.log(minEatingSpeed([312884470], 312884469));
export {};
problems/00876-middle-of-the-linked-list/README.md
# [SOLVED] 876 - Middle of the Linked List
[Middle of the Linked List](https://leetcode.com/problems/middle-of-the-linked-list/)
## notes
## summary
## tags
easy linked list
problems/00876-middle-of-the-linked-list/typescript/solution.ts
class ListNode {
val: number;
next: ListNode | null;
constructor(val?: number, next?: ListNode | null) {
this.val = val === undefined ? 0 : val;
this.next = next === undefined ? null : next;
}
}
/* case: null */
/* case: head.next === null */
/* case: h1.next -> h2; h2.next -> null */
function middleNode(head: ListNode | null): ListNode | null {
if (head === null) {
return null;
}
if (head.next === null) {
return head;
}
/* we must have been passed an ll with at least two nodes */
let middleNode = null;
const arr = [];
let current: ListNode | null = head;
while (current) {
arr.push(current);
current = current.next;
}
if (arr.length % 2 === 0) {
/* even: take second middle node */
middleNode = arr[arr.length / 2];
} else {
/* odd: take middle node */
middleNode = arr[Math.floor(arr.length / 2)];
}
return middleNode;
}
export {};
problems/00981-time-based-key-value-store/README.md
# [SOLVED] 981 - TIme Based Key-Value Store
[TIme Based Key-Value Store](https://leetcode.com/problems/time-based-key-value-store/)
## notes
### first thoughts
- attempted typescript a few weeks ago and realized I needed to use a map but
did not realize I need to use a binary search. had to look up solution and
learn about binary search.
### golang implementation
- the criteria that the calls to `set` will be with timestamps that are strictly
increasing is clue, but I am not sure how yet
- I can create a lookup that contains a slice of structs that contain both value
and timestamp, but then I will have to search it for `get`, which I could use
binary search on to be fast. is there a way I can do the lookup in O(1)?
## summary
- comp.t: Set: O(1), Get: O(log n)
- comp.s: O(n) for map object
- submitted working version that was similar to approach to others
- got hung up on how to implement binary search for non equality condition
(e.g. the largest number lower than or equal to target)
- also got hung up thinking if there was an O(1) Get implementation
- noticed much more idiomatic golang code for implementation and did improved
implementation
## tags
medium data structure
medium array
medium map
medium binary search
problems/00981-time-based-key-value-store/golang/solution-1.go
package main
import "fmt"
type TimestampVal struct {
value string
timestamp int
}
type TimeMap struct {
lookup map[string][]TimestampVal
}
func Constructor() TimeMap {
m := make(map[string][]TimestampVal)
return TimeMap{m}
}
func (this *TimeMap) Set(key string, value string, timestamp int) {
vals := this.lookup[key]
if len(vals) == 0 {
this.lookup[key] = []TimestampVal{{value, timestamp}}
} else {
vals = append(vals, TimestampVal{value, timestamp})
this.lookup[key] = vals
}
fmt.Println(this.lookup)
}
func (this *TimeMap) Get(key string, timestamp int) string {
if vals, ok := this.lookup[key]; ok {
candidateIndex := -1
// bs for closest without going over timestamp
for l, m, r := 0, -1, len(vals)-1; l <= r; {
m = l + (r-l)/2
fmt.Println(l, m, r, timestamp, candidateIndex, vals)
if vals[m].timestamp == timestamp {
return vals[m].value
}
if timestamp < vals[m].timestamp {
// our midpoint has overshot our timestamp, move r
r = m - 1
} else if vals[m].timestamp < timestamp {
// our midpoint _could_ be a candidate
if candidateIndex < vals[m].timestamp {
// only update it if it is better than our previous
candidateIndex = m
}
l = m + 1
} else {
// can't happen
}
}
if candidateIndex != -1 {
return vals[candidateIndex].value
}
}
return ""
}
/**
* Your TimeMap object will be instantiated and called as such:
* obj := Constructor();
* obj.Set(key,value,timestamp);
* param_2 := obj.Get(key,timestamp);
*/
func main() {
obj := Constructor()
obj.Set("foo", "bar", 1)
fmt.Println(obj.Get("foo", 1))
fmt.Println(obj.Get("foo", 3))
obj.Set("foo", "bar2", 4)
fmt.Println(obj.Get("foo", 4))
fmt.Println(obj.Get("foo", 5))
}
problems/00981-time-based-key-value-store/golang/solution-2.go
package main
// this takes a more idiomatic approach and cleans up the binary search logic
type record struct {
value string
timestamp int
}
type TimeMap struct {
lookup map[string][]record
}
func Constructor() TimeMap {
return TimeMap{
lookup: map[string][]record{},
}
}
func (this *TimeMap) Set(key string, value string, timestamp int) {
if _, ok := this.lookup[key]; !ok {
// not found, set an empty
this.lookup[key] = []record{}
}
this.lookup[key] = append(this.lookup[key], record{value, timestamp})
}
func (this *TimeMap) Get(key string, timestamp int) string {
var res string
records := this.lookup[key]
// bs for closest without going over timestamp
for l, m, r := 0, -1, len(records)-1; l <= r; {
m = l + (r-l)/2
if records[m].timestamp == timestamp {
// we are done
return records[m].value
} else if timestamp < records[m].timestamp {
// our midpoint has overshot our timestamp, move r
r = m - 1
} else if records[m].timestamp < timestamp {
// our midpoint _could_ be a candidate. because of the way that binary
// search works, our left pointer will never be decremented. this means
// that if we get into this condition we know that we will have the
// closest possible (at the moment) timestamp without going over
res = records[m].value
l = m + 1
} else {
// can't happen
}
}
return res
}
problems/00981-time-based-key-value-store/typescript/solution.ts
/* thoughts: */
/* - no removal to consider */
/* - need to store all timestamped values for a given key */
/* - max([timestamp_prev1, timestamp_prev2]) <= timestamp */
/* - use a map as backing datastructure */
/* - store an array of objects */
type Recording = {
timestamp: number;
value: string;
};
/* space: O(n) */
class TimeMap {
lookup: Map<string, Recording[]>;
constructor() {
this.lookup = new Map<string, Recording[]>();
}
/* time: O(1) */
set(key: string, value: string, timestamp: number): void {
const recordings = this.lookup.get(key) ?? [];
recordings.push({ timestamp: timestamp, value: value });
this.lookup.set(key, recordings);
}
/* time: O(1) + O(log n) */
get(key: string, timestamp: number): string {
const maybeRecordings = this.lookup.get(key) ?? [];
let val = "";
let leftIndex = 0;
let rightIndex = maybeRecordings.length - 1;
while (leftIndex <= rightIndex) {
const midIndex = leftIndex + Math.floor((rightIndex - leftIndex) / 2);
const mid = maybeRecordings[midIndex];
if (mid.timestamp <= timestamp) {
val = mid.value;
leftIndex = midIndex + 1;
} else {
rightIndex = midIndex - 1;
}
}
return val;
}
}
/**
* Your TimeMap object will be instantiated and called as such:
* var obj = new TimeMap()
* obj.set(key,value,timestamp)
* var param_2 = obj.get(key,timestamp)
*/
export {};
problems/01337-the-k-weakest-rows-in-a-matrix/README.md
# [SOLVED] 1337 - The K Weakest Rows in a Matrix
[The K Weakest Rows in a Matrix](https://leetcode.com/problems/the-k-weakest-rows-in-a-matrix/)
## notes
### golang implementation
- need to read every value (could stop when encounter a 0)
- need two items to do strength comparison: the number of soldiers and the index
of the row.
- cannot do a fixed strength calculation because index only comes into play when
comparing equivalent numbers of soldiers.
- feels like a map and sort
## summary
- comp.t: O(m + n) + O(m log m) because we sort so total O(n + m log m)
- comp.s: O(m) we store information for each row
- some optimizations are: use a min heap, use binary search to find the index of
the last 1
## tags
easy matrix
easy array
easy sort
problems/01337-the-k-weakest-rows-in-a-matrix/golang/solution.go
package main
import "sort"
type regiment struct {
cnt int
idx int
}
func kWeakestRows(mat [][]int, k int) []int {
var rows []regiment
for i, val := range mat {
var cnt int
for _, s := range val {
if s == 0 {
break
}
cnt += s
}
rows = append(rows, regiment{cnt, i})
}
sort.Slice(rows, func(i, j int) bool {
if rows[i].cnt == rows[j].cnt {
return rows[i].idx < rows[j].idx
}
return rows[i].cnt < rows[j].cnt
})
var indices []int
for _, reg := range rows {
indices = append(indices, reg.idx)
}
return indices[:k]
}
func main() {
}
problems/01337-the-k-weakest-rows-in-a-matrix/typescript/solution.ts
function kWeakestRows(mat: number[][], k: number): number[] {
return mat
.map((row, i) => {
return [
row.reduce((memo, person) => {
return (memo += person);
}, 0),
i,
];
}) /* [[2,0], [4,1], [1,2], [2,3], [5, 4]] */
.sort((a, b) => {
if (a[0] === b[0]) {
return a[1] - b[1];
}
return a[0] - b[0];
}) /* [[1,2], [2,0], [2,3], [4,1], [5,4]] */
.map((strength) => {
return strength[1];
}) /* [2, 0, 3, 1, 4] */
.slice(0, k); /* [2, 0, 3] */
}
export {};
problems/01342-number-of-steps-to-reduce-a-number-to-zero/README.md
# [SOLVED] 1342 - Number of Steps to Reduce a Number to Zero
[Number of Steps to Reduce a Number to Zero](https://leetcode.com/problems/number-of-steps-to-reduce-a-number-to-zero/)
## notes
### golang implementation thoughts
- perform the successive operations and keep count
## summary
- comp.t: O(log n)? we divide the space in half at every even number, but I do
not know how many even numbers we will encounter for a given n. I do not
remember how to tell which even numbers divide in half to other evens.
- comp.s: O(1)
## tags
easy math
problems/01342-number-of-steps-to-reduce-a-number-to-zero/golang/solution.go
package main
func numberOfSteps(num int) int {
var count int
for 0 < num {
count += 1
if num%2 == 0 {
num /= 2
} else {
num -= 1
}
}
return count
}
func main() {
}
problems/01342-number-of-steps-to-reduce-a-number-to-zero/typescript/solution.ts
function numberOfSteps(num: number): number {
let count = 0;
let reduced = num;
while (reduced !== 0) {
if (reduced % 2 === 0) {
reduced = reduced / 2;
} else {
reduced = reduced - 1;
}
count++;
}
return count;
}
export {};
problems/01672-richest-customer-wealth/README.md
# [SOLVED] 1672 - Richest Customer Wealth
[Richest Customer Wealth](https://leetcode.com/problems/richest-customer-wealth/)
## notes
## summary
## tags
easy array
problems/01672-richest-customer-wealth/typescript/solution.ts
function maximumWealth(accounts: number[][]): number {
return accounts /* [[1,5],[7,3],[3,5]] */
.map((customerAccounts) => {
return customerAccounts.reduce((memo, balance) => {
return memo + balance;
}, 0);
}) /* [6,10,8] */
.sort((a, b) => {
return b - a;
})[0];
}
export {};
problems/02281-sum-of-total-strength-of-wizards/README.md
# [OPEN] 2281 - Sum of Total Strength of Wizards
[Sum of Total Strength of Wizards](https://leetcode.com/problems/sum-of-total-strength-of-wizards/)
## notes
### first thoughts
- feels kind of like dynamic programming as `helper([1,3])` is the same as
`helper([3,1])`
- if I sort the values added to `min` and `sum` I can use them as cache keys
- I cannot sort the entire `strength` as that would mess up the subarrays
- is there significance to the modulo being `10^9 + 7`?
- feels like I need two for loops to go through each permutation of
subarray... not very efficient
- state will be the subarray
- transition function: `str = min(sub) * sum(sub)`
- comp.t: O(n^2) for operations times O(n log n) for the sort. O(n^3 log n)
which is really bad. need to find a different approach
## summary
## tags
problems/02281-sum-of-total-strength-of-wizards/golang/attempt-1.go
package main
import (
"fmt"
"math"
"sort"
)
func sum(input []int) int {
var total int
for _, val := range input {
total += val
}
return total
}
func totalStrength(strength []int) int {
normalization := (int(math.Pow(10, 9)) + 7)
lookup := make(map[string]int)
helper := func(input []int) int {
sub := make([]int, len(input), cap(input))
copy(sub, input)
sort.Ints(sub)
min := sub[0]
total := sum(sub)
key := fmt.Sprintf("%d-%d", min, total)
if val, ok := lookup[key]; ok {
return val
}
localStrength := min * total
lookup[key] = localStrength
return localStrength
}
var total int
for i := 0; i < len(strength); i++ {
for j := i + 1; j <= len(strength); j++ {
total += helper(strength[i:j])
}
}
return total % normalization
}
func main() {
fmt.Println(totalStrength([]int{1, 3, 1, 2}))
fmt.Println(totalStrength([]int{5, 4, 6}))
fmt.Println(totalStrength([]int{747, 812, 112, 1230, 1426, 1477, 1388, 976, 849, 1431, 1885, 1845, 1070, 1980, 280, 1075, 232, 1330, 1868, 1696, 1361, 1822, 524, 1899, 1904, 538, 731, 985, 279, 1608, 1558, 930, 1232, 1497, 875, 1850, 1173, 805, 1720, 33, 233, 330, 1429, 1688, 281, 362, 1963, 927, 1688, 256, 1594, 1823, 743, 553, 1633, 1898, 1101, 1278, 717, 522, 1926, 1451, 119, 1283, 1016, 194, 780, 1436, 1233, 710, 1608, 523, 874, 1779, 1822, 134, 1984}))
}
problems/tsconfig.json
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
utilities/edits/edits.go
package main
import (
"fmt"
"io/fs"
"io/ioutil"
"log"
"path/filepath"
"regexp"
"strings"
)
func main() {
// take a directory
filepath.Walk("../../problems", func(path string, info fs.FileInfo, err error) error {
if err != nil {
fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
return err
}
if ok, _ := regexp.MatchString(".*/node_modules/.*", path); ok {
return nil
}
// find all files by a certain name, at any depth
if ok, _ := regexp.MatchString(".*/README.md", path); ok {
fmt.Println(path)
input, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalln(err)
}
// read their contents
lines := strings.Split(string(input), "\n")
// replace with "# [SOLVED]"
firstLine := strings.Split(lines[0], " ")
firstLine = append(firstLine[:2], firstLine[1:]...)
firstLine[1] = "[SOLVED]"
lines[0] = strings.Join(firstLine, " ")
// rewrite files
output := strings.Join(lines, "\n")
err = ioutil.WriteFile(path, []byte(output), 0644)
if err != nil {
log.Fatalln(err)
}
}
return nil
})
}
utilities/edits/go.mod
module github.com/klandergren/leetcode/utilities/edits
go 1.19
utilities/leet/go.mod
module github.com/klandergren/leetcode/utilities/leet
go 1.19
utilities/leet/leet.go
package main
import (
"errors"
"flag"
"fmt"
"log"
"os"
"strings"
)
func main() {
const (
DefaultNumber = -1
DefaultTitle = ""
DefaultLanguage = ""
DefaultURL = ""
)
supportedLanguageSolutionFilenameLookup := map[string]string{
"typescript": "solution.ts",
"golang": "solution.go",
}
supportedLanguageSolutionFilecontentsLookup := map[string]string{
"typescript": "\n\nexport{};",
"golang": "package main\n\nfunc main() {\n\n}\n",
}
numberFlag := flag.Int("number", DefaultNumber, "the problem number")
titleFlag := flag.String("title", DefaultTitle, "the problem title")
languageFlag := flag.String("language", DefaultLanguage, "the solution language")
urlFlag := flag.String("url", DefaultURL, "the problem URL")
flag.Parse()
if *numberFlag == DefaultNumber {
log.Fatal("missing --number flag")
}
if *titleFlag == DefaultTitle {
log.Fatal("missing --title flag")
}
if *languageFlag == DefaultLanguage {
log.Fatal("missing --language flag")
}
if *urlFlag == DefaultURL {
log.Fatal("missing --url flag")
}
solutionFileName, ok := supportedLanguageSolutionFilenameLookup[*languageFlag]
if !ok {
log.Fatal("language not supported")
}
// number
problemNumber := fmt.Sprintf("%05d", *numberFlag)
// title
lowerCaseTitle := strings.ToLower(*titleFlag)
titleComponents := strings.Split(lowerCaseTitle, " ")
cleanedTitle := strings.Join(titleComponents, "-")
// directories
problemDirectory := fmt.Sprintf("problems/%s-%s", problemNumber, cleanedTitle)
languageSolutionDirectory := fmt.Sprintf("%s/%s", problemDirectory, *languageFlag)
// files
readmeFilePath := fmt.Sprintf("%s/README.md", problemDirectory)
readmeText := fmt.Sprintf("# [OPEN] %d - %s\n\n[%s](%s)\n\n## notes\n\n## summary\n\n## tags\n\n", *numberFlag, *titleFlag, *titleFlag, *urlFlag)
solutionFilePath := fmt.Sprintf("%s/%s", languageSolutionDirectory, solutionFileName)
solutionText, ok := supportedLanguageSolutionFilecontentsLookup[*languageFlag]
if !ok {
log.Fatal("language not supported")
}
// create directories
os.MkdirAll(languageSolutionDirectory, 0755)
// create files
readme, err := os.OpenFile(readmeFilePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0755)
defer readme.Close()
if err != nil {
if errors.Is(err, os.ErrExist) {
log.Printf("could not create %s. skipping. message: %v\n", readmeFilePath, err)
} else {
log.Fatalf("could not create %s. message: %v\n", readmeFilePath, err)
}
} else {
fmt.Fprintln(readme, readmeText)
}
solution, err := os.OpenFile(solutionFilePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0755)
defer solution.Close()
if err != nil {
if errors.Is(err, os.ErrExist) {
log.Printf("could not create %s. skipping. message: %v\n", solutionFilePath, err)
} else {
log.Fatalf("could not create %s. message: %v\n", solutionFilePath, err)
}
} else {
fmt.Fprintln(solution, solutionText)
}
}