二刷完剑指Offer后又刷了一遍Leetcode Top 100专栏的题目,听说基本上能涵盖面试的算法题,总体来说收获还是很大的,下面贴出答案,又不懂的可以给我留言,博主会及时解答。
我的github
准备把春招复习的知识都整理到github上,一边是自己做个总结,一边也能供大家参考
——leetcode数据库 19道题
——剑指Offer 66道题
以下摘自leetcode Top100精选题目
文章目录
-
-
- 1.两数之和
- 2.两数相加
- 3.无重复字符的最长子串
- 4.寻找两个有序数组的中位数
- 5.最长回文子串
- 10.正则表达式匹配
- 11.盛最多水的容器
- 15.三数之和
- 17.电话号码的字母组合
- 19.删除链表的倒数第N个节点
- 20有效的括号
- 21.合并两个有序链表
- 22.生成括号
- 23.合并K个排序链表
- 31.下一个排列
- 32.最长有效括号
- 33.搜索旋转排序数组
- 34.在排序数组中查找元素的第一个和最后一个位置
- 39.组合总和
- 42.接雨水
- 46.全排列
- 48.旋转图像
- 49.字母异位词分组
- 53.最大子序和
- 55.跳跃游戏
- 56.合并区间
- 62.不同路径
- 64.最小路径和
- 70.爬楼梯
- 72.编辑距离
- 75.颜色分类
- 76.最小覆盖子串
- 78.子集
- 79.单词搜索
- 84.柱状图中最大的矩形
- 85.最大矩形
- 94.二叉树的中序遍历
- 96.不同的二叉搜索树
- 98.验证二叉搜索树
- 101.对称二叉树
- 102.二叉树的层序遍历
- 104.二叉树的最大深度
- 105.从前序遍历和中序遍历序列构造二叉树
- 114.二叉树展开为链表
- 121.买卖股票的最佳时机
- 124.二叉树中的最大路径和
- 128.最长连续序列
- 136.只出现一次的数字
- 139.单词拆分
- 141.环形链表
- 142.环形链表Ⅱ
- 146.LRU缓存机制
- 148.排序链表
- 152.乘机最大子序列
- 155.最小栈
- 160.相交链表
- 169.求众数
- 198.打家劫舍
- 200.岛屿的个数
- 206.反转链表
- 207.课程表
- 208.实现Trie(前缀树)
- 215.数组中的第K个最大元素
- 221.最大正方形
- 226.翻转二叉树
- 234.回文链表
- 236.二叉树的最近公共祖先
- 238.除自身以外数组的乘积
- 239.滑动窗口的最大值
- 240.搜索二维矩阵Ⅱ
- 279.完全平方数
- 283.移动零
- 287.寻找重复数
- 297.二叉树的序列化和反序列化
- 300.最长上升子序列
- 309.最佳买卖股票时期含冷冻期
- 312.戳气球
- 322.零钱兑换
- 337.打家劫舍Ⅲ
- 338.比特位计数
- 347.前K个高频元素
- 394.字符串解码
- 406.根据身高重建队列
- 416.分割等和子集
- 437.路径总和Ⅲ
- 438.找到字符串中所有字母异位词
- 448.找到所有数组中消失的数字
- 461.汉明距离
- 494.目标和
- 538.把二叉搜索树转换为累加树
- 543.二叉树的直径
- 560.和为K的子数组
- 572.另一个树的子树
- 581.最短无序连续子数组
- 617.合并二叉树
- 647.回文子串
- 771.宝石与石头
-
1.两数之和
题目描述:
给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
Solution:
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i])) {
return new int[]{
map.get(nums[i]), i};
}
map.put(target - nums[i], i);
}
return null;
}
}
2.两数相加
题目描述:
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
节点结构:
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x; }
}
Solution:
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
int sum = 0; // 结果
int more = 0; // 进位
ListNode pre = dummy;
while (l1 != null || l2 != null || more > 0) {
sum = (l1 == null ? 0 : l1.val) + (l2 == null ? 0 : l2.val) + more;
more = sum / 10;
sum %= 10;
ListNode node = new ListNode(sum);
pre.next = node;
pre = node;
l1 = l1 == null ? null : l1.next;
l2 = l2 == null ? null : l2.next;
}
return dummy.next;
}
}
3.无重复字符的最长子串
题目描述:
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
Solution:
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() < 1) {
return 0;
}
int[] map = new int[256];
int l = 0;
int r = 0; // 滑动窗口为[l, r),其间为不重复的元素
int res = 0;
while (l < s.length()) {
if (r < s.length() && map[s.charAt(r)] == 0) {
map[s.charAt(r++)]++;
res = Math.max(res, r - l);
} else {
map[s.charAt(l++)]--;
}
}
return res;
}
}
4.寻找两个有序数组的中位数
题目描述:
给定两个大小为 m 和 n 的有序数组 nums1
和 nums2
。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1
和 nums2
不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
Solution:
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 保证nums1不是最长的,时间复杂度可转化为O(log(Min(m, n)))
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
int left = 0;
int right = nums1.length;
int halfLen = (nums1.length + nums2.length + 1) >> 1;
while (left <= right) {
int i = (left + right) >> 1; // nums1[i, nums1.length)为要分割的右半部分
int j = halfLen - i; // nums2[j, nums2.length)为要分割的右半部分
if (i < right && nums2[j - 1] > nums1[i]) {
// nums1分割点此时需要右移
left++;
} else if (i > left && nums1[i - 1] > nums2[j]) {
// nums1 分割点此时需要左移
right--;
} else {
int leftMax = (i == 0) ? nums2[j - 1] :
(j == 0 ? nums1[i - 1] : Math.max(nums1[i - 1], nums2[j - 1]));
if (((nums1.length + nums2.length) & 1) == 1) {
return leftMax * 1.0;
}
int rightMin = (i == nums1.length) ? nums2[j] :
(j == nums2.length ? nums1[i] : Math.min(nums1[i], nums2[j]));
return (leftMax + rightMin) / 2.0;
}
}
return 0.0;
}
}
5.最长回文子串
题目描述:
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
Solution:
/**
* 中心扩展法
*/
public class Solution {
private int left;
private int len;
public String longestPalindrome(String s) {
if (s == null || s.length() < 2) {
return s;
}
for (int i = 0; i < s.length(); i++) {
find(s, i, i); // 奇数长度
find(s, i, i + 1); // 偶数长度
}
return s.substring(left, left + len);
}
private void find(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
if (right - left + 1 > len) {
len = right - left + 1;
this.left = left;
}
right++;
left--;
}
}
}
10.正则表达式匹配
题目描述:
给定一个字符串 (s
) 和一个字符模式 (p
)。实现支持 '.'
和 '*'
的正则表达式匹配。
'.' 匹配任意单个字符。
'*' 匹配零个或多个前面的元素。
匹配应该覆盖整个字符串 (s
) ,而不是部分字符串。
说明:
s
可能为空,且只包含从a-z
的小写字母。p
可能为空,且只包含从a-z
的小写字母,以及字符.
和*
。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "aa"
p = "a*"
输出: true
解释: '*' 代表可匹配零个或多个前面的元素, 即可以匹配 'a' 。因此, 重复 'a' 一次, 字符串可变为 "aa"。
示例 3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 'c' 可以不被重复, 'a' 可以被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
Solution:
public class Solution {
public boolean isMatch(String s, String p) {
if (s == null || p == null) {
return false;
}
return isMatch(s, p, 0, 0);
}
private boolean isMatch(String str, String pattern, int s, int p) {
// 正则表达式已用尽,如果字符串还未匹配完,则返回false
if (p == pattern.length()) {
return str.length() == s;
}
// 正则表达式下一位为*,此时考虑两种情况
if (p + 1 < pattern.length() && pattern.charAt(p + 1) == '*') {
// 若正则表达式当前位字符与字符串当前位置相匹配,则匹配1位或者0位
if (s < str.length() && (str.charAt(s) == pattern.charAt(p) || pattern.charAt(p) == '.')) {
return isMatch(str, pattern, s, p + 2) || isMatch(str, pattern, s + 1, p);
}
// 若正则表达式当前位字符与字符串当前位置不匹配,则匹配0位
return isMatch(str, pattern, s, p + 2);
}
// 匹配1位
if (s < str.length() && (str.charAt(s) == pattern.charAt(p) || pattern.charAt(p) == '.')) {
return isMatch(str, pattern, s + 1, p + 1);
}
return false;
}
}
11.盛最多水的容器
题目描述:
给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
**说明:**你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
Solution:
/**
* 利用滑动窗口解决
*/
public class Solution {
public int maxArea(int[] height) {
int res = 0;
int left = 0;
int right = height.length - 1;
while (left < right) {
res = Math.max(res, Math.min(height[left], height[right]) * (right - left));
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return res;
}
}
15.三数之和
题目描述:
给定一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 *a,b,c ,*使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
Solution:
/**
* 采用滑动窗口,时间复杂度为:O(n log(n))
*/
public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
if (nums == null || nums.length < 3) {
return list;
}
// 先排序,同时避免求重复解
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2 && nums[i] <= 0;) {
int l = i + 1;
int r = nums.length - 1;
while (l < r) {
int sum = nums[i] + nums[l] + nums[r];
if (sum == 0) {
list.add(Arrays.asList(nums[i], nums[l++], nums[r--]));
while (l < r && nums[l] == nums[l - 1]) {
l++;
}
while (r > l && nums[r] == nums[r + 1]) {
r--;
}
} else if (sum < 0) {
l++;
} else {
r--;
}
}
i++;
while (i < nums.length - 2 && nums[i] == nums[i - 1]) {
i++;
}
}
return list;
}
}
17.电话号码的字母组合
题目描述:
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
Solution:
public class Solution {
private List<String> res = new ArrayList<>();
private String[] map = {
"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() < 1) {
return res;
}
dfs(digits, 0, "");
return res;
}
private void dfs(String digits, int index, String str) {
if (index == digits.length()) {
res.add(str);
return;
}
String dict = map[digits.charAt(index) - '0'];
for (int i = 0; i < dict.length(); i++) {
dfs(digits, index + 1, str + dict.charAt(i));
}
}
}
19.删除链表的倒数第N个节点
题目描述:
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
Solution:
/*
* 双指针
*/
public class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = head;
ListNode slow = dummy;
for (int i = 0; i < n; i++) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
20有效的括号
题目描述:
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
Solution:
public class Solution {
public static boolean isValid(String s) {
if (s == null || (s.length() & 1) == 1) {
return false;
}
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '[' || s.charAt(i) == '{' || s.charAt(i) == '(') {
stack.push(s.charAt(i));
} else if (s.charAt(i) == ']' && (stack.isEmpty() || stack.pop() != '[')) {
return false;
} else if (s.charAt(i) == '}' && (stack.isEmpty() || stack.pop() != '{')) {
return false;
} else if (s.charAt(i) == ')' && (stack.isEmpty() || stack.pop() != '(')) {
return false;
}
}
return stack.isEmpty();
}
}
21.合并两个有序链表
题目描述:
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
Solution:
public class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode pre = dummy;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
pre.next = l1;
l1 = l1.next;
} else {
pre.next = l2;
l2 = l2.next;
}
pre = pre.next;
}
pre.next = l1 == null ? l2 : l1;
return dummy.next;
}
}
22.生成括号
题目描述:
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
Solution:
public class Solution {
private List<String> list = new ArrayList<>();
public List<String> generateParenthesis(int n) {
if (n < 1) {
return list;
}
generate(n, 0, 0, "");
return list;
}
private void generate(int n, int left, int right, String str) {
if (left == right && left == n) {
list.add(str);
}
if (left < n) {
generate(n, left + 1, right, str + "(");
}
if (left > right) {
generate(n, left, right + 1, str + ")");
}
}
}
23.合并K个排序链表
题目描述:
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
Solution:
public class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) {
return null;
}
ListNode dummy = new ListNode(0);
ListNode pre = dummy;
// 使用小顶堆,每次取出的都是最小的节点
Queue<ListNode> minHeap = new PriorityQueue<>(Comparator.comparingInt(node -> node.val));
for (ListNode list : lists) {
if (list != null) {
minHeap.offer(list);
}
}
while (!minHeap.isEmpty()) {
pre.next = minHeap.poll();
pre = pre.next;
if (pre.next != null) {
minHeap.offer(pre.next);
}
}
return dummy.next;
}
}
31.下一个排列
题目描述:
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3
→ 1,3,2
3,2,1
→ 1,2,3
1,1,5
→ 1,5,1
Solution:
/**
* 求下一个全排列,可分为两种情况:
* 1.例如像 5 4 3 2 1这样的序列,已经是最大的排列,即每个位置上的数非递增,这时只需要翻转整个序列即可
* 2.例如像 1 3 5 4 2这样的序列,要从后往前找到第一个比后面一位小的元素的位置,即第二个位置的3,然后与其后第一个比它大的元素交换位置,得到 1 4 5 3 2,再将 5 3 2翻转得到 1 4 2 3 5即可
*/
public class Solution {
public void nextPermutation(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
int firstSmall = -1;
for (int i = nums.length - 2; i >= 0; i--) {
if (nums[i] < nums[i + 1]) {
firstSmall = i;
break;
}
}
if (firstSmall == -1) {
reverse(nums, 0, nums.length - 1);
return;
}
for (int i = nums.length - 1; i > firstSmall; i--) {
if (nums[i] > nums[firstSmall]) {
swap(nums, i, firstSmall);
reverse(nums, firstSmall + 1, nums.length - 1);
return;
}
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private void reverse(int[] nums, int start, int end) {
while (start < end) {
swap(nums, start++, end--);
}
}
}
32.最长有效括号
题目描述:
给定一个只包含 '('
和 ')'
的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
Solution:
public class Solution {
public int longestValidParentheses(String s) {
if (s == null || s.length() < 2) {
return 0;
}
int res = 0;
int start = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else {
if (stack.isEmpty()) {
start = i + 1;
} else {
stack.pop();
res = stack.isEmpty() ? Math.max(res, i - start + 1) : Math.max(res, i - stack.peek());
}
}
}
return res;
}
}
33.搜索旋转排序数组
题目描述:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7]
可能变为 [4,5,6,7,0,1,2]
)。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1
。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
Solution:
public class Solution {
public int search(int[] nums, int target) {
if (nums == null
|| nums.length < 1
|| (target < nums[0] && target > nums[nums.length - 1])) {
return -1;
}
int low = 0;
int high = nums.length - 1;
while (low <= high) {
int mid = (low + high) >> 1;
if (nums[mid] == target) {
return mid;
}
if (nums[mid] >= nums[low]) {
// 左边有序
if (nums[mid] > target && nums[low] <= target) {
// 在有序边
high = mid - 1;
} else{
// 在无序边
low = mid + 1;
}
} else {
// 右边有序
if (nums[mid] < target && nums[high] >= target) {
// 在有序边
low = mid + 1;
} else {
// 在无序边
high = mid - 1;
}
}
}
return -1;
}
}
34.在排序数组中查找元素的第一个和最后一个位置
题目描述:
给定一个按照升序排列的整数数组 nums
,和一个目标值 target
。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]
。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
Solution:
public class Solution {
public int[] searchRange(int[] nums, int target) {
if (nums == null || nums.length < 1) {
return new int[]{
-1, -1};
}
int low = 0;
int high = nums.length - 1;
while (low <= high) {
int mid