1. 概括
在一串有序数列中,两根指针一头一尾,向中间靠拢直到相遇,时间复杂度为O(n),
类型题有Two Sum, Partition等
2. Two Sum类型
LintCode 607 · 两数之和 III-数据结构设计
要点:需要考虑重复数字,数据本身是无序的
方案一:使用list存放数字。
add()方法:用到插入排序,将新数字插入到list的末尾,不断与前面有序的数组元素依次进行比较,如果没有在正确的位置交换两元素,继续下一轮比较,如果在正确的位置则结束。时间复杂度O(n), 空间复杂度O(n)
find()方法:对有序list双指针。时间复杂度O(n)
方法二:使用HashMap存放数字,及其出现的频次
add()方法:直接加入哈希表中,注意更新数字出现的频次。时间复杂度O(1)
find()方法:遍历HashMap里的每个num,检查target - num是否也在HashMap中。 时间复杂度O(n)
LeetCode 15. 三数之和
要点:首先需要对输入数组nums排序,经典two sum需要在有序列中进行。 要求中不可包含重复的三元组,一定要去重。
解法:选定一个点a,另外两个点的目标和为-a,转化为two sum问题。时间复杂度O(n^2)
去重:
// 外层数字循环,去重 // 前提条件:相同的数字必须相邻 if(index > 0 && nums[index-1] == nums[index]){ continue; }
// 内层双指针去重 if(nums[left] + nums[right] == target){ ...... ...... // 如果左指针当前数字与左侧的数字相同,左指针右移,跳过重复 while(left < right && nums[left] == nums[left-1]){ left++; } // 如果右指针当前数字与右侧的数字相同,右指针左移,跳过重复 while(left < right && nums[right] == nums[right+1]){ right--; } }
LeetCode 611. 有效三角形的个数
要点:两小边之和大于最大边;要进行two sum需要先对数组排序;无需去掉重复答案
解法:固定最大边,对之前的子数组进行two sum操作,因为只需求解的个数,因此无需一一枚举,可以批量求解的个数。
时间复杂度O(n^2),空间复杂度O(1)
LeetCode 18. 四数之和
解法:类似于三数之和的做法,双重循环固定两个数,然后做two sum。要记得去重。
时间复杂度O(n^3),空间复杂度O(k) k为解的个数
LeetCode 454. 四数相加 II
解法:用到了hashmap,将 a,b 组成的和及其组成方案个数统计在hash里,然后再去枚举 c,d 的组合,然后找 -(c+d) 在 hash 里的组合数。
时间复杂度O(n^2)
3. Partition类型
1)经典分区算法
给定一个整数数组nums和一个整数k,将所有小于k的元素移到左边,所有大于等于k的元素移到右边。时间复杂度 O(n),代码示例:
while(left <= right){
// 左指针寻找一个不属于左边的数
while(left <= right && nums[left] < k){
left++;
}
// 右指针寻找一个不属于右边的数
while(left <= right && nums[right] >= k){
right--;
}
if(left <= right){
// 交换左右指针数字,使得双方都到正确的一端
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
LintCode 144 · 交错正负数
要点:数据确保正负数个数相差不超过1;不使用额外空间;不需要保持正整数或负整数原来的顺序
思路:先用O(n)的时间将正整数和负整数分成左右两区,然后间隔交换正负数。
时间复杂度O(n)
注:1)要根据正负数个数的情况,设定两边交换的起点
LeetCode 75. 颜色分类 (3种颜色)
要点:array + 固定有限种元素排序 ——> 有限次快排分区
思路:可以用同向双指针做两次分区,注意墙的初始位置是-1,即 int lastSmallPointer = -1;
每当遇到nums[i] < pivot的情况,先右移“墙”指针,再交换元素,即 nums[i] <=> nums[lastSmallPointer]
时间复杂度 O(n)
LintCode 143 · 颜色分类 II (k种颜色)
思路:类似于快速排序的思想,按颜色个数k对数组分区,递归的程序设计方式
时间复杂度 O(nlogk)
LeetCode 283. 移动零
要点:在原数组上操作;最小化操作数
思路:使用同向双指针,填充指针fillPointer指向将被非零数填充的位置,移动指针movePointer不断前移,直到指向一个非零数,把该数赋值给填充指针指向的元素,并左移一位fillPointer。当movePointer移动结束后,将fillPointer右侧的非零数赋值为0
时间复杂度O(n)
4. 拓展
1) List 与 Arraylist
2)heap
3)HashMap底层实现 getOrDefault()方法
4)链表