Leetcode热题100道
👏作者简介:大家好,我是 枫度柚子🍁,Java摆烂选手,很高兴认识大家 👀
📕CSDN/掘金/B站: 枫吹过的柚 🍁
🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
🛰微信群: 加微信 QaQ-linv
🐧QQ群: 995832569
哈希
两数之和_1
给定一个整数数组 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]
class Solution {
public int[] twoSum(int[] nums, int target) {
if (nums == null || nums.length == 0)
return new int[] {};
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
return new int[] { map.get(target - nums[i]), i };
}
map.put(nums[i], i);
}
return new int[] {};
}
}
字母异位词分组_49
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
- 将每串字符串的字符进行排序后作为key,然后放到对应的value集合里
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
if (strs == null || strs.length == 0)
return new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] chs = str.toCharArray();
Arrays.sort(chs);
String key = new String(chs);
// 根据key获取value集合
List<String> values = map.getOrDefault(key, new ArrayList<>());
values.add(str);
map.put(key, values);
}
return new ArrayList<>(map.values());
}
}
最长连续序列_128
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
双指针
滑动窗口
子串
普通数组
矩阵
链表
二叉树
图论
回溯
二分查找
-
int matrix = {{1, 3, 5, 7}, {10, 11, 16, 20}, {23, 30, 34, 60}}
- int m = matrix.length; 行数
- int n = matrix[0].length; 列数
搜索插入位置_35
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
class Solution {
public int searchInsert(int[] nums, int target) {
if (nums == null || nums.length == 0)
return -1;
int l = 0, r = nums.length - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if(target > nums[mid]) l = mid + 1;
else r = mid - 1;
}
// 没找到就插入 程序退出条件应该是l==n了,所以返回l和n
return l;
}
}
搜索二维矩阵_74
给你一个满足下述两条属性的 m x n 整数矩阵:
- 每行中的整数从左到右按非严格递增顺序排列。
- 每行的第一个整数大于前一行的最后一个整数。
给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。
示例 1:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true
示例 2:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
// m为行数、n为列数
int m = matrix.length,n = matrix[0].length;
// 把二维数组映射到一维
int l = 0,r = m * n - 1;
while(l <= r){
int mid = l + (r - l) / 2;
if(target == get(matrix,mid)) return true;
else if(target < get(matrix,mid)) r = mid - 1;
else if(target > get(matrix,mid)) l = mid + 1;
}
return false;
}
// 通过一维坐标访问二维数组的坐标的元素
int get(int[][] matrix,int index){
int n = matrix[0].length;
int i = index / n, j = index % n;
return matrix[i][j];
}
}
在排序数组中查找元素的第一个和最后一个位置_34
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
- 分为两个二分查找对应前、后下标,但是需要注意first前面的值可能等于mid 或 second后面的值等于mid
class Solution {
public int[] searchRange(int[] nums, int target) {
if (nums == null || nums.length == 0)
return new int[] { -1, -1 };
// first => 0 -> len - 1 ,没找到返回-1
int first = findFirst(nums, target, 0, nums.length - 1);
// second => first -> len - 1,没找到返回-1
int second = findSecond(nums, target, Math.max(first, 0), nums.length - 1);
return new int[] { first, second };
}
int findFirst(int[] nums, int target, int l, int r) {
while (l <= r) {
int mid = l + (r - l) / 2;
if (target == nums[mid]) {
// 说明mid之前就出现了
if (mid - 1 >= 0 && nums[mid - 1] == nums[mid]) {
r = mid - 1;
} else {
return mid;
}
} else if (target < nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return -1;
}
int findSecond(int[] nums, int target, int l, int r) {
while (l <= r) {
int mid = l + (r - l) / 2;
if (target == nums[mid]) {
// 说明mid之前就出现了
if (mid + 1 <= r && nums[mid + 1] == nums[mid]) {
l = mid + 1;
} else {
return mid;
}
} else if (target < nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return -1;
}
}
搜索旋转排序数组_33
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -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
示例 3:
输入:nums = [1], target = 0
输出:-1
- 数组经过旋转会保证两块有序,分别进行处理
class Solution {
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0)
return -1;
int l = 0, r = nums.length - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (target == nums[mid])
return mid;
// 如果左边有序
else if (nums[mid] >= nums[l]) { // 注意
if (target >= nums[l] && target < nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
}
// 如果右边有序
else {
if (target <= nums[r] && target > nums[mid]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
}
return -1;
}
}
寻找旋转排序数组中的最小值_153
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
- 若旋转
4次,则可以得到[4,5,6,7,0,1,2] - 若旋转
7次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。
示例 3:
输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
- 由于数组不包含重复元素,并且只要当前的区间长度不为 1,mid就不会与r重合
class Solution {
public int findMin(int[] nums) {
if (nums == null || nums.length == 0)
return -1;
int l = 0, r = nums.length - 1;
while (l < r) { // 注意
int mid = l + (r - l) / 2;
if (nums[mid] < nums[r]) {
r = mid;
} else {
l = mid + 1;
}
}
return nums[l];
}
}
寻找两个正序数组的中位数_4
给定两个大小分别为 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
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 用 len 表示合并后数组的长度,
// 如果是奇数,我们需要知道第 (len+1)/2 个数就可以了,如果遍历的话需要遍历 int(len/2 ) + 1 次。
// 如果是偶数,我们需要知道第 len/2和 len/2+1 个数
// 也是需要遍历 len/2+1 次。所以遍历的话,奇数和偶数都是 len/2+1 次
// 返回中位数的话,奇数需要最后一次遍历的结果就可以了,偶数需要最后一次和上一次遍历的结果
// 所以我们用两个变量 left 和 right,right 保存当前循环的结果,在每次循环前将 right 的值赋给 left
// 这样在最后一次循环的时候,left 将得到 right 的值,也就是上一次循环的结果,接下来 right 更新为最后一次的结果
int m = nums1.length, n = nums2.length;
int len = m + n;
int left = -1, right = -1;
// 用 num1_start 和 num2_start 分别表示当前指向 A 数组和 B 数组的位置
int num1_start = 0, num2_start = 0;
for (int i = 0; i < (len / 2) + 1; i++) {
left = right;
// ( 如果left < right 说明在left的数组里 或者越界了)
if (num1_start < m && (num2_start >= n || nums1[num1_start] < nums2[num2_start])) {
right = nums1[num1_start++];
} else {
// 如果left > right 说明在right的数组里
right = nums2[num2_start++];
}
}
// 如果是偶数
if ((len & 1) == 0) {
return (left + right) / 2.0;
} else {
return right;
}
}
}
栈
-
Stack
- push 入栈
- pop 出栈并返回元素
有效的括号_20
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
- 遍历每个字符,如果是左括号就入栈,否则就进行左出栈判断当前右括号与它是否匹配,不匹配就false,最终栈为空说明满足
class Solution {
public boolean isValid(String s) {
if (s == null || s.length() == 0)
return false;
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '{' || c == '[') {
stack.push(c);
} else {
if (!stack.isEmpty()) {
Character top = stack.pop();
if (!( (c == ')' && top == '(') ||
(c == ']' && top == '[') ||
(c == '}' && top == '{'))) {
return false;
}
} else {
return false;
}
}
}
return stack.isEmpty();
}
}
最小栈_155
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack()初始化堆栈对象。void push(int val)将元素val推入堆栈。void pop()删除堆栈顶部的元素。int top()获取堆栈顶部的元素。int getMin()获取堆栈中的最小元素。
示例 1:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
-
通过两个Stack完成
- 一个Stack正常存
- 一个minStack先判断是否为空,为空就先存,不为空时取当前值和栈顶元素小的那个存
class MinStack {
Stack<Integer> stack;
Stack<Integer> minStack;
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int val) {
stack.push(val);
// 如果为空就存当前值
if (minStack.isEmpty()) {
minStack.push(val);
} else {
minStack.push(Math.min(minStack.peek(), val));
}
}
public void pop() {
if (stack.isEmpty())
return;
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
字符串解码_394
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例 2:
输入:s = "3[a2[c]]"
输出:"accaccacc"
示例 3:
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
示例 4:
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
- 一个Stack存倍数 numStack
- 一个Stack存上次结果 resStack
- 每次出栈用来追加后面的字符,需要注意的是数字可能不是个位数,当为左括号时,先预留一串空的,当为右括号时就要开始乘法得到一串字符串segment,resStack栈顶出栈 + segment 即为当前res
class Solution {
public String decodeString(String s) {
if (s == null || s.length() == 0)
return "";
Stack<Integer> numStack = new Stack<>();
Stack<String> resStack = new Stack<>();
int num = 0;
StringBuilder res = new StringBuilder();
for (char c : s.toCharArray()) {
if (c >= '0' && c <= '9') {
num = num * 10 + c - '0';
} else if (c == '[') {
numStack.push(num);
resStack.push(res.toString());
num = 0;
res = new StringBuilder();
} else if (c == ']') {
StringBuilder segment = new StringBuilder();
Integer curNum = numStack.pop();
for (int i = 0; i < curNum; i++) {
segment.append(res.toString());
}
// 上次的出栈 + 本段字符
res = new StringBuilder(resStack.pop() + segment);
} else {
// 将括号中的字符拼出来
res.append(c);
}
}
return res.toString();
}
}
每日温度_739
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
-
用Stack(存比当前值小的下标)
- 遍历数组,判断当前值是否比Stack栈顶元素对应的值小,如果小就Stack栈顶元素出栈,存当前下标
-
最终结果res通过 当前值下标 - 栈顶元素的值(下标)
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
if (temperatures == null || temperatures.length == 0)
return new int[] {};
Stack<Integer> stack = new Stack<>();
int[] res = new int[temperatures.length];
for (int i = 0; i < temperatures.length; i++) {
// 保证出栈非空,有大的就出栈一个,没有默认都为0
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
Integer curTopIdx = minStack.pop();
res[curTopIdx] = i - curTopIdx;
}
stack.push(i);
}
return res;
}
}
柱状图中最大的矩形_84
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4]
输出: 4
- 用Stack(存入比当前值大的下标),计算面积 = width(右边下标 - 左边下标 - 1) * 较低高度(下标对应的值)
class Solution {
public int largestRectangleArea(int[] heights) {
if (heights == null || heights.length == 0)
return 0;
Stack<Integer> stack = new Stack<>();
int max = 0;
// 将元素放入新数组居中
int[] newHeights = new int[heights.length + 2];
newHeights[0] = 0;
newHeights[newHeights.length - 1] = 0;
for (int i = 1; i < heights.length + 1; i++) {
newHeights[i] = heights[i - 1];
}
for (int i = 0; i < newHeights.length; i++) {
// 栈顶元素对应的值 > 当前元素的值
while (!stack.isEmpty() && newHeights[i] < newHeights[stack.peek()]) {
// 栈顶元素,之前最小的高度
int height = newHeights[stack.pop()];
int left = stack.peek();
int width = i - left - 1;
int area = width * height;
max = Math.max(max, area);
}
stack.push(i);
}
return max;
}
}
堆
-
PriorityQueue
- offer 新增
- poll 删除头元素并返回
- peek 获取头
数组中的第K个最大元素_215
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
- 采用最小堆,元素入堆,剔除前K大的元素
class Solution {
public int findKthLargest(int[] nums, int k) {
if(nums == null || nums.length == 0) return 0;
// 使用最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll();
}
}
return minHeap.peek();
}
}
前K个高频元素_347
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
- 采用Map存放出现频率,再使用最小堆,遍历频率Map的key,剔除前K大的元素
class Solution {
public int[] topKFrequent(int[] nums, int k) {
if (nums == null || nums.length == 0)
return new int[] {};
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// 使用最小堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> freqMap.get(a) - freqMap.get(b));
// 遍历频率Map
for (Integer num : freqMap.keySet()) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll();
}
}
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = minHeap.poll();
}
return res;
}
}
数据流的中位数_295
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
arr = [2,3,4]的中位数是3。 - 例如
arr = [2,3]的中位数是(2 + 3) / 2 = 2.5。
实现 MedianFinder 类:
MedianFinder()初始化MedianFinder对象。void addNum(int num)将数据流中的整数num添加到数据结构中。double findMedian()返回到目前为止所有元素的中位数。与实际答案相差10-5以内的答案将被接受。
示例 1:
输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]
解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0
-
借助两个堆,长度不相等
- 最小堆放元素,然后放最小堆的poll
- 最大堆放元素,然后放最大堆的poll
class MedianFinder {
PriorityQueue<Integer> small;
PriorityQueue<Integer> large;
public MedianFinder() {
// 最小堆 保存较大的一半
small = new PriorityQueue<>();
// 最大堆 保存较小的一半
large = new PriorityQueue<>((a, b) -> b - a);
}
public void addNum(int num) {
if (small.size() != large.size()) {
small.offer(num);
// 存小的
large.offer(small.poll());
} else {
large.offer(num);
// 存大的
small.offer(large.poll());
}
}
public double findMedian() {
if (small.size() > large.size()) {
return small.peek();
} else if (small.size() < large.size()) {
return large.peek();
} else {
return (small.peek() + large.peek()) / 2.0;
}
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/