leetcode刷题笔记

347 阅读23分钟

两数相加(链表)

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

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

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

前提条件

//链表构造函数
function ListNode(val, next) {
  this.val = val === undefined ? 0 : val;
  this.next = next === undefined ? null : next;
}
// 通过数组创建链表
function createList(arr) {
  let dummy = new ListNode(0);
  let cur = dummy;
  arr.forEach((element, index) => {
    cur.next = new ListNode(element);
    cur = cur.next;
  });
  return dummy.next;
}
let l1 = createList([2, 4, 3]);
let l2 = createList([5, 6, 4]);

题解

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function (l1, l2) {
  // 哨兵节点
  let dummy = new ListNode(0);
  let cur = dummy;
  // 进位
  let carry = 0;
  // 遍历两个链表
  while (l1 !== null || l2 !== null) {
    // 两节点的和
    let sum = 0;
    if (l1 !== null) {
      sum += l1.val;
      l1 = l1.next;
    }
    if (l2 !== null) {
      sum += l2.val;
      l2 = l2.next;
    }
    sum += carry;
    // 计算下一个节点的值
    cur.next = new ListNode(sum % 10);
    // 计算进位
    carry = Math.floor(sum / 10);
    // 计算下一个节点
    cur = cur.next;
  }
  // 如果发现还有进位的 那就在链表末端加一位
  if (carry > 0) {
    cur.next = new ListNode(carry);
  }
  return dummy.next;
};
console.log(addTwoNumbers(l1, l2));

无重复字符的最长子串

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

silding window 1.创建一个set 2.两个指针,一个指针(i)指向字符串开头,一个指针(j)遍历字符串 3.如果set里没有s[i],说明目前为止,还没重复字符,把s[i]添加到set里,然后更新最大长度 4.如果set里有s[i],则从set里开始删除s[j],并递增j,再检查set里是否有s[i],知道set里没有s[i]为止 5.重复3、4,直至遍历完成

/**
 * @param {string} s
 * @return {number}
 */
/**
 * silding window
 * 1.创建一个set
 * 2.两个指针,一个指针(i)指向字符串开头,一个指针(j)遍历字符串
 * 3.如果set里没有s[i],说明目前为止,还没重复字符,把s[i]添加到set里,然后更新最大长度
 * 4.如果set里有s[i],则从set里开始删除s[j],并递增j,再检查set里是否有s[i],知道set里没有s[i]为止
 * 5.重复3、4,直至遍历完成
 */
var lengthOfLongestSubstring = function (s) {
  const curWindow = new Set();
  let i = 0,
    j = 0,
    maxLength = 0;
  if (s.length <= 1) {
    return s.length;
  }
  for (i; i < s.length; i++) {
    // 如果窗口不存在当前遍历的值
    if (!curWindow.has(s[i])) {
      // 加入窗口
      curWindow.add(s[i]);
      // 计算窗口长度
      maxLength = Math.max(curWindow.size, maxLength);
    } else {
      // 如果窗口存在当前遍历值,那就需要从窗口的头,逐个删除,直到不存在为止
      while (curWindow.has(s[i])) {
        curWindow.delete(s[j]);
        j++;
      }
      // 删除完成后,把当前值加入窗口
      curWindow.add(s[i]);
    }
  }
  return maxLength;
};
console.log(lengthOfLongestSubstring("dvdf"));

最长回文子串

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

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

1.如果字符串长度小于2,直接返回原字符串

2.定义两个变量,一个start存储当前找到的最大回文字符串的起始位置,另一个maxLength记录字符串的长度(终止位置就是start+maxLength)

3.创建一个helper function,判断左边和右边是否越界,同时最左边的字符是否等于最右边的字符如果以上3个三个条件都满足, 则判断是否需要更新回文字符串最大长度及最大字符串的起始位置然后将left--,right++,继续判断,直到不满足三个条件之一。

4.遍历字符串,每个位置调用helper function两遍第一遍检查i-1,i+1,第二遍检查i,i+1(为什么要检查2遍?)

/**
 * 1.如果字符串长度小于2,直接返回原字符串
 * 2.定义两个变量,一个start存储当前找到的最大回文字符串的起始位置,另一个maxLength记录字符串的长度(终止位置就是start+maxLength)
 * 3.创建一个helper function,判断左边和右边是否越界,同时最左边的字符是否等于最右边的字符如果以上3个三个条件都满足,
 * 则判断是否需要更新回文字符串最大长度及最大字符串的起始位置然后将left--,right++,继续判断,直到不满足三个条件之一。
 * 4.遍历字符串,每个位置调用helper function两遍第一遍检查i-1,i+1,第二遍检查i,i+1(为什么要检查2遍?)
 */
​
/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function (s) {
  if (s.length < 2) return s;
  // 起始点
  let start = 0;
  let maxLength = 1;
  function expandAroundCnter(left, right) {
    // 判断越界情况
    while (left >= 0 && right < s.length && s[left] === s[right]) {
      // 如果这次回文长度最长
      if (right - left + 1 > maxLength) {
        maxLength = right - left + 1;
        start = left;
      }
      // 向两边延展
      left--;
      right++;
    }
  }
  //假设以当前值为中心,去查找回文。有两种情况
  for (let i = 0; i < s.length; i++) {
    // 奇数
    expandAroundCnter(i - 1, i + 1);
    // 偶数
    expandAroundCnter(i, i + 1);
  }
  return s.slice(start, start + maxLength);
};
console.log(longestPalindrome("babad"));

三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

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

1.给数组排序

2.遍历数组,从0遍历到length-2(为什么?)

3.如果当前的数字等于前一个数字,则跳过这个数(为什么?)

4.如果数字不同,则设置start = i+1,end=length-1,查看i,start和end三个数的和比零大还是小,如果比0小,start++,如果比0大,end--,如果等于0,把这三个数加入到结果里

5.返回结果

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
  // 结果集
  const result = [];
  // 排序
  nums = nums.sort((a, b) => a - b);
  //共三个指针, nums.length - 2 预留三个位置
  for (let i = 0; i < nums.length - 2; i++) {
    // 为了防止重复,指针移动前后的值不能重复,否则再移动到下一个值
    if (nums[i] !== nums[i - 1]) {
      // 从i开始 ,start和end进行收缩计算
      let start = i + 1,
        end = nums.length - 1;
      while (start < end) {
        if (nums[i] + nums[start] + nums[end] == 0) {
          result.push([nums[i], nums[start], nums[end]]);
          start++;
          end--;
          // 排除重复
          while (nums[start] == nums[start - 1]) {
            start++;
          }
          while (nums[end] == nums[end + 1]) {
            end++;
          }
        } else if (nums[i] + nums[start] + nums[end] > 0) {
          // 如果结果超出 则尾部收缩
          end--;
        } else {
          // 如果结果小于0 则头部收缩
          start++;
        }
      }
    }
  }
  return result;
};
const nums = [-1, 0, 1, 2, -1, -4];
// 输入:nums = [-1,0,1,2,-1,-4]
// 输出:[[-1,-1,2],[-1,0,1]]
console.log(threeSum(nums));

删除链表的倒数第 N 个结点(链表)

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

前提条件

//链表构造函数
function ListNode(val, next) {
  this.val = val === undefined ? 0 : val;
  this.next = next === undefined ? null : next;
}
// 通过数组创建链表
function createList(arr) {
  let dummy = new ListNode(0);
  let cur = dummy;
  arr.forEach((element, index) => {
    cur.next = new ListNode(element);
    cur = cur.next;
  });
  return dummy.next;
}
let head = createList([1, 2, 3, 4, 5]);

题解

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function (head, n) {
  // 哨兵节点
  let dummy = new ListNode();
  dummy.next = head;
  // 初始都指向哨兵节点
  let n1 = dummy;
  let n2 = dummy;
  // 将第二个指针向后移动n个位置
  for (let i = 0; i < n; i++) {
    n2 = n2.next;
  }
  // 将指针一起向后移,直到第二个指针超出位置,那么n1就是当前倒数节点的前一个节点
  while (n2.next !== null) {
    n2 = n2.next;
    n1 = n1.next;
  }
  // 删除倒数节点
  n1.next = n1.next.next;
  return dummy.next;
};
​
console.log(removeNthFromEnd(head, 2));

有效的括号

视频题解

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括号。

1.创建一个HashMap,把括号配对放进去

2.创建一个stack,for循环遍历字符串对于每一个字符,如果map里有这个key,那说明它是个左括号,从map里取得相对应的右括号(为什么?)

把它push进stack里。否则的话,它就是右括号,需要pop出stack里的第一个字符然后看它是否等于当前的字符。如果不相符,则返回false。

3.循环结束后如果stack不为空,说明还剩一些左括号没有被闭合,返回false。 否则返回true。

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
  // 括号map
  const bracketMap = new Map();
  bracketMap.set("(", ")");
  bracketMap.set("[", "]");
  bracketMap.set("{", "}");
  // 栈列表 存储结果
  const stack = [];
  for (let i = 0; i < s.length; i++) {
    if (bracketMap.has(s[i])) {
      // 匹配到开始括号 将对应的结束括号放入栈中
      stack.push(bracketMap.get(s[i]));
    } else {
      // 匹配到结束括号,将栈顶元素与当前值做对比,相等则继续,否则结束
      if (s[i] !== stack.pop()) {
        return false;
      }
    }
  }
  // 最后查看栈中是否还有元素
  if (stack.length !== 0) return false;
  return true;
};
console.log(isValid("{}[]}"));

合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
var mergeTwoLists = function (l1, l2) {
  const dummy = new ListNode();
  let cur = dummy;
  while (l1 !== null && l2 !== null) {
    if (l1.val < l2.val) {
      cur.next = l1;
      l1 = l1.next;
    } else {
      cur.next = l2;
      l2 = l2.next;
    }
    cur = cur.next;
  }
  if (l1 !== null) {
    cur.next = l1;
  }
  if (l2 !== null) {
    cur.next = l2;
  }
  return dummy.next;
};

console.log(mergeTwoLists(l1, l2));

两两交换链表中的节点

视频题解

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

输入:head = [1,2,3,4]
输出:[2,1,4,3]

image-20221227162359539

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function (head) {
  let dummy = new ListNode();
  dummy.next = head;
  let current = dummy;
  while (current.next !== null && current.next.next !== null) {
    let n1 = current.next;
    let n2 = current.next.next;
    current.next = n2;
    n1.next = n2.next;
    n2.next = n1;
    current = n1;
  }
  return dummy.next;
};

字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。

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

1.检查是否为空数组

2.建立一个长度为26的数组,起始值为0

3.遍历所有字符串,将字母的出现频率放到数组的对应位置里(利用ascii码)

4.遍历数组,按照相同字母出现频率进行分组归类(使用hashMap)。

5.遍历map,将结果返回

每个单词出现次数大于10会出错

/**
 * @param {string[]} strs
 * @return {string[][]}
 */
var groupAnagrams = function (strs) {
  // 检查是否为空数组
  if (strs.length == 0) return [];
  const map = new Map();
  for (const item of strs) {
    // 26个字符位置 初始值为0
    const chars = new Array(26).fill(0);
    // 找到对应位置则数量加1
    for (let i = 0; i < item.length; i++) {
      let ascii = item.charCodeAt(i) - 97;
      chars[ascii]++;
    }
    // 拼接键值
    let key = chars.join("");
    // 用map记录结果集
    if (map.has(key)) {
      map.set(key, [...map.get(key), item]);
    } else {
      map.set(key, [item]);
    }
  }
  // 遍历map 得到结果
  const result = [];
  for (const arr of map) {
    result.push(arr[1]);
  }
  return result;
};
console.log(groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"]));

第二种解法

/**
 * @param {string[]} strs
 * @return {string[][]}
 */
var groupAnagrams = function (strs) {
  // 检查是否为空数组
  if (strs.length == 0) return [];
  const strsCopy = [];
  for (const str of strs) {
    let res = str.split("").sort((a, b) => {
      return a.charCodeAt() - b.charCodeAt();
    });
    strsCopy.push(res.join(""));
  }
  console.log(strsCopy);
  const map = new Map();
  strsCopy.forEach((item, index) => {
    if (map.has(item)) {
      map.set(item, [...map.get(item), strs[index]]);
    } else {
      map.set(item, [strs[index]]);
    }
  });
  const result = [];
  for (const arr of map) {
    result.push(arr[1]);
  }
  return result;
};

回文数

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

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

例如,121 是回文,而 123 不是。

示例 1:

输入:x = 121 输出:true 示例 2:

第一种解法

/**
 * @param {number} x
 * @return {boolean}
 */
// 1221 121 11211
var isPalindrome = function (x) {
  let str = String(x);
  const len = str.length;
  let n1 = 0;
  let n2 = 0;
  if (len % 2 == 0) {
    n1 = len / 2 - 1;
    n2 = n1 + 1;
  } else {
    n1 = Math.floor(len / 2) - 1;
    n2 = n1 + 2;
  }
  while (n1 >= 0 || n2 < len) {
    if (str.charAt(n1) !== str.charAt(n2)) {
      return false;
    }
    n1--;
    n2++;
  }
  return true;
};

第二解法

/**
 * @param {number} x
 * @return {boolean}
 */
var isPalindrome = function (x) {
  let str = String(x).split("");
  while (str.length > 1) {
    if (str.pop() !== str.shift()) {
      return false;
    }
  }
  return true;
};

最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function (nums) {
  // 记录中途的结果集
  const memo = [];
  memo[0] = nums[0];
  // 最大值
  let result = nums[0];
  for (let i = 1; i < nums.length; i++) {
    // 如果当前值大于与前一个结果的和 则舍弃前面的结果 直接从当前值开始计算
    memo[i] = Math.max(nums[i] + memo[i - 1], nums[i]);
    // 找到最大值
    if (result < memo[i]) {
      result = memo[i];
    }
  }
  return result;
};

console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4]));

跳跃游戏

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。

示例 1:

输入:nums = [2,3,1,1,4] 输出:true 解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

动态规划(正向)

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canJump = function (nums) {
  let maxLength = nums.length;
  // 结果集 1 表示通路 -1表示错误路径 0 未经过
  const dp = Array(maxLength).fill(0);
  // 最后一个元素一定满足要求 
  dp[maxLength - 1] = 1;
  function jump(pos) {
    // 查看结果集 已经计算过则直接返回结果
    if (dp[pos] == -1) {
      return false;
    } else if (dp[pos] == 1) {
      return true;
    }
    // 边界判断 如果从当前跳跃最大值超出最大长度 则取最大长度 得出最大跳跃步数
    let jumpSteps = Math.min(pos + nums[pos], maxLength - 1);
    // 从step+1 开始跳跃 
    for (let i = pos + 1; i <= jumpSteps; i++) {
      // 递归计算结果
      let res = jump(i);
      if (res === true) {
        // 如果结果为true 则将当前位置改为1 并返回true
        dp[pos] = 1;
        return true;
      }
    }
    // 如果以上计算都未返回 说明当前路径不通 结果赋为-1
    dp[pos] = -1;
    return false;
  }
  let res = jump(0);
  return res;
};
console.log(canJump([2, 2, 0, 1, 1, 4]));

动态规划(反向)

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canJump = function (nums) {
  let maxLength = nums.length;
  // 结果集 1 表示通路 -1表示错误路径 0 未经过
  const dp = Array(maxLength).fill(0);
  // 最后一个元素一定满足要求
  dp[maxLength - 1] = 1;
  // 反向跳跃
  for (let i = maxLength - 2; i >= 0; i--) {
    let maxJump = Math.min(i + nums[i], maxLength - 1);
    for (let j = i + 1; j <= maxJump; j++) {
      if (dp[j] == 1) {
        dp[i] = 1;
        break;
      }
    }
  }
  return dp[0] == 1;
};
console.log(canJump([2, 2, 0, 1, 1, 4]));

贪心算法

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canJump = function (nums) {
  // 跳跃的终点
  let endJump = nums.length - 1;
  for (let i = nums.length - 2; i >= 0; i--) {
    // 如果当前元素能跳的最远距离 超过跳跃的重点  则将终点前移 
    if (i + nums[i] >= endJump) {
      endJump = i;
    }
  }
  // 如果终点为0 则说明通过
  return endJump == 0;
};

合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]] 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

1.数组排序

2.逐个按要求合并

/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function (intervals) {
  // 边界判断
  if (intervals.length <= 1) return intervals;
  // 给数组排序
  intervals = intervals.sort((pre, next) => {
    return pre[0] - next[0];
  });
  let result = [];
  let cur = intervals[0];
  for (let i = 1; i < intervals.length; i++) {
    // 如果当前的右值 大于 下一元素的左值  说明可以合并
    if (intervals[i][0] <= cur[1]) {
      // 合并
      cur = [cur[0], Math.max(intervals[i][1], cur[1])];
    } else {
      result.push(cur);
      cur = intervals[i];
    }
  }
  // 如果cur还有数据 则将cur放入结果集
  if (cur.length !== 0) {
    result.push(cur);
  }
  return result;
};

console.log(
  merge([
    [1, 3],
    [2, 6],
    [8, 10],
    [15, 18]
  ])
);

不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:

输入:m = 3, n = 7 输出:28

起点111111
1234567
136101521终点28

1.机器人只向下或者向右移动,则可推测 上边缘 和左边缘的路径只有一种

2.当前单元格的路径数量,等于左元素和上元素的和

/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function (m, n) {
  let res = [];
  // 创建二维数组
  for (let i = 0; i < n; i++) {
    res.push(Array(m));
  }
  // 上边界设为一
  for (let i = 0; i < m; i++) {
    res[0][i] = 1;
  }
  // 左边界设为一
  for (let i = 0; i < n; i++) {
    res[i][0] = 1;
  }
  // 计算每个格子的路径数量  注意横纵坐标与二维下标的关系
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      res[j][i] = res[j][i - 1] + res[j - 1][i];
    }
  }
  // 返回终点
  return res[n - 1][m - 1];
};
console.log(uniquePaths(7, 3));

加一

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:

输入:digits = [1,2,3] 输出:[1,2,4] 解释:输入数组表示数字 123。

/**
 * @param {number[]} digits
 * @return {number[]}
 */
var plusOne = function (digits) {
  let carry = false;
  for (let i = digits.length - 1; i >= 0; i--) {
    let res = digits[i] + 1;
    if (res < 10) {
      digits[i] = res;
      carry = false;
      break;
    } else {
      digits[i] = res - 10;
      carry = true;
    }
  }
  if (carry) {
    digits.unshift(1);
  }
  return digits;
};
console.log(plusOne([1, 2, 3, 9]));

爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

中心思想:

1.第n阶的爬法是 n-1和n-2阶爬法的和

2.动态规划 记录历史计算

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function (n) {
  let dp = [1, 2];
  for (let i = 2; i < n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }
  return dp[n - 1];
};

console.log(climbStairs(3));

矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。

示例 1:

输入:matrix = [[1,1,1],[1,0,1],[1,1,1]] 输出:[[1,0,1],[0,0,0],[1,0,1]]

1.检查并标记第一行和第一列是否有0(firstColHasZerofirstRowHasZero)*

2.使用第一行和第一列,来标记其余行列是否含有0

3.接下来,利用第一行和第一列的标0情况,将matrix中的数字标0

4.最后,处理第一行和第一列如果firstColHasZero等于true将第一列全设为0如果firstRowHasZero等于true,将第一列全设为0*

/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var setZeroes = function (matrix) {
  let leftBorderSetZero = false;
  let topBorderSetZero = false;
  for (let item of matrix[0]) {
    if (item === 0) {
      topBorderSetZero = true;
    }
  }
  for (let item of matrix) {
    if (item[0] === 0) {
      leftBorderSetZero = true;
    }
  }
  for (let i = 1; i < matrix[0].length; i++) {
    for (let j = 1; j < matrix.length; j++) {
      if (matrix[j][i] == 0) {
        matrix[j][0] = 0;
        matrix[0][i] = 0;
      }
    }
  }
  for (let i = 1; i < matrix[0].length; i++) {
    if (matrix[0][i] == 0) {
      for (let j = 1; j < matrix.length; j++) {
        matrix[j][i] = 0;
      }
    }
    if (topBorderSetZero) {
      matrix[0][0] = 0;
      matrix[0][i] = 0;
    }
  }
  for (let i = 1; i < matrix.length; i++) {
    if (matrix[i][0] == 0) {
      for (let j = 1; j < matrix[0].length; j++) {
        matrix[i][j] = 0;
      }
    }
    if (leftBorderSetZero) {
      matrix[i][0] = 0;
      matrix[0][0] = 0;
    }
  }

  return matrix;
};

console.log(setZeroes([[1], [0]]));

子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

记住代码结构

var subsets = function (nums) { let result = []; function backtrack(start, curr) { 把curr添加到数组result for (let i = start; i < nums.length; i++) { 把nums[i]添加到数组result

backtarck[i+1,curr]

把curr的最后一个元素删除

} } backtrack(0, []); return result; };

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function (nums) {
  let result = [];
  function backtrack(start, curr) {
    result.push([...curr]);
    for (let i = start; i < nums.length; i++) {
      curr.push(nums[i]);
      backtrack(i + 1, curr);
      curr.pop();
    }
  }
  backtrack(0, []);
  return result;
};
console.log(subsets([1, 2, 3]));

子集 II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:

输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsetsWithDup = function (nums) {
  let result = [];
  nums.sort((a, b) => {
    return a - b;
  });
  function backtrack(start, curr) {
    result.push([...curr]);
    for (let i = start; i < nums.length; i++) {
      if (i > start && nums[i] === nums[i - 1]) {
        continue;
      }
      curr.push(nums[i]);
      backtrack(i + 1, curr);
      curr.pop();
    }
  }
  backtrack(0, []);
  return result;
};

console.log(subsetsWithDup([4, 4, 4, 1, 4]));

买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4] 输出:5 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  let maxpf = 0,
    minPrice = prices[0];
  for (let i = 1; i < prices.length; i++) {
    // 如果当天的价格大于最低价 则计算利润  否则更新最低价
    if (minPrice > prices[i]) {
      minPrice = prices[i];
    } else if (prices[i] - minPrice > maxpf) {
      // 当天卖出的利润 大于最大利润 则更新最大利润
      maxpf = prices[i] - minPrice;
    }
  }
  return maxpf;
};
console.log(maxProfit([7, 1, 5, 3, 6, 0, 4]));

买卖股票的最佳时机 II

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

示例 1:

输入:prices = [7,1,5,3,6,4] 输出:7 解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。 总利润为 4 + 3 = 7 。

使用贪心算法,一天一天的叠加,只计算上升段

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  if (prices.length == 0) return 0;
  let maxpf = 0;
  for (let i = 0; i < prices.length - 1; i++) {
    if (prices[i + 1] < prices[i]) {
      maxpf += prices[i + 1] - prices[i];
    }
  }
  return maxpf;
};
console.log(maxProfit([1, 2, 3, 4, 5]));

验证回文串

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。

示例 1:

输入: s = "A man, a plan, a canal: Panama" 输出:true 解释:"amanaplanacanalpanama" 是回文串。

/**
 * @param {string} s
 * @return {boolean}
 */
var isPalindrome = function (s) {
  s = s.toLowerCase().replace(/[\W_]/g, "");
  if (s.length < 2) return true;
  let left = 0;
  let right = s.length - 1;
  while (left < right) {
    if (s[left] !== s[right]) {
      return false;
    }
    left++;
    right--;
  }
  return true;
};
console.log(isPalindrome("A man, a plan, a canal: Panama"));

加油站

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2] 输出: 3 解释: 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。 因此,3 可为起始索引。

/**
 * @param {number[]} gas
 * @param {number[]} cost
 * @return {number}
 */
var canCompleteCircuit = function (gas, cost) {
  let totalGas = 0,
    totalCost = 0;
  for (let i = 0; i < gas.length; i++) {
    totalGas += gas[i];
    totalCost += cost[i];
  }
  // 判断总油量和总消耗量 确定有无走通的可能  
  if (totalCost > totalGas) return -1;
  // 假设到当前的节点剩余的油量
  let currentGas = 0;
  // 起点
  let start = 0;

  for (let i = 0; i < gas.length; i++) {
    // 判断是否能到下一个节点
    currentGas = currentGas + gas[i] - cost[i];
    // 剩余油量小于0 说明无法到下一个节点  则换一个节点
    if (currentGas < 0) {
      currentGas = 0;
      start = i + 1;
    }
  }
  return start;
};
let gas = [1, 2, 3, 4, 5],
  cost = [3, 4, 5, 1, 2];
console.log(canCompleteCircuit(gas, cost));

环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

示例 1:

输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。

map解法

var hasCycle = function (head) { 
  let nodeMap = new Map();
  while (head !== null) {
    if (nodeMap.has(head)) {
      return true;
    }
    nodeMap.set(head, true);
    head = head.next;
  }
  return false;
};

快慢指针

慢指针每次走一步,快指针每次走两步

如果链表有环,则快慢指针一定会在环中相遇

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function (head) {
  if (head == null) return false;
  let fast = head;
  let slow = head;
  while (fast.next !== null && fast.next.next !== null) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) {
      return true;
    }
  }
  return false;
};

环形链表 II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function (head) { 
  let nodeMap = new Map();
  while (head !== null) {
    if (nodeMap.has(head)) {
      return head;
    }
    nodeMap.set(head, true);
    head = head.next;
  }
  return null;
};

乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

子数组 是数组的连续子序列。

示例 1:

输入: nums = [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6。

难点:负负可以得正,不能排除小值。最小值乘以负数也可成为最大值

关键:连续数组 乘积

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxProduct = function (nums) {
  // 记录当前的最大值
  let maxProductList = [nums[0]];
  // 记录当前的最大值
  let minProductList = [nums[0]];
  let max = nums[0];
  for (let i = 1; i < nums.length; i++) {
    // 取出当前值,当前值与上一次最大值的乘积,当前值与上一次最小值的乘积的最大值
    maxProductList[i] = Math.max(
      nums[i],
      nums[i] * maxProductList[i - 1],
      nums[i] * minProductList[i - 1]
    );
    // 同理 获得最小值
    minProductList[i] = Math.min(
      nums[i],
      nums[i] * maxProductList[i - 1],
      nums[i] * minProductList[i - 1]
    );
    // 每次计算最大值
    max = Math.max(max, maxProductList[i]);
  }
  return max;
};
console.log(maxProduct([2, 3, -2, 4]));

相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

自定义评测:

评测系统 的输入如下(你设计的程序 不适用 此输入):

intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0 listA - 第一个链表 listB - 第二个链表 skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数 skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数 评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。

示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3 输出:Intersected at '8' 解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。 在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 — 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。

map

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
  let nodeMap = new Map();
  while (headA !== null) {
    nodeMap.set(headA, headA);
    headA = headA.next;
  }
  while (headB !== null) {
    if(nodeMap.has(headB)){
      return headB
    }
    headB = headB.next;
  }

  return null;
};

双指针

控制两个链表走相同的路程。

在链表走到末端的时候,切换到另一个链表,继续遍历,相等则为相交点

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function (headA, headB) {
  let n1 = headA;
  let n2 = headB;
  while (n1 !== n2) {
    if (n1 == null) {
      n1 = headB;
    } else {
      n1 = n1.next;
    }
    if (n2 == null) {
      n2 = headA;
    } else {
      n2 = n2.next;
    }
  }
  return n1;
};

打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。

/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function (nums) {
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  const dp = [nums[0]];
  dp[1] = Math.max(nums[0], nums[1]);
  for (let i = 2; i < nums.length; i++) {
    dp[i] = Math.max(nums[i] + dp[i - 2], dp[i - 1]);
  }
  console.log(dp);
  return dp[nums.length - 1];
};
console.log(rob([2,7,9,3,1]));

岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] 输出:1

利用深度优先遍历,将岛屿沉没

1.当遍历到当前元素等于1时,将当前元素改为0

2.遍历当前元素的上下左右元素,重复1的过程

/**
 * @param {character[][]} grid
 * @return {number}
 */
var numIslands = function (grid) {
  let count = 0;
  // 深度遍历
  function dfs(row, col) {
    // 限定边界
    if (row < 0 || row > grid.length - 1 || col < 0 || col > grid[0].length)
      return;
    if (grid[row][col] === "1") {
      // 将当前元素改为0
      grid[row][col] = "0";
      // 递归四周的元素
      dfs(row + 1, col);
      dfs(row - 1, col);
      dfs(row, col + 1);
      dfs(row, col - 1);
      return true;
    }
  }
  // 遍历所有的元素
  for (let i = 0; i < grid.length; i++) {
    for (let j = 0; j < grid[0].length; j++) {
      if (dfs(i, j)) {
        count++;
      }
    }
  }
  return count;
};

console.log(
  numIslands([    ["1", "1", "0", "0", "0"],
    ["1", "1", "0", "0", "0"],
    ["0", "0", "1", "0", "0"],
    ["0", "0", "0", "1", "1"]
  ])
);