目录
- 前言
- 数组
- 字符串
- 哈希表
- 链表
- 二叉树
- 17. 144. 二叉树前序遍历 (Binary Tree Preorder Traversal) - Easy
- 18. 145. 二叉树后序遍历 (Binary Tree Postorder Traversal)
- 19. 146. 二叉树中序遍历 (Binary Tree Inorder Traversal)
- 20. 102. 二叉树层序遍历 (Binary Tree Level Order Traversal) - Medium
- 21. 226. 翻转二叉树 (Invert Binary Tree) - Easy
- 22. 101. 对称二叉树 (Symmetric Tree) - Easy
- 23. 104. 二叉树的最大深度 (Maximum Depth of Binary Tree) - Easy
- 24. 111. 二叉树的最小深度 (Minimum Depth of Binary Tree) - Easy
- 25. 257. 二叉树的所有路径 (Binary Tree Paths) - Medium
- 26. 112. 路径总和 (Path Sum) - Easy
- 27. 113. 路径总和II (Path Sum II) - Medium
前言
本文参考力扣官网、以及该文章:https://zhuanlan.zhihu.com/p/632616380
本套题目涵盖了数组、字符串、哈希表、链表、二叉树等常见数据结构,以及二分查找、滑动窗口、深度优先搜索(DFS)、广度优先搜索(BFS)等经典算法。通过这些题目的学习,您将不仅掌握算法的基本原理,还能在实际编程中运用自如。
特别推荐小白读者尝试这些题目,因为它们将从零开始,引导您逐步深入算法的世界。相信学完这些题目后,您会对算法有豁然开朗的感觉,不仅能够在面试中应对自如,还能在未来的工作中更加得心应手。
数组
数组(Array)是一种用于存储多个相同类型元素的数据结构。在编程中,数组提供了一种便捷的方式来组织和访问大量数据。数组通常具有固定大小,每个元素在数组中都有一个唯一的索引,通过索引可以快速定位和访问数组中的元素。
1. 704. 二分查找 (Binary Search) - Easy
public class BinarySearch {
public int search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
}
解析:
这段代码实现了经典的二分查找算法。下面是对这段代码的解析:
-
BinarySearch 类:这是一个 Java 类,用于实现二分查找算法。
-
search 方法:search 方法接受两个参数,一个是整型数组 nums,另一个是要查找的目标值 target。该方法返回目标值在数组中的索引,如果不存在则返回 -1。
-
left 和 right 变量:这两个变量分别表示当前查找范围的左右边界。初始时,left 指向数组第一个元素的索引 0,right 指向数组最后一个元素的索引 nums.length - 1。
-
while 循环:利用 while 循环进行二分查找,当 left 小于等于 right 时,继续查找。
-
mid 变量:计算中间位置 mid,通过 (left + right) / 2 或者 left + (right - left) / 2 这种方式避免整数溢出。
-
比较 nums[mid] 与 target:
- 如果 nums[mid] 等于 target,则找到目标值,返回 mid。
- 如果 nums[mid] 小于 target,则说明目标值在右侧,更新 left 为 mid + 1。
- 如果 nums[mid] 大于 target,则说明目标值在左侧,更新 right 为 mid - 1。
-
循环直到 left 大于 right,表示查找范围为空,此时返回 -1,表示未找到目标值。
这段代码实现了简洁高效的二分查找算法,时间复杂度为 O(log n),适用于有序数组的查找。在实际应用中,二分查找是一种常用且重要的算法,能够快速地定位目标值所在的位置,提高查找效率。
2. 27. 移除数组元素 (Remove Element) - Easy
public class RemoveElement {
public int removeElement(int[] nums, int val) {
int i = 0;
for (int j = 0; j < nums.length; j++) {
if (nums[j] != val) {
nums[i] = nums[j];
i++;
}
}
return i;
}
}
解析:
这段代码实现了一个移除数组元素的函数,用于移除整型数组中指定值 val 的元素。下面是对这段代码的解析:
-
RemoveElement 类:这是一个 Java 类,用于实现移除数组元素的功能。
-
removeElement 方法:removeElement 方法接受两个参数,一个是整型数组 nums,另一个是要移除的目标值 val。该方法返回移除目标值后的新数组长度。
-
i 变量:i 用于记录新数组的索引位置,初始值为 0。
-
for 循环:遍历整型数组 nums。
-
j 变量:循环变量 j,用于遍历原始数组 nums。
-
条件判断:如果 nums[j] 不等于目标值 val,则将 nums[j] 的值复制到新数组位置 i,并将 i 自增 1。
-
返回结果:最终返回新数组的长度 i,即移除目标值后的有效元素个数。
通过这段代码,我们可以看到移除元素的思路是通过双指针(i 和 j)进行处理,当遇到不等于目标值的元素时,将其放入新数组中,并维护新数组的长度。这种方法不需要重新创建数组,能够在原地操作,实现了移除元素的功能。这样的算法时间复杂度为 O(n),其中 n 为数组的长度。如果有任何其他问题或疑问,请随时提出。
3. 977.有序数组的平方(easy)
public class SortedSquares {
public int[] sortedSquares(int[] nums) {
int[] result = new int[nums.length];
int i = 0, j = nums.length - 1, k = nums.length - 1;
while (i <= j) {
if (Math.abs(nums[i]) > Math.abs(nums[j])) {
result[k--] = nums[i] * nums[i];
i++;
} else {
result[k--] = nums[j] * nums[j];
j--;
}
}
return result;
}
}
这段代码实现了一个函数,用于对给定整数数组中的每个元素进行平方操作,并按非递减顺序(即从小到大)排序。下面是对这段代码的解析:
-
SortedSquares 类:这是一个 Java 类,用于实现对整数数组中每个元素进行平方并排序的功能。
-
sortedSquares 方法:sortedSquares 方法接受一个整型数组 nums 作为参数,并返回一个新的经过平方且排序后的整型数组。
-
result 数组:首先创建一个与原始数组相同长度的新数组 result,用于存储平方后的结果。
-
i、j 和 k 变量:分别表示三个指针,i 指向原数组的起始位置,j 指向原数组的末尾位置,k 用于在 result 数组中逆序存储平方值。
-
while 循环:通过 while 循环来遍历原始数组,直到 i 大于 j。
-
条件判断:比较 nums[i] 的绝对值和 nums[j] 的绝对值大小,如果 nums[i] 的绝对值大,则将 nums[i] 的平方值存入 result 数组,同时 i 自增;否则将 nums[j] 的平方值存入 result 数组,同时 j 自减。
-
返回结果:最终返回经过平方且排序后的 result 数组。
这段代码利用双指针的方法,通过比较原数组中两端元素的绝对值大小,将较大的平方值依次存入新数组中,并实现了非递减排序。这样的算法时间复杂度为 O(n),其中 n 为数组的长度。
比较 nums[i] 的绝对值和 nums[j] 的绝对值大小的原因是为了实现最终结果数组的非递减排序。通过比较绝对值大小,可以确保在平方后,较大的数值会排在数组的末尾,从而实现最终结果的非递减排序。
4. 209.长度最小的子数组 (Minimum Size Subarray Sum) - Medium
描述:
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
如: 输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
public class MinSizeSubarraySum {
public int minSubArrayLen(int s, int[] nums) {
int n = nums.length;
int minLen = Integer.MAX_VALUE;
int left = 0, sum = 0;
for (int i = 0; i < n; i++) {
sum += nums[i];
while (sum >= s) {
minLen = Math.min(minLen, i - left + 1);
sum -= nums[left++];
}
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
}
这段代码是解决一个名为"长度最小的子数组"(Minimum Size Subarray Sum)的问题,该问题属于中等难度。下面是对代码的解释:
minSubArrayLen
方法接收两个参数:整数s
和整型数组nums
。- 首先,初始化变量
n
表示数组nums
的长度,minLen
初始化为整型最大值Integer.MAX_VALUE
,left
初始化为 0,sum
初始化为 0。 - 接下来,使用一个
for
循环遍历数组nums
:- 在循环中,将当前元素加到
sum
中。 - 然后,使用一个
while
循环,当sum
大于等于目标值s
时,进入循环:- 更新
minLen
为当前最小长度(i - left + 1
)和minLen
之间的较小值。 - 将窗口左边界向右移动一位,也就是减去
nums[left]
的值,并更新left
变量。
- 更新
- 在循环中,将当前元素加到
- 最终返回
minLen
的值,如果没有找到符合条件的子数组,则返回 0。
这段代码的核心思想是使用滑动窗口的方法,通过维护一个窗口来寻找符合条件的子数组,其中子数组的和大于等于目标值 s
,并且长度最小。在每次循环中,不断调整窗口的大小,更新最小长度,直到遍历完整个数组。
这种方法的时间复杂度为 O(n),其中 n 是数组 nums
的长度,因为我们只需要遍历数组一次。
滑动窗口
滑动窗口算法(Sliding Window Algorithm)是一种用于解决数组或字符串中子数组或子串问题的有效技巧。
该算法通过维护一个窗口来处理连续的元素序列,可以在 O(n) 的时间复杂度内解决很多子数组/子串问题。
基本思想:
- 初始化两个指针,通常是起始指针和结束指针,表示窗口的左右边界。
- 移动结束指针扩大窗口,直到找到符合条件的子数组/子串,同时记录最小或最大值,或者进行其他操作。
- 一旦找到符合条件的窗口,移动起始指针缩小窗口,直到不满足条件为止,然后再移动结束指针。
- 不断重复步骤 2 和步骤 3,直到遍历完整个数组或字符串。
滑动窗口算法适用于解决以下类型的问题:
- 求子数组/子串的最大值、最小值、平均值等
- 求满足某些条件的子数组/子串(如和等于目标值、长度符合要求等)
- 寻找某种特定子数组/子串(如连续元素唯一、所有元素都满足某种性质等)
总的来说,滑动窗口算法是一种非常高效的解决子数组/子串问题的方法,对于需要处理连续元素序列的算法问题非常有用。
滑动窗口其实也是双指针的一种,需要重点把握的是 窗口内是什么? 窗口起始位置如何移动? 窗口结束位置如何移动? 搞清楚这三个问题基本上就能拿下滑动窗口 滑动窗口有着自己的套路
非常正确!滑动窗口算法的关键在于理解和把握好窗口的定义、窗口起始位置的移动以及窗口结束位置的移动。下面简要说明一下这三个问题:
-
窗口内是什么?
- 窗口是一个区间,通常用两个指针表示。窗口内包含了需要处理的元素子集。
- 在最小子数组和问题中,窗口内的元素是满足条件(如和大于等于目标值)的子数组。
-
窗口起始位置如何移动?
- 窗口起始位置的移动是根据当前窗口内的情况来决定的。
- 在最小子数组和问题中,当窗口内的和大于等于目标值时,我们可以尝试缩小窗口大小,即移动起始位置,以找到更小的子数组。
-
窗口结束位置如何移动?
- 窗口结束位置的移动也是根据当前窗口内的情况来决定的。
- 在最小子数组和问题中,当窗口内的和小于目标值时,我们需要扩大窗口大小,即移动结束位置,以尝试找到更多符合条件的元素。
通过合理地调整窗口的起始位置和结束位置,我们可以在遍历过程中不断更新窗口内的元素,从而解决各种涉及连续子数组的问题,如求和、乘积、最大值、最小值等。
掌握好这些基本概念和套路,对理解和应用滑动窗口算法是非常有帮助的。
详细解析
输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组
。
上述代码是用来解决「长度最小的子数组」问题,也就是找到数组中和大于等于给定目标值 s 的最短连续子数组的长度。让我们代入例子 s = 7,nums = [2, 3, 1, 2, 4, 3],并一步一步进行代码执行过程的讲解:
-
初始化参数:
- n = 6 (数组 nums 的长度)
- minLen = Integer.MAX_VALUE (用于记录找到的最小子数组的长度,初始为整型最大值)
- left = 0 (窗口的左边界)
- sum = 0 (窗口内元素的和)
-
开始遍历数组:
-
当 i = 0 时,nums[0] = 2。更新 sum = 2。
- 进入 while 循环,因为 sum = 2 < 7,不满足条件,继续循环。
-
当 i = 1 时,nums[1] = 3。更新 sum = 5。
- 进入 while 循环,因为 sum = 5 < 7,不满足条件,继续循环。
-
当 i = 2 时,nums[2] = 1。更新 sum = 6。
- 进入 while 循环,因为 sum = 6 < 7,不满足条件,继续循环。
-
当 i = 3 时,nums[3] = 2。更新 sum = 8。
- 进入 while 循环,因为 sum = 8 >= 7,满足条件。计算当前子数组的长度为 i - left + 1 = 3,更新 minLen = 3。
- 缩小窗口:sum -= nums[left],sum = 8 - 2 = 6,left 右移一位。
-
继续遍历数组。
-
…
-
-
返回结果:
- 遍历完成后,返回 minLen = 2,即找到的长度最小的子数组的长度为 2。
通过这个代码示例和讲解,希望你能更好地理解滑动窋口算法在解决子数组问题中的应用。如果有任何疑问或需要进一步解释,请随时告诉我。