数组
二分查找
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
题解:如果等于nums[middle],返回middle;否则返回left或者low。
移除元素
在排序数组中查找target的开始位置和结束位置。
二分法不可能会漏掉正确结果的。
思路:将开始位置和结束位置的查找分为两部分代码:findLeft()和findRight();
如何循环能够得到左部的最小值和右部的最大值?
finLeft():当已经找到nums[mid]==target时,让right=mid-1,继续二分查找,重复上述步骤,若还有更新的nums[mid]==target,则更新左边界值。
findRight()同理。
关键代码:
findLeft(): findRight():
找到非负数x的平方根
1)首先是左闭右闭,所以while(left<=right)
2)搞清楚二分法是从中间开始向两边,当middle值小于Target值后,先记录此时的middle值,然后继续往右移进行试探看还有没有符合的值,因为只可能出现在大于该middle值的情况,直到left>right。和上题findRight()思路相同
有效的完全平方数
num/middle用double型 ,middle是int型 比较时会进行类型转换由int转换为double,只有当为整数时才算是平方根。
滑动窗口
有序数组的平方
数组元素是非递减的,因此平方之后最大值只有可能在数组的最左端和最右端,因此可以使用两边指针进行遍历,进行大小的比较并赋给新的数组。
长度最小的子数组⭐
使用滑动窗口的思想,外层循环控制结束位置j,内层循环控制起始位置i,看似是双层循环,但时间复杂度是o(2n)。
水果成篮
自己想法:使用backet1
和backet2
表示篮子1和篮子2;使用backet1Account
和backet2Account
分别表示两个篮子里水果的数量,内层循环将i指针向前移动。
查看代码
public int totalFruit(int[] fruits) {int backet1=fruits[0];int backet2=0;int backet1Account=0;int backet2Account=0;int result=0;int i=0;for (int j = 0; j < fruits.length; j++) {if (fruits[j]==backet1){backet1Account++;}else if (fruits[j]==backet2 ||backet2Account==0 ){backet2=fruits[j];backet2Account++;}else{while (backet1Account>0 && backet2Account>0){if (fruits[i]==backet1){backet1Account--;}else {backet2Account--;}i++;}if (backet1Account==0){backet1=fruits[j];backet1Account++;}if(backet2Account==0){backet2=fruits[j];backet2Account++;}}result=result>backet1Account+backet2Account?result:backet1Account+backet2Account;}return result;}
最优想法:不需要单独设置表示两个篮子里水果数量的参数,一旦下一个水果不在已有篮子里,i=j-1;内层循环将i指针向前移动,得到i位置后,将i位置的水果放入篮子1,当前j位置的水果放入篮子2,总的数量用下标表示。
public int totalFruit(int[] fruits) {int backet1=fruits[0];int backet2=0;int result=0;int i=0;for (int j = 0; j < fruits.length; j++) {if (fruits[j]!=backet1 && fruits[j]!=backet2){i=j-1;while(i>=1 && fruits[i]==fruits[i-1]){i--;}backet1=fruits[i];backet2=fruits[j];}result=result>(j-i+1)?result:(j-i+1);}return result;}
滑动窗口的模板
//[left,right)窗口左闭右开
int left=0;
int right=0;
while(right<s.length)
{//此时先将当前right指向的内容移入窗口移入窗口之后需要改变的变量和进行的操作right++;while(left<right && 窗口满足要求){//将当前left指向的内容移出窗口移出之后改变相关变量和进行操作left++;}
}
对于最小覆盖子串,除了使用Map外,也可以用字符的ascII码来进行计算,使用int[]存储子串中含有的字符串个数,一旦在s中发现有的话,就将int[]中对应字符的数量--,然后count--
字符串的排列、找到字符串中所有字母异位词
如果给两个字符串s和t,判断t是否为s的子串或是否s包含t的排列,用t的长度固定滑动窗口的大小,初始化将s的前t.length()个长度的字符情况存储在int数组中,int数组的大小由字符串中字符的类型决定,最大为ascii表的长度,为128。
每次循环滑动窗口向前移一位,即left++,right++,移完之后和存储t字符串情况的数组进行比较,Arrays.equal(int[] a,int[] b)。
例:567. 字符串的排列 - 力扣(LeetCode )
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
无重复字符的最长子串
对于找不重复的最长子串,可以用int[]数组是否大于1来判断,大于1时则right指向的字符子串中已经包含,此时先将left指向的字符移出,然后再left++(而不是直接移到right的位置×!!)。
例:3. 无重复字符的最长子串 - 力扣(LeetCode)
具体代码
public int lengthOfLongestSubstring(String s) {int left=0;int right=0;int count=0;int[] curr=new int[128];while (right<s.length()){if (curr[s.charAt(right)]<1){curr[s.charAt(right)]++;right++;} else if (curr[s.charAt(right)]>=1) {curr[s.charAt(left)]--;left++;}count= count>right-left?count:right-left;}return count;}
螺旋矩阵
当输入的矩阵并不一定行数列数相等时,要考虑到四种不同的情况,分别是:
-
- 行数为1行或者列数为1列 直接从左往右 或者从上往下遍历
- 行数(列数)为2的倍数且行数≤列数(列数≤行数) 遍历完整个循环之后 就可以结束 不需要后续操作
- 行数=列数,且为奇数时 只需要在循环结束后再遍历一下当前最中心的值
- 行数(列数)为奇数且行数<列数(列数<行数) 在循环结束之后再继续遍历(列数-行数+1)个中心列元素或者是再继续遍历(行数-列数+1)个中心行元素
螺旋矩阵代码
public List<Integer> spiralOrder(int[][] matrix) {int row=matrix.length;//行数int column=matrix[0].length;//列数ArrayList<Integer> list = new ArrayList<>();int x=0;//矩阵的行位置int y=0;//矩阵列位置int loop=1;//循环的次数int offset=1;//遍历的长度 每次循环一遍右边界减一int i=0,j=0;//针对一行或者一列的情况if (column==1 ){while (x<row){list.add(matrix[x++][0]);}}else if (row==1){while (y<column){list.add(matrix[0][y++]);}}//循环主体while (loop<=Math.min(row/2,column/2)){for (j=y;j<column-offset;j++){list.add(matrix[x][j]);}for (i = x;i < row-offset; i++) {list.add(matrix[i][j]);}for (;j>y;j--){//注意:j是大于y而不是大于0list.add(matrix[i][j]);}for (;i>x;i--){//注意:i是大于x而不是大于0list.add(matrix[i][j]);}loop++;//循环次数++x++;//行位置往里移动一位y++;//列位置往下移动一位offset++;//行和列中不需要遍历的个数+1}//如果存在列的个数是2的倍数且行数>=列数时,循环完正好结束,没有里层剩余,列和行反过来一样if ((column%2==0 && column<row) ||(row%2==0 && column>row)){return list;}//当里层有剩余时i=x;j=y;if ( i<column && j<row){ //因为上面while大循环中最后x++,y++ 所以需要进行一下判断if (row==column && row%2==1){//如果行数和列数相等且为奇数,那么只剩最中间一个没有遍历list.add(matrix[i][j]);} else if (row>column) {//如果行数>列数 且列数是奇数 那么中间有有一小列元素没有遍历while (i-j <=row-column){//没有遍历的个数是行数-列数+1list.add(matrix[i++][j]);}}else if (row<column){//行数列数反过来 同上while (j-i <=column-row){list.add(matrix[i][j++]);}}}return list;}
区间和
直接每次输入区间后,都需要遍历一次区间中的数据,因此时间消耗为0(m*n) m表示输入区间的次数,n为数组的元素个数
解决方法:使用前缀和,将前i个元素之和存储在一个数组中,每次输入区间之后,直接用末位置的和➖(初位置-1)的和,当初位置为0时区间和就为末位置之和。
区间和代码
import java.util.*;
public class Main{public static void main (String[] args) {Scanner scan= new Scanner(System.in);int arrLength=scan.nextInt();int[] Array=new int[arrLength];int[] p=new int[arrLength];int i=0;Array[i]=scan.nextInt();p[i]=Array[0];for(i=1;i<arrLength;i++){Array[i]=scan.nextInt();p[i]=p[i-1]+Array[i]; }while(scan.hasNextInt()){int a=scan.nextInt();int b=scan.nextInt();if (a==0){System.out.println(p[b]);}else{System.out.println(p[b]-p[a-1]);}}scan.close();}
}
开发商购买土地
其核心在于,划分要么横着一刀,要么竖着一刀,且只能是一刀。
思路:先验证横着一刀得到两个区域差的最小值:遍历每一行,在每行最后一个位置加入后计算区域差,sum-count-count,其中(sum-count)表示此一刀下面的区域,count表示一刀上面的区域,用abs(下面的区域和➖上面区域和)则表示区域差;同理再试竖着的一刀。
前缀和和优化暴力求解都是o(m*n)
暴力求解代码
public class Main{public static void main (String[] args) {Scanner scanner=new Scanner(System.in);int n=scanner.nextInt();int m=scanner.nextInt();int[][] vec=new int[n][m];int sum=0;int result=Integer.MAX_VALUE;for (int i=0;i<n ;i++ ){for (int j=0;j<m ;j++ ){vec[i][j]=scanner.nextInt(); sum+=vec[i][j];} } int count=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){count+=vec[i][j];if (j==m-1){result=Math.min(result,Math.abs(sum-count-count));} }}count=0;for(int j=0;j<m;j++){for(int i=0;i<n;i++){count+=vec[i][j];if(i==n-1){result=Math.min(result,Math.abs(sum-count-count));}}}System.out.println(result);scanner.close();}
}
数组基础总结
数组的元素是不能删除的,只能覆盖!!
二维数组在内存中并不是连续的,其实质是一维数组的一维数组,即对象中存储着一个一维数组的地址,这个一维数组中存储着每行数组的起始地址。