数组算法
数组是存放在连续内存空间上的相同类型数据的集合。
需要两点注意的是
数组下标都是从0开始的。
数组内存空间的地址是连续的
正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
数组的元素是不能删的,只能覆盖。
二分查找
只要看到给出的数组是有序数组,都可以想一想是否可以使用二分法
- 暴力解法时间复杂度:O(n)
- 二分法时间复杂度:O(logn)
搜索插入的位置
Leetcode:35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
1 | func searchInsert(nums []int, target int) int { |
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
在排序数组中查找元素的第一个和最后一个位置
X的平方根
Leetcode:69.x 的平方根
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
思路:二分法,由于平方根一定小于它的一半,所以右边界可以减半;其次,由于与插入元素是插入边界右边,所以需要return right + 1
,而平方根要求保留整数部分 ,小数部分将被舍去,所以是return right
1 | func mySqrt(x int) int { |
双指针法
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
复杂度
- 暴力解法时间复杂度:O(n^2)
- 双指针时间复杂度:O(n)
移除元素
Leetcode:27.移除元素
给你一个数组 nums
和一个值 val
,你需要原地移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路:
暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。但是这样的时间复杂度:O(n^2)。
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
1
2
3
4
5
6
7
8
9
10
11func removeElement(nums []int, val int) int {
slow := 0
for fast := 0; fast < len(nums); fast++ {
// 满足条件,就执行更新操作
if nums[fast] != val {
nums[slow] = nums[fast] // 移动元素,往前覆盖
slow++ // 注意此处的slow++是放在上面移动元素之后的,因为是要删除元素,所以先移动元素,再slow++
}
}
return slow
}- 时间复杂度:O(n)
- 空间复杂度:O(1)
删除有序数组中的重复项
Leetcode:26.删除有序数组中的重复项
给你一个有序数组 nums ,请你原地删除重复出现的元素,使每个元素 *只出现一次* ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在原地*修改输入数组* 并在使用 O(1) 额外空间的条件下完成。
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
思路:双指针法,与移除元素题目不同,此处的slow++
需要在移动元素代码(nums[slow] = nums[fast]
)之前执行,因为是删除重复项,所以slow++
后相同元素就保留下来一个。
1 | func removeDuplicates(nums []int) int { |
移动零
Leetcode:283.移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
1 | 输入: nums = [0,1,0,3,12] |
示例 2:
1 | 输入: nums = [0] |
思路:快指针找到不为0的值,然后与指向0值的慢指针交换,让0后移,再移动慢指针。
1 | func moveZeroes(nums []int) { |
有序数组的平方
Leetcode:977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
1 | 输入:nums = [-4,-1,0,3,10] |
思路:
每个数平方之后,排个序,这个时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度
双指针法:数组其实是有序的, 只不过负数平方之后可能成为最大数了。那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
如果
A[i] * A[i] < A[j] * A[j]
那么result[k--] = A[j] * A[j];
。如果
A[i] * A[i] >= A[j] * A[j]
那么result[k--] = A[i] * A[i];
。
1 | func sortedSquares(nums []int) []int { |
此时的时间复杂度为O(n),相对于暴力排序的解法O(n + nlog n)还是提升不少的。
模拟栈
匹配问题都是模拟栈的强项
比较含退格的字符串
Leetcode:844.比较含退格的字符串
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true
。#
代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
示例 1:
1 | 输入:s = "ab#c", t = "ad#c" |
思路:模拟栈方法,如果字符不为#,就把字符添加到栈里,如果字符为#,并且栈不为空,则弹出栈中的字符(相当于消除字符),最后对比两个栈是否相等。
1 | func backspaceCompare(s string, t string) bool { |
时间复杂度为O(n)
空间复杂度:由于使用了额外的空间模拟栈,所以为O(n)
滑动窗口
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。
- 暴力解法时间复杂度:O(n^2)
- 滑动窗口时间复杂度:O(n)
模版:
1 | // 首先明确一点(left, right]为所求区间 |
长度最小的子数组
Leetcode:209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
输入:target = 4, nums = [1,4,4]
输出:1
思路:在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
定义两个指针,一个快指针代表着滑动窗口的右边界,慢指针代表着左边界,先增大右边界,然后再缩小左边界。
1 | func minSubArrayLen(target int, nums []int) int { |
- 时间复杂度:O(n)
- 空间复杂度:O(1)
不要以为for里放一个while就以为是O(n^2), 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
最小覆盖子串
Leetcode:76.最小覆盖子串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
1 | 输入:s = "ADOBECODEBANC", t = "ABC" |
思路:左右窗口都是从0开始,先移动右窗口,待窗口包含整个字符串t
后,再移动左窗口,逐个缩小范围,获取最小子串结果;过程中需要用到两个map遍历,分别保存字符t
和保存窗口中字符。
1 | func minWindow(s string, t string) string { |
字符串的排列
Leetcode:567.字符串的排列
给你两个字符串 s1
和 s2
,写一个函数来判断 s2
是否包含 s1
的排列。如果是,返回 true
;否则,返回 false
。
换句话说,s1
的排列之一是 s2
的 子串 。
示例 1:
1 | 输入:s1 = "ab" s2 = "eidbaooo" |
思路:由于只需要判断s2
中是否包含s1
的排列,所以窗口中的个数不能大于s1
的个数,用right - left >= len(s1)
作为移动左指针的条件。
1 | func checkInclusion(s1 string, s2 string) bool { |
无重复字符的最长子串
Leetcode:3.无重复字符的最长子串
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
1 | 输入: s = "abcabcbb" |
思路:1、右指针右移;2、根据题意收缩窗口;3、左指针右移更新窗口;4、缩完左窗口后根据题意计算结果
1 | func lengthOfLongestSubstring(s string) int { |