听从同事姐姐的建议,从今天开始,我将整理力扣hot100题的解法,不论简单困难,按顺序记录。
目录
1、两数之和
题目内容
题解
2、两数相等
题目内容
题解
3、无重复字符的最长子串
题目内容
题解
4、寻找两个正序数组的中位数
题目内容
题解
5、最长回文子串
题目内容
题解
6、盛水最多的容器
题目内容
题解
7、三数之和
题目内容
题解
8、电话号码的字母组合
题目内容
题解
9、删除链表中倒数第n个节点
题目内容
题解
给定一个整数数组 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;
};