引申:也包含部分String的题目。String可以转化为Character[]数组进行处理。
二分法:有序数组
知识点:
- 时间复杂度:O(logn)。空间复杂度:O(1)
- 只要看到给出的数组是有序数组,都可以想一想是否可以使用二分法。
写法注意:
边界条件处理:到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?要在二分查找的过程中,保持不变量。
左闭右闭写法,target 在左区间,[left, middle - 1];在右区间,[middle + 1, right]
int left = 0;
int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) {
// 当left==right,区间[left, right]依然有效
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else {
// nums[middle] == target
return middle;
}
}
左闭右开[)写法(推荐)
while (left < right) {
// 因为left == right的时候,在[left, right)是无效的空间
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在 [middle+1, right)中
} else {
// nums[middle] == target
return middle; // 数组中找到目标值的情况,直接返回下标
}
}
例题1
https://leetcode-cn.com/problems/search-insert-position/solution/sou-suo-cha-ru-wei-zhi-by-leetcode-solution/
class Solution {
public int searchInsert(int[] nums, int target) {
int n = nums.length;
int left = 0, right = n - 1, ans = n;
while (left <= right) {
// 优化写法,位运算。数学意义:右移一位相当于除2,右移n位相当于除以2的n次方。
// 同时避免(left + right)/2,防止溢出
int mid = ((right - left) >> 1) + left;
if (target <= nums[mid]) {
ans = mid;
right = mid - 1;
} else {
// > nums[mid],取mid+1
left = mid + 1;
}
}
return ans;
}
}
双指针
知识点:
- 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。在数组和链表的操作中是非常常见的。
双指针的例题,都是通过快慢指针进行操作,然后命中。最多就是命中条件不同,而有了不同的题型。
例题1
https://leetcode-cn.com/problems/remove-element/solution/yi-chu-yuan-su-by-leetcode/
public int removeElement(int[] nums, int val) {
int i = 0;
for (int j = 0; j < nums.length; j++) {
if (nums[j] != val) {
nums[i] = nums[j];
i++;
}
}
return i;
}
例题2
https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/
排序加双指针:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
// 在-1,-1,-1,0……的场景下。第一个-1时没有限制
// first可以为-1,second与third都可以为-1。此时认为已经将-1开头的所有工况都遍历了
// 今后就不允许再以-1开头
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);
}
}
}
return ans;
}
}
例题3
https://leetcode-cn.com/problems/container-with-most-water/solution/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/
两个指针指向的数字中较小值∗指针之间的距离。移动数字较小的那个指针。
public class Solution {
public int maxArea(int[] height) {
int l = 0, r = height.length - 1;
int ans = 0;
while (l < r) {
int area = Math.min(height[l], height[r]) * (r - l);
ans = Math.max(ans, area);
if (height[l] <= height[r]) {
++l;
}
else {
--r;
}
}
return ans;
}
}
滑动窗口
知识点:
- 可伸缩的滑动窗口,不断的调节窗口的起始index和终止index,判断能否当前窗口能否命中条件。
- 与双指针的思路类似,也是通过2个指针在一次遍历中完成需要两次遍历的工作。滑动窗口也可以理解为双指针法的一种。
主要确定三点:
- 窗口内是什么?命中条件是什么?
- 如何移动窗口的起始位置start?
- 如何移动窗口的结束位置end?一般是以end<n来判断的,滑动窗口最后一位到达终点即为结束。
例题1
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int n = nums.length;
if (n == 0) {
return 0;
}
int ans = Integer.MAX_VALUE;
int start = 0, end = 0;
int sum = 0;
while (end < n) {
// 滑动窗口end坐标到达终点即为结束
sum += nums[end];
while (sum >= s) {
ans = Math.min(ans, end - start + 1);
sum -= nums[start];
start++;
}
end++;
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}
模拟
知识点:
- 模拟上下左右移动:int[][] directions = { {0, 1}, {1, 0}, {0, -1}, {-1, 0}};
- 控制方向:directionIndex = (directionIndex + 1) % 4;
- 查询坐标:假如m行n列。获取行:curNum/n;获取列:curNum%n
例题1
class Solution {
public int[][] generateMatrix(int n) {
int maxNum = n * n;
int curNum = 1;
int[][] matrix = new int[n][n];
int row = 0, column = 0;
int[][] directions = {
{
0, 1}, {
1, 0}, {
0, -1}, {
-1, 0}}; // 右下左上
int directionIndex = 0;
while (curNum <= maxNum) {
matrix[row][column] = curNum;
curNum++;
int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];
if (nextRow < 0 || nextRow >= n || nextColumn < 0 || nextColumn >= n || matrix[nextRow][nextColumn] != 0) {
directionIndex = (directionIndex + 1) % 4; // 顺时针旋转至下一个方向
}
row = row + directions[directionIndex][0];
column = column + directions[directionIndex][1];
}
return matrix;
}
}