二分查找
1. 基础版(返回插入点)
public static int version1(int[] nums, int target) {
/*
* 要求:
* 参数:一个有序,从小到大,无重复元素的数组;一个目标值
* 找到返回目标值索引,没找到返回-i-1
*
* 复习的时候自己重新走一遍过程吧,自己走一遍才能真理解
* */
int i = 0;
int j = nums.length - 1;
int m = 0;
while (i <= j) {
m = (i + j) / 2;
if (nums[m] < target) {
i = m + 1;
} else if (target < nums[m]) {
j = m - 1;
} else {
return m;
}
}
return -i - 1;
}
2. leftmost
public static int version52(int[] nums, int target) {
int i = 0;
int j = nums.length - 1;
int candidate = -1;
int m = 0;
while (i <= j) {
m = (i + j) / 2;
if (nums[m] < target) {
i = m + 1;
} else if (target < nums[m]) {
j = m - 1;
} else {
candidate = m;
j = m - 1;
}
}
return candidate;
}
3. leftmost(返回插入值)
public static int version6(int[] nums, int target) {
int i = 0;
int j = nums.length - 1;
int m = 0;
while (i <= j) {
m = (i + j) / 2;
if (nums[m] < target) {
i = m + 1;
} else {
j = m - 1;
}
}
return i;
}
4. 应用场景
求排名,求前任,求后任,求最近邻居,范围查询(后面两个都是基于前任和后任)
双指针
1. 双指针
(移动元素,删除数组中的重复项,有序数组的平方)
2. 滑动窗口
2.0 下面用到的API方法
用到的字符串方法 toCharArray() charAt() substring() 用到的map集合方法 put() get() getOrDefault() size() containKey()
2.1 长度最小的子数组
public static int minSubArrayLen(int target, int[] nums) {
int fastIndex = 0;
int slowIndex = 0;
int ans = Integer.MAX_VALUE;
int sum = 0;
//target的范围>=1,没有0和负数
while (fastIndex < nums.length) {
sum = sum + nums[fastIndex];
while (sum >= target) {
//保留并追踪最小的长度值
ans = Math.min(ans, fastIndex - slowIndex + 1);
sum = sum - nums[slowIndex];
slowIndex++;
}
fastIndex++;
}
//重点!!还有考虑没有一个值的情况,通过ans的赋值情况来判断
//return ans;
return ans == Integer.MAX_VALUE ? 0 : ans;
}
2.2 水果成篮
public static int test(int[] fruits) {
/*
就两重点,一个是这个题中HashMap是什么用的,起什么作用
另一个是滑动窗口类题目的通用解法
(最重要的是要有一个跟踪最大值或最小值的变量)
*/
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
int maxFruit = 0;
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < fruits.length; fastIndex++) {
//这句很重要,一行代码干了很多事,厉害
map.put(fruits[fastIndex], map.getOrDefault(fruits[fastIndex], 0) + 1);
while (map.size() > 2) {
//当集合中元素大于2时,就要开始从左到右缩小滑动窗口了
// 要逐渐增加满指针,然后减少map集合中的元素
// 这步减少map集合中的元素应该正确吧 考古:正确的
map.put(fruits[slowIndex], map.get(fruits[slowIndex]) - 1);
if (map.get(fruits[slowIndex]) == 0) {
map.remove(fruits[slowIndex]);
}
slowIndex++;
}
maxFruit = Math.max(maxFruit, fastIndex - slowIndex + 1);
}
return maxFruit;
}
2.3 最小覆盖字串
public static String test(String s, String t) {
//初始化集合
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
//现在开始定义变量
int slowIndex = 0;
int fastIndex = 0;
int valid = 0;
int start = 0;
int minLen = Integer.MAX_VALUE;
//现在开始进入第一层循环
while (fastIndex < s.length()) {
//更新窗口数据,valid+1,
char c = s.charAt(fastIndex);//获取此时的key!!
fastIndex++;
if (need.containsKey(c)) {
//下面两个连犯两次低级错误
//window.put(c,need.get(c)+1);
//window.put(c,need.getOrDefault(c,0)+1);
window.put(c,window.getOrDefault(c,0)+1);
if (need.get(c).equals(window.get(c))) valid++;
}
//现在开始进入第二层循环
while (need.size() == valid) {
//更新固定数据
if (fastIndex - slowIndex + 1 <= minLen) {//滑动窗口的常见做法,例如Math.min(minLen, fastIndex-slowIndex+1)
start = slowIndex;
minLen = fastIndex - slowIndex ;//+ 1;//这步是为什么//稍微动脑子想一想,start到start+minLen
}
//更新窗口数据,valid-1
char d = s.charAt(slowIndex);
slowIndex++;
if (need.containsKey(d)) {
if (need.get(d).equals(window.get(d))) valid--;
window.put(d,window.get(d)-1);
}
}
}
return minLen==Integer.MAX_VALUE?"":s.substring(start,start+minLen);
}
3. 收获
3.1 双指针技巧
3.2 滑动窗口的通用做法
两次判断:快指针何时移动,慢指针何时移动。慢指针的判断在快指针的判断中嵌套
一个用来跟踪窗口最大值或最小值的变量
3.3 哈希表用处
3.4 预处理思想
通过对数据的预处理,可以简化问题的复杂度,或是减少算法中的冗余操作。
预处理思想在很多算法问题中都有应用,比如在处理大量数据时先进行排序、构建索引等,以减少后续操作的复杂度。
3.5 动态调整策略
解决问题时,根据当前的情况动态调整策略是一种非常重要的思维方式。在滑动窗口算法中,就体现在根据窗口内数据的变化动态调整窗口的大小。
这种思维方式可以应用于很多算法设计中,比如在动态规划中根据前一状态来调整当前的决策,在贪心算法中根据当前情况来做出最优选择等。
螺旋矩阵
public static int[][] generateSpiralMatrix(int n, int m) {
/*
思路:
初始化一个数组,一个变量用来填充数组,四个变量为边界
进入循环,条件:是否填充完成
以左为始,右为终,不断填充,然后收缩该边界
为了处理最后的情况,加一个if判断
*/
int[][] matrix = new int[m][n];
int num = 1; // 用于填充的数字,从1开始
int top = 0, bottom = m - 1, left = 0, right = n - 1;
//while (left <= right && top <= bottom) {
while (num <= n * m) {
/*
一个循环,变量i等于最左值,条件i<=right,i就++
然后循环内给二维数组赋值
这层循环完,top++
*/
// 填充上边
for (int i = left; i <= right; i++) {
matrix[top][i] = num++;
}
top++;
// 填充右边
for (int i = top; i <= bottom; i++) {
matrix[i][right] = num++;
}
right--;
//这两个判断是用来处理最中间的情况的
// 填充下边
if (top <= bottom) { //如果上下还有没处理的行的话,那就继续填充,否则直接退出
for (int i = right; i >= left; i--) {
matrix[bottom][i] = num++;
}
bottom--;
}
// 填充左边
if (left <= right) {
for (int i = bottom; i >= top; i--) {
matrix[i][left] = num++;
}
left++;
}
}
return matrix;
}