【金三银四】27道基础算法题,学完让你对算法有豁然开朗的感觉(推荐小白)

目录

前言

本文参考力扣官网、以及该文章: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;
   }
}

解析:
这段代码实现了经典的二分查找算法。下面是对这段代码的解析:

  1. BinarySearch 类:这是一个 Java 类,用于实现二分查找算法。

  2. search 方法:search 方法接受两个参数,一个是整型数组 nums,另一个是要查找的目标值 target。该方法返回目标值在数组中的索引,如果不存在则返回 -1。

  3. left 和 right 变量:这两个变量分别表示当前查找范围的左右边界。初始时,left 指向数组第一个元素的索引 0,right 指向数组最后一个元素的索引 nums.length - 1。

  4. while 循环:利用 while 循环进行二分查找,当 left 小于等于 right 时,继续查找。

  5. mid 变量:计算中间位置 mid,通过 (left + right) / 2 或者 left + (right - left) / 2 这种方式避免整数溢出。

  6. 比较 nums[mid] 与 target

    • 如果 nums[mid] 等于 target,则找到目标值,返回 mid。
    • 如果 nums[mid] 小于 target,则说明目标值在右侧,更新 left 为 mid + 1。
    • 如果 nums[mid] 大于 target,则说明目标值在左侧,更新 right 为 mid - 1。
  7. 循环直到 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 的元素。下面是对这段代码的解析:

  1. RemoveElement 类:这是一个 Java 类,用于实现移除数组元素的功能。

  2. removeElement 方法:removeElement 方法接受两个参数,一个是整型数组 nums,另一个是要移除的目标值 val。该方法返回移除目标值后的新数组长度。

  3. i 变量:i 用于记录新数组的索引位置,初始值为 0。

  4. for 循环:遍历整型数组 nums。

  5. j 变量:循环变量 j,用于遍历原始数组 nums。

  6. 条件判断:如果 nums[j] 不等于目标值 val,则将 nums[j] 的值复制到新数组位置 i,并将 i 自增 1。

  7. 返回结果:最终返回新数组的长度 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;
   }
}

这段代码实现了一个函数,用于对给定整数数组中的每个元素进行平方操作,并按非递减顺序(即从小到大)排序。下面是对这段代码的解析:

  1. SortedSquares 类:这是一个 Java 类,用于实现对整数数组中每个元素进行平方并排序的功能。

  2. sortedSquares 方法:sortedSquares 方法接受一个整型数组 nums 作为参数,并返回一个新的经过平方且排序后的整型数组。

  3. result 数组:首先创建一个与原始数组相同长度的新数组 result,用于存储平方后的结果。

  4. i、j 和 k 变量:分别表示三个指针,i 指向原数组的起始位置,j 指向原数组的末尾位置,k 用于在 result 数组中逆序存储平方值。

  5. while 循环:通过 while 循环来遍历原始数组,直到 i 大于 j。

  6. 条件判断:比较 nums[i] 的绝对值和 nums[j] 的绝对值大小,如果 nums[i] 的绝对值大,则将 nums[i] 的平方值存入 result 数组,同时 i 自增;否则将 nums[j] 的平方值存入 result 数组,同时 j 自减。

  7. 返回结果:最终返回经过平方且排序后的 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)的问题,该问题属于中等难度。下面是对代码的解释:

  1. minSubArrayLen 方法接收两个参数:整数 s 和整型数组 nums
  2. 首先,初始化变量 n 表示数组 nums 的长度,minLen 初始化为整型最大值 Integer.MAX_VALUEleft 初始化为 0,sum 初始化为 0。
  3. 接下来,使用一个 for 循环遍历数组 nums
    • 在循环中,将当前元素加到 sum 中。
    • 然后,使用一个 while 循环,当 sum 大于等于目标值 s 时,进入循环:
      • 更新 minLen 为当前最小长度(i - left + 1)和 minLen 之间的较小值。
      • 将窗口左边界向右移动一位,也就是减去 nums[left] 的值,并更新 left 变量。
  4. 最终返回 minLen 的值,如果没有找到符合条件的子数组,则返回 0。

这段代码的核心思想是使用滑动窗口的方法,通过维护一个窗口来寻找符合条件的子数组,其中子数组的和大于等于目标值 s,并且长度最小。在每次循环中,不断调整窗口的大小,更新最小长度,直到遍历完整个数组。

这种方法的时间复杂度为 O(n),其中 n 是数组 nums 的长度,因为我们只需要遍历数组一次。

滑动窗口

滑动窗口算法(Sliding Window Algorithm)是一种用于解决数组或字符串中子数组或子串问题的有效技巧。

该算法通过维护一个窗口来处理连续的元素序列,可以在 O(n) 的时间复杂度内解决很多子数组/子串问题。

基本思想:

  1. 初始化两个指针,通常是起始指针和结束指针,表示窗口的左右边界。
  2. 移动结束指针扩大窗口,直到找到符合条件的子数组/子串,同时记录最小或最大值,或者进行其他操作。
  3. 一旦找到符合条件的窗口,移动起始指针缩小窗口,直到不满足条件为止,然后再移动结束指针。
  4. 不断重复步骤 2 和步骤 3,直到遍历完整个数组或字符串。

滑动窗口算法适用于解决以下类型的问题:

  • 求子数组/子串的最大值、最小值、平均值等
  • 求满足某些条件的子数组/子串(如和等于目标值、长度符合要求等)
  • 寻找某种特定子数组/子串(如连续元素唯一、所有元素都满足某种性质等)

总的来说,滑动窗口算法是一种非常高效的解决子数组/子串问题的方法,对于需要处理连续元素序列的算法问题非常有用。

滑动窗口其实也是双指针的一种,需要重点把握的是 窗口内是什么? 窗口起始位置如何移动? 窗口结束位置如何移动? 搞清楚这三个问题基本上就能拿下滑动窗口 滑动窗口有着自己的套路

非常正确!滑动窗口算法的关键在于理解和把握好窗口的定义、窗口起始位置的移动以及窗口结束位置的移动。下面简要说明一下这三个问题:

  1. 窗口内是什么?

    • 窗口是一个区间,通常用两个指针表示。窗口内包含了需要处理的元素子集。
    • 在最小子数组和问题中,窗口内的元素是满足条件(如和大于等于目标值)的子数组。
  2. 窗口起始位置如何移动?

    • 窗口起始位置的移动是根据当前窗口内的情况来决定的。
    • 在最小子数组和问题中,当窗口内的和大于等于目标值时,我们可以尝试缩小窗口大小,即移动起始位置,以找到更小的子数组。
  3. 窗口结束位置如何移动?

    • 窗口结束位置的移动也是根据当前窗口内的情况来决定的。
    • 在最小子数组和问题中,当窗口内的和小于目标值时,我们需要扩大窗口大小,即移动结束位置,以尝试找到更多符合条件的元素。

通过合理地调整窗口的起始位置和结束位置,我们可以在遍历过程中不断更新窗口内的元素,从而解决各种涉及连续子数组的问题,如求和、乘积、最大值、最小值等。

掌握好这些基本概念和套路,对理解和应用滑动窗口算法是非常有帮助的。

详细解析

输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组

上述代码是用来解决「长度最小的子数组」问题,也就是找到数组中和大于等于给定目标值 s 的最短连续子数组的长度。让我们代入例子 s = 7,nums = [2, 3, 1, 2, 4, 3],并一步一步进行代码执行过程的讲解:

  1. 初始化参数

    • n = 6 (数组 nums 的长度)
    • minLen = Integer.MAX_VALUE (用于记录找到的最小子数组的长度,初始为整型最大值)
    • left = 0 (窗口的左边界)
    • sum = 0 (窗口内元素的和)
  2. 开始遍历数组

    • 当 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 右移一位。
    • 继续遍历数组。

  3. 返回结果

    • 遍历完成后,返回 minLen = 2,即找到的长度最小的子数组的长度为 2。

通过这个代码示例和讲解,希望你能更好地理解滑动窋口算法在解决子数组问题中的应用。如果有任何疑问或需要进一步解释,请随时告诉我。

字符串

5. 344. 反转字符串 (Reverse String) - Easy

6. 541. 反转字符串II (Reverse String II)

7. 剑指 Offer 05. 替换空格 (Replace Spaces)

哈希表

8. 1. 两数之和 (Two Sum) - Easy

9. 242. 有效的字母异位数 (Valid Anagram) - Easy

10. 349. 两个数组的交集 (Intersection of Two Arrays) - Easy

11. 383. 赎金信 (Ransom Note)

链表

12. 203. 移除链表元素 (Remove Linked List Elements) - Easy

13. 206. 反转单链表 (Reverse Linked List) - Easy

14. 24. 两两交换链表中的元素 (Swap Nodes in Pairs) - Medium

15. 19. 删除链表倒数第N个节点 (Remove Nth Node From End of List) - Easy

16. 142. 环形链表 (Linked List Cycle) - Medium

二叉树

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

  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值