Neetcode 150問ロードマップに挑戦する

Neetcode
Neetcodeを150問解く
Neetcodeのロードマップ
Leetcode

1. Tow sum
Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
You can return the answer in any order.
Example 1:
Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Explanation: Because nums[0] + nums[1] == 9, we return [0, 1].
Example 2:
Input: nums = [3,2,4], target = 6
Output: [1,2]
Example 3:
解答
function twoSum(nums: number[], target: number): number[] {
let output: Map<number, number> = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (output.has(complement)) {
return [output.get(complement), i];
}
output.set(nums[i], i);
}
return []
}
解説・参考
Time complexity: O(n2)
Space complexity: O(1)
- O記法(オーダー記法)
- Map
Object: 攻撃者がオブジェクトのプロトタイプを上書きできる可能性があり、 オブジェクトインジェクション攻撃 につながる可能性
Mapは関数、keyはオブジェクト、あらゆるプリミティブを格納することが可能。
プログラムの計算量を求める

3. Two sum
解答
class Solution {
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
twoSum(nums, target) {
const map = new Map();
for (let index = 0; index < nums.length; index++) {
const num = nums[index];
const complement = target - num;
const sumIndex = map.get(complement);
const isTarget = map.has(complement);
if (isTarget) {
return [index, sumIndex];
}
map.set(num, index);
}
return [-1, -1];
}
}

4. Group Anagrams
Given an array of strings strs, group all anagrams together into sublists. You may return the output in any order.
An anagram is a string that contains the exact same characters as another string, but the order of the characters can be different.
解答
function groupAnagrams(strs) {
const anagrams = {};
for (let str of strs) {
const key = str.split('').sort().join('');
if (!anagrams[key]) {
anagrams[key] = [];
}
// Append the original string to the array corresponding to the sorted key
anagrams[key].push(str);
}
return Object.values(anagrams);
}
const ans = {};
for (const s of strs) {
const count = Array(26).fill(0);
for (const c of s) {
count[c.charCodeAt(0) - 'a'.charCodeAt(0)]++;
}
const key = count.join('#');
if (!ans[key]) {
ans[key] = [];
}
ans[key].push(s);
}
return Object.values(ans);
}

5. Top K Elements in List
Given an integer array nums and an integer k, return the k most frequent elements within the array.
The test cases are generated such that the answer is always unique.
You may return the output in any order.
解答
topKFrequent(nums, k) {
const anagrams = {};
const counts = []
for (let num of nums) {
const key = `key-${num}`
if (!anagrams[key]) {
anagrams[key] = [];
}
anagrams[key].push(num);
}
Object.values(anagrams).sort((a,b) => b.length - a.length).forEach((array, i) => {
if(i + 1 <= k) {
counts.push(array[0])
}
})
return counts

6. String Encode and Decode
Design an algorithm to encode a list of strings to a single string. The encoded string is then decoded back to the original list of strings.
Please implement encode and decode
解答
class Solution {
/**
* @param {string[]} strs
* @returns {string}
*/
encode(strs) {
let result = '';
for (let s of strs) {
result += ${s.length}#${s};
}
return result;
}
- #を使用して、文字列の長さと実際の文字列を区切っている(#は区切り文字として使用)
/**
* @param {string} str
* @returns {string[]}
*/
decode(str) {
let result = [];
let i = 0;
while (i < str.length) {
let j = i;
while (str[j] !== '#') {
j++;
}
let length = parseInt(str.substring(i, j), 10);
i = j + 1;
j = i + length;
result.push(str.substring(i, j));
i = j;
}
return result;
}
解説
エンコード(encoding)について
データや情報を、ある形式から別の形式に変換することを指す(異なるシステムや環境で情報の保存や伝送を効率的に行う為)
エンコード例
- 文字エンコード:「A」という文字をUTF-8というエンコード方式でバイトに変換すると、バイト値01000001となり、コンピュータ内部で読み取り可能。
- データ圧縮のためのエンコード: 音楽や画像などのファイルは、容量が大きくなりやすいので、効率よく圧縮して保存するためにエンコードが使用される。
- URLエンコード: スペースや特殊記号を含めるとURLに問題が発生するためURLエンコードを行う。
今回の実装では、文字列の長さとその文字列自体を結合して一つの文字列にしており、送信や保存を簡単にするためのエンコードの例。その後に元の文字列リストに戻す(デコードする)ことが可能。

7. Products of Array Discluding Self
Given an integer array nums, return an array output where output[i] is the product of all the elements of nums except nums[i].
Each product is guaranteed to fit in a 32-bit integer.
Follow-up: Could you solve it in O(n) O(n) time without using the division operation?
解答
productExceptSelf(nums) {
let allExceptNums = []
for (let i = 0; i < nums.length; i++) {
let initialValue = 1
nums.map((v, index) => {
if(i === index) return
initialValue *= v
})
if(Number.isInteger(initialValue) && initialValue >= -2147483648 && initialValue <= 2147483647) {
allExceptNums.push(initialValue)
}
}
return allExceptNums
}

8. Valid Sudoku
You are given a a 9 x 9 Sudoku board board. A Sudoku board is valid if the following rules are followed:
Each row must contain the digits 1-9 without duplicates.
Each column must contain the digits 1-9 without duplicates.
Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without duplicates.
Return true if the Sudoku board is valid, otherwise return false
Note: A board does not need to be full or be solvable to be valid.
解答
isValidSudoku(board) {
for (let i = 0; i < board.length; i++) {
const pickedRow = new Set()
const pickedColumn = new Set()
const squares = new Set()
for (let j = 0; j < board.length; j++) {
// Row
const NoCommaRo = board[i].filter((d) => d !== '.')
if(pickedRow.has(NoCommaRo)) return false
pickedRow.add(NoCommaRo)
// Column
const NoCommaCo = board.map(digit => digit[0]).filter((d) => d !== '.')
if(pickedColumn.has(NoCommaCo)) return false
pickedColumn.add(NoCommaCo)
// squares
let miniNum = board[ 3 * Math.floor(i / 3) + Math.floor(j / 3) ][ 3 * (i % 3) + (j % 3) ]
if(miniNum !== '.' && squares.has(miniNum)) return false
squares.add(miniNum)
}
}
return true
}
解説・参考
for (let i...)
for (let j...)
board[i][j]
board[j][i]
// Let's loop! // This is all happening inside our inner for loop. When we reach the end
// we go back to the i loop, i++, reset j to 0 and do the whole thing all over again,
// but this time we're checking our second row and second column. So cool, right?!
i = 0 j = 0
board[i][j] board[j][i] j++
[0][0] = 5 [0][0] = 5 1
[0][1] = 3 [1][0] = 6 2
[0][2] = . [2][0] = . 3
[0][3] = . [3][0] = 8 4
[0][4] = 7 [4][0] = 4 5
[0][5] = . [5][0] = 7 6
[0][6] = . [6][0] = . 7
[0][7] = . [7][0] = . 8
[0][8] = . [8][0] = .

9. Longest Consecutive Sequence
Given an array of integers nums, return the length of the longest consecutive sequence of elements.
A consecutive sequence is a sequence of elements in which each element is exactly 1 greater than the previous element.
You must write an algorithm that runs in O(n) time.
解答
longestConsecutive(nums) {
if(!nums.length) return 0
const array = new Set(nums.sort((a,b) => a - b))
let cs = 0
for (const num of array) {
if(!array.has(num -1)){
let length = 1
while(array.has(num + length)){
length++
}
cs = Math.max(cs, length)
}
}
return cs
}
解説
-
num + length
:連番かどうか確認 -
if (!array.has(num - 1))
:連続数列の開始点かどうかを判定するために使用。
num - 1 が存在しない場合、それはその数が連続数列の開始点であることを意味するので、そこから連続数列の長さを計算する。
例えば、nums = [100, 4, 200, 1, 3, 2] の場合、このアルゴリズムは次のように動作します。
Set に変換すると、array = {1, 2, 3, 4, 100, 200} になります。
ループ内で、1 は 1 - 1 = 0 が Set に存在しないため、1 から数列をスタートし、1, 2, 3, 4 の長さ4の連続数列が見つかります。
2, 3, 4 はすでに 1 から始まる数列の一部なので、2 - 1 = 1 が存在するため、計算をスキップします。
次に、100 は 100 - 1 = 99 が存在しないため、ここから数列をスタートしますが、連続する数字がないため長さ1の数列になります。同様に、200 も単独の数字として長さ1の数列になります。

10. Is Palindrome ☑️
Given a string s, return true if it is a palindrome, otherwise return false.
A palindrome is a string that reads the same forward and backward. It is also case-insensitive and ignores all non-alphanumeric characters.
isAlphanumeric(char) {
return (char >= 'a' && char <= 'z') ||
(char >= 'A' && char <= 'Z') ||
(char >= '0' && char <= '9');
}
isPalindrome(s) {
let trim = ''
for(let c of s) {
if(this.isAlphanumeric(c)) {
trim = trim + c.toLowerCase()
}
}
const reverse = trim.split("").reverse().join("")
if(trim === reverse) return true
return false
}

11. Two Integer Sum II
Given an array of integers numbers that is sorted in non-decreasing order.
Return the indices (1-indexed) of two numbers, [index1, index2], such that they add up to a given target number target and index1 < index2. Note that index1 and index2 cannot be equal, therefore you may not use the same element twice.
There will always be exactly one valid solution.

12. Three Integer Sum
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] where nums[i] + nums[j] + nums[k] == 0, and the indices i, j and k are all distinct.
The output should not contain any duplicate triplets. You may return the output and the triplets in any order.
threeSum(nums) {
let targetArray = []
const isAllZroNums = nums.flat().every((num) => num === 0)
if(isAllZroNums) {
targetArray.push([0,0,0])
return targetArray
}
for(let i=0; i < nums.length; i++) {
for(let j=0; j < nums.length; j++) {
for(let k=0; k < nums.length; k++) {
if(nums[i] + nums[j] + nums[k] === 0) {
const threeInteger = [nums[i] , nums[j] , nums[k]]
targetArray.push(threeInteger)
}
}
}
}
const isAllZro = targetArray.flat().every((num) => num === 0)
if(isAllZro) return []
return targetArray.slice(0,2)
}
解答
threeSum(nums) {
const res = new Set();
nums.sort((a, b) => a - b);
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
for (let k = j + 1; k < nums.length; k++) {
if (nums[i] + nums[j] + nums[k] === 0) {
res.add(JSON.stringify([nums[i], nums[j], nums[k]]));
}
}
}
}
return Array.from(res).map(item => JSON.parse(item))
}
解説・参考
二重配列のソートについて、array配列のindex[1]を基準にsortしたければreturnするデータのindexを指定する。
new Set()
で重複排除 → Setの中身は一意であることが保証される。
JSON.stringifyを使用する理由
new Set()
重複する要素を自動的に取り除くデータ構造ではあるが、プリミティブな値や文字列しか保存できないため、三つ組みの配列 [nums[i], nums[j], nums[k]] をそのまま Set に追加すると、配列が重複しているかどうかを認識できない(JavaScriptでは配列は異なるオブジェクトとして判断される)
JSON.stringify
は配列を JSON 文字列に変換する。これにより、内容が同じ配列が同じ文字列として認識されるようになり、例えば、[1, 2, -3] と [1, 2, -3] はどちらも "["1", "2", "-3"]" という同じ文字列に変換され、これによりSet は内容が同じ三つ組みを重複として認識することが可能。

13. Max Water Container
You are given an integer array heights where heights[i] represents the height of the bar. You may choose any two bars to form a container. Return the maximum amount of water a container can store.
解答
maxArea(heights) {
const copyHeights = structuredClone(heights)
const [first, second] = new Set(copyHeights.sort((a,b) => b - a))
const fIndex = heights.indexOf(first)
const sIndex = heights.lastIndexOf(second)
const length = fIndex - sIndex
return Math.abs(second * length)
}
Solution
maxArea(heights) {
let res = 0;
for (let i = 0; i < heights.length; i++) {
for (let j = i + 1; j < heights.length; j++) {
res = Math.max(res, Math.min(heights[i], heights[j]) * (j - i));
}
}
return res;
}
解説
水量は、2つのバーの高さの短い方によって決まり、2つのバーの間の距離(横幅)も影響する。
- Math.min(heights[i], heights[j]):heights[i] と heights[j] のうち、短い方の高さを取得する。
- (j - i) で2つのバーの間の距離(横幅)を計算する。
- Math.max(res, ...): これまでに計算された最大水量(res)と、現在のペアによって計算された水量を比較し、大きい方を res に更新する。

14. Trapping Rain Water
You are given an array non-negative integers heights which represent an elevation map. Each value heights[i] represents the height of a bar, which has a width of 1. Return the maximum area of water that can be trapped between the bars.
思考
各X座標において溜まる水の量が存在する。ある地点の両側をみて、両側に現在地より高い地点があった場合に、それらのうち低い方の高さまで水が溜まる性質がある。
各x座標について右側,左側を計算する処理を追加する。
trap(height) {
if(!height.length) return 0
let capacity = 0
for(let i = 0; i < height.length; i++) {
const left_max = isNaN(height[i - 1]) ? height[i] : height[i - 1]
const right_max = isNaN(height[i + 1]) ? height[i] : height[i + 1]
capacity += Math.max(0, Math.min(left_max,right_max) - height[i])
}
return capacity
}
上記処理の場合、max値ではなく左右の値を見て算出しているだけなので、不正解になる。
各x座標について右側,左側を計算するので,時間計算量は0(n2)となる。
解答1
trap(height) {
if (!height.length) {
return 0;
}
let n = height.length;
let res = 0;
for (let i = 0; i < n; i++) {
let leftMax = height[i];
let rightMax = height[i];
for (let j = 0; j < i; j++) {
leftMax = Math.max(leftMax, height[j]);
}
for (let j = i + 1; j < n; j++) {
rightMax = Math.max(rightMax, height[j]);
}
res += Math.min(leftMax, rightMax) - height[i];
}
return res;
}
解答2
trap(height) {
if (!height || height.length === 0) {
return 0;
}
let l = 0;
let r = height.length - 1;
let leftMax = height[l];
let rightMax = height[r];
let res = 0;
while (l < r) {
if (leftMax < rightMax) {
l++;
leftMax = Math.max(leftMax, height[l]);
res += leftMax - height[l];
} else {
r--;
rightMax = Math.max(rightMax, height[r]);
res += rightMax - height[r];
}
}
return res;
}

15. Valid Parentheses
You are given a string s consisting of the following characters: '(', ')', '{', '}', '[' and ']'.
The input string s is valid if and only if:
Every open bracket is closed by the same type of close bracket.
Open brackets are closed in the correct order.
Every close bracket has a corresponding open bracket of the same type.
解答
isValid(s) {
const validStack = []
const closeToOpen = {
')': '(',
']': '[',
'}': '{'
}
for(let b of s){
if(closeToOpen[b]) {
if(validStack.length > 0 && validStack[validStack.length - 1] === closeToOpen[b]){
validStack.pop()
} else {
return false
}
} else {
validStack.push(b)
}
}
return validStack.length === 0
}

155. min stack
Design a stack class that supports the push, pop, top, and getMin operations.
MinStack() initializes the stack object.
void push(int val) pushes the element val onto the stack.
void pop() removes the element on the top of the stack.
int top() gets the top element of the stack.
int getMin() retrieves the minimum element in the stack.
Each function should run in O(1) O(1) time.
以下の機能を持つスタックを作成する。
- push(x) -- スタックに要素xをpushする
- pop() -- スタックの一番上から要素を取り除く
- top() -- 一番上の要素を取得する
- getMin() -- スタック内の最小要素を定数時間で返す
解答
class MinStack {
constructor() {
this.stack = []; // スタック
this.minStack = []; // スタックの最小要素
}
push(val) {
this.stack.push(val)
if(this.minStack.length === 0) {
this.minStack.push(val)
} else {
this.minStack.push(Math.min(this.minStack[this.minStack.length - 1],val))
}
}
pop() {
this.stack.pop()
this.minStack.pop()
}
top() {
return this.stack[this.stack.length - 1]
}
getMin() {
return this.minStack[this.minStack.length - 1]
}
}
解説
スタックと最小単位を格納する変数を設定
this.stack = []; // スタック
this.minStack = []; // スタックの最小要素
pop
とpush
についてはそのままの実装が可能。
push(val) {
this.MinStack.push(val)
}
pop() {
this.MinStack.pop()
}
定数時間について: 定数時間とは問題の計算にかかる時間が入力として与えられるデータの大きさに依存せず一定であることを指す。つまり、pushやpopの処理をgetMin()で呼び出した場合、定数時間の条件には当てはまらなくなってしまう。pushする際は現在の最小値と追加される値を比較して小さい方を保持する。
push(val) {
this.stack.push(val)
if(this.minStack.length === 0) {
this.minStack.push(val)
} else {
this.minStack.push(Math.min(this.minStack[this.minStack.length - 1],val))
}
}
pop() {
this.stack.pop()
this.minStack.pop()
}

Evaluate Reverse Polish Notation
You are given an array of strings tokens that represents a valid arithmetic expression in Reverse Polish Notation.Return the integer that represents the evaluation of the expression.
The operands may be integers or the results of other operations.
The operators include '+', '-', '*', and '/'.
Assume that division between integers always truncates toward zero.
stringの配列で渡された値を算術(逆ポーランド記法を使用)する。
逆ポーランド記法とは・・・普段私たちが使用している中置記法は演算の優先順位を考慮したり、カッコの中を先に演算したりする必要があるので、プログラムで処理しにくい。「逆ポーランド記法」は、プログラムで処理しやすいものであり、「AB+」のように、値と値の後に演算子を置きます。
【逆ポーランド記法の計算式の値を得る手順】
(1) 逆ポーランド記法の計算式の先頭から順に1文字ずつ取り出す。
(2) 取り出した文字が値(変数や数値)ならスタックにプッシュする。
(3) 取り出した文字が演算子ならスタックから2つのデータをポップし、両者の演算結果の値をスタックにプッシュする。
(4) (1)に戻って繰り返し、最後の文字を取り出して処理したら、その時点でスタックに残っている1つの値が計算式の値である。
解答
class Solution {
/**
* @param {string[]} tokens
* @return {number}
*/
evalRPN(tokens) {
if(!tokens.length) {
return 0
}
const stack = []
for(const token of tokens) {
switch(token) {
case '+':
stack.push(stack.pop() + stack.pop())
break
case '-':
stack.push(-stack.pop() + stack.pop())
break
case '*':
stack.push(stack.pop() * stack.pop())
break
case '/':
stack.push(Math.trunc(1 / stack.pop() * stack.pop()))
break
default: stack.push(Number(token))
}
}
const result = stack.pop()
return result
}
}
解説
逆ポーランド記法の数式の計算
tokenが数値以外の場合は計算式を適用し、pop()
で取り出し計算する。逆に数値の場合はpushしてstacksいておく。整数同士の割り算は常にゼロに向かって切り捨てられることが条件の為、Math.trunc()
で引数として与えた数の小数部の桁を取り除く。