双指针法
双指针法(快慢指针法)
: 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。
27. 移除元素
给你一个数组 nums
和一个值 val
,你需要 原地
移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3
, 函数应该返回新的长度 2
, 并且 nums
中的前两个元素均为 2
。 你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2
, 函数应该返回新的长度 5
, 并且 nums
中的前五个元素为 0, 1, 3, 0, 4
。
你不需要考虑数组中超出新长度后面的元素。
思路:使用快慢指针,fast
用于遍历整个数组,判断指向的数值是否是val
,slow
用于记录数值不是val
的元素的个数,也方便在此处改变原nums
数组中元素的值。
定义快慢指针
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
慢指针:指向更新 新数组下标的位置
Go代码
func removeElement(nums []int, val int) int {
//思路:使用快慢指针,fast用于遍历整个数组,判断指向的数值是否是val
//slow用于记录数值不是val的元素的个数,也方便在此处改变原nums数组中元素的值
if len(nums) == 0 {
return -1
}
slow := 0
for _,num:= range nums {
if num == val { // 当前值是val就跳过
continue
}
nums[slow] = num // 当前值不是val,就放在慢指针的位置,相当于后续元素前移
slow++
}
return slow
}
26. 删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums
,请你 原地
删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
更改数组 nums
,使 nums
的前 k
个元素包含唯一元素,并按照它们最初在 nums
中出现的顺序排列。nums
的其余元素与 nums
的大小不重要。返回 k
。
判题标准:
系统会用下面的代码来测试你的题解:
int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案
int k = removeDuplicates(nums); // 调用
assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
assert nums[i] == expectedNums[i];
}
如果所有断言都通过,那么您的题解将被 通过。
示例 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 < = n u m s . l e n g t h < = 3 ∗ 1 0 4 1 <= nums.length <= 3 * 10^4 1<=nums.length<=3∗104
- − 1 0 4 < = n u m s [ i ] < = 1 0 4 -10^4 <= nums[i] <= 10^4 −104<=nums[i]<=104
- n u m s 已按非严格递增排列 nums 已按 非严格递增 排列 nums已按非严格递增排列
思路: 快慢指针,从第二个数开始,只要与前一个数相等,则可以跳过他,快指针移动一步,不相等时,放于慢指针的位置,慢指针移动一步。
func removeDuplicates(nums []int) int {
if len(nums) == 0 {
return -1
}
if len(nums) == 1 {
return 1
}
// 简单遍历一遍,遇到重复的则跳过,遇到不重复的则放到数组开始出现重复时的下标处即可
slow,fast := 1,1 // 第一个不重复的元素是从索引1开始的
for i := fast;i < len(nums);i++ {
if nums[i] == nums[i - 1] {
continue
}
nums[slow] = nums[i]
slow++
}
return slow
}
283. 移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
提示:
1
<
=
n
u
m
s
.
l
e
n
g
t
h
<
=
1
0
4
1 <= nums.length <= 10^4
1<=nums.length<=104
−
2
31
<
=
n
u
m
s
[
i
]
<
=
2
31
−
1
-2^{31} <= nums[i] <= 2^{31} - 1
−231<=nums[i]<=231−1
进阶:你能尽量减少完成的操作次数吗?
思路:实际所有非0
数移动到前面之后,后续的索引位置都赋值为0
就相当于将0
移到了末尾
Go代码
func moveZeroes(nums []int) {
// 因为要求原地操作,又要将0移到末尾
// 实际所有非0数移动到前面之后,后续的索引位置都赋值为0就相当于将0移到了末尾
if len(nums) == 0 {
return
}
slow ,fast := 0,0
for i := fast ;i < len(nums);i++ {
if nums[i] == 0 {
continue //i进入下轮循环i++,实际上,i充当的就是快指针
}
nums[slow] = nums[i]
slow++
}
// 后续的位置置为0
for i:= slow;i < len(nums);i++ {
nums[i] = 0
}
return
}
844. 比较含退格的字符串
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true
。# 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
示例 1:
输入:s = "ab#c", t = "ad#c"
输出:true
解释:s 和 t 都会变成 "ac"。
示例 2:
输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 ""。
示例 3:
输入:s = "a#c", t = "b"
输出:false
解释:s 会变成 "c",但 t 仍然是 "b"。
提示:
- 1 < = s . l e n g t h , t . l e n g t h < = 200 1 <= s.length, t.length <= 200 1<=s.length,t.length<=200
- s 和 t 只含有小写字母以及字符 ‘#’
进阶:
你可以用 O ( n ) O(n) O(n) 的时间复杂度和 O ( 1 ) O(1) O(1) 的空间复杂度解决该问题吗?
思路:主要是要注意不要索引越界
Go代码
func backspaceCompare(s string, t string) bool {
// 分别遍历两个数组,如果遇到#,则慢指针回退一步,否则将当前数字放于慢指针处,最后比较两个两个字符串是否相等
if len(s) == 0 && len(t) == 0 {
return true
}
sb,tb := []byte(s),[]byte(t)
slow,slow2,fast := 0,0,0
for i := fast ;i < len(sb);i++{
if sb[i] == '#' {
if slow > 0 { // 注意slow不要越界,因为可能第一个字符就是#,或出现的#比已经出现的字符更多
slow--
}
continue // 可以遇到多个#,此时slow已经是0,无需递减可直接continue
}
sb[slow] = sb[i]
slow++
}
sbRes := string(sb[0:slow])
fmt.Println(sbRes)
for i := fast ;i < len(tb);i++{
if tb[i] == '#' {
if slow2 > 0 { // 注意slow2不要越界,因为可能第一个字符就是#,或出现的#比已经出现的字符更多
slow2--
}
continue // 可以遇到多个#,此时slow2已经是0,无需递减可直接continue
}
tb[slow2] = tb[i]
slow2++
}
tbRes := string(tb[0:slow2])
fmt.Println(tbRes)
if sbRes == tbRes {
return true
}
return false
}
方法二:使用栈来解
最容易想到的方法是将给定的字符串中的退格符和应当被删除的字符都去除,还原给定字符串的一般形式。然后直接比较两字符串是否相等即可。
具体地,我们用栈处理遍历过程,每次我们遍历到一个字符:
-
如果它是退格符,那么我们将栈顶弹出;
-
如果它是普通字符,那么我们将其压入栈中。
Go代码
func build(str string) string {
s := []byte{}
for i := range str {
if str[i] != '#' {
s = append(s, str[i])
} else if len(s) > 0 {
s = s[:len(s)-1]
}
}
return string(s)
}
func backspaceCompare(s, t string) bool {
return build(s) == build(t)
}
977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
提示:
- 1 < = n u m s . l e n g t h < = 1 0 4 1 <= nums.length <= 10^4 1<=nums.length<=104
- − 1 0 4 < = n u m s [ i ] < = 1 0 4 -10^4 <= nums[i] <= 10^4 −104<=nums[i]<=104
- n u m s 已按非递减顺序排序 nums 已按 非递减顺序 排序 nums已按非递减顺序排序
进阶:
请你设计时间复杂度为 O ( n ) O(n) O(n)的算法解决本问题
思路:我们知道一个负数平方后,会是正数,所以如果两个数都是负数的情况,负值越小(如-5
比-4
小),平方后反而越大(25
大于16
)。
双指针法,因为负数的平方是正数,所以平方后较大值一定是在数组的两端,越往中间靠近0
的位置,平方后越小
Go 代码
func sortedSquares(nums []int) []int {
// 思路:双指针法,因为负数的平方是正数,所以平方后较大值一定是在数组的两端,越往中间靠近0的位置,平方后越小
if len(nums) == 0 {
return []int{}
}
res := make([]int,len(nums))
k := len(nums) - 1 // 从后往前放元素,因为较大的放结果数组后面
i,j := 0,len(nums) - 1
// 因为最后指向的那个数字也是要计算放入res中的,所以是<=,而不是<
for(i <= j) {
if nums[i] * nums[i] < nums[j] * nums[j] {
res[k] = nums[j] * nums[j]
j--
} else {
res[k] = nums[i] * nums[i]
i++
}
k-- // 尾部索引减一
}
return res
}