写在前文:由于LeetCode100的答案多种多样,本文针对每一题只写一种解法(尽可能简单易懂)。针对不容易理解的,采用图片形式。
1. 哈希
1.1 两数之和
题目描述:
-
给定一个整数数组
nums
和一个整数目标值target
,请你在该数组中找出 和为目标值target
的那 两个 整数,并返回它们的数组下标。 -
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
-
你可以按任意顺序返回答案。
力扣链接:
https://leetcode.cn/problems/two-sum/description/【简单】
解题思路:
-
实例化一个
HashMap
来保存<值, 索引>
-
遍历HashMap,找到就返回索引下标,找不到就添加元素
核心代码:
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
核心代码:
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,去除重复元素
- 从序列的最小值开始找,更新最大值
核心代码:
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++
-
-
核心代码:
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/【中等】
解题思路:
- 初始化左,右指针,最大面积。
- 计算当前面积, 更新最大面积,短指针向中间移动
核心代码:
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【中等】
解题思路:
- 三数之和变成两数之和
- 避免重复解
- 直接看代码+注释,理解背下来吧
核心代码:
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]
注意:本题采用的是动态规划解法。。。理解不了就背这个图。
核心代码
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保存不含重复字符子串
核心代码:
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比较两个数组是否相同(索引对应的值全部一样)
- 本质:空间换时间
核心代码:
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;
}
}
未完待续。。。