1.使用二分法解题,题目一般包含有序数组等条件,解题重点是左右边界一定要定义好
1.1普通二分法查找数据,分清左右区间就行
1.2二分法找重复元素范围,这个需要分左右区间,
1.3找数组中两个元素和等于指定数据。(1)先排序Arrays.sort(),(2)双指针查找
2.快慢指针问题:由于数组的元素地址是连续的,不能删除数据,只能覆盖, 所以涉及到删除元素时,使用快慢指针用快指针元素覆盖慢指针元素
3.双指针法:从数组两端向中间查找找到指定元素
4.滑动窗口法:当问题中涉及到找满足条件的子数组,就要考虑滑动窗口方法。先找到一个满足条件的窗口,然后不断往后移动,直到找完元素,找到最符合条件的窗口
5.二维数组问题
int[][] matrix matrix.length0代表行等于0,matrix[0].length0代表列等于0
(1)使用二分法找指定位置数,效率高于暴力
*给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。
如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
示例 4:
输入: nums = [1,3,5,6], target = 0
输出: 0
示例 5:
输入: nums = [1], target = 0
输出: 0*/
public class Number34 {
public int searchInsert(int[] nums, int target) {
int left=0,right=nums.length-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]>target){
right=mid-1;
}else{
return mid;
}
}
return left;
}
}
/*
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
*/
public class Number704 {
/* 思路:
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,
使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,
可要想一想是不是可以用二分法了。
思路
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。
二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?
大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
下面我用这两种区间的定义分别讲解两种不同的二分写法。
二分法第一种写法
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。
区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
class Solution {
public int search(int[] nums, int target) {
// 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return -1;
}
}
二分法第二种写法
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。
有如下两点:
while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid;
}
return -1;
}
}
*/
}
(2)使用快慢指针进行元素删除
*给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。*/
public class Number27 {
//双指针法
public int removeElement(int[] nums, int val) {
//有的同学可能说了,多余的元素,删掉不就得了。
//要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
//快慢指针
int fastIndex=0;
int slowIndex;
for(slowIndex=0;fastIndex<nums.length;fastIndex++){
if(nums[fastIndex]!=val){
nums[slowIndex]=nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
}
(3)使用滑动窗口法解决数组区间问题
*给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,
并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0*/
public class Number209 {
//滑动窗口解法
/* 最长窗口模板
for(枚举选择)
右边界
while(不符合条件)
左边界
更新结果*/
public int minSubArrayLen(int target, int[] nums) {
int left=0;
int sum=0;
//定义result为无限大的数,用于比较,找到较小的
int result=Integer.MAX_VALUE;
for(int right=0;right<nums.length;right++){
sum+=right;
while(sum>=target){
//right-left+1 就是窗口大小
result=Math.min(result,right-left+1);
//左指针往后移,不断缩小窗口,找到长度最短窗口
sum-=nums[left];
left++;
}
}
return result==Integer.MAX_VALUE?0:result;
}
}
/*在一排树中,第 i 棵树产生 tree[i] 型的水果。
你可以从你选择的任何树开始,然后重复执行以下步骤:
把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。
移动到当前树右侧的下一棵树。如果右边没有树,就停下来。
请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。
你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。
用这个程序你能收集的水果树的最大总量是多少?
示例 1:
输入:[1,2,1]
输出:3
解释:我们可以收集 [1,2,1]。
示例 2:
输入:[0,1,2,2]
输出:3
解释:我们可以收集 [1,2,2]
如果我们从第一棵树开始,我们将只能收集到 [0, 1]。
示例 3:
输入:[1,2,3,2,2]
输出:4
解释:我们可以收集 [2,3,2,2]
如果我们从第一棵树开始,我们将只能收集到 [1, 2]。
示例 4:
输入:[3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:我们可以收集 [1,2,1,1,2]
如果我们从第一棵树或第八棵树开始,我们将只能收集到 4 棵水果树。*/
public class Number904 {
public static void main(String[] args) {
int[] tree={3,3,3,1,2,1,1,2,3,3,4};
System.out.println(totalFruit(tree));
}
public static int totalFruit(int[] tree) {
//滑动窗口解法
if (tree == null || tree.length == 0) return 0;
int n = tree.length;
Map<Integer, Integer> map = new HashMap<>();
int maxLen = 0,//记录水果数
left = 0;
for (int i = 0; i < n; i++) {
//getOrDefault(Object key, V defaultValue)
//当Map集合中有这个key时,就使用这个key对应的value值,如果没有就使用默认值defaultValue
map.put(tree[i], map.getOrDefault(tree[i], 0) + 1); // 右边界
while (map.size() > 2) { // 不符合条件:水果种类大于2
map.put(tree[left], map.get(tree[left]) - 1);//缩进左边界时,减少频次。
//map.remove()移除键值对
if (map.get(tree[left]) == 0) {
map.remove(tree[left]);
} //如果值为0直接移除这个元素
//左边界右移,每计算一次,右移一次
left++;
}
maxLen = Math.max(maxLen, i - left + 1); // 更新结果
}
return maxLen;
}
}
(4)双指针法
/*给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]*/
public class Number977 {
class Solution {
//双指针法
//数组其实是有序的, 只不过负数平方之后可能成为最大数了。
//那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
//此时可以考虑双指针法了,i指向起始位置,j指向终止位置。
//定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
public int[] sortedSquares(int[] nums) {
//定义左右指针
int left=0;
int right=nums.length-1;
//创建一个辅助数组,用于存放结果
int[] result=new int[nums.length];
//因为最大数只能在两侧,所以结果从大到小倒叙加入
int index=result.length-1;
while(left<=right){
//找到最大的数,然后不断内移
if(nums[left]*nums[left]>nums[right]*nums[right]){
result[index--]=nums[left]*nums[left];
//先让left加1,再右移
++left;
}else{
result[index--]=nums[right]*nums[right];
--right;
}
}
return result;
}
}
(5)二维数组
/*在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,
输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
限制:
0 <= n <= 1000
0 <= m <= 1000*/
public class offer04 {
public static void main(String[] args) {
int[][] matrix={
{1, 4, 7, 11, 15},
{2, 5, 8, 12, 19},
{3, 6, 9, 16, 22},
{10, 13, 14, 17, 24},
{18, 21, 23, 26, 30}
};
System.out.println(findNumberIn2DArray(matrix,9));
System.out.println(findNumberIn2DArray2(matrix,17));
}
//方法1,暴力查找,遍历每一个元素,看有没有相同的。
public static boolean findNumberIn2DArray(int[][] matrix,int target){
//matrix.length==0代表行等于0,matrix[0].length==0代表列等于0
if (matrix==null || matrix.length==0 || matrix[0].length==0){
return false;
}
//定义行和列
int rows=matrix.length, columns=matrix[0].length;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (matrix[i][j]==target){
return true;
}
}
}
return false;
}
/*方法二:线性查找
由于给定的二维数组具备每行从左到右递增以及每列从上到下递增的特点,当访问到一个元素时,可以排除数组中的部分元素。
从二维数组的右上角开始查找。如果当前元素等于目标值,则返回 true。如果当前元素大于目标值,则移到左边一列。
如果当前元素小于目标值,则移到下边一行。
可以证明这种方法不会错过目标值。如果当前元素大于目标值,说明当前元素的下边的所有元素都一定大于目标值,
因此往下查找不可能找到目标值,往左查找可能找到目标值。如果当前元素小于目标值,
说明当前元素的左边的所有元素都一定小于目标值,因此往左查找不可能找到目标值,往下查找可能找到目标值。
若数组为空,返回 false
初始化行下标为 0,列下标为二维数组的列数减 1
重复下列步骤,直到行下标或列下标超出边界
获得当前下标位置的元素 num
如果 num 和 target 相等,返回 true
如果 num 大于 target,列下标减 1
如果 num 小于 target,行下标加 1
循环体执行完毕仍未找到元素等于 target ,说明不存在这样的元素,返回 false。
*/
public static boolean findNumberIn2DArray2(int[][] matrix,int target) {
// //matrix.length==0代表行等于0,matrix[0].length==0代表列等于0
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
//定义行和列长度
int rows = matrix.length, columns = matrix[0].length;
//初始化行下标为 0,列下标为二维数组的列数减 1,即右上角的数字
int row = 0, column = columns - 1;
while (row < rows && column >= 0) {
int num = matrix[row][column];
if (num == target) {
return true;
} else if (num > target) {
column--;
} else {
row++;
}
}
return false;
}
}