写在前文:由于LeetCode100的答案多种多样,本文针对每一题只写一种解法(尽可能简单易懂)。针对不容易理解的题目,采用图片形式 或博主自认为讲解不错的视频链接来提供解题思路。
1. 哈希
1.1 两数之和
题目描述:
-
给定一个整数数组
nums和一个整数目标值target,请你在该数组中找出 和为目标值target的那 两个 整数,并返回它们的数组下标。 -
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
-
你可以按任意顺序返回答案。
力扣链接:
https://leetcode.cn/problems/two-sum/description/【简单】
解题思路:
-
实例化一个
HashMap来保存<值, 索引> -
遍历HashMap,找到就返回索引下标,找不到就添加元素
核心代码:
java
class Solution {
public int[] twoSum(int[] nums, int target) {
// map保存<值,索引>
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); // 返回[索引1,索引2]
}
return new int[0]; // 返回[]
}
}
1.2 字母异位词分组
题目描述:
-
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
-
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
力扣链接:
https://leetcode.cn/problems/group-anagrams/description【中等】
解题思路:
- Map保存
<排序后的字符串, List<String>> - 遍历strs, 依次添加到Map
核心代码:
java
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
// 1. Map保存<排序后的str, List<String>>
Map<String, List<String>> map = new HashMap<>();
// 2. 遍历strs, 依次添加到Map
for (String str : strs){
char[] charStr = str.toCharArray();
Arrays.sort(charStr);
String orderStr = new String(charStr);
if (map.containsKey(orderStr)){
map.get(orderStr).add(str);
}else{
List<String> temp = new ArrayList<>();
temp.add(str);
map.put(orderStr,temp);
}
}
return new ArrayList<List<String>>(map.values());
}
}
1.3 最长连续序列
题目描述:
-
给定一个未排序的整数数组
nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 -
请你设计并实现时间复杂度为
O(n)的算法解决此问题。
力扣链接:
https://leetcode.cn/problems/longest-consecutive-sequence/description【中等】
解决思路:
- 将nums数组的所有元素放入HashSet,去除重复元素
- 从序列的最小值开始找,更新最大值
核心代码:
java
class Solution {
public int longestConsecutive(int[] nums) {
// 1. 将nums数组的所有元素放入HashSet, 去除重复元素
HashSet<Integer> hs = new HashSet<>();
for (int i : nums){
hs.add(i);
}
int ans = 0;
for (int i : hs){
// 2. 只从序列的最小值开始找
if (!hs.contains(i - 1)){
int curAns = 1;
while(hs.contains(i+1)){
i++;
curAns++;
}
ans = Math.max(ans,curAns);
}
}
return ans;
}
}
2. 双指针
2.1 移动零
题目描述:
-
给定一个数组
nums,编写一个函数将所有0移动到数组的末尾,同时保持非零元素的相对顺序。 -
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
力扣链接:
https://leetcode.cn/problems/move-zeroes/【简单】
解题思路:
-
核心思想:当 left指针指向第一个0,right指针指向第一个非0时,交换元素
-
解题流程:
-
初始化left指针位0,right指针为0
-
遍历right指针
-
如果right指针的元素为0,right++
-
如果right指针的元素不为0,交换元素,left++,right++
-
-
核心代码:
java
class Solution {
public void moveZeroes(int[] nums) {
// 1. 初始化left和right指针为0
int left = 0;
int right = 0;
// 2. 遍历right指针。right指针的元素为0 ? right++ : {swap(l,r), l++, r++}
while(right < nums.length){
if (nums[right] == 0){
right++;
}else{
swap(nums,left,right);
left++;
right++;
}
}
}
public void swap(int[] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
2.2 盛最多水的容器
题目描述:
-
给定一个长度为
n的整数数组height。有n条垂线,第i条线的两个端点是(i, 0)和(i, height[i])。 -
找出其中的两条线,使得它们与
x轴共同构成的容器可以容纳最多的水。 -
返回容器可以储存的最大水量。
-
**说明:**你不能倾斜容器。
力扣链接:
https://leetcode.cn/problems/container-with-most-water/description/【中等】
解题思路:
- 初始化左,右指针,最大面积。
- 计算当前面积, 更新最大面积,短指针向中间移动
核心代码:
java
class Solution {
public int maxArea(int[] height) {
// 1. 初始化左右指针, 最大面积
int left = 0;
int right = height.length - 1;
int maxArea = 0;
// 2. 计算当前面积, 更新最大面积,短指针向中间移动
while(left < right){
int curArea = (right - left) * Math.min(height[left],height[right]);
maxArea = Math.max(maxArea,curArea);
if (height[left] < height[right]){
left++;
}else{
right--;
}
}
return maxArea;
}
}
2.3 三数之和
题目描述:
-
给你一个整数数组
nums,判断是否存在三元组[nums[i], nums[j], nums[k]]满足i != j、i != k且j != k,同时还满足nums[i] + nums[j] + nums[k] == 0。请你返回所有和为0且不重复的三元组。 -
**注意:**答案中不可以包含重复的三元组。
力扣链接:
https://leetcode.cn/problems/3sum/description【中等】
解题思路:
- 三数之和变成两数之和
- 避免重复解
- 直接看代码+注释,理解背下来吧
核心代码:
java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList<>(); // 创建一个列表用于存储最终的答案
Arrays.sort(nums); // 首先对数组进行排序,这样可以方便地使用双指针方法查找组合
for (int i = 0; i < nums.length - 2; ++i){ // 循环遍历数组中的每一个元素,但最后两个元素不需要作为起始点考虑
if (i > 0 && nums[i] == nums[i-1]) continue; // 如果当前元素和前一个元素相同,则跳过以避免重复解
// 设置目标值为 -nums[i],因为我们需要找到三个数之和为0的组合,即 nums[i] + nums[l] + nums[r] = 0
int target = - nums[i];
int l = i + 1; // 左指针初始化为当前元素的下一个位置
int r = nums.length - 1; // 右指针初始化为数组最后一个元素的位置
while(l < r){ // 当左指针小于右指针时继续循环
int sum = nums[l] + nums[r]; // 计算左右指针所指元素的和
if (target == sum){ // 如果找到的和正好等于目标值
ans.add(Arrays.asList(nums[i], nums[l], nums[r])); // 将找到的三元组添加到答案中
// 跳过所有相同的左指针指向的值,避免重复解
while(l < r && nums[l] == nums[l + 1]) l++;
// 跳过所有相同的右指针指向的值,避免重复解
while(l < r && nums[r] == nums[r - 1]) r--;
l++; // 移动左指针
r--; // 移动右指针
} else if(target > sum){ // 如果需要更大的和,移动左指针
l++;
}else{ // 如果需要更小的和,移动右指针
r--;
}
}
}
return ans; // 返回所有找到的不重复的三元组
}
}
2.4 接雨水
题目描述:
- 给定
n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
力扣链接:
https://leetcode.cn/problems/trapping-rain-water/description【困难】
解题思路
-
对于下标 i,下雨后水能到达的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的雨水量等于下标 i 处的水能到达的最大高度减去 height[i]。
-
leftMax[i] 表示下标 i 及其左边的位置中,height 的最大高度 ,rightMax[i] 表示下标 i 及其右边的位置中,height 的最大高度。
-
动态规划
-
计算每个下标i可以接的雨水量:
sum[i] = min(i左侧最大高度,i右侧最大高度) - height[i]

注意:本题采用的是动态规划解法。。。理解不了就背这个图。
核心代码
java
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n == 0) return 0;
int[] leftMax = new int[n];
int[] rightMax = new int[n];
// 填充 leftMax
int curLeftMax = 0;
for (int i = 0; i < n; ++i) {
leftMax[i] = Math.max(height[i], curLeftMax);
curLeftMax = leftMax[i]; // 更新当前最大值
}
// 填充 rightMax
int curRightMax = 0;
for (int i = n - 1; i >= 0; --i) {
rightMax[i] = Math.max(height[i], curRightMax);
curRightMax = rightMax[i]; // 更新当前最大值
}
// 计算能接多少水
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}
3. 滑动窗口
3.1 无重复字符的最长子串
题目描述:
- 给定一个字符串
s,请你找出其中不含有重复字符的 最长 子串 的长度。
力扣链接:
https://leetcode.cn/problems/longest-substring-without-repeating-characters/【中等】
解题思路:
- 双指针滑动
- Set保存不含重复字符子串
核心代码:
java
class Solution {
public int lengthOfLongestSubstring(String s) {
// 使用 HashSet 来记录当前窗口中的字符(便于快速判断是否有重复字符)
Set<Character> set = new HashSet<>();
// ans:记录最长无重复子串的长度
// left:滑动窗口左指针
// right:滑动窗口右指针
int ans = 0, left = 0, right = 0;
// 右指针从左向右移动,扩展窗口
for (; right < s.length(); right++) {
char ch = s.charAt(right); // 当前要加入窗口的字符
// 如果当前字符已经在集合中存在,说明窗口中有重复字符
// 需要不断将左指针右移,直到重复字符被移除
while (set.contains(ch)) {
set.remove(s.charAt(left)); // 移除最左边的字符
left++; // 左指针右移
}
// 此时窗口中已经没有重复字符,把当前字符加入集合
set.add(ch);
// 计算当前窗口长度,并更新最大值
ans = Math.max(ans, right - left + 1);
}
return ans; // 返回最长无重复子串的长度
}
}
3.2 找到字符串中所有字符异位词
题目描述:
- 给定两个字符串
s和p,找到s中所有p的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
踩坑点:题目描述不清晰,两个单词包含相同的字符 且每个字符出现的次数也相同。
力扣链接:
https://leetcode.cn/problems/find-all-anagrams-in-a-string/description/【中等】
解题思路:
- 创建两个长度为26的数组分别表示目标字符串p和滑动窗口的字符频率
- Arrays.equal比较两个数组是否相同(索引对应的值全部一样)
- 本质:空间换时间
核心代码:
java
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> ans = new ArrayList<>();
int len_s = s.length();
int len_p = p.length();
// 特殊情况处理
if (len_s < len_p) return ans;
// 创建两个长度为26的数组分别表示目标字符串p和滑动窗口的字符频率
int[] pCount = new int[26];
int[] windowCount = new int[26];
// 初始化目标p的字符频率
for (char ch : p.toCharArray()) {
pCount[ch - 'a']++;
}
// 滑动窗口初始化:前 len_p 个字符
for (int i = 0; i < len_p; i++) {
char ch = s.charAt(i);
windowCount[ch - 'a']++;
}
// 如果初始窗口匹配,添加起始索引0
if (Arrays.equals(pCount, windowCount)) {
ans.add(0);
}
// 开始滑动窗口
for (int i = len_p; i < len_s; i++) {
// 移除最左边的字符
char leftChar = s.charAt(i - len_p);
windowCount[leftChar - 'a']--;
// 添加右边新进来的字符
char rightChar = s.charAt(i);
windowCount[rightChar - 'a']++;
// 检查当前窗口是否匹配
int startIdx = i - len_p + 1;
if (Arrays.equals(pCount, windowCount)) {
ans.add(startIdx);
}
}
return ans;
}
}
4. 子串
4.1 和为K的子数组
题目描述:
给定一个整数数组 nums 和一个整数 k,找到数组中所有 和为 k 的连续子数组,返回这些子数组的起始索引列表。结果顺序不限。
示例:
输入:nums = [1,1,1], k = 2
输出:[0,1]
解释:子数组 [1,1](起始索引0)和 [1,1](起始索引1)的和均为2。
力扣链接:
https://leetcode.cn/problems/subarray-sum-equals-k/description/【中等】
踩坑点:
- 前缀和需记录所有出现的位置,而非仅计数,以支持多起点匹配。
- 哈希表的键为前缀和,值为对应前缀和的所有索引列表。
- 初始状态需包含前缀和为0时的索引-1,确保边界情况覆盖。
解题思路:
- 前缀和与哈希表结合:利用前缀和快速计算子数组和,并通过哈希表记录历史前缀和的位置。
- 实时更新与查询 :遍历数组时,计算当前前缀和,检查是否存在
当前前缀和 - k的历史前缀和,若存在则所有对应位置的下一位即为有效子数组的起点。 - 空间换时间:哈希表存储前缀和及其索引列表,牺牲额外空间换取线性时间复杂度。
核心代码:
java
import java.util.*;
class Solution {
public List<Integer> findSubarraySum(int[] nums, int k) {
List<Integer> result = new ArrayList<>();
Map<Integer, List<Integer>> prefixSumMap = new HashMap<>();
prefixSumMap.put(0, new ArrayList<>(Collections.singletonList(-1))); // 初始前缀和0,索引-1
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
// 查找是否存在sum - k的前缀和
if (prefixSumMap.containsKey(sum - k)) {
for (int startIdx : prefixSumMap.get(sum - k)) {
result.add(startIdx + 1);
}
}
// 更新当前前缀和的索引列表
prefixSumMap.computeIfAbsent(sum, key -> new ArrayList<>()).add(i);
}
return result;
}
}
4.2 滑动窗口最大值
题目描述:
-
给你一个整数数组
nums,有一个大小为k的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的k个数字。滑动窗口每次只向右移动一位。 -
返回 滑动窗口中的最大值 。
力扣链接:
https://leetcode.cn/problems/sliding-window-maximum/description/【困难】
解题思路:
- 维护一个单调递减队列
核心代码:
java
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
// 准备工作
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
// 未形成窗口
for (int i = 0; i < k; i++){
while(!deque.isEmpty() && deque.peekLast() < nums[i])
deque.removeLast();
deque.addLast(nums[i]);
}
res[0] = deque.peekFirst();
// 形成窗口
for (int i = k; i < nums.length; i++){
if (deque.peekFirst() == nums[i - k])
deque.removeFirst();
while(!deque.isEmpty() && deque.peekLast() < nums[i])
deque.removeLast();
deque.addLast(nums[i]);
res[i - k + 1] = deque.peekFirst();
}
return res;
}
}
4.3 最小覆盖子串
题目描述:
- 给你一个字符串
s、一个字符串t。返回s中涵盖t所有字符的最小子串。如果s中不存在涵盖t所有字符的子串,则返回空字符串""。
注意:
- 对于
t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量。 - 如果
s中存在这样的子串,我们保证它是唯一的答案。
力扣链接:
https://leetcode.cn/problems/minimum-window-substring/description/【困难】
解题思路:
双指针+两个哈希表。解题思路视频链接: https://www.bilibili.com/video/BV1sJ4m1g727/
核心代码:
java
class Solution {
public String minWindow(String s, String t) {
int m = s.length(), n = t.length();
if (m < n) return "";
// 1. 定义两个HashMap记录s和t中字符出现的次数
Map<Character, Integer> sMap = new HashMap<>();
Map<Character, Integer> tMap = new HashMap<>();
// 2. 初始化sMap和tMap
for (char ch : t.toCharArray()) {
tMap.put(ch, tMap.getOrDefault(ch, 0) + 1);
sMap.put(ch, 0); // 初始化sMap中的键,值设为0
}
String curMinStr = "";
int curMinLen = Integer.MAX_VALUE;
// 3. 双指针遍历(核心算法)
int left = 0, right = 0;
int count = 0; // 记录当前窗口中满足tMap要求的字符个数
while (right < m) {
char rightChar = s.charAt(right);
if (tMap.containsKey(rightChar)) {
sMap.put(rightChar, sMap.getOrDefault(rightChar, 0) + 1);
if (sMap.get(rightChar).equals(tMap.get(rightChar))) {
count++;
}
}
right++;
// 当找到一个包含所有t中字符的窗口时,尝试缩小窗口
while (count == tMap.size()) {
if (right - left < curMinLen) {
curMinLen = right - left;
curMinStr = s.substring(left, right);
}
char leftChar = s.charAt(left);
if (sMap.containsKey(leftChar)) {
sMap.put(leftChar, sMap.get(leftChar) - 1);
if (sMap.get(leftChar) < tMap.get(leftChar)) {
count--;
}
}
left++;
}
}
return curMinStr;
}
private boolean matches(Map<Character, Integer> sMap, Map<Character, Integer> tMap) {
// 遍历tMap的所有key
for (Character ch : tMap.keySet()){
if (sMap.getOrDefault(ch, 0) < tMap.get(ch))
return false;
}
return true;
}
}
5. 普通数组
5.1 最大子数组和
题目描述:
-
给你一个整数数组
nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 -
子数组是数组中的一个连续部分。
解题思路:
- 直接看代码
核心代码:
java
class Solution {
public int maxSubArray(int[] nums) {
int maxSum = nums[0]; // 最大子数组和
int currentSum = nums[0]; // 当前子数组和
for(int i = 1; i < nums.length; ++i){
// 更新当前子数组和(如果当前子数组和变大则更新,若不变大则跳过)
currentSum = Math.max(nums[i] + currentSum, nums[i]);
// 更新最大子数组和
maxSum = Math.max(maxSum, currentSum);
}
return maxSum;
}
}
5.2 合并区间
题目描述:
- 以数组
intervals表示若干个区间的集合,其中单个区间为intervals[i] = [starti, endi]。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
解题思路:
力扣链接:
https://leetcode.cn/problems/merge-intervals/description/【中等】
核心代码:
java
class Solution {
public int[][] merge(int[][] intervals) {
// 1. 将列表中的区间按照左端点升序排序
Arrays.sort(intervals,new Comparator<int[]>(){
public int compare(int[] interval1, int[] interval2){
return interval1[0] - interval2[0];
}
});
List<int[]> merged = new ArrayList<>();
// 2. 遍历intervals
for (int i = 0; i < intervals.length; ++i){
int L = intervals[i][0], R = intervals[i][1];
// 增加值
if (merged.size() == 0 || merged.get(merged.size()-1)[1] < L){
merged.add(new int[]{L,R});
}else{
// 更新端点
merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() -1)[1],R);
}
}
return merged.toArray(new int[merged.size()][]);
}
}
5.3 轮转数组
题目描述:
- 给定一个整数数组
nums,将数组中的元素向右轮转k个位置,其中k是非负数。
解题思路:
- 对k取模
- 反转三次
力扣链接:
https://leetcode.cn/problems/rotate-array/description/【中等】
核心代码:
java
class Solution {
public void rotate(int[] nums, int k) {
// !!!踩坑点, 对k取模
int n = nums.length;
k = k % n; // 避免 k 大于数组长度的情况
if (k == 0) return;
reverse(nums,0,nums.length-1);
reverse(nums,0,k-1);
reverse(nums,k,nums.length-1);
}
public void reverse(int[] nums,int l, int r){
for (int i = l,j = r; i < j; i++,j--){
int temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
}
}
}
5.4 除自身以外的数组
题目描述:
-
给你一个整数数组
nums,返回 数组answer,其中answer[i]等于nums中除nums[i]之外其余各元素的乘积 。 -
题目数据 保证 数组
nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 -
请 **不要使用除法,**且在
O(n)时间复杂度内完成此题。
力扣链接:
https://leetcode.cn/problems/product-of-array-except-self/description/【中等】
解题思路:
使用了两个辅助数组 leftCur 和 rightCur 来分别记录每个元素左边和右边所有数的乘积:
leftCur[i]表示nums[0] * nums[1] * ... * nums[i-1]rightCur[i]表示nums[i+1] * nums[i+2] * ... * nums[n-1]- 最终结果是:
ans[i] = leftCur[i] * rightCur[i]
这个方法的时间复杂度是 O(n) ,空间复杂度也是 O(n)(因为用了两个额外的数组)。
核心代码:
java
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] leftCur = new int[n];
int[] rightCur = new int[n];
leftCur[0] = 1;
for (int i = 1; i < n; ++i){
leftCur[i] = leftCur[i - 1] * nums[i - 1]; //
}
rightCur[n - 1] = 1;
for (int j = n - 2; j >= 0; --j) {
rightCur[j] = rightCur[j + 1] * nums[j + 1]; //
}
int[] ans = new int[n];
for (int i = 0; i < n; ++i) {
ans[i] = leftCur[i] * rightCur[i];
}
return ans;
}
}
5.4 缺失的第一个正数
题目描述:
-
给你一个未排序的整数数组
nums,请你找出其中没有出现的最小的正整数。 -
请你实现时间复杂度为
O(n)并且只使用常数级别额外空间的解决方案。
力扣链接:
https://leetcode.cn/problems/first-missing-positive/【困难】
解题思路:
- 结果只可能属于[1,N+1],其中N是数组的长度
- 遍历nums数组,将其中元素值属于[1,N]的元素插入nums数组
核心代码:
java
class Solution {
public int firstMissingPositive(int[] nums) {
// 1. 用于保存所有[1,N]之间的正数(初始化为0)
int[] arr = new int[nums.length];
// 2. 填充数组
for (int i = 0; i < nums.length; i++){
if (nums[i] > 0 && nums[i] <= nums.length){
arr[nums[i]-1] = nums[i];
}
}
// 3. 找到第一个为0的数组
for (int i = 0; i < nums.length; i++){
if (arr[i] == 0){
return i+1;
}
}
return nums.length+1;
}
}
6. 矩阵
6.1 矩阵置零
题目描述:
- 给定一个
*m* x *n*的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法**。**
力扣链接:
https://leetcode.cn/problems/set-matrix-zeroes/description/【中等】
解题思路:
- new两个数组,一个保存行,一个保存列
- 看代码吧
核心代码:
java
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length; // 总行数
int n = matrix[0].length; // 总列数
// boolean数组默认初始值是false
boolean[] row = new boolean[m]; // row[i]为true表示matrix的第i行需要填0
boolean[] col = new boolean[n]; // col[i]为true表示matrix的第i列需要填0
for (int i = 0; i < m; ++i){
for (int j = 0; j < n; ++j){
if (matrix[i][j] == 0){
row[i] = true;
col[j] = true;
}
}
}
// 将matrix的行填充0
for (int i = 0; i < m; ++i){
if (row[i] == true){
for (int j = 0; j < n; j++){
matrix[i][j] = 0;
}
}
}
// 将matrix的列填充0
for (int i = 0; i < n; ++i){
if (col[i] == true){
for (int j = 0; j < m; j++){
matrix[j][i] = 0;
}
}
}
}
}
6.2 螺旋矩阵
题目描述:
- 给你一个
m行n列的矩阵matrix,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
力扣链接:
解题思路:
核心代码:
java
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
if(matrix.length == 0)
return new ArrayList<Integer>();
// l表示左边界索引,r表示右边届索引,t表示最上索引,b表示最下索引
int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1;
int x = 0;
Integer[] res = new Integer[(r+1)*(b+1)];
while(true){
for (int i = l; i <= r; i++) res[x++] = matrix[t][i]; // left to right
if (++t > b) break;
for (int i = t; i <=b; i++) res[x++] = matrix[i][r]; // top to bottom
if (l > --r) break;
for (int i = r; i >= l; i--) res[x++] = matrix[b][i]; // right to left
if (t > --b) break;
for (int i = b; i >=t; i--) res[x++] = matrix[i][l]; // bottom to top
if (++l > r) break;
}
return Arrays.asList(res);
}
}
6.3 旋转图像
题目描述:
-
给定一个 n × n 的二维矩阵
matrix表示一个图像。请你将图像顺时针旋转 90 度。 -
你必须在**原地** 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
力扣链接:
https://leetcode.cn/problems/rotate-image/description/【中等】
解题思路:
- 先转置
- 再沿对称轴交换

核心代码:
java
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// Step1: 转置矩阵
for (int i = 0; i < n; ++i){
for (int j = i+1; j < n; ++j){
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// Step 2: 每一行反转(左右翻转)
for (int i = 0; i < n; i++) {
for (int j = 0; j < n / 2; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = temp;
}
}
}
}
6.4 搜索二维矩阵
题目描述:
编写一个高效的算法来搜索 *m* x *n* 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
力扣链接:
https://leetcode.cn/problems/search-a-2d-matrix-ii/description【中等】
解题思路:
- 将矩阵逆时针旋转45度,变成一个图
- 二叉搜索树

核心代码:
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length; // 行数
int n = matrix[0].length; // 列数
int row = 0;
int col = n - 1; // 从右上角开始
while (row < m && col >= 0) {
int cur = matrix[row][col];
if (cur == target) {
return true;
} else if (cur > target) {
col--; // 向左移一列
} else {
row++; // 向下移一行
}
}
return false;
}
}
7. 链表
7.1 相交链表
题目描述:
-
给你两个单链表的头节点
headA和headB,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null。图示两个链表在节点
c1开始相交

力扣链接:
https://leetcode.cn/problems/intersection-of-two-linked-lists/description/【简单】
解题思路:

a-c+x+b=b-c+x+a- 两个指针
A和B分别从链表 A 和 B 的头节点开始遍历:- 如果当前节点不为
null,就向后移动; - 如果到了末尾(即当前节点为
null),就跳到另一个链表的头部继续遍历; - 当两个指针相等时,要么是在相交点 ,要么就是在都为 null 的时候(也就是不相交的情况)。
- 如果当前节点不为
核心代码:
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode A = headA, B = headB;
while(A != B){
A = A != null ? A.next : headB;
B = B !=null ? B.next : headA;
}
return A;
}
}
7.2 反转链表
题目描述:
- 给你单链表的头节点
head,请你反转链表,并返回反转后的链表。

踩坑点:初始化pre和cur结点要正确。
-
核心代码:
java/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode reverseList(ListNode head) { ListNode cur = head, pre = null; while (cur != null){ ListNode temp = cur.next; // 暂存后继节点 cur.next = pre; pre = cur; cur = temp; } return pre; } }
7.3 回文链表
题目描述:
- 给你一个单链表的头节点
head,请你判断该链表是否为回文链表。如果是,返回true;否则,返回false。
力扣链接:
https://leetcode.cn/problems/palindrome-linked-list/description/【简单】
解题思路:
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer> list = new ArrayList<>();
ListNode cur = head;
while(cur != null){
list.add(cur.val);
cur = cur.next;
}
Integer[] arr = list.toArray(new Integer[0]); // 其中new Integer[0]的作用是告诉list集合的返回类型是Integer类型
int l = 0, r = arr.length - 1;
while(l < r){
if (arr[l] == arr[r]){
l++;
r--;
}else{
return false;
}
}
return true;
}
}
7.4 环形链表
题目描述:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
力扣链接:
https://leetcode.cn/problems/linked-list-cycle/description/【简单】
解题思路:
- 快慢指针:若一个链表有环,则快慢指针终会相遇
- 快慢指针初始化:慢指针索引0开始,快指针索引1开始
核心代码:
java
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
// 排除边界值(0个/1一个结点的情况)
if (head == null || head.next == null){
return false;
}
// 快慢指针(>=2个结点的情况)
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
7.5 环形链表||
题目描述:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始 )。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
力扣链接:
https://leetcode.cn/problems/linked-list-cycle-ii/description/【中等】
解题思路:
- 快慢指针两次相遇
- 第一相遇之后,快指针从头开始一步一步走
核心代码:
java
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
// 初始化快慢指针位于head
ListNode fast = head, slow = head;
// 快慢指针第一次相遇
while(true){
if (fast == null || fast.next == null) return null;
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
// 快慢指针第二次相遇
fast = head;
while(slow != fast){
slow = slow.next;
fast = fast.next;
}
return fast;
}
}
7.5 合并两个有序链表
题目描述:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
力扣链接:
https://leetcode.cn/problems/merge-two-sorted-lists/description/【简单】
解题思路:
双指针
核心代码:
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode left = new ListNode();
ListNode cur = left;
while(list1 != null && list2 != null){
if (list1.val < list2.val){
cur.next = list1;
list1 = list1.next;
}else{
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 != null ? list1 : list2;
return left.next;
}
}
7.6 两数相加
题目描述:
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
力扣链接:
https://leetcode.cn/problems/add-two-numbers/description/【中等】
解题思路:
双指针
核心代码:
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = new ListNode();
ListNode cur = head;
int temp = 0;
while(l1 !=null || l2 !=null){
int l1Val;
int l2Val;
if (l1 == null) {
l1Val = 0;
}else{
l1Val = l1.val;
l1 = l1.next;
}
if (l2 == null) {
l2Val = 0;
}else{
l2Val = l2.val;
l2 = l2.next;
}
int total = l1Val + l2Val + temp;
temp = total / 10;
int val = total % 10;
ListNode newNode = new ListNode(val);
cur.next = newNode;
cur = cur.next;
}
if (temp != 0){
ListNode newNode = new ListNode(temp);
cur.next = newNode;
cur = cur.next;
}
return head.next;
}
}
未完待续。。。