leetCode练习记录

362 阅读11分钟

5.8-字母异位词分组

题解:

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例 1:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

代码:

/**
 * @param {string[]} strs
 * @return {string[][]}
 */
var groupAnagrams = function(strs) {
    let hash = new Map();

    strs.forEach( str => {
        let key = str.split('').sort((a,b)=>a.localeCompare(b)).join()
        if(hash.has(key)){
            let temp = hash.get(key);
            temp.push(str)
            hash.set(key, temp);
        }else{
            hash.set(key, [str]);
        }
    })

    return [...hash.values()];
};

5.7-括号生成

题解:

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:[
       "((()))",
       "(()())",
       "(())()",
       "()(())",
       "()()()"
     ]

代码:

/**
 * @param {number} n
 * @return {string[]}
 */
var generateParenthesis = function(n) {
    let result = [];
    function dfs(str,left,right){
        if(left === 0 && right === 0) {
            result.push(str);
            return;
        }
        if(left>right) {
            return;
        }
        if(left>0) {
            dfs(str+'(',left-1,right)
        }
        if(right>0) {
            dfs(str+')',left,right-1)
        }
    }
    dfs("",n,n);
    return result;
};

5.6-最接近的三数之和

题解:

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

示例 1:

例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.

与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

代码:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var threeSumClosest = function(nums, target) {
    nums.sort((a,b)=>a-b);
    let sum = nums[0] + nums[1] + nums[2];
    for(let m = 0; m<nums.length-2; m++) {
        let i=m+1,j=nums.length-1;
        while(i<j) {
            let add = nums[i] + nums[j] + nums[m];
            if(add === target) return add;
            sum = Math.abs(add-target) < Math.abs(sum-target) ?
            add : sum;
            add < target ? i++ : j--;
        }
    }
    return sum;
};

5.5-删除链表的倒数第N个节点

题解:

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例 1:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

代码:

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let preHead = new ListNode(0);
    preHead.next = head
    let p = preHead, q = preHead;
    for(let i = 0; i < n; i++){
        q = q.next;
    }
    while(q&&q.next){
        q = q.next;
        p = p.next;
    }
    p.next = p.next.next;
    // console.log(head);
    return preHead.next;
};

5.4-合并有序数组

题解:

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

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

示例 1:

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

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

代码:

var threeSum = function(nums) {
    nums.sort((a,b)=> a-b);
    let start = 0, end = nums.length-1,result = [];
    while(start < end) {
        let rest = -nums[start]-nums[end];
        for(let i=start+1;i<=end-1;i++){
            if(nums[i]===rest){
                result.push([nums[start],nums[i],nums[end]]);
                console.log(i, [nums[start],nums[i],nums[end]]);
                while(start<end && nums[start]==nums[start+1]) start++;
                while(start<end && nums[end]==nums[end-1]) end--;
                break;
            }
        }
        nums[end-1] <= rest ? start++ : end--;
    }
    return result;
};

5.3-整数转罗马数字

题解:

罗马数字包含以下七种字符: 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"

代码:

/**
 * @param {number} num
 * @return {string}
 */
var intToRoman = function(num) {
    let map = [[1000,'M'],[900,'CM'],[500,'D'],[400,'CD'],[100,'C'],[90,'XC'],[50,'L'],[40,'XL'],[10,'X'],[9,'IX'],[5,'V'],[4,'IV'],[1,'I']];
    let result = '', index = 0;
    while (index < 13){
        while(map[index][0]<=num){
            num-=map[index][0];
            result+=map[index][1]
        }
        index++;
    }
    return result;
};

4.29-字符串转整数

题解:

请你来实现一个 atoi 函数,使其能将字符串转换成整数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:

如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0 。

示例 1:

输入: "42"
输出: 42

示例 2:

输入: "   -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
     我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例 3:

输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
     因此无法执行有效的转换。

代码:

/**
 * @param {string} str
 * @return {number}
 */
var myAtoi = function(str) {
    const num = parseInt(str);
    if(isNaN(num)) return 0
    else if(num<Math.pow(-2,31) || num>Math.pow(2,31)-1) return num < Math.pow(-2, 31) ? Math.pow(-2, 31) : Math.pow(2, 31) - 1;
    else return num
};

4.28-合并有序数组

题解:

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例 1:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]

代码:

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
  // p1指向nums1最后一个数,p2指向nums2最后一个数,p指向排序后的数组最后一个数
  let p1 = m-1, p2 = n-1, p = m + n -1; 
  while( (p1>=0) && (p2>=0) ){
    nums1[p--] = nums1[p1] > nums2[p2] ? nums1[p1--] : nums2[p2--];
  }
  for(let i=0; i<p2+1; i++){
    nums1[i] = nums2[i];
  }
  return nums1;
};

4.26-有效的数独

题解:

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-sudoku
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

输入:
[
  ["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"]
]
输出: true

代码:

/**
 * @param {character[][]} board
 * @return {boolean}
 */
var isValidSudoku = function(board) {
    let rows = [[],[],[],[],[],[],[],[],[],],
        columns = [[],[],[],[],[],[],[],[],[],],
        boxes = [[],[],[],[],[],[],[],[],[],];
    for(let i in board){
        for(let j in board[i]){
            let num = board[i][j];
            if(num !== ".") {
                let k = 3*Math.floor(i/3) + Math.floor(j/3);
                
                if(arrayHasNum(rows[i],num)){
                    rows[i].push(num)
                }else{
                    return false
                }

                if(arrayHasNum(columns[j],num)){
                    columns[j].push(num)
                }else{
                    return false
                }
                
                if(arrayHasNum(boxes[k],num)){
                    boxes[k].push(num)
                }else{
                    return false
                }
            }
        }
    };
    return true;
};

function arrayHasNum(array, target) {
    let flag = true;
    for(let i in array){
        if(array[i]===target) flag = false;
    }
    return flag;
}

4.25-全排列

题解:

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例 1:

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

代码:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function(nums) {
    let result = [];// 存放结果数组
    let backtrack = (path) =>{
        if(nums.length === path.length) result.push(path);
        for(let n of nums){
            if(!path.includes(n)){
                path.push(n);
                backtrack(path.slice());
                path.pop();
            }
        }
    }
    backtrack([])
    return result;
};

4.23-盛最多水的容器

题解:

给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

示例 1:

输入:[1,8,6,2,5,4,8,3,7]
输出:49

代码:

var maxArea = function(height) {
    let i = 0; j = height.length-1;
    let max = (j-i) * Math.min(height[i],height[j]);
    while(i<j){
        if(height[i]<=height[j]){ i++ }
        else{ j-- }
        let v = (j-i) * Math.min(height[i],height[j]);
        max = Math.max(max,v);
    }
    return max;
};

4.22-Z 字形变换

题解:

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:

L   C   I   R
E T O E S I I G
E   D   H   N

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"

示例 1:

输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"

代码:

/**
 * @param {string} s
 * @param {number} numRows
 * @return {string}
 */
var convert = function(s, numRows) {
    let flag = -1,temp=0,res=[],result="";
    if(numRows==1) return s;
    // 初始化结果数组
    for(let i=0 ; i<numRows ; i++){
        res[i] = '';
    }
    // 重组
    for(let i of s){
        if(temp == 0 || temp == numRows -1) flag = -flag;
        res[temp] += i;
        temp += flag;
    }
    for(let i of res){
        result+=i;
    }
    return result
};

4.21-两数相加

题解:

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

代码:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    let add = 0;// add记录进位
    let result = new ListNode(),cur = result;
    while(l1||l2){
        let n1 = l1.val ? l1.val : 0;
        let n2 = l2.val ? l2.val : 0;
        let num = n1+n2+add;// 暂存相加结果
        add = num >= 10 ? 1 : 0;
        num = num >= 10 ? num-10 : num;

        cur.next = new ListNode(num);
        cur = cur.next;

        l1 = l1.next ? l1.next : 0;
        l2 = l2.next ? l2.next : 0;
    }

    // 处理最高位进位情况
    if(add>0){
        cur.next = new ListNode(add);
    }
    return result.next;
};

4.19-最长回文字符串

题解:

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。。

代码:

/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
    let left = 0,right = 0,max = 1;
    let start = 0;// 存储最长回文字符串指针

    // 特殊判断
    if(s.length < 2) return s;

    for(let center = 0; center < s.length; center++){
        let cur = s[center];// 当前字母
        let len = 1;// 当前循环长度
        left = center-1;right = center+1;

        // 向左扩展,如果当前值和左边相等
        while(s[left]==cur&&left>=0){
            len++;
            left--;
        }

        while(s[right]==cur&&right<s.length){
            right++;
            len++;
        }

        while(s[left]==s[right] && left>=0 && right<s.length){
            left--;right++;len+=2;
        }

        if(len>max){
            max = len;
            start = left;
        }
    }
    return s.substring(start+1,start+1+max);
};

4.18-无重复字符的最长子串

题解:

 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

思路:

  1. 设置一个滑动窗口“q”,记录没有重复的字符串

    1.1 如果新的字母,窗口中没有,给窗口添加进新的字符串

    1.2 如果新的字母是重复字母,裁剪窗口到新字母之后的位置

  2. i为当前字母,cur为当前窗口长度,max为历史窗口最长的长度

  3. 以“wkew”为例:

    3.1 i = w; q->"w"; cur->1; max->1

    3.2 i = k; q->"wk"; cur->2; max->2

    3.3 i = e; q->"wke"; cur->3; max->3

    3.4 i = w; 这时候发现w已经出现过,q=“wkew”,因此需要裁剪窗口到w第一次出现之后: 即 q->"kew"; cur->3; max->3

代码:

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let q = ""; // 滑动队列
    let cur = 0, max = 0;
    for(let i of s){
        if(q.indexOf(i)==-1){
            q+=i;
            max = Math.max(++cur,max)
        }else{
            q+=i;
            q=q.slice(q.indexOf(i)+1);
            cur = q.length;
        }
    }
    return max;
};