双指针算法_day2

双指针算法

双指针算法是我们在学习中常见的一种算法,指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。按照分类可以分为对撞指针快慢指针分离双指针。我们利用双指针,可以改善时间复杂度,暴力算法的时间复杂度往往是 O(n*n) 。而双指针利用了区间「单调性」的性质,可以将时间复杂度降到 O(n)

1. 对撞指针

对撞指针:指的是两个指针 leftright 分别指向序列第一个元素和最后一个元素,然后 left 指针不断递增,right 不断递减,直到两个指针的值相撞(即 left == right),或者满足其他要求的特殊条件为止。

1.1 对撞指针求解步骤

  1. 使用两个指针 leftrightleft 指向序列第一个元素,即:left = 0right 指向序列最后一个元素,即:right = len(nums) - 1
  2. 在循环体中将左右指针相向移动,当满足一定条件时,将左指针右移,left += 1。当满足另外一定条件时,将右指针左移,right -= 1
  3. 直到两指针相撞(即 left == right),或者满足其他要求的特殊条件时,跳出循环体。

1.2 对撞指针伪代码模板


int left = 0;
int right = nums.length - 1;

while (left < right) {
   if (满足要求的特殊条件) {
       return 符合条件的值;
   } else if (一定条件 1) {
       left++;
   } else if (一定条件 2) {
       right--;
   }
}

return 没找到 或 找到对应值;

1.3 对撞指针运用(力扣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 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums 已按 非递减顺序 排序

进阶:

  • 请你设计时间复杂度为 O(n) 的算法解决本问题

思路解析:数组为非递减顺序排列,那么代表可能会存在相同的元素。我们可以直接暴力破解,直接将所有的元素进行平方,然后进行排序。代码如下。

import java.util.Arrays;
class Solution {
    public int[] sortedSquares(int[] nums) {
        for(int i = 0 ; i < nums.length ;i++){
            nums[i] = nums[i] * nums[i];
        }
          Arrays.sort(nums);
        return nums;

    }    
}

进阶:对于这道题,我们可以直接使用对撞指针来求解,一个指针从前面遍历,一个指针从后面遍历,然后进行比较,如果前面的元素平方大于后面元素的平方那就交换位置。时间复杂度为时间复杂度为 O(n)


class Solution {
   public int[] sortedSquares(int[] nums) {
       int len = nums.length;
       int[] result = new int[len];
       int left = 0, right = len - 1;
       int i = n - 1;

       while (left <= right) {
           int leftNum = nums[left] * nums[left];
           int rightNum = nums[right] * nums[right];

           if (leftNum > rightNum) {
               result[i--] = leftNum;
               left++;
           } else {
               result[i--] = rightNum;
               right--;
           }
       }

       return result;
   }
}

1.4对撞指针运用(力扣167)

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1]numbers[index2] ,则 1 <= index1 < index2 <= numbers.length

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1index2

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

示例 1:

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。

示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。

示例 3:

输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。

提示:

  • 2 <= numbers.length <= 3 * 104
  • -1000 <= numbers[i] <= 1000
  • numbers非递减顺序 排列
  • -1000 <= target <= 1000
  • 仅存在一个有效答案

思路解析:给出的数组还是非递减顺序排列,要找到满足相加之和等于目标数 target 的两个数,我们可以从两边分别开始遍历,如果得到的结果小于目标值,那么left++,否则right++,注意返回的为数组形式。


class Solution {
   public int[] twoSum(int[] numbers, int target) {
       int left = 0;
       int right = numbers.length - 1;

       while (left < right) {
           int sum = numbers[left] + numbers[right];

           if (sum == target) {
               return new int[]{left+1, right+1};
           } else if (sum < target) {
               left++;
           } else {
               right--;
           }
       }

       return new int[0]; // 返回一个空数组
   }
}

1.5对撞指针运用(力扣125)

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false

示例 1:

输入: s = "A man, a plan, a canal: Panama"
输出:true
解释:"amanaplanacanalpanama" 是回文串。

示例 2:

输入:s = "race a car"
输出:false
解释:"raceacar" 不是回文串。

示例 3:

输入:s = " "
输出:true
解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。
由于空字符串正着反着读都一样,所以是回文串。

提示:

  • 1 <= s.length <= 2 * 105
  • s 仅由可打印的 ASCII 字符组成

思路解析:我们首先要把输入的字符串转化为只含有小写字母的字符串,然后进行左右指针开始遍历,主要是对于字符串库函数的应用。


class Solution {
    public boolean isPalindrome(String s) {
        StringBuffer sgood = new StringBuffer();
        int length = s.length();
        for (int i = 0; i < length; i++) {
            char ch = s.charAt(i);
            if (Character.isLetterOrDigit(ch)) {
                sgood.append(Character.toLowerCase(ch));
            }
        }
        int n = sgood.length();
        int left = 0, right = n - 1;
        while (left < right) {
            if (Character.toLowerCase(sgood.charAt(left)) != Character.toLowerCase(sgood.charAt(right))) {
                return false;
            }
            ++left;
            --right;
        }
        return true;
    }
}

2.快慢指针

快慢指针:指的是两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为 「快指针(fast)」,移动慢的指针被称为「慢指针(slow)」。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。

2.1 快慢指针求解步骤

  1. 使用两个指针 slowfastslow 一般指向序列第一个元素,即:slow = 0fast 一般指向序列第二个元素,即:fast = 1
  2. 在循环体中将左右指针向右移动。当满足一定条件时,将慢指针右移,即 slow += 1。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 fast += 1
  3. 到快指针移动到数组尾端(即 fast == len(nums) - 1),或者两指针相交,或者满足其他特殊条件时跳出循环体。

2.2 快慢指针伪代码模板

int slow = 0;
int fast = 1;
while (fast < arr.length) {
    if (/* 满足要求的特殊条件 */) {
        slow++;
    }
    fast++;
}
return //返回合适的值

2.3对撞指针运用(力扣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 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按 非严格递增 排列

思路解析:题目要求我们原地删除出现的重复元素,定义两个快慢指针。然后通过循环遍历整个数组,比较两个指针所指向的元素是否相等。如果不相等,将第二个指针所指向的元素赋值给第一个指针的下一个元素,并将第一个指针向后移动一位。如果相等,则只将第二个指针向后移动一位。最后返回第一个指针所指向的索引加一,即为去重后的数组长度。


class Solution {
   public int removeDuplicates(int[] nums) {
       if (nums.length == 0) {
           return 0;
       }

       int show = 0;
       int fast = 1;

       while (fast < nums.length) {
           if (nums[show] != nums[fast]) {
               show++;
               nums[show] = nums[fast];
           }
           fast++;
       }

       return show + 1;
   }
}

你可能感兴趣的:(算法学习,算法,java,c++)