双指针的相关练习
文章目录
1.移除元素
第一种同向双指针
思想
第一种同向双指针
1.定义两个指针(双指针法)通过一个快指针和慢指针在一个循环下完成两个循环的工作。
2.这里定义两个同向双指针
3.慢指针 指向更新 新数组下标的位置
4.快指针 寻找新数组的元素 ,新数组就是不含有目标元素的数组
5.通过while循环先让快指针找不是目标值的值,找到后添加到慢指针所指的位置
6.添加后慢指针向后移动
7.当快指针到数组末尾即找完,这样就用一个循环完成了两个循环操作
代码实现
package com.algo.array;
public class Algo27 {
public int removeElement(int[] nums, int val) {
/**
* 时间复杂度:
* 在这段代码中,我们使用了同向双指针的方法.
* 慢指针 slowindex 用于更新新数组的下标位置
* 快指针 fastindex 用于寻找新数组的元素。
* 整个过程只需要一次遍历数组 nums,因此时间复杂度为 O(n),其中 n 是数组 nums 的长度。
*
* 空间复杂度:
* 在这段代码中,我们没有使用额外的数据结构,只是在原数组 nums 上进行元素的移动操作
* 因此没有使用额外的空间,空间复杂度为 O(1),是常数级别的。
*/
//移除指定元素
//所以采用双指针来解决 同向双指针
//定义两个指针 分别为快指针 慢指针
int slowindex = 0;//慢指针 指向更新 新数组下标的位置
int fastindex = 0;//快指针 寻找新数组的元素 ,新数组就是不含有目标元素的数组
while (fastindex<nums.length){
if(nums[fastindex]!=val){
nums[slowindex++]=nums[fastindex];
}
fastindex++;
}
return slowindex;
}
}
第二种反向双指针
思路
1.定义两个指针 left指向数组的第一位 right指向数组的末尾
2.当left指针遍历没有遇到目标值时就往后移,遇到则停止后移,等待right指针向前遍历
3.right向前遍历,遇到目标值就向前移,直到遇到不是目标值,这时和left指针交换元素
4.交换完元素后分别向后和向前移动
5.最后返回left指针(leftIndex一定指向了最终数组末尾的下一个元素)
代码实现
package com.algo.array;
public class Algo27 {
public int removeElement1(int[] nums, int val){
/**
* 移除指定元素
* 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
//定义两个指针 分别为正向指针 逆向指针
int leftindex = 0;
int rightindex = nums.length-1;
while (leftindex<=rightindex){
//找左边等于val的元素
while (leftindex<=rightindex&&nums[leftindex]!=val){
leftindex++;
}
//找右边不等于val的元素
while (leftindex<=rightindex&&nums[rightindex]==val){
rightindex--;
}
// 将右边不等于val的元素覆盖左边等于val的元素
if(leftindex<rightindex){
nums[leftindex++]=nums[rightindex--];
}
}
return leftindex;// leftIndex一定指向了最终数组末尾的下一个元素
}
}
2.删除排序数组中的重复项
力扣:26. 删除有序数组中的重复项 - 力扣(Leetcode)
思路
1.定义两个指针 慢指针为0 快指针为1
2.当快慢指针所指的元素的值相等时,快元素向后移,慢指针不动,慢指针指向元素第一次出现的位置
3.当快指针移动到所指元素的值不相等时,先将慢指针后移一位然后将快指针所指元素赋给慢指针所指的位置
4.当跳出循环后,left+1,因为left是从零开始的,有效个数从1开始
代码实现
package com.algo.array;
public class Algo26 {
public int removeDuplicates(int[] nums) {
/**
* 时间复杂度:
*
* 遍历数组一次,每次比较两个元素是否相等,所以时间复杂度为 O(n),其中 n 是数组的长度。
* 空间复杂度:
*
* 由于算法只使用了常数个额外变量,空间复杂度为 O(1),是常量级别的。不需要额外的空间来存储数据。
*/
//定义两个指针
int slowIndex = 0;//慢指针
int fastIndex = 1;//快指针
while (fastIndex<nums.length){
if(nums[slowIndex]==nums[fastIndex]){
fastIndex++;
}else {
nums[++slowIndex]=nums[fastIndex];
}
}
return slowIndex+1;
}
}
3.移动零
思路
1.定义两个指针 慢指针为0 快指针为1
2.当快指针所指值不为零而慢指针所指值为零就交换,否则不交换
3.交换后慢指针后移
4.交没交换都将快指针后移
代码实现
package com.algo.array;
public class Algo283 {
public void moveZeroes(int[] nums) {
/**
* 时间复杂度:
*
* 该算法使用了双指针的方式,其中快指针 fastIndex 遍历整个数组,慢指针 slowIndex 最多遍历整个数组一次。
* 在每次循环中,慢指针遇到非零元素时只进行一次赋值操作,快指针遇到非零元素时也只进行一次赋值操作,因此每次循环的操作次数是常数级别的。
* 因此,整个算法的时间复杂度为 O(n),其中 n 为数组的长度。
* 空间复杂度:
*
* 该算法只使用了常数个额外变量,包括快指针、慢指针和一个临时变量,没有使用额外的数据结构。
* 因此,整个算法的空间复杂度为 O(1),是一个原地修改数组的算法。
*/
/**
* 定义两个双指针 快指针 fastIndex 慢指针slowIndex
* 慢指针遇到0就不动 快指针遇到不为零的就和慢指针交换值
* 以此类推
* 0,1,0,3,12
*/
int fastIndex = 1;
int slowIndex = 0;
while (fastIndex < nums.length) {
if (nums[fastIndex] != 0) {
if (nums[slowIndex] == 0) { // 只有 slowIndex 指向零元素时才进行交换
//交换
int temp = nums[slowIndex];
nums[slowIndex] = nums[fastIndex];
nums[fastIndex] = temp;
}
slowIndex++; // slowIndex 只有在交换时才移动
}
fastIndex++;
}
}
}
4.比较含退格的字符串
力扣:844. 比较含退格的字符串 - 力扣(Leetcode)
第一种
思路
1.定义两个指针,分别指向两个字符串的尾部
2.定义两个整数,分别代表需要跳过的字符数
3.向前遍历,跳过被 ‘#’ 删除的字符,直接比较有效字符
1)当第一个字符串遇到’#',就将记录第一个跳过字符数的值加1,同时指针向前移1
2)当没有遇到’#'时,先判断第一个跳过字符数的值是否为零,不为零则记录跳过几个字符
3)同理,第二个字符串一样步骤
4)最后比较两个有效字符
5)比较完后两个指针向前移
4.当两个指针所指字符不相等时,返回 false
5.最后比较完两个字符串,如果没有返回 false,则返回 true
代码实现
package com.algo.array;
public class Algo844 {
public boolean backspaceCompare1(String s, String t) {
/**
* 使用双指针,分别指向两个字符串的末尾,从后往前遍历,可以省去使用栈的空间。
* 在遍历过程中,跳过被 '#' 删除的字符,直接比较有效字符。
* 当两个指针所指字符不相等时,返回 false。
* 最后比较完两个字符串,如果没有返回 false,则返回 true。
* <p>
* 优化后的代码避免了使用额外的栈数据结构,只使用了常数级别的额外空间,
* 时间复杂度仍然为 O(m+n),但空间复杂度降低为 O(1)。
*
* @param s
* @param t
* @return
*/
public boolean backspaceCompare(String s, String t) {
int i = s.length() - 1; // 指向 s 的末尾
int j = t.length() - 1; // 指向 t 的末尾
int skipS = 0; // 记录 s 中需要被跳过的字符个数
int skipT = 0; // 记录 t 中需要被跳过的字符个数
while (i >= 0 || j >= 0) {
// 跳过 s 中被 '#' 删除的字符
while (i >= 0) {
//一旦遇到'#' 就skipS++;
if (s.charAt(i) == '#') {
skipS++;
i--;
} else if (skipS > 0) {//判断需要跳过几个字符
skipS--;
i--;
} else {
break;
}
}
// 跳过 t 中被 '#' 删除的字符
while (j >= 0) {
//一旦遇到'#' 就skipT++;
if (t.charAt(j) == '#') {
skipT++;
j--;
} else if (skipT > 0) {//判断需要跳过几个字符
skipT--;
j--;
} else {
break;
}
}
// 比较有效字符
if (i >= 0 && j >= 0 && s.charAt(i) != t.charAt(j)) {
return false;
}
// 指针继续向前移动
i--;
j--;
}
return true;
}
}
第二种
思路
1.代码中使用了两个栈,分别存储了字符串S和T中的字符
2.先遍历第一个字符串,如果不是’#'就进栈
3.当栈中的元素不为空时,遇到’#'就出栈
4.指针后移
5.对第二个字符串也是如此
6.如果两个栈中的元素个数不一致时直接false
7.每次都拿两个栈的栈顶元素进行判断
8.如果有一次不一样直接返回false
代码实现
package com.algo.array;
import java.util.Stack;
public class Algo844 {
public boolean backspaceCompare1(String s, String t) {
/**
* 时间复杂度:
*
* 首先,两个 while 循环分别对字符串S和T进行遍历,时间复杂度分别为 O(n) 和 O(m),其中 n 和 m 分别为字符串S和T的长度。
* 在每个循环中,对栈的操作(入栈、出栈)都是常数时间复杂度的操作。
* 最后,对两个栈进行比较,时间复杂度为 O(max(n, m)),因为需要比较两个栈中的最大长度。
* 综上所述,整体的时间复杂度为 O(max(n, m)),其中 n 和 m 分别为字符串S和T的长度。
*
* 空间复杂度:
*
* 代码中使用了两个栈,分别存储了字符串S和T中的字符,因此需要额外的空间来存储这两个栈。
* 字符串的长度可能影响栈的大小,但在最坏情况下,栈的大小可能达到字符串的长度的最大值,即 O(max(n, m))。
* 其他的变量和操作都是常数空间复杂度的。
* 综上所述,整体的空间复杂度为 O(max(n, m)),其中 n 和 m 分别为字符串S和T的长度。
*/
//利用栈的特点来解决
Stack<Character> stack = new Stack<>();
Stack<Character> stack1 = new Stack<>();
int temp = 0;
while (temp < s.length()) {
//如果不是'#'就进栈
if (s.charAt(temp) != '#') {
stack.add(s.charAt(temp));
} else if (stack.size() > 0) {
//当栈中的元素不为空时,遇到'#'就出栈
stack.pop();
}
temp++;//后移
}
temp = 0;
while (temp < t.length()) {
if (t.charAt(temp) != '#') {
//如果不是'#'就进栈
stack1.add(t.charAt(temp));
} else if (stack1.size() > 0) {
//当栈中的元素不为空时,遇到'#'就出栈
stack1.pop();
}
temp++;//后移
}
//如果两个栈中的元素个数不一致时直接false
if (stack.size() != stack1.size()) {
return false;
}
while (stack.size() != 0) {
Character character = stack.pop();//拿到栈顶元素
Character character1 = stack1.pop();//拿到栈顶元素
//如果有一次不一样直接返回false
if (!character.equals(character1)) {
return false;
}
}
return true;
}
}
5.有序数组的平方
力扣:977. 有序数组的平方 - 力扣(Leetcode)
第一种
思路
1.利用双指针从数组的两端开始遍历
2.比较两端的平方值的大小,将较大的平方值填入新数组的末尾,并向内移动指针
3.由于数组已经按非递减顺序排序,因此平方值也是按非递减顺序排序的
4.当双指针相遇时,新数组中的所有平方值都已经填充完毕,且按非递减顺序排序
代码实现
package com.algo.array;
import java.util.Arrays;
public class Algo977 {
/**
* 算法的核心思想是利用双指针从数组的两端开始遍历,
* 比较两端的平方值的大小,将较大的平方值填入新数组的末尾,并向内移动指针。
* 由于数组已经按非递减顺序排序,因此平方值也是按非递减顺序排序的。
* 最终,当双指针相遇时,新数组中的所有平方值都已经填充完毕,且按非递减顺序排序。
* 整个过程的时间复杂度为 O(n),其中 n 是数组的长度。
* @param nums
* @return
*/
public int[] sortedSquares(int[] nums){
int[] result = new int[nums.length];
int left = 0;
int right = nums.length - 1;
int index = nums.length - 1; // 从新数组的末尾开始填充
while (left <= right) {
int leftSquare = nums[left] * nums[left];
int rightSquare = nums[right] * nums[right];
if (leftSquare > rightSquare) {
result[index] = leftSquare;
left++;
} else {
result[index] = rightSquare;
right--;
}
index--;
}
return result;
}
}
第二种
思路
1.将数组中的元素平方后继续存放到刚才的位置
2.运用数组自动排序方法sort()实现排序
3.返回即可
代码实现
package com.algo.array;
import java.util.Arrays;
public class Algo977 {
/**
* 时间复杂度:
*
* 遍历整数数组nums并计算平方的过程需要 O(n) 的时间复杂度,其中 n 是数组的长度。
* 调用Arrays.sort()方法对数组进行排序的时间复杂度为 O(n log n),其中 n 是数组的长度。
* 因此,总体的时间复杂度为 O(n) + O(n log n) = O(n log n),
* 因为在渐进符号中,n log n 的增长速度更快,所以最终的时间复杂度可以近似看作 O(n log n)。
*
* 空间复杂度:
*
* 方法中没有使用额外的数据结构,只是对输入的数组进行原地修改,因此没有额外的空间消耗,空间复杂度为 O(1)。
* 综上所述,这段代码的时间复杂度为 O(n log n),空间复杂度为 O(1)。
* @param nums
* @return
*/
public int[] sortedSquares1(int[] nums) {
for (int i = 0; i <nums.length ; i++) {
nums[i] = (int)Math.pow(nums[i],2);
}
Arrays.sort(nums);
return nums;
}
}