每天一个算法题(javascript)

全部代码地址:https://github.com/jimoruyan/leetcode

题库来源:https://leetcode-cn.com/problemset/all/

1.两数之和

 // 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。
        // 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

        // 示例:

        // 给定 nums = [2, 7, 11, 15], target = 9
        // 因为 nums[0] + nums[1] = 2 + 7 = 9
        // 所以返回[0, 1]

        var twoSum1 = function(nums , target){
            for(var i = 0,length = nums.length;i

2.无重复字符的最长子串

 // 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。
        // 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

        // 示例:

        // 给定 nums = [2, 7, 11, 15], target = 9
        // 因为 nums[0] + nums[1] = 2 + 7 = 9
        // 所以返回[0, 1]

        var twoSum1 = function(nums , target){
            for(var i = 0,length = nums.length;i

3.整数反转

 // 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

        // 示例 1:
        // 输入: 123
        // 输出: 321

        // 示例 2:
        // 输入: -123
        // 输出: -321

        // 示例 3:
        // 输入: 120
        // 输出: 21
        // 注意:

        // 假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为[−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。

        //解题思路
        // result * 10 + x % 10 取出末位 x % 10(负数结果还是负数,无需关心正负),拼接到 result 中。
        // x / 10 去除末位,| 0 强制转换为32位有符号整数。
        // 通过 | 0 取整,无论正负,只移除小数点部分(正数向下取整,负数向上取整)。
        // result | 0 超过32位的整数转换结果不等于自身,可用作溢出判断。

        var reverse = function (x) {
            let res = 0;
            while (x !== 0) {
                res = res * 10 + x % 10;
                x = x / 10 | 0;
            }
            return (res | 0) === res ? res : 0
        };

4.罗马数字转整数

 //罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

        // 字符          数值
        // I             1
        // V             5
        // X             10
        // L             50
        // C             100
        // D             500
        // M             1000
        // 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做  XXVII, 即为 XX + V + II 。

        // 通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

        // I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
        // X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
        // C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
        // 给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。

        // 示例 1:

        // 输入: 3
        // 输出: "III"
        // 示例 2:

        // 输入: 4
        // 输出: "IV"
        // 示例 3:

        // 输入: 9
        // 输出: "IX"
        // 示例 4:

        // 输入: 58
        // 输出: "LVIII"
        // 解释: L = 50, V = 5, III = 3.
        // 示例 5:

        // 输入: 1994
        // 输出: "MCMXCIV"
        // 解释: M = 1000, CM = 900, XC = 90, IV = 4.

        // 思路
        // 首先将所有的组合可能性列出并添加到哈希表中
        // 然后对字符串进行遍历,由于组合只有两种,一种是 1 个字符,一种是 2 个字符,其中 2 个字符优先于 1 个字符
        // 先判断两个字符的组合在哈希表中是否存在,存在则将值取出加到结果 ans 中,并向后移2个字符。不存在则将判断当前 1 个字符是否存在,存在则将值取出加到结果 ans 中,并向后移 1 个字符
        // 遍历结束返回结果 ans
        var romanToInt = function (s) {
            const map = {
                I: 1,
                IV: 4,
                V: 5,
                IX: 9,
                X: 10,
                XL: 40,
                L: 50,
                XC: 90,
                C: 100,
                CD: 400,
                D: 500,
                CM: 900,
                M: 1000
            };
            let ans = 0;
            for (let i = 0; i < s.length;) {
                if (i + 1 < s.length && map[s.substring(i, i + 2)]) {
                    ans += map[s.substring(i, i + 2)];
                    i += 2;
                } else {
                    ans += map[s.substring(i, i + 1)];
                    i++;
                }
            }
            return ans;
        };
        //下面有一个更简单的
        //主要思路是如果相邻的两个数字左边的比右边的小就减去小值,反之则加之
        var romanToInt = function (s) {
            var hashNum = {
                "I": 1,
                "V": 5,
                "X": 10,
                "L": 50,
                "C": 100,
                "D": 500,
                "M": 1000
            }
            var result = 0;
            for (let i = 0; i < s.length; i++) {
                //越界返回und,判断后返回false,三目运算走 result += hashNum[s[i]]
                hashNum[s[i]] < hashNum[s[i + 1]] ? result -= hashNum[s[i]] : result += hashNum[s[i]]
            }
            return result;
        };

5.有效的括号

// 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

        // 有效字符串需满足:

        // 左括号必须用相同类型的右括号闭合。
        // 左括号必须以正确的顺序闭合。
        // 注意空字符串可被认为是有效字符串。

        // 找到最内层的括号对,消去,重复此过程,若存在无法消去的字符则说明字符串无效。
        var isValid = function (s) {
            while (s.length) {
                var temp = s;
                s = s.replace('()', '');
                s = s.replace('[]', '');
                s = s.replace('{}', '');
                if (s == temp) return false
            }
            return true;
        };
        // 可以边遍历边匹配。也就是遍历的时候遇到左括号存入数组,下次遇到的第一个右括号必须和数组中最后一个元素匹配,否则为无效字符串,匹配完成后从数组中删除此元素。若最终数组为空,表示括号已全部匹配完,字符串有效。


        var isValid = function (s) {
            var map = {
                "(": ")",
                "[": "]",
                "{": "}"
            }
            var leftArr = []
            for (var ch of s) {
                if (ch in map) leftArr.push(ch); //为左括号时,顺序保存
                else { //为右括号时,与数组末位匹配
                    if (ch != map[leftArr.pop()]) return false;
                }
            }
            return !leftArr.length //防止全部为左括号
        };

6.移除元素

 // 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
        // 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
        // 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

        // 方法1 直接覆盖
        // 遇到不同于 val 的项,就将它直接覆盖到 nums 数组中,从第一项开始覆盖
        // 遍历完数组,不同于 val 的项都安排到了 nums 数组的前头

        var removeElement = (nums, val) => {
            let index = 0
            for (let i = 0; i < nums.length; i++) {
                if (nums[i] !== val) {
                    nums[index] = nums[i]
                    index++
                }
            }
            return index
        }
        // 方法2 末尾项覆盖(双指针)
        // 指向头尾的双指针
        // 遇到等于val的项,就拿数组的末尾项覆盖它
        // 末尾项搬到前面来了,将尾指针左移一位
        // 如果遇到不同于val的项,左指针就+1,考察下一项
        // 循环结束的条件是两个指针交叉相遇

        var removeElement = (nums, val) => {
            let index = 0, last = nums.length - 1
            while (index <= last) {
                if (nums[index] === val) {
                    nums[index] = nums[last]
                    last--
                } else {
                    index++
                }
            }
            return index
        }

7.实现strStr

// 实现 strStr() 函数。
        // 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。

        var strStr = function (haystack, needle) {
            return haystack.indexOf(needle)
        };
        var strStr = function (haystack, needle) {
            if (needle === "") return 0
            for (var i = 0; i < haystack.length; i++) {
                if (haystack[i] === needle[0]) {
                    if (haystack.substring(i, i + needle.length) === needle) return i;
                }
            }
            return -1
        };

8.搜索查找位置

 // 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

        // 你可以假设数组中无重复元素。

        // 示例 1:

        // 输入: [1, 3, 5, 6], 5
        // 输出: 2
        // 示例 2:

        // 输入: [1, 3, 5, 6], 2
        // 输出: 1
        // 示例 3:

        // 输入: [1, 3, 5, 6], 7
        // 输出: 4
        // 示例 4:

        // 输入: [1, 3, 5, 6], 0
        // 输出: 0

        // 1.暴力解法
        var searchInsert = function (nums, target) {
            if (nums[0] > target) {
                return 0;
            }
            for (let i = 0; i < nums.length; i++) {
                if (nums[i] >= target) {
                    return i;
                }
            }
            return nums.length - 1
        };
        // 2.二分法
        var searchInsert = function (nums, target) {
            if (nums[0] > target) {
                return 0;
            } else if (nums[nums.length - 1] < target) {
                return nums.length;
            }
            let start = 0;
            end = nums.length - 1;
            while (start <= end) {
                let mid = Math.round((start + end) / 2);
                if (nums[mid] === target) {
                    return mid;
                } else if (nums[mid] < target) {
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }
            }
            return start;
        };

9.寻找两个正序数组的中位数

 // 给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
        // 请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n)) 。
        // 你可以假设 nums1 和 nums2 不会同时为空。

        // 示例 1:

        // nums1 = [1, 3]
        // nums2 = [2]

        // 则中位数是 2.0
        // 示例 2:

        // nums1 = [1, 2]
        // nums2 = [3, 4]

        // 则中位数是 (2 + 3)/2 = 2.5


        var findMedianSortedArrays = function (nums1, nums2) {
            const arr = [...nums1, ...nums2].sort((a, b) => a - b);
            let { length } = arr;
            return length % 2 ? arr[Math.floor(length / 2)] : (arr[length / 2] + arr[length / 2 - 1]) / 2;
        }

10.回文数

 // 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

        // 示例 1:

        // 输入: 121
        // 输出: true
        // 示例 2:

        // 输入: -121
        // 输出: false
        // 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
        // 示例 3:

        // 输入: 10
        // 输出: false
        // 解释: 从右向左读, 为 01 。因此它不是一个回文数。

        var isPalindrome = function (x) {
            var len = String(x).length
            var arr = Array.from(String(x))
            if (x < 0) {
                return false
            }
            if (len <= 1) {
                return true
            }
            for (let i = 0; i < len;) {
                len = arr.length
                if (arr.length <= 1) return true
                if (Number(arr[i]) == Number(arr[len - 1])) {
                    arr.splice(0, 1) //删除首尾
                    arr.splice(len - 2, 1) //删除首尾
                } else {
                    return false
                }
            }
            return true
        };

        var isPalindrome = function (x) {
            return x.toString() == x.toString().split("").reverse().join("");
        }

11.最长的公共前缀

        // 编写一个函数来查找字符串数组中的最长公共前缀。

        // 如果不存在公共前缀,返回空字符串 ""。

        // 示例 1:

        // 输入: ["flower","flow","flight"]
        // 输出: "fl"
        // 示例 2:

        // 输入: ["dog","racecar","car"]
        // 输出: ""
        // 解释: 输入不存在公共前缀。
        // 说明:

        // 所有输入只包含小写字母 a-z 。


        // 标签:链表
        // 当字符串数组长度为 0 时则公共前缀为空,直接返回
        // 令最长公共前缀 ans 的值为第一个字符串,进行初始化
        // 遍历后面的字符串,依次将其与 ans 进行比较,两两找出公共前缀,最终结果即为最长公共前缀
        // 如果查找过程中出现了 ans 为空的情况,则公共前缀不存在直接返回
        // 时间复杂度:O(s)O(s),s 为所有字符串的长度之和


        var longestCommonPrefix = function (strs) {
            if (strs.length == 0)
                return "";
            let ans = strs[0];
            for (let i = 1; i < strs.length; i++) {
                let j = 0;
                for (; j < ans.length && j < strs[i].length; j++) {
                    if (ans[j] != strs[i][j])
                        break;
                }
                ans = ans.substr(0, j);
                if (ans === "")
                    return ans;
            }
            return ans;
        };

12.三数之和

// 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

// 注意:答案中不可以包含重复的三元组。

//  

// 示例:

// 给定数组 nums = [-1, 0, 1, 2, -1, -4],

// 满足要求的三元组集合为:
// [
//   [-1, 0, 1],
//   [-1, -1, 2]
// ]
   
// 标签:数组遍历
// 首先对数组进行排序,排序后固定一个数 nums[i]nums[i],再使用左右指针指向 nums[i]nums[i]后面的两端,数字分别为 nums[L]nums[L] 和 nums[R]nums[R],计算三个数的和 sumsum 判断是否满足为 00,满足则添加进结果集
// 如果 nums[i]nums[i]大于 00,则三数之和必然无法等于 00,结束循环
// 如果 nums[i]nums[i] == nums[i-1]nums[i−1],则说明该数字重复,会导致结果重复,所以应该跳过
// 当 sumsum == 00 时,nums[L]nums[L] == nums[L+1]nums[L+1] 则会导致结果重复,应该跳过,L++L++
// 当 sumsum == 00 时,nums[R]nums[R] == nums[R-1]nums[R−1] 则会导致结果重复,应该跳过,R--R−−
// 时间复杂度:O(n^2)O(n 
// 2
//  ),nn 为数组长度

 var threeSum = function(nums) {
    let ans = [];
    const len = nums.length;
    if(nums == null || len < 3) return ans;
    nums.sort((a, b) => a - b); // 排序
    for (let i = 0; i < len ; i++) {
        if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
        if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
        let L = i+1;
        let R = len-1;
        while(L < R){
            const sum = nums[i] + nums[L] + nums[R];
            if(sum == 0){
                ans.push([nums[i],nums[L],nums[R]]);
                while (L 0) R--;
        }
    }        
    return ans;
};

13.最接近的三数之和

 // 给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

        // 示例:

        // 输入:nums = [-1,2,1,-4], target = 1
        // 输出:2
        // 解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。


        // 解法:双指针
        // 思路
        // 确定第一个数,在左右指针移动过程中,更新与target差值最小的结果
        // 技巧
        // 排序原数组
        // nums[right] >= nums[left]
        // 确定一个数 x
        // res = x + nums[left] + nums[right]
        // 当 sum - target < res - target 时
        // res = sum
        // 当 sum == target 时
        // 返回 sum 即为所求
        // 当 sum > target
        // 根据从小到大的排序方式,左右指针不能再增大,只有右指针能够缩小,进而缩小 sum 值
        // right--
        // 当 sum < target
        // 原理同上,只不过先从小的元素累加起
        // left++
        /**
         * @param {number[]} nums
         * @param {number} target
         * @return {number}
         */
        var threeSumClosest = function (nums, target) {
            nums.sort((a, b) => a - b);
            let res = nums[0] + nums[1] + nums[2];
            let n = nums.length;
            for (let i = 0; i < n; i++) {
                let left = i + 1;
                let right = n - 1;
                while (left < right) {
                    let sum = nums[i] + nums[left] + nums[right];
                    if (Math.abs(res - target) > Math.abs(sum - target)) {
                        res = sum;
                    } else if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else if (sum === target) {
                        return res;
                    }
                }
            }
            return res;
        };

14.电话号码的字母组合

//给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
        // 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

        // 示例:
        // 输入:"23"
        // 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

        var letterCombinations = function (digits) {
            let result = []
            if (digits.length == 0) return []
            let numMap = {
                2: 'abc',
                3: 'def',
                4: 'ghi',
                5: 'jkl',
                6: 'mno',
                7: 'pqrs',
                8: 'tuv',
                9: 'wxyz'
            }
            for (let code of digits) {
                let word = numMap[code]
                if (result.length > 0) {
                    let tmp = []
                    for (let char of word) {
                        for (let old of result) {
                            tmp.push(old + char)
                        }
                    }
                    result = tmp
                } else {
                    result.push(...word)
                }
            }
            return result
        }

15.括号生成

 // 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
        // 示例:
        // 输入:n = 3
        // 输出:[
        //        "((()))",
        //        "(()())",
        //        "(())()",
        //        "()(())",
        //        "()()()"
        //      ]
        let generateParenthesis = (n) => {
            let res = []
            let dfs = (s, left, right) => {
                if (left == n && right == n) return res.push(s)
                if (left < n) dfs(s + '(', left + 1, right)
                if (right < left) dfs(s + ')', left, right + 1)
            }
            dfs('', 0, 0)
            return res
        }

16.合并k个排序链表

 // 合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
        // 示例:
        // 输入:
        // [
        //   1->4->5,
        //   1->3->4,
        //   2->6
        // ]
        // 输出: 1->1->2->3->4->4->5->6
        function ListNode(val) {
            this.val = val;
            this.next = null;
        }
        var mergeKLists = function (lists) {
            if (!lists || lists.length == 0) return null;
            let arr = [];
            let res = new ListNode(0);
            lists.forEach(list => {
                let cur = list;
                while (cur) {
                    arr.push(cur.val);
                    cur = cur.next;
                }
            })
            let cur = res;
            arr.sort((a, b) => a - b).forEach(val => {
                let node = new ListNode(val);
                cur.next = node;
                cur = cur.next;

            })
            return res.next;
        };

17.排序数组去重

// 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
        // 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

        // 示例 1:
        // 给定数组 nums = [1,1,2], 
        // 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 
        // 你不需要考虑数组中超出新长度后面的元素。
        // 示例 2:
        // 给定 nums = [0,0,1,1,1,2,2,3,3,4],
        // 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
        // 你不需要考虑数组中超出新长度后面的元素。


        var removeDuplicates = function (nums) {
            var j = 0;
            var n = nums.length;
            for (let i = 1; i < n; i++) {
                if (nums[i] != nums[i - 1]) {
                    j++;
                    nums[j] = nums[i];
                }
            }
            return j + 1;
        };

 

你可能感兴趣的:(JavaScript,算法,javascript,算法)