# 前言
下面我们开始第一课啦。
在每一节的内容中我将会先讲解一些基本的知识点,然后会提出一些经典题型帮助大家入门。
在这个过程中我也会不断迭代我创作内容的形式和方法,和大家一起探索,如何可以简单,轻松上手入门数据结构与算法。
# 数组基础知识
-
数组的定义我们并不陌生,它包括一个索引来指定数组中元素的位置,在常见的编程语言
JavaScript,Java中我们的索引都是从0开始的。也就是说a[3]对应的是数组中的第四个元素。 -
数组的特点是
支持随机访问。也就是说我们可以使用数组的索引来访问固定位置的元素,比如 a[0]表示访问数组第一个元素。这一个特点使得数组在提高时间复杂度的时候会有很巧妙的作用,我们接下来会提到这个用法。 -
数组的关键是,
索引和寻址。也就是我们如何去管理数组的索引值,有的时候索引值使用的巧妙,会为我们巧妙的解决问题提供帮助,下面的例题讲解部分,大家会有体会。 -
数组的基本操作包括以及对应的时间复杂度
标题 时间复杂度 查询 O(1) 插入 O(n) 删除 O(n) 末尾插入 O(1) 头部插入 O(n) -
变长数组
实现的方法是初始阶段创建一个空数组,分配一个预估值的常数作为数组大小。下面是操作变长数组过程中会遇到的两种情形:
- 在插入元素的情况下,如果空间不够,我们需要
重新申请2倍大小的空间,把数组的内容拷贝到新的空间,释放旧空间 - 在弹出元素的情况下,如果空间利用率不到 25%,则
释放一半的空间
- 在插入元素的情况下,如果空间不够,我们需要
# 实战环节
-
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。 示例 1: 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 输出:[1,2,2,3,5,6] 解释:需要合并 [1,2,3] 和 [2,5,6] 。 合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。 示例 2: 输入:nums1 = [1], m = 1, nums2 = [], n = 0 输出:[1] 解释:需要合并 [1] 和 [] 。 合并结果是 [1] 。 示例 3: 输入:nums1 = [0], m = 0, nums2 = [1], n = 1 输出:[1] 解释:需要合并的数组是 [] 和 [1] 。 合并结果是 [1] 。 注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。 提示: nums1.length == m + n nums2.length == n 0 <= m, n <= 200 1 <= m + n <= 200 -109 <= nums1[i], nums2[j] <= 109 进阶: 你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?这个题目中明确指出了 nums1 的空间是
m + n,是足够承载我们处理后数组的空间大小的。所以我们可以在nums1的空间上完成对应操作。我们可以创建三个索引,分别对应第一个数组的末尾索引,第二个数组的末尾索引,还有最后处理好的数组末尾索引。我们在这个过程中不断的移动处理好数组末尾的指针。最后别忘了处理后面数组剩下的元素。这样的话可以保证我们最后的时间复杂度是 O(1)。class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int tail1 = m - 1, tail2 = n - 1, pointer = m + n - 1; while(tail1 >= 0 && tail2 >= 0) { if(nums1[tail1] > nums2[tail2]) { nums1[pointer] = nums1[tail1]; pointer--; tail1--; } else { nums1[pointer] = nums2[tail2]; pointer--; tail2--; } } while(tail2 >= 0) { nums1[pointer] = nums2[tail2]; pointer--; tail2--; } } } // Time complexity : O(n) , n is the length of the array // Space complexity : O(1) -
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过: 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。 返回 k 。 示例 1: 输入:nums = [1,1,2] 输出:2, nums = [1,2,_] 解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 示例 2: 输入:nums = [0,0,1,1,1,2,2,3,3,4] 输出:5, nums = [0,1,2,3,4] 解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。 提示: 1 <= nums.length <= 3 * 104 -104 <= nums[i] <= 104 nums 已按 非严格递增 排列这个题目的解法我们可以使用经典的
双指针。针对这个题目我们可以创建一个指针始终指向下一个非重复元素,如果我们在遍历的过程中发现有重复元素,那么我们更新这个指针。class Solution { public int removeDuplicates(int[] nums) { if(nums.length == 0) return 0; int i = 0; for(int j = 0; j < nums.length; j++){ if(nums[j] != nums[i]){ i++; nums[i] = nums[j]; } } return i+1; } } // Time complexity : O(n) , n is the length of the array // Space complexity : O(1) -
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums = [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nums = [0] 输出: [0] 提示: 1 <= nums.length <= 104 -231 <= nums[i] <= 231 - 1 进阶:你能尽量减少完成的操作次数吗?这个题的解法我们可以继续使用双指针,其中
left指针指向用来指向下一个非零元素,right指针用来遍历完整数组。最后不要忘记把处理完数组的最后元素都补成0。class Solution { public void moveZeroes(int[] nums) { int left = 0, right = 0; while(right < nums.length) { if(nums[right] != 0) { nums[left] = nums[right]; left++; } right++; } while(left < nums.length) { nums[left] = 0; left++; } } }