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;
}