1. 数组
1.1 二分查找写法
对于边界的选取和节点的更新有两种写法:
边界条件 | 左节点更新 | 右节点更新 | |
左闭右闭,[left,right] | left<=right | left = mid+1 | right = mid-1 |
左闭右开,[left,right) | left<right | left = mid+1 | right = mid |
对于二者的区别,主要是右指针指向的节点是否在考虑范围内:第一种在考虑范围内,第二种不在考虑范围内。
中值的计算方法:
int middle = left + ((right - left) >> 1);
public int search(int[] nums, int target) {
//左闭右开 [l,r)
int l = 0;
int r = nums.length;
//由于r不属于有效范围内,所以r==l时既查找完毕
while (l < r) {
int mid = l + ((r - l) >> 1);
if (target == nums[mid]) { //先将是否为寻找的数值判断一下
return mid;
} else if (target > nums[mid]) {
l = mid + 1; //数值在右边,让左边界为mid+1
} else {
r = mid; //数值在左边,让r等于mid
}
}
//走到这没返回说明没找到,返回-1
return -1;
}
变体:
搜索插入位置:
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
模板和原型类似,小于target<插入位置<大于target,由于插入位置为仅次于target的右边,所以直接将l更新到mid+1即可,最后返回l,等于直接返回
在排序数组中查找元素的第一个和最后一个位置 :
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
注意边界赋值条件,当寻找左边界时:右边的全不要(大于等于目标值,更新右节点,左边界赋值为右节点-1);寻找右边界时:左边的全不要(小于等于目标值,更新左节点,右边界赋值为左节点)。
x的平方根:
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
将问题转化为,求0-x间的哪个数的平方更接近x,将问题转化成平方的形式计算。
有效的完全平方数
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
和上一个变种类似
1.2 双指针:移动数组元素
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
值得一提的是双指针写法,有两种双指针,都从头开始和相向双指针
都从头开始:快指针必须走完数组长度
相向双指针:(前提条件:题目对移动后的数组元素位置不做要求)慢指针从0开始,快指针从后面开始,当两个指针相遇时停止。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int leftIndex = 0;
int rightIndex = nums.size() - 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一定指向了最终数组末尾的下一个元素
}
};
变体
删除排序数组中的重复项(按序去重)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
和上述题目类似,注意边界条件,while循环首先判断索引是否越界。
移动零
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
双指针,for循环遍历,交换元素。
比较含退格的字符串
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
和上一题类似,此题用栈操作更方便
思路2:倒叙双指针
有序数组的平方
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
普通解法:平方后排序
进阶解法:相向双指针,从头和尾向中间移动
1.3 滑动窗口:长度最小的子数组
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
是典型的滑动窗口,但是在写滑动窗口时,要使用for循环,滑动窗口的右侧每次更新1步,左侧使用while循环判断是否符合条件。
变体:
水果成篮:寻找包含两种元素的最长子序列
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
典型滑动窗口,但同时需要使用HashMap来记录窗口内的值和数量,在判断Map的size超出阈值时,可以利用左侧边界的节点一定在map里来进行数量上的判断,而不用多个if。
最小覆盖字串
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
滑动窗口,可以利用ASCLL的数组
1.4 矩阵打印:螺旋矩阵II
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
四个边界(上右下左依次打印,每打印完向中间逼近1)
变体:
螺旋矩阵
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
方法和上述类似,一圈一圈遍历,但是注意此矩阵不是方阵,所以每次要加一个判断是否退出循环
2. 链表
2.1 两两交换链表中的节点
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
使用递归解法,出口是head==null;head.next==null,然后只关注前两个节点如何构件以及和后面的链表如何链接即可。
2.2 删除链表的倒数第N个节点
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
快慢指针一次扫描,都从newHead开始出发,n减到0快指针停止,再次一起出发,快指针下一个为空时,慢指针下一个节点就是要删除的。
2.3 链表相交
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
省空间费时间:双指针,记录步数,让长链表的先走插值步,然后一起走,相等返回
省代码(不省时间,时间复杂度mn,因为contains查找时间复杂度是O(n)):Set存储第一个链表的节点,然后第二个节点每个查询一次。
2.4 环形链表II找入口
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
快慢指针,循环条件:快指针和快指针next不为空,当快慢指针重合时跳出循环
让慢指针从头开始走,快指针从相遇的节点开始走,相遇返回。
3. 哈希表
哈希表形式:数组(适合题目设计的数字或字符种类数不多),HashSet
哈希表常用api:
方法名 | 方法参数 | 方法返回值 | 方法描述 | 代码示例 |
---|---|---|---|---|
add(E e) | e - 要添加 的元素 | boolean - 如果元素 尚未存在于集合中, 则返回 true。 | 将指定的元素添加到 集合中,如果元素已 经存在,则集合不改 变,并返回 false。 | boolean added = set.add("element1"); |
remove(Object o) | o - 要移除 的元素 | boolean - 如果元素 存在并被移除,则 返回 true。 | 从集合中移除指定的 元素,如果元素不存 在,则集合不改变, 并返回 false。 | boolean removed = set.remove("element1"); |
contains(Object o) | o - 要检查 是否存在 的元素 | boolean - 如果元素 存在于集合中,则 返回 true。 | 如果集合包含指定的 元素,则返回 true。 | boolean contains = set.contains("element1"); |
size() | 无 | int - 集合中的元素 数量。 | 返回集合中元素数量。 | int size = set.size(); |
isEmpty() | 无 | boolean - 如果集合 为空,则返回 true。 | 如果集合不包含任何 元素,则返回 true。 | boolean isEmpty = set.isEmpty(); |
clear() | 无 | 无 | 从集合移除所有元素。 | set.clear(); |
iterator() | 无 | Iterator<E> 在集合 上的迭代器。 | 返回一个在集合上的 迭代器,按照元素插 入的顺序迭代集合中 的元素。 | Iterator<String> iterator = set.iterator(); |
迭代器写法:
ArrayList<String> practiceStr = new ArrayList<>();
Collections.addAll(practiceStr, "apple", "cat", "dog", "big");
Iterator<String> iterator = practiceStr.iterator();
while (iterator.hasNext()){
log.info(iterator.next());
}
3.1 有效的字母异位词
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
哈希表遍历两个字符串解决,可以使用数组和ASCLL码代替哈希表
变体:
字母异位词分组
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
遍历字符串数组,两种处理方法:
1. 将每个字符串按字母顺序排序,然后将排序后的字符串作为key存储
2. 统计每个字符出现的次数,将其拼接作为key
注意:返回值可以使用return new ArrayList<List<String>>(hm.values());
3.2 两个数组的交集
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
两个HashSet遍历数组完成
变体:
两个数组交集Ⅱ(将重复的元素也返回)
一个HashSet+ArrayList即可完成,参考题目数据不大,小于1000,可以使用数组代替set。
进阶部分:
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果
nums1
的大小比nums2
小,哪种方法更优? - 如果
nums2
的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
1. 排序号可以使用双指针,相等记录,同时后移,否则小的后移
2. 时间上排序<Hash,空间上排序>Hash
3. 一种是使用Hash,将nums1保存到哈希表,nums2只涉及到查询,每次加载一部分即可。另外一种是使用外部排序,在磁盘上进行排序,典型的是归并排序,然后使用双指针。
归并代码:
public class Msort {
public static void main(String[] args) {
int[] arrays = new int[] {1,5,4,3,2};
sort(arrays,0,arrays.length - 1);
System.out.print(Arrays.toString(arrays));
}
public static void sort(int[] arr, int left, int right) {
int[] temp = new int[arr.length];
if(left >= right) {
return;
}
int mid = (left + right)/2; // 2
sort(arr,left,mid);
sort(arr, mid + 1, right);
int i = left;
int j = mid + 1;
int t = 0;
while(i <= mid && j <= right) {
if(arr[i] <= arr[j]) {
temp[t] = arr[i];
i++;
t++;
}else {
temp[t] = arr[j];
j++;
t++;
}
}
while(i <= mid) {
temp[t] = arr[i];
i++;
t++;
}
while(j <= right) {
temp[t] = arr[j];
j++;
t++;
}
int k = 0;
int tempIndex = left;
while(tempIndex <= right) {
arr[tempIndex] = temp[k];
k++;
tempIndex++;
}
}
}
3.3 快乐数
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
该题有特性:
1. 最终得到数字1
2. 陷入循环
3. 越来越大,这种情况不会发生,三位数最大不超过243,四位数及以上每次计算会丢失一位精度
方法一:将每次计算的结果保存到HashSet中,遇到重复的不为1的直接返回false
方法二:快慢指针,快指针计算两次,慢指针计算一次,最终快慢指针会相遇,判断此时他们的值是否为1.
3.4 四数相加II
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
看到形如:A+B....+N=0的式子,要转换为(A+...T)=-((T+1)...+N)再计算,这个T的分割点一般是一半,特殊情况下需要自行判断。定T是解题的关键。
该题需要两次双层for循环来解决,第一次将前两个数组的每一对数值相加,存储和的值和次数。第二次双重for循环依次遍历即可。
3.5 三数之和
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
暴力解法:O(n^3)
哈希表:O(n^2)+O(n),空间O(n^2)
双指针:先排序,然后双层for循环,使用尾指针向头逼近来代替第三层循环。
要注意,答案不能出现重复元组,所以每次的循环要先判断是否和上一次元素不同。
3.6 四数之和
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
该题要求的是符合条件的数组,而不是数量,所以不能使用两个双层for循环,可以使用两层for循环+相向双指针,记住两个外层循环要首先排除数值相等情况。
在指针位置确定后,首先判断是否满足条件,满足则保存结果并且将左右指针分别更新为不相等的下一个位置。如果不满足条件则直接更新左指针或右指针