1. 双指针思想
双指针不仅指两个指针,也可以是两个变量,指向两个值。
有三种类型:
- 快慢型:一前一后
- 对撞型:从两端向中间靠拢
- 背向型:从中间向两端分开
2. 删除元素专题
2.1原地移除元素
(1)快慢指针
思路:每次找到等于val就移动数组当val值比较多的时候时间复杂度太高,使用fast指针扫描等于val的值连续删除,当找到一段连续val中第一个不等于val的值的时候就覆盖实现批量删除。
class Solution {
public int removeElement(int[] nums, int val) {
int slow = 0;
int fast = 0;
int len = nums.length;
while(fast < nums.length){
if(nums[fast] == val){
fast++;
len--;
}else{
nums[slow++] = nums[fast++];
}
}
return len;
}
}
(2)对撞指针
思路:两个指针一左一右,题目没有规定元素顺序,故当左指针指向要删除的值的时候就将右指针的值替换上来,右指针左移一位,实现删除一位的效果。
class Solution {
public int removeElement(int[] nums, int val) {
int right = nums.length - 1;
int left = 0;
int len = nums.length;
while(left <= right){
if(nums[left] != val){
left++;
}else{
nums[left] = nums[right--];
len--;
}
}
return len;
}
}
2. 2删除有序数组中的重复项
思路:双指针,当一直重复fast指针就一直往后扫,直到扫到不重复的值,然后将不重复的值覆盖slow指向的重复的值,注意slow初始化为1,这样第一次进循环一定相等。
两个关键点:slow保留指针什么时候移动、fast指向怎么算符合条件的值
此处slow指针当元素开始不重复,即fast指向新值的时候移动
nums[fast] != nums[fast- 1]代表指向新值
class Solution {
public int removeDuplicates(int[] nums) {
int slow = 1;
int fast = 1;
while(fast < nums.length){
if(nums[fast] != nums[fast- 1]){
nums[slow++] = nums[fast];
}
fast++;
}
return slow;
}
}
当删除使元素出现k个重复:
前k个不需要管,slow表示需要保留的值
当且仅当 nums[slow−k]=nums[fast]时,当前待检查元素 nums[fast]不应该被保留
(因为此时必然有 nums[slow−k]=nums[slow−(k-1)]=...=nums[fast])
例如:int[] arr = new int[]{1,1,1,2, 2,3,4,5,5,6,7,8,8,8,8,9};
此处slow指针当重复的元素不超过k个时移动
nums[fast] != nums[slow - k]代表fast指向的值在重复的k个元素范围之内
public static int removeDuplicatesk(int[] nums,int k) {
int slow = k;
int fast = k;
while(fast < nums.length){
if(nums[fast] != nums[slow - k]){
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
扩展:出现重复的元素都不要
此处slow指针当前后元素都只出现一次时移动
nums[fast] == nums[fast -1]判断元素是否重复了
public static int removeDuplicates0(int[] nums,int k) {
int slow = 0;
int fast = 1;
int count = 0;//用来判断元素是否出现重复
while(fast < nums.length){
if (nums[fast] == nums[fast -1]){
count++;
} else if(nums[fast] != nums[fast -1]&&count!=0){
//当元素出现不重复并且fast指向的是新值
nums[slow] = nums[fast];
//开始新的值重置
count = 0;
} else if (nums[fast] != nums[fast -1]&&count == 0){
//当元素不重复,并且fast指向前也是不重复的值
//这时slow保留指针才移动
nums[++slow] = nums[fast];
}
fast++;
}
return count == 0 ? slow+1 : slow;
}
3.元素奇偶移动
思路:对撞型双指针,当left指到奇数,right指到偶数就相互交换。需要注意一些边界处理细节,避免数组越界情况。
class Solution {
public int[] sortArrayByParity(int[] nums) {
int left = 0;
int right = nums.length - 1;
while(left<right){
while(left<right && nums[left] % 2 == 0){
left++;
}
while(left<right && nums[right] % 2 != 0){
right--;
}
if(left<right){
int t = nums[left];
nums[left] = nums[right];
nums[right] = t;
left++;
right--;
}
}
return nums;
}
}
4.数组轮转
思路:
方法一、暴力解法,找到开始轮转的数,然后剪切拼接到新的数组。需要注意k可能比数组长度大得取模
class Solution {
public void rotate(int[] nums, int k) {
int[] res = new int[nums.length];
int left = 0;
int right = nums.length;
int kk = k;
while( k % nums.length>0){
right--;
k--;
}
int i = 0;
for(;i<kk%nums.length;i++){
res[i] = nums[right%nums.length];
right++;
}
for(;i<nums.length;i++){
res[i] = nums[left++];
}
System.arraycopy(res, 0, nums, 0, nums.length);
}
}
优化:i+k将当前元素往右移动k,取模避免数组越界
class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
int[] newArr = new int[n];
for (int i = 0; i < n; ++i) {
newArr[(i + k) % n] = nums[i];
}
System.arraycopy(newArr, 0, nums, 0, n);
}
}
方法二、翻转,往右轮转结果就是数组后面的值移动到了前面,进行一次整体翻转,再在k处分成左右两部分再次翻转就是往右轮转的最终效果了。
class Solution {
public void rotate(int[] nums, int k) {
revers(nums,0,nums.length-1);
revers(nums,0,(k%nums.length)-1);
revers(nums,(k%nums.length),nums.length-1);
}
public void revers(int[] nums,int l,int r){
while(l<r){
int t = nums[l];
nums[l] = nums[r];
nums[r] = t;
l++;
r--;
}
}
}
5.数组的区间
思路:题目的意思是说不连续递增的就断开输出
双指针,slow指向区间起始,fast负责寻找到区间结束
class Solution {
public List<String> summaryRanges(int[] nums) {
List<String> res = new ArrayList<>();
int slow = 0;
int fast = 0;
while(fast<nums.length){
if(fast + 1 == nums.length || nums[fast] + 1 != nums[fast+1]){
if(slow == fast){
res.add("" + nums[slow]);
}else{
res.add(nums[slow] + "->" + nums[fast]);
}
slow = fast+1;
}
fast++;
}
return res;
}
}