双指针法
双指针,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。
换言之,双指针法充分使用了数组有序这一特征,从而在某些情况下能够简化一些运算。
也就是在一个数组上,使用2个指针来同时遍历数组,此时的遍历的时间复杂度为O(n)。
function fn (list) {
var left = 0;
var right = list.length - 1;
//遍历数组
while (left <= right) {
left++;
// 一些条件判断 和处理
... ...
right--;
}
}
以经典题为例:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 #card=math&code=O%281%29&id=Z5Grv) 额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。你不需要考虑数组中超出新长度后面的元素。力扣链接
示例 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。
暴力解题
很明显暴力解法的时间复杂度是#card=math&code=O%28n%5E2%29&id=D5U7O),这道题目暴力解法在leetcode上是可以过的。
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
for (int i = 0; i < size; i++) {
if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
for (int j = i + 1; j < size; j++) {
nums[j - 1] = nums[j];
}
i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
size--; // 此时数组的大小-1
}
}
return size;
}
};
- 时间复杂度:
#card=math&code=O%28n%5E2%29&id=LEFW4)
- 空间复杂度:
#card=math&code=O%281%29&id=KqMOl)
双指针法
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
class Solution {
public int removeElement(int[] nums, int val) {
int index = 0;
for(int i = 0; i < nums.length; i++) {
if(nums[i] != val) {
nums[index++] = nums[i];
}
}
return index;
}
}
- 时间复杂度:
#card=math&code=O%28n%29&id=O6cTW)
- 空间复杂度:
#card=math&code=O%281%29&id=wp0hV)
相关题目
删除排序数组中的重复项
题目描述(力扣链接)
解题方法:
相等直接赋值给上一个指针,但注意 i 从第一个开始,所以第一个应该首先加进去
class Solution {
public int removeDuplicates(int[] nums) {
int slowIndex = 0;
int i = 1;
while (i < nums.length) {
if (nums[slowIndex] == nums[i]) {
i++;
} else {
// 此处为++slowIndex,因为第一个需要加进去
nums[++slowIndex] = nums[i];
}
}
// 此处返回的是长度
return slowIndex + 1;
}
}
移除零
题目描述(力扣链接)
解题方法:
双指针法直接得出答案
class Solution {
public void moveZeroes(int[] nums) {
int index = 0;
for(int i = 0; i < nums.length; i++) {
if(nums[i] != 0){
nums[index++] = nums[i];
}
}
// 后面依次赋0即可
for(;index < nums.length; index++) {
nums[index] = 0;
}
}
}
比较含退格的字符串
题目描述(力扣链接)
解题方法:
先转化为数组,非 ’#‘ 直接将值赋值到上一个指针,如果为 ’#‘ 则将指针减一,下次赋值就会覆盖,最后截取开始到指针得字符数组。
class Solution {
public boolean backspaceCompare(String s, String t) {
return change(s).equals(change(t));
}
public static String change(String s) {
int index = 0;
// 先转化为数组
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; i++) {
// 如果不为#则赋值
if (chars[i] != '#') {
chars[index++] = chars[i];
}
// 如果为#号,且index在不在第一个位置则进行回退
else if(chars[i] == '#' && index != 0) {
index--;
}
}
// 注意截取,并转化为字符串
return String.valueOf(chars,0,index);
}
}
有序数组的平方
题目描述(力扣链接)
解题方法:
方法一:
直接将所有树平方,再排序,此时时间复杂度是 #card=math&code=O%28n%20%2B%20n%5Clog%20n%29&id=Psw33)
class Solution {
public int[] sortedSquares(int[] nums) {
// 解法一
int index = 0;
for (int i = 0; i < nums.length; i++) {
nums[index++] = nums[i] * nums[i];
}
// 使用sort自动排序
Arrays.sort(nums);
return nums;
}
}
方法二:
双指针法,时间复杂度为O(n)。
此时数组左右两边分别使用left和right指针进行平方判断,大得数字在新数组中倒序赋值 。(详细解法)
class Solution {
public int[] sortedSquares(int[] nums) {
// 双指针解法二
int index = nums.length - 1, left = 0, right = nums.length - 1;
int[] result = new int[nums.length];
while (left <= right) {
if (nums[left] * nums[left] > nums[right] * nums[right]) {
result[index--] = nums[left] * nums[left];
left++;
}else {
result[index--] = nums[right] * nums[right];
right--;
}
}
return result;
}
}
两个数字上的双指针法
两个数组的交集 II
https://www.yuque.com/jugelizi-rxt6d/dqbihl/nam035
见题一