目录
1. 数组中的双指针
当在数组[1,2,2,2,3,3,3,5,5,7,8]中想要删除重复的元素使其变成[1,2,3,5,7,8]时,使用每删除一个一个元素,数组后面的值就往前移一位太麻烦,且效率较低。
利用双指针思想,首先定义slow和fast两个指针,slow指针表示当前及之前的元素都是不重复的,fast则是一直往后面找不重复的元素,找到之后就将slow往后移一位,将arr[slow]=arr[fast]即可,然后fast继续往后移,找不重复的元素。如下图,fast指针寻找的条件是arr[slow]!=arr[fast],所以slow在fast找到之前都不能移位置。
上面的为快慢指针,还有就是从两端一起走到中间的称为对撞型指针或相向指针。
最后一种是从中间向两端走的背向型指针。
2. 删除元素专题
2.1 删除数组中指定的所有值
给你一个数组 nums
和一个值 val
,你需要原地移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
2.1.1 解法一:快慢双指针
可以利用上面的快慢指针思想,定义两个指针slow和fast都为0,因为此时的判断条件变成了nums[fast]和val之间的关系,所以此时的slow之前的元素都是有效部分,不包括slow指向的元素。
以val=2为例。
public static int removeElement(int[] nums, int val) {
int slow=0;
int fast=0;
for (;fast < nums.length; fast++) {
if (nums[fast]!=val){
nums[slow]=nums[fast];
slow++;
}
}
return slow;
}
2.1.2 解法二:对撞型双指针
核心思想是从右侧找到不是val的值来顶替左侧是val的值,以val=2为例。
当left<right时,left左侧就是删除2的所以元素了。
public static int removeElement_2(int[] nums, int val) {
int left=0;
int right=nums.length-1;
while (left<=right){
if (nums[left]==val&&nums[right]!=val){
int temp=nums[left];//在这里将左右两个元素交换之后,下面的两个if就可在交换之后正常向中间走,否则right需额外指定++;
nums[left]=nums[right];
nums[right]=temp;
}
if(nums[left]!=val)left++;
if (nums[right]==val)right--;
}
return left;
}
2.1.3 解法三:对撞双指针+覆盖
当nums[left]==val时,就将nums[right]位置的元素覆盖nums[left],此时left不++,因为nums[right]的值可能也是val
public static int removeElement_3(int[] nums, int val) {
int right=nums.length-1;
int left=0;
while (left <=right ){
if (nums[left]==val){
nums[left]=nums[right];
right--;
}else left++;
}
return left;
}
2.2 删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。 - 返回
k
。
解法:此题的解法就是解释双指针中所用到的解法,利用快慢指针。
public static int removeDuplicates(int[] nums) {
int slow=0;
int fast=1;
while (fast<=nums.length-1){
if (nums[slow]!=nums[fast]){
slow++;
nums[slow]=nums[fast];
}
fast++;
}
return slow+1;
}
3. 元素奇偶移动专题
给你一个整数数组 nums
,将 nums
中的的所有偶数元素移动到数组的前面,后跟所有奇数元素。
返回满足此条件的 任一数组 作为答案。
示例 :
输入:nums = [3,1,2,4]
输出:[2,4,3,1]
解释:[4,2,3,1]、[2,4,1,3] 和 [4,2,1,3] 也会被视作正确答案。
利用双指针对撞型,即定义左右指针向中间靠拢,左指针找奇数,右指针找偶数,两者互换位置即可。
public int[] sortArrayByParity_my(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==1)right--; //找到偶数
if (nums[left]%2 > nums[right]%2){ //奇数和偶数互换位置
int temp=nums[left];
nums[left]=nums[right];
nums[right]=temp;
}
}
return nums;
}
4. 数组轮转问题
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
示例 :
输入: nums = [1,2,3,4,5,6,7], k = 3
输出:
[5,6,7,1,2,3,4]
解释:
向右轮转 1 步:
[7,1,2,3,4,5,6]
向右轮转 2 步:
[6,7,1,2,3,4,5]
向右轮转 3 步:
[5,6,7,1,2,3,4]
之前在链表中也遇到了类似的题,在链表中可以利用双指针截断前后链表再连接,现在在数组中可以先将整个数组反转之后,再将指定区间的数组反转即可达到目标。
例如[1,2,3,4,5,6,7]中,先反转为[7,6,5,4,3,2,1],再将前三个[7,6,5]和后4个[4,3,2,1]反转为[5,6,7]和[1,2,3,4]即可。
public void rotate_my(int[] nums, int k) {
k%=nums.length;
reverse_my(nums,0,nums.length-1);
reverse_my(nums,0,k-1);
reverse_my(nums,k,nums.length-1);
}
public void reverse_my(int[] nums, int start, int end) {//反转
while (start<end){
int temp=nums[start];
nums[start]=nums[end];
nums[end]=temp;
start++;
end--;
}
}
5. 数组的区间问题
给定一个 无重复元素 的 有序 整数数组 nums
。
返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums
的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums
的数字 x
。
列表中的每个区间范围 [a,b]
应该按如下格式输出:
"a->b"
,如果a != b
"a"
,如果a == b
示例 1:
输入:nums = [0,1,2,4,5,7] 输出:["0->2","4->5","7"] 解释:区间范围是: [0,2] --> "0->2" [4,5] --> "4->5" [7,7] --> "7"
public List<String> summaryRanges_my(int[] nums) {
List<String> res=new ArrayList<>();
int slow=0;
for (int fast = 0; fast < nums.length; fast++) {
if (fast+1==nums.length || nums[fast]+1!=nums[fast+1]){
StringBuilder sb=new StringBuilder();
sb.append(nums[slow]);
if (slow!=fast){ //当是单独的一个数字时就不需要箭头
sb.append("->").append(nums[fast]);
}
res.add(sb.toString());
slow=fast+1;
}
}
return res;
}