听从同事姐姐的建议,从今天开始,我将整理力扣hot100题的解法,不论简单困难,按顺序记录。
目录
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
这道题比较简单,我是直接对原数组进行深拷贝后,排序成递增数组,然后使用双指针寻找相加等于target的两个值,然后在原数组中找出索引即可。双指针的优势就是,可以同时在两个方向上往中间遍历,且可以根据数字大小决定移动方向。
- /**
- * @param {number[]} nums
- * @param {number} target
- * @return {number[]}
- */
- var twoSum = function (nums, target) {
- let arr = JSON.parse(JSON.stringify(nums));
- arr.sort((a, b) => (a < b ? -1 : 1));
- // 双指针
- let l = 0,
- r = arr.length - 1;
- while (l < r) {
- if (arr[l] + arr[r] === target) {
- break;
- } else if (arr[l] + arr[r] < target) {
- l++;
- } else {
- r--;
- }
- }
- let temp = 0;
- let pos1 = nums.indexOf(arr[l]);
- let pos2 =
- (temp = nums.indexOf(arr[r])) === pos1 ? nums.lastIndexOf(arr[r]) : temp;
- return [pos1, pos2];
- };
官方题解的思路是将nums中的数字及其位置存入哈希表,然后这样就可以遍历一次得到结果了。按照这个思路,我实现了一次。
- /**
- * @param {number[]} nums
- * @param {number} target
- * @return {number[]}
- */
- var twoSum = function (nums, target) {
- const map = {};
- for (let i = 0; i < nums.length; i++) {
- let num = nums[i];
- if (map[target - num] !== undefined) {
- return [map[target - num], i];
- } else {
- map[num] = i;
- }
- }
- };
注意,JS中对象的值可能是undefined,也可能是0,都会判断为false,因此不能简单地使用if(map[target-num]),这样如果map[target-num]是0就进不去了。
在这里不得不哭泣一下,之前刷题都是用Java,对hashmap很熟悉,现在很久没有用Java,居然对hashmap陌生了......
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
这道题,我的思路很简单,就是依次遍历每个节点,记录当前相加结果和上一次相加的进位,然后给新链表赋值。需要注意的是,遍历结束后,可能会存在一个空的节点,这时候要判断,如果上一次进位值还有,就把进位值赋给这个节点,否则置空。
- /**
- * Definition for singly-linked list.
- * function ListNode(val, next) {
- * this.val = (val===undefined ? 0 : val)
- * this.next = (next===undefined ? null : next)
- * }
- */
- /**
- * @param {ListNode} l1
- * @param {ListNode} l2
- * @return {ListNode}
- */
- var addTwoNumbers = function (l1, l2) {
- // 当前相加值和进位值
- let now = 0,
- last = 0;
- // 结果链表
- let pos = new ListNode(-1),
- res = pos,
- last_pos = null;
- while (l1 || l2) {
- if (!last_pos) {
- last_pos = pos;
- }
- if (l1 && l2) {
- let all = l1.val + l2.val + last;
- now = all % 10;
- last = parseInt(all / 10);
- last_pos = pos;
- pos.val = now;
- pos.next = new ListNode(-1);
- pos = pos.next;
- l1 = l1.next;
- l2 = l2.next;
- } else if (l1) {
- let all = l1.val + last;
- now = all % 10;
- last = parseInt(all / 10);
- last_pos = pos;
- pos.val = now;
- pos.next = new ListNode(-1);
- pos = pos.next;
- l1 = l1.next;
- } else {
- let all = l2.val + last;
- now = all % 10;
- last = parseInt(all / 10);
- last_pos = pos;
- pos.val = now;
- pos.next = new ListNode(-1);
- pos = pos.next;
- l2 = l2.next;
- }
- }
- if (last) {
- pos.val = last;
- } else {
- last_pos.next = null;
- }
- return res;
- };
官方思路大同小异,不再赘述。
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
这道题是利用滑动窗口的思想,维持一个窗口内始终是无重复子串。具体思路是,建立两个快慢指针,如果目前窗口内不包含最右侧字符,那么快指针向右移动;否则,记录当前窗口的大小,然后慢指针向右移动。最终,快指针可能越过最后一个索引,这时还要记录窗口的大小,否则,如果在快指针到达最后一个索引后就停止,会丢失最后一个索引也符合无重复子串的情况。
- /**
- * @param {string} s
- * @return {number}
- */
- var lengthOfLongestSubstring = function (s) {
- if (!s || s.length === 0) {
- return 0;
- }
- if (s.length === 1) {
- return 1;
- }
- // 滑动窗口
- let left = 0,
- right = 1,
- max_len = 0;
- while (right <= s.length) {
- if (right === s.length) {
- max_len = Math.max(max_len, s.length - left);
- right++;
- } else {
- let subs = s.substring(left, right);
- if (subs.indexOf(s[right]) === -1) {
- right++;
- } else {
- max_len = Math.max(max_len, right - left);
- left++;
- }
- }
- }
- return max_len;
- };
官方思路也是滑动窗口,不再赘述。
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
我的思路比较简单,对于这两个数组,如果将它们合并成一个递增数组,那么中位数就是中间位置的那个数字。因此,只需要计算出中间位置的索引,然后按照递增顺序一个个寻找即可。注意,区别奇数个数字和偶数个数字的数组。
- /**
- * @param {number[]} nums1
- * @param {number[]} nums2
- * @return {number}
- */
- var findMedianSortedArrays = function (nums1, nums2) {
- let n1 = nums1.length,
- n2 = nums2.length;
- let res = 0,
- middle = Math.round((n1 + n2) / 2);
- let i1 = 0,
- i2 = 0,
- arr = [];
- while (i1 < n1 || i2 < n2) {
- if (i1 < n1 && i2 < n2) {
- if (nums1[i1] <= nums2[i2]) {
- arr.push(nums1[i1]);
- i1++;
- } else {
- arr.push(nums2[i2]);
- i2++;
- }
- } else if (i1 < n1) {
- arr.push(nums1[i1]);
- i1++;
- } else {
- arr.push(nums2[i2]);
- i2++;
- }
- if ((n1+n2)%2 === 0&& arr.length === middle + 1) {
- res = (arr[arr.length - 1] + arr[arr.length - 2]) / 2;
- break;
- }else if((n1+n2)%2 !== 0&& arr.length === middle){
- res = arr[arr.length - 1];
- break;
- }
- }
- return res;
- };
但是缺点是,这样找的时间复杂度不是O(log(m+n))。一般要实现log级别的复杂度,都要使用二分查找。官方题解中就是使用了二分查找的方法。
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
这道题做过很多次了,我的思想始终是,让第i个字符,分别做最中间字符和中间左侧字符,去寻找最大值即可。
- /**
- * @param {string} s
- * @return {string}
- */
- var longestPalindrome = function (s) {
- if (s.length === 0) {
- return "";
- }
- let max_len = 1,
- res = s[0];
- for (let i = 0; i < s.length; i++) {
- // 以第i个字符做中间或者中间左侧字符
- // 做中间字符
- let len1 = 1,
- l1 = i - 1,
- r1 = i + 1;
- while (l1 >= 0 && r1 < s.length) {
- if (s[l1] === s[r1]) {
- len1 += 2;
- l1--;
- r1++;
- if (len1 > max_len) {
- res = s.substring(l1 + 1, r1);
- max_len = len1;
- }
- } else {
- break;
- }
- }
- // 左中间左侧字符
- let len2 = 0,
- l2 = i,
- r2 = i + 1;
- while (l2 >= 0 && r2 < s.length) {
- if (s[l2] === s[r2]) {
- len2 += 2;
- l2--;
- r2++;
- if (len2 > max_len) {
- res = s.substring(l2 + 1, r2);
- max_len = len2;
- }
- } else {
- break;
- }
- }
- }
- return res;
- };
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
仍然是双指针动态规划的思路。从左右两侧分别开始遍历,移动较小的那边,计算面积并记录最大面积,直到左右指针相遇即可。
- /**
- * @param {number[]} height
- * @return {number}
- */
- var maxArea = function (height) {
- let left = 0,
- right = height.length - 1,
- res = 0;
- while (left < right) {
- if (height[left] <= height[right]) {
- res = Math.max(res, height[left] * (right - left));
- left++;
- } else {
- res = Math.max(res, height[right] * (right - left));
- right--;
- }
- }
- return res;
- };
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
这道题实际上也是利用双指针,只不过复杂一些。具体方法是,先对nums排序,从左往右遍历nums,将第i个数字作为三元组的最左边的数字。然后,令left=i+1,right=nums.length-1,如果三个数相加等于0,则加入答案中,并且,还要移动left、right,且要跳过跟当前left、right相等的数字;如果相加小于0,说明left处数字太小,left++;否则,right--。
- /**
- * @param {number[]} nums
- * @return {number[][]}
- */
- var threeSum = function (nums) {
- if (!nums || nums.length < 3) {
- return [];
- }
- // 排序
- nums.sort((a, b) => a - b);
- // 结果数组
- const arr = [];
- // 遍历数组
- // 以第i个数字为最左侧数字
- for (let i = 0; i < nums.length; ) {
- if (nums[i] > 0) {
- break;
- }
- let num = nums[i];
- let left = i + 1,
- right = nums.length - 1;
- while (left < right) {
- if (num + nums[left] + nums[right] === 0) {
- arr.push([num, nums[left], nums[right]]);
- // 找到不重复的数字
- let j = left + 1;
- while (j < nums.length) {
- if (nums[j] === nums[left]) {
- j++;
- } else {
- left = j;
- break;
- }
- }
- left = j;
- j = right - 1;
- while (j >= 0) {
- if (nums[j] === nums[right]) {
- j--;
- } else {
- right = j;
- break;
- }
- }
- } else if (num + nums[left] + nums[right] < 0) {
- left++;
- } else {
- right--;
- }
- }
- while (++i < nums.length && nums[i] === num);
- }
- return arr;
- };
但是注意,不可以使用ES6的Set,对nums进行去重。 因为一旦去重,就改变了原来数字的组成,比如,[0,0,0]就变成了[0],[-1,-1,2,0]就变成了[-1,2,0],本来是有解的,去重后就没有了。这里跳过重复数字,是为了让解不重复。
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
这道题只需要递归求解即可。
- const map = {
- 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"],
- };
-
- /**
- * @param {string} digits
- * @return {string[]}
- */
- var letterCombinations = function (digits) {
- let arr = [];
- if (digits.length === 0) {
- return arr;
- }
- if (digits.length === 1) {
- for (let c of map[digits]) {
- arr.push(c);
- }
- return arr;
- }
-
- let now = digits[0];
- let s = digits.substring(1, digits.length);
- let sub = letterCombinations(s);
- for (let c of map[now]) {
- for (let sub_c of sub) {
- arr.push(c + sub_c);
- }
- }
- return arr;
- };
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
我就是将节点存在数组中,然后删除即可。要注意删除的节点正好是头结点和最后一个节点的情况。
- /**
- * Definition for singly-linked list.
- * 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 pos = head;
- const arr = [];
- while (pos) {
- arr.push(pos);
- pos = pos.next;
- }
- let len = arr.length;
- if (len === n) {
- return head.next;
- }
- arr[len - n - 1].next = len - n + 1 < len ? arr[len - n + 1] : null;
- return head;
- };
这种解法是最没有技术含量的。可以采用双指针解法,思路是快慢指针。
在链表的题目中,常常在第一个节点前面加上一个虚拟节点,从它开始遍历。这里就是这样,fast和slow两个指针一开始都指向虚拟节点。然后,移动fast指针n次,这样fast就在slow前面n个的地方。接着,同时移动fast和slow,直到fast到达最后一个节点。这时,就可以删除slow后面的节点,然后返回虚拟节点的下一个节点作为答案即可了。
还是要注意删除的是头结点和最后一个节点的边界情况。
- /**
- * Definition for singly-linked list.
- * 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 node = new ListNode(-1, head);
- let fast = node,
- slow = node;
- // 快慢指针相隔的节点数
- let count = 0;
- while (count < n) {
- fast = fast.next;
- count++;
- }
- // 同时移动快慢指针,直到fast成为最后一个节点
- while (fast.next) {
- fast = fast.next;
- slow = slow.next;
- }
- // 删除掉slow的下一个节点即可
- slow.next = slow.next ? slow.next.next : null;
- return node.next;
- };