我的刷题笔记一-leetcode

367 阅读8分钟

记录刷题日常,如果你有更好的解题思路,可以分享

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]
输入: nums = [3,2,4], target = 6
输出: [1,2]
var twoSum = function(nums, target) { 
    let map = new Map();
    for(let i = 0;i<nums.length;i++) { 
        const n = nums[i]; 
        const n2 = target - n; 
        if(map.has(n2)) { 
            return [map.get(n2),i]
        } else {
            map.set(n,i) 
        } 
    } 
};

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

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

图解

两数相加.png

输入: l1 = [2,4,3], l2 = [5,6,4]
输出: [7,0,8]
输入: l1 = [0], l2 = [0]
输出: [0]
输入: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出: [8,9,9,9,0,0,0,1]
  function ListNode(val, next) {
     this.val = (val===undefined ? 0 : val)
     this.next = (next===undefined ? null : next)
  }
  var addTwoNumbers = function(l1, l2) {
   let list = new ListNode(0); // 设置一个节点;
   let head = list;  // 虚拟节点
   let carry = 0;  // 进位  两数相加大于1时  carry = 1;
   while(l1 || l2) {
       let l1_val = l1 ? l1.val : 0;
       let l2_val = l2 ? l2.val : 0;
       let sum = l1_val + l2_val + carry;
 
       carry = sum >= 10 ? 1 :0;
       list.next = new ListNode(sum % 10 , null);  // 8 + 9 取 7 carry 进1
       list = list.next;  // 循环指向下一个节点

       l1 = l1 ? l1.next : l1;  // l1 和l2链表长度不一定相等
       l2 = l2 ? l2.next : l2;  //指向下一个节点
   }   
   if(carry == 1) {
       list.next = new ListNode(1,null);
   } 
   /*
   head的第一个节点为0,并指向list的头节点,有人就会疑问链表已经改变,
   但是因为链表是单向的,return head.next就是代表没有0这个节点,返回0以后的节点
   */ 
   return head.next
};

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

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

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
思路-双指针

建立一个set对象,当满足条件,没有重复时,滑动右指针,当不满足条件时,滑动左指针,当滑动左指针满足条件时,继续滑动右指针

双指针.png

var lengthOfLongestSubstring = function(s) {
    let i = 0; // 左指针
    let j = 0 ;// 右指针
    let length = 0; // 两个指针之间的长度
    let maxLength = 0;  // 最大长度
    let set = new Set();
    // 右指针滑倒最后一个字符的下标
    while(j < s.length) {
      if(!set.has(s[j])) {  
          set.add(s[j]); //滑动右指针,满足条件,添加
          length++;
          if(length > maxLength) {
              maxLength = length; // 记录maxlength
          }
          j++;
      } else {
        // 不满足条件,即set对象里面存在字符,不需要添加
        // 说明左指针指向的元素set中存在,即删除左指针指向的元素
        while(set.has(s[j])) {
            set.delete(s[i])
            i++;
            length--;
        }
        // 左指针继续滑动,满足条件,右指针继续滑动
        set.add(s[j]);
        length++;
        j++;
      }
    } 
   return maxLength;
};

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

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

输入: nums1 = [1,3], nums2 = [2]
输出: 2.00000
解释: 合并数组 = [1,2,3] ,中位数 2
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
思路

合并数组,排序,找到中位数

var findMedianSortedArrays = function(nums1, nums2) {
    let num;
    let list = nums1.concat(nums2); //合并数组
    let arr = quickSort(list); // 从小到大排序
    let length = arr.length;
    if(length === 0) return null;
    if(length % 2 === 0) {  // 区分奇偶的中位数
      let midIndex = length / 2;
      num =  (arr[midIndex] + arr[midIndex - 1] ) / 2;
    } else {
      let midIndex = Math.floor(length / 2);
      num =  arr[midIndex];
    }
    return num;

};
function quickSort(arr) {
    const length = arr.length;
    if (length === 0) return [];
    let midIndex = Math.floor(length / 2)
    let midValue = arr.splice(midIndex, 1)[0]

    let left = []
    let right = []

    // 注意:这里不用直接用 length ,而是用 arr.length 。因为 arr 已经被 splice 给修改了
    for (let i = 0; i < arr.length; i++) {
        const n = arr[i]
        if (n < midValue) {
            // 小于 midValue ,则放在 left
            left.push(n)
        } else {
            // 大于 midValue ,则放在 right
            right.push(n)
        }
    }
    return quickSort(left).concat(
        [midValue],
        quickSort(right)
    )
}

5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。
输入: s = "cbbd"
输出: "bb"
var longestPalindrome = function (s) {
    // 中心扩散法
    if (s.length === 0) return '';
    let maxLength = 1; // 非空字符最小回文长度为1
    let maxIndex = 0; // 最长回文字串的起始下标
    for (let i = 0; i < s.length; i++) {
        let currenIndex = i; // 当前下标;
        let left = i; // 左指针
        let right = i; // 右指针
        // 假设s = 'abaccabr'; i = 4; s[i] = c;  和 left-1比较是否相等
        while (left != 0) {
            if (s[left] === s[left - 1]) {
                left--;
            } else {
                break;
            }
        }
        // 假设s = 'abaccabr'; i = 3; s[i] = c;  和 right + 1比较是否相等
        while (right != s.length - 1) {
            if (s[right] === s[right + 1]) {
                right++;
            } else {
                break;
            }
        }
        // 假设s = 'abachcabr'; i = 4; s[i] = h; left - 1 和 right + 1比较是否相等
        while (left != 0 && right != s.length - 1) {
            if (s[left - 1] === s[right + 1]) {
                left--;
                right++;
            } else {
                break;
            }
        }
        // 计算满足回文子串的长度  如aa  aba  -> right - left + 1
        if (right - left + 1 > maxLength) {
            maxLength = right - left + 1; // 更新最长长度;
            maxIndex = left; // 起始下标
        }
    }
    return s.substring(maxIndex, maxIndex + maxLength)
};

6. Z 字形变换

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

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

P   A   H   N
A P L S I I G
Y   I   R

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

输入: s = "PAYPALISHIRING", numRows = 3
输出: "PAHNAPLSIIGYIR"
输入:s = "PAYPALISHIRING", numRows = 4
输出:"PINALSIGYAHRPI"
解释:
P     I    N
A   L S  I G
Y A   H R
P     I
输入: s = "A", numRows = 1
输出: "A"

z.png

思路

创建一个数组,数组的下标对应横向的字符,比如numRors = 4时,下标为 0 ,1,2,3, 当从上往下时,index = index + 1 ,从下往上 index = index - 1;

var convert = function(s, numRows) {
    if(numRows == 1) return s;
    // 创建一个数组
    let arr = new Array(numRows).fill(''); // arr = [ '', '', '' ]
    let index = 0; // 数组arr的下标
    let direction = 1; // 从上往下为 1  从下往上 - 1;
    for (let j = 0; j < s.length; j++) {
        arr[index] += s[j]; // 将每列的字符拼接
        index = index + direction;
        if (index == 0) { // 从上往下
            direction = 1;
        }
        if(index == numRows - 1) {  // 从下往上
            direction = -1;
        }
    }
    return arr.join('')
};

7. 整数反转

给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [−231,  231 − 1] ,就返回 0。

输入: x = 123
输出: 321
输入: x = -123
输出: -321
输入: x = 120
输出: 21
输入: x = 0
输出: 0
var reverse = function(x) {
  let min = -Math.pow(2,31)  // 边界
  let max = Math.pow(2,31)
  if(x == 0) return 0;  
  let y = Math.abs(x) // 绝对值
  let arr = String(y).split('').reverse();
  let res = Number(arr.join(''))  //1230 ->  0321  ->   321
  let result =  x > 0 ? res : `-${res}`;
  return (result > max || result < min) ?   0 : result;
};
函数 myAtoi(string s) 的算法如下:

1 读入字符串并丢弃无用的前导空格  
2 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
3 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。 
4 将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
5 如果整数数超过 32 位有符号整数范围 [−231,  231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
6 返回整数作为最终结果。
理解

1 去掉前后空格; 2和3 以正负数字开头; 4如果没有数字或以数字结尾返回0; 5 超过边界返回边界值。

var myAtoi = function(s) {
    let max = Math.pow(2,31) - 1;
    let min = -Math.pow(2,31);
  //利用正则
    let res = s.trim().match(/^[-|+]{0,1}[0-9]+/)
    /**
    ^:匹配字符串开头
    [+|-]:代表一个+字符或-字符
    ?:前面一个字符可有可无
    \d:一个数字
    +:前面一个字符的一个或多个
    \D:一个非数字字符
    *:前面一个字符的0个或多个 
    */ 
    if(res != null){
        if(res[0] > max){
            return max
        }
        if(res[0] < min){
            return min
        }

        return res[0]
    }
    return 0
};

9. 回文数

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。

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

输入: x = 121
输出: true
输入: x = -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
输入: x = 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
var isPalindrome = function(x) {
  if(x < 0 ) return false;
  // 将数字反过来,和x相等
  let y = String(x).split('').reverse().join('');
  if(x == y) {
      return true;
  } else {
      return false;
  }
};

10. 正则表达式匹配-暂没解出

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

'.' 匹配任意单个字符 '*' 匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

输入: s = "aa", p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
输入: s = "ab", p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
var isMatch = function(s, p) {
    // return new RegExp('^' + p + '$').test(s);
};

11. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。 question_11.jpg

输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49
输入: height = [1,1]
输出: 1
var maxArea = function(height) {
  // 理解题意  组成容器的面积是width * height ,其中组成容器的两根柱子有长有短,以短的为主,
  // 如图  width = 9 -2 = 7; height取87中的7
  // 数组为每根柱子的高度     高度对应的是下标
  let left = 0;  
  let right =  height.length - 1;
  let max = 0;
  while(left < right) {
      let h = Math.min(height[left],height[right]); // 高度取最小值
      let w = right - left;
      let waterArea = h * w;
      if(waterArea > max) {
          max = waterArea;
      }
    //   当left = 0;right = height.length - 1时候, height[left] < height[right],left++
     if(height[left] < height[right]) {
         left ++ ;
     } else {
         right --;
     }
  }
  return max
};

12. 整数转罗马数字

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

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

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

I 可以放在 V (5) 和 X (10) 的左边,来表示 49。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900
输入: num = 3
输出: "III"
输入: num = 9
输出: "IX"
输入: num = 58
输出: "LVIII"
解释: L = 50, V = 5, III = 3.
输入: num = 1994
输出: "MCMXCIV"
解释: M = 1000, CM = 900, XC = 90, IV = 4.
  • 1 <= num <= 3999
var intToRoman = function(num) {
  let t = ['','M','MM','MMM']; // 0 1000 2000 3000
  let h = ['','C','CC','CCC','CD','D','DC','DCC','DCCC','CM']; // 0 100-900
  let tens = ['','X','XX','XXX','XL','L','LX','LXX','LXXX','XC']; //0 10-90
  let bits = ['','I','II','III','IV','V','VI','VII','VIII','IX'];// 0-9
  return  t[Math.floor(num / 1000)] + 
          h[Math.floor((num % 1000) / 100)] + 
          tens[Math.floor((num % 100) / 10)] + 
          bits[Math.floor(num % 10)] 
};

13. 罗马数字转整数

输入: s = "III"
输出: 3
输入: s = "IV"
输出: 4
输入: s = "IX"
输出: 9
输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
var romanToInt = function(s) {
   let map = {
       'I': 1,
       'V': 5,
       'X': 10,
       'L': 50,
       'C': 100,
       'D': 500,
       'M': 1000,
       'a': 4,
       'b': 9,
       'p': 40,
       'q': 90,
       'x': 400,
       'y': 900
   }
   let obj = {
       'IV':'a',
       'IX':'b',
       'XL':'p',
       'XC':'q',
       'CD':'x',
       'CM':'y'
    }
    Object.keys(obj).forEach(key => {
       s = s.replace(key,obj[key]);
    })
   let num = 0;
   for(let item of s) {
       num += map[item]
   }
   return num;
};

14. 最长公共前缀

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

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

输入: strs = ["flower","flow","flight"]
输出: "fl"
输入: strs = ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀
var longestCommonPrefix = function(strs) {
  let length = strs.length;
  let list = strs[0]; // 公共子串,以第一个元素为例
  let res = '';
  for(let i = 0; i < list.length; i++) {
        res += list[i]
        let flag = strs.every(val => val.indexOf(res) === 0)
        if(!flag) return list.substr(0, i)
    }
  return res
};

15. 三数之和

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

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

输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
输入: nums = []
输出: []
输入: nums = [0]
输出: []
图解

4.png

5.png

6.png

var threeSum = function(nums) {
   let length = nums.length; 
   if(length < 3) return [];
    let arr= []
   nums = nums.sort((a,b) => a-b) // 升序
   for(let i = 0; i< length - 2; i++) {
       let s = nums[i];
       if(s > 0) break; // nums为升序数组,如果第一项都大于0,意味都不满足条件
        if (i > 0 && nums[i] == nums[i - 1]) { //元素去重 [-4 ,-1,-1,0,1,2] 如果连续的元素相同,直接跳过
            continue;
        }
       let left = i + 1; // 左指针
       let right = length - 1; //右指针
       while(left < right) {
           let sum = nums[i] + nums[left] + nums[right];
           // [-4 ,-1,-1,0,1,2,2,3,3,3,3];
           if(sum > 0) {
                // 即元素3多次重复,没有必要一直相加,去重
               while(nums[right -1] === nums[right]) right--
               right --;
           }else if(sum < 0) {
               //即元素-1多次重复,没有必要一直相加,去重
               while(nums[left + 1] === nums[left]) left ++ 
               left ++ ;
           }else {
                arr.push([nums[i],nums[left],nums[right]]);
                while(nums[left  + 1] === nums[left]) left ++ 
                while(nums[right - 1] === nums[right]) right--
                left ++ ;
                right --;
           }
       }
   }
   return arr
};

16. 最接近的三数之和

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

输入: nums = [-1,2,1,-4], target = 1
输出: 2
解释: 与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
输入: nums = [0,0,0], target = 1
输出: 0
var threeSumClosest = function(nums, target) {
   let ans = Infinity; // Infinity(无穷大)在 js 中是一个特殊的数字,它的特性是:它比任何有限的数字都大
   let length = nums.length;
    nums.sort((a,b)=>a-b);
   for(let i = 0;i< length -2; i++) {
       let left = i + 1;
       let right = length - 1;
       while(left < right) {
           let sum = nums[i] + nums[left] + nums[right];  // 和
           if(sum > target){
                right--;
            }else if(sum < target){
                left++;
            }else{
                return target
            }
            if(Math.abs(sum - target) < Math.abs(ans - target)) {
              ans = sum // 返回的三叔之和,sum
            }
       }
   }
   return ans
};

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

200px-telephone-keypad2svg.png

输入: digits = "23"
输出: ["ad","ae","af","bd","be","bf","cd","ce","cf"]
输入: digits = ""
输出: []
输入: digits = "2"
输出: ["a","b","c"]
var letterCombinations = function(digits) {
  let res = []
  let obj = {
      2: ['a','b','c'],
      3: ['d','e','f'],
      4: ['g','h','i'],
      5: ['j','k','l'],
      6: ['m','n','o'],
      7: ['p','q','r','s'],
      8: ['t','u','v'],
      9: ['w','x','y','z'],
  }
   if(!digits) return [];
   function dfs(curStr,i){
      // curStr  当前的字符串   如'a','b'等
      if(i > digits.length - 1) {
          res.push(curStr);
          return
      }
      let strList = obj[digits[i]];  // ['a','b','c']
      for(let item of strList) {
          dfs(curStr + item , i + 1)
      }
   }
   dfs('' , 0);
   return res;
};
var letterCombinations = function(digits) {
    const k = digits.length;
    const map = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"];
    if(!k) return [];
    if(k === 1) return map[digits].split("");

    const res = [], path = [];
    backtracking(digits, k, 0);
    return res;

    function backtracking(n, k, a) {
        if(path.length === k) {
            res.push(path.join(""));
            return;
        }
        for(const v of map[n[a]]) {
            path.push(v);
            backtracking(n, k, a + 1);
            path.pop();
        }

    }
};

18. 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n a、b、c 和 d 互不相同 nums[a] + nums[b] + nums[c] + nums[d] == target 你可以按 任意顺序 返回答案 。

输入: nums = [1,0,-1,0,-2,2], target = 0
输出: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
输入: nums = [2,2,2,2,2], target = 8
输出: [[2,2,2,2]]
var fourSum = function(nums, target) {
   let length = nums.length;
   let arr = [];
   if(length < 4) return [];
   nums.sort((a,b) => a- b)
   for(let i = 0; i < length - 3;i++) {
       if(i > 0 && nums[i] == nums[i -1]) continue;  // 去重
       for(let j = i + 1; j< length - 2 ; j++) {
            if(j > i + 1 && nums[j] == nums[j -1]) continue;
            let left = j + 1;
            let right = length - 1;
            while(left < right) {
                let sum = nums[i] + nums[j] + nums[left] + nums[right];
                if(sum > target) {
                    while(nums[right - 1] === nums[right]) right--
                    right --;
                } else if(sum < target) {
                    while(nums[left  + 1] === nums[left]) left ++ 
                    left ++;
                } else {
                   arr.push([nums[i],nums[j],nums[left],nums[right]])
                   while(nums[left + 1] === nums[left]) left ++ 
                   while(nums[right - 1] === nums[right]) right--
                    left ++;
                    right --;
                }
            } 
       }
   }
   return arr
};

19. 删除链表的倒数第 N 个结点

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

1.jpg

图解

2.png

3.png

  function ListNode(val, next) {
     this.val = (val===undefined ? 0 : val)
     this.next = (next===undefined ? null : next)
 }
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
  if(head.next === null) return null; // 只有一个节点时
  let newNode = new ListNode(0); // 创建一个0的节点,方便返回结果
  newNode.next = head; // 并指向链表的头节点
  let pre = newNode; // 三个指针
  let target = head;
  let next = head;
  while(n) {
      n --;
      next = next.next;
  }
  while(next) {
      pre = pre.next;
      target = target.next;
      next = next.next;
  }
  pre.next = target.next;
  return newNode.next;
};
根据链表长度以及倒数的n来查找顺数n的位置
  function ListNode(val, next) {
     this.val = (val===undefined ? 0 : val)
     this.next = (next===undefined ? null : next)
  }
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
  let dummy = new ListNode(0);
  dummy.next = head;  // 指向链表头节点
  let pre = dummy;
  let length = 0; // 计算出链表的长度
  while(pre && pre.next) {
      pre = pre.next;
      length ++;
  }
  let differ = length - n;  // 顺数第几个
  pre = dummy; // 计算length的时候pre已经是整个链表了;重新指向dummy
  let index = 0
  while(index < differ) {
      index ++ 
      pre = pre.next;
  }
  pre.next = pre.next ? pre.next.next :  null;
  return dummy.next;
};

20. 有效的括号

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。

输入: s = "()"
输出: true
输入: s = "()[]{}"
输出: true
输入: s = "(]"
输出: false
输入: s = "([)]"
输出: false
输入: s = "{[]}"
输出: true
/**
 * @param {string} s
 * @return {boolean}
 */
var checkValid = function (left, right) {
    if (left === '(' && right === ')') return true;
    if (left === '{' && right === '}') return true;
    if (left === '[' && right === ']') return true;
    return false
}
var isValid = function (s) {
    const length = s.length;
    if (length % 2 === 1) return false;  // 括号成对出现,是偶数
    let stack = [];
    let leftSmbols = '([{';
    let rightSmbols = ')]}';
    for (let i = 0; i < length; i++) {
        const str = s[i];
        if (leftSmbols.includes(str)) {
            stack.push(str)  // 入栈
        } else if (rightSmbols.includes(str)) {
            let lastStr = stack[stack.length - 1]; // 最后一位来判断  后进先出一一对应
            if (checkValid(lastStr, str)) {
                stack.pop()  // 出栈    后进先出
            } else {
                return false
            }
        }
    }
    return stack.length === 0;
}