双指针的相关练习

双指针的相关练习

1.移除元素

力扣:27. 移除元素 - 力扣(Leetcode)

第一种同向双指针

思想

第一种同向双指针

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.移动零

力扣:283. 移动零 - 力扣(Leetcode)

思路

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值