Open21

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

まさきちまさきち

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)

https://leetcode.com/problems/two-sum/editorial/


  • O記法(オーダー記法)

https://qiita.com/asksaito/items/59e0d48408f1eab081b5#:~:text=ことを指す。-,O記法(オーダー記法),的なオーダーをまとめる。

  • Map

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map

Object: 攻撃者がオブジェクトのプロトタイプを上書きできる可能性があり、 オブジェクトインジェクション攻撃 につながる可能性
Mapは関数、keyはオブジェクト、あらゆるプリミティブを格納することが可能。


プログラムの計算量を求める

https://qiita.com/cotrpepe/items/1f4c38cc9d3e3a5f5e9c

Hidden comment
Hidden comment
Hidden comment
Hidden comment
Hidden comment
Hidden comment
Hidden comment
まさきちまさきち

3. Two sum

https://neetcode.io/problems/two-integer-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

https://neetcode.io/problems/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

https://neetcode.io/problems/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?

https://neetcode.io/problems/products-of-array-discluding-self



解答

    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.

https://neetcode.io/problems/valid-sudoku



解答

    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
    }


解説・参考

https://qiita.com/harashoo/items/cc5c354b09dada29af37

https://teratail.com/questions/303032

https://rebecca-margaret.medium.com/solving-leetcode-36-valid-sudoku-5c00cc2cf91a

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.

https://neetcode.io/problems/longest-consecutive-sequence



解答

    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.

https://neetcode.io/problems/is-palindrome

    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.

https://neetcode.io/problems/two-integer-sum-ii

まさきちまさきち

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.

https://neetcode.io/problems/three-integer-sum

    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を指定する。

https://qiita.com/ryomaDsakamoto/items/eae134f6d8af36519560


new Set()で重複排除 → Setの中身は一意であることが保証される。

https://qiita.com/ShinKano/items/651781814e9ecbc33f4c


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.

https://neetcode.io/problems/max-water-container

解答

    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 に更新する。

https://zenn.dev/s_yasunaga/articles/e87b16cb0be378

まさきちまさきち

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.

https://neetcode.io/problems/trapping-rain-water


思考

各X座標において溜まる水の量が存在する。ある地点の両側をみて、両側に現在地より高い地点があった場合に、それらのうち低い方の高さまで水が溜まる性質がある。

https://qiita.com/grouse324/items/43ef1aba7332624a67e0

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