1. 一维二分查找
[模板题](704. 二分查找 - 力扣(LeetCode))
-
二分法的两种写法
- 左闭右闭(判断条件<=;[mid]在判断相等时候取到过,所以更换区间要避开mid)
class Solution { public int search(int[] nums, int target) { int left=0; int right=nums.length-1; while(left<=right){ int mid=(left+right)/2; if(nums[mid]==target) return mid; else if(nums[mid]<target) left=mid+1; else right=mid-1; } return -1; } }- 左闭右开(左边界避开mid,右边界可取mid)
class Solution { public int search(int[] nums, int target) { int left=0; int right=nums.length; while(left<right){ int mid=(left+right)/2; if(nums[mid]==target) return mid; else if(nums[mid]<target) left=mid+1; else right=mid; } return -1; } }
[相关1](35. 搜索插入位置 - 力扣(LeetCode))
-
模板题变形,返回时判断是插入到当前位置还是往后插入
public int searchInsert(int[] nums, int target) { int left=0; int right=nums.length; int mid=0; while(left<right){ mid=(left+right)/2; if(nums[mid]==target) return mid; else if(nums[mid]<target) left=mid+1; else right=mid; } return nums[mid]>target? mid:mid+1; } }
[相关2](34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode))
-
左右分别进行二分查找,当找到符合条件值的时候,判断是在找左边界还是再找右边界,更新结果,继续缩小范围,注意循环不变量,右边是开区间就要一直是开区间
public int[] searchRange(int[] nums, int target) { int [] res=new int[]{-1,-1}; res[0]=help ( nums, target, true); res[1]=help ( nums, target, false); return res; } public int help(int [] nums, int target,boolean bool){ int res=-1; int left=0, right=nums.length,mid; while(left<right){ mid=(left+right)/2; if(target<nums[mid]) right=mid; else if(target>nums[mid]) left=mid+1; else{ res=mid; //逐渐逼近 if(bool) right=mid; else left=mid+1; } } return res; } }
[相关3](69. x 的平方根 - 力扣(LeetCode))
-
注意过程中用除法,乘法越界
public int mySqrt(int x) { int l=1; int r=x; int mid=0; while(l<=r){ mid=(l+r)/2; if(x/mid==mid) return mid; else if(x/mid<mid) r=mid-1; else l=mid+1; } } }
[相关4](367. 有效的完全平方数 - 力扣(LeetCode))
-
过程中用除法,注意right可以直接缩小到num/2+1
public boolean isPerfectSquare(int num) { int left = 1, right = num / 2 + 1; while (left <= right) { // int mid = left + ((right - left) >> 1); int mid=(left+right)/2; if (mid > num / mid) { right = mid - 1; } else if (mid < num / mid) { left = mid + 1; } else if (mid == num / mid){ return num % mid == 0; } } return false; } }
2.移除数组元素(位置元素双指针)
[模板题](27. 移除元素 - 力扣(LeetCode))
- 对所有元素都要进行判断,所以快指针正常遍历元素(for循环中的i),慢指针指向位置,将符合条件的元素放到慢指针的位置
class Solution {
public int removeElement(int[] nums, int val) {
int slow=0;// position
for(int i=0;i<nums.length;i++){
if(nums[i]!=val){
nums[slow]=nums[i];
slow++;
}
}
return slow;
}
}
[相关1](26. 删除有序数组中的重复项 - 力扣(LeetCode))
- 慢指针slow,快指针i,遇到符合条件的值放到slow的下一个位置
class Solution {
public int removeDuplicates(int[] nums) {
int slow=0;
for(int i=1;i<nums.length;i++){
if(nums[slow]!=nums[i]){
nums[++slow]=nums[i];
}
}
return slow+1;
}
}
[相关2](283. 移动零 - 力扣(LeetCode))
- slow指向位置,i遍历元素,碰到符合条件的元素放到i,最后再进行末尾置0否则会逻辑混乱,循环结束后i指向符合条件的最后一个位置
class Solution {
public void moveZeroes(int[] nums) {
int index=0;
for(int i=0;i<nums.length;i++){
if(nums[i]!=0){
nums[index]=nums[i];
index++;
}
}
for(int i=index;i<nums.length;i++){
nums[i]=0; }
}
}
[相关3](844. 比较含退格的字符串 - 力扣(LeetCode))
- 快指针遍历每一个数组元素,慢指针string builder,遇到string用stringbuilder操作,直接再string上操作太困难
class Solution {
public boolean backspaceCompare(String s, String t) {
s = removeBackspace(s);
t = removeBackspace(t);
return s.equals(t);
}
private String removeBackspace(String str){
StringBuilder sb = new StringBuilder();
for(char ch : str.toCharArray()){
if('#'!= ch){
sb.append(ch);
}else if(sb.length() > 0){
sb.deleteCharAt(sb.length() -1);
}
}
return sb.toString();
}
}
[相关4](977. 有序数组的平方 - 力扣(LeetCode))
- 另一种双指针,指针位置,两个指针分别指向首位元素,判断是否符合条件,操作后移动指针
class Solution {
public int[] sortedSquares(int[] nums) {
int[] res=new int[nums.length];
int l=0;
int r=nums.length-1;
for(int i=nums.length-1;i>=0;i--){
int ll=nums[l]*nums[l];
int rr=nums[r]*nums[r];
if(ll>=rr){
res[i]=ll;
l++;
}else{
res[i]=rr;
r--;
}
}
return res;
}
}
3. 双指针滑窗
模板
for(右边界遍历元素){
逐个操作直到符合条件(和达到target,出现重复元素)->备选答案
while(依旧符合条件){
移动左边界;
随左边界移动修改结果;
}
}
[模板题](209. 长度最小的子数组 - 力扣(LeetCode))
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left=0 ;
int res=nums.length+1;
int sum=0;
for(int right=0;right<nums.length;right++){
if(sum<target) sum+=nums[right];
while(sum>=target){
res=Math.min(right-left+1,res);
sum-=nums[left++];
}
}
return res==nums.length+1? 0:res;
}
}
[相关1](76. 最小覆盖子串 - 力扣(LeetCode))
-
子串,子数组(不改变位置,只是拿来一部分)考虑用滑窗
-
统计字母,数组hash
-
先对t进行操作,初始化hash
-
for循环滑窗
-
需要判断滑窗中是否已经包含了t/备选答案,
-
int num=t.length;
-
滑窗右端是i,对于每一个s(i);hash[s(i)]--;
- hash[s(i)]<0;无关字母/t中字母但是冗余
- hash[s(i)]>=0 不冗余的t中字母,num--
- 当num==0之后,表示找到备选答案,之后每次移动滑窗,滑窗中必定包含答案,移动滑窗只是一个优化的过程
-
当num==0时,表示当前【i,j】是备选答案
-
更新左边界:while(i,j中包含备选答案)j++;
-
当hash[s(j)]<0&&num==0两个条件都符合时才移动左边界
- hash[s(j)]<0,左边界字母不再t中/在t中且冗余
- ++hash[s[j++]]; j所指字母出现在t中且数量正好
-
-
class Solution {
public String minWindow(String s, String t) {
if (s.length() < t.length()) return "";
int[] hash = new int[128];
char[] schar = s.toCharArray();
char[] tchar = t.toCharArray();
for (char c : tchar) ++hash[c];
String res = "";
int nums = tchar.length;
for (int i = 0, j = 0; i < s.length(); ++i) {
--hash[schar[i]];
if(hash[schar[i]] >= 0) --nums;
while (nums == 0 && hash[schar[j]] < 0)
++hash[schar[j++]];
if (nums == 0) {
if (res.equals("") || res.length() > i - j + 1)
res = s.substring(j, i + 1);
}
}
return res;
}
}
[相关2](3. 无重复字符的最长子串 - 力扣(LeetCode))
- 思路同上 int[] hash记录出现次数
- while判断条件和上题目不同,移动左边界的目的是找到重复字符的下一个字符
class Solution {
public int lengthOfLongestSubstring(String s) {
int res=1;
//int n=s.size();
int j=0;
int []hash=new int[128];
for(int i=0;i<s.length();i++){
hash[s.charAt(i)]++;
while(hash[s.charAt(i)]>1){
--hash[s.charAt(j++)];
}
res=Math.max(res,i-j+1);
}
return s.length()==0?0:res;
}
}
4. 螺旋矩阵
- 确定转几圈
- 初始化 startX, startY, offset
while(loop>0){
int i = startX;
int j = startY;
//四个方向循环
//更新
startX++;
startY++;
offSet++;
loop--;
}
正方形处理中心点
矩形根据长款关系处理中心部分
//针对列大于行且,行不是偶数的时候;
if ( (m > n && n%2!=0) ){
for (int i = n/2; i < m-n/2; i++) {
list.add(matrix[n/2][i]);
}
}
//针对行大于列且,列不是偶数的时候;
if ( (m < n && m%2 !=0)){
for (int i = m/2; i < n-m/2; i++) {
list.add(matrix[i][m/2]);
}
}
[模板题](54. 螺旋矩阵 - 力扣(LeetCode))
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List list = new LinkedList();
int startX = 0;
int startY = 0;
int offset = 1;
int loop =Math.min( matrix.length,matrix[0].length )/ 2;//行列中选择小的进行循环
int n = matrix.length;
int m = matrix[0].length;
while (loop > 0) {
int i = startX;
int j = startY;
//上边的边从左到右;
for (; j <m - offset; j++) {
list.add(matrix[i][j]);
}
//右边的列从上到下;
for (; i < n - offset; i++) {
list.add(matrix[i][j]);
}
//下边的边从右到左;
for (; j > startY; j--) {
list.add(matrix[i][j]);
}
//左边的边从下到上;
for (; i > startX; i--) {
list.add(matrix[i][j]);
}
loop--;
offset += 1;
startX++;
startY++;
}
if (n == m &&n % 2 ==1){
list.add(matrix[n/2][n/2]);
}
//针对列大于行且,行不是偶数的时候;
if ( (m > n && n%2!=0) ){
for (int i = n/2; i < m-n/2; i++) {
list.add(matrix[n/2][i]);
}
}
//针对行大于列且,列不是偶数的时候;
if ( (m < n && m%2 !=0)){
for (int i = m/2; i < n-m/2; i++) {
list.add(matrix[i][m/2]);
}
}
return list;
}
}
[相关1](59. 螺旋矩阵 II - 力扣(LeetCode))
class Solution {
public int[][] generateMatrix(int n) {
int[][]res=new int[n][n];
int num=1;
int startX=0;
int startY=0;
int offSet=1;
int loop=n/2;
while(loop>0){
int i=startX;
int j=startY;
for(;j<n-offSet;j++){
res[i][j]=num++;
}
for(;i<n-offSet;i++){
res[i][j]=num++;
}
for(;j>startY;j--){
res[i][j]=num++;
}
for(;i>startX;i--){
res[i][j]=num++;
}
startX++;
startY++;
offSet++;
loop--;
}
if(n%2!=0){
res[n/2][n/2]=num;
}
return res;
}
}
[相关2](剑指 Offer 29. 顺时针打印矩阵 - 力扣(LeetCode))
- 另一种最后中心处理代码写法
- 注意越界处理
int m = matrix.length;
int n = 0;//为了防止m=0时,matrix[0].length索引越界
if (m != 0) {
n = matrix[0].length;
}
int res[] = new int[m * n];
代码
class Solution {
public int[] spiralOrder(int[][] matrix) {
int m = matrix.length;
int n = 0;//为了防止m=0时,matrix[0].length索引越界
if (m != 0) {
n = matrix[0].length;
}
int res[] = new int[m * n];
int loop=Math.min(m,n)/2;
int offset=1;
int startX = 0;
int startY = 0;
int count=0;
int mid = Math.min(m, n) % 2;
while(loop>0){
int i=startX;
int j=startY;
for(;j<n-offset;j++) res[count++]=matrix[i][j];
for(;i<m-offset;i++) res[count++]=matrix[i][j];
for(;j>startY;j--) res[count++]=matrix[i][j];
for(;i>startX;i--) res[count++]=matrix[i][j];
loop--;
startX++;
startY++;
offset++;
}
if (mid == 1) {
if (m == n) {
int k = m / 2;
res[count++] = matrix[k][k];
}
else if (m > n) {
for (int i = startX; i < m - offset + 1; i++) {
res[count++] = matrix[i][startY];
}
}
else {
for (int j = startY; j < n - offset + 1; j++) {
res[count++] = matrix[startX][j];
}
}
}
return res;
}
}
5. 矩阵二分法
//每次比较能明确的排除一行/一列(一维数组可以直接分一半)
//所以左上角右下角不可以用
//两边条件相同,没有限制,无法明确的排除
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
//每次比较能明确的排除一行/一列(一维数组可以直接分一半)
//所以左上角右下角不可以用
//两边条件相同,没有限制,无法明确的排除
int startX=0;
int startY=matrix[0].length-1;
boolean flag=false;
while(startX<matrix.length&&startY>=0){
if(matrix[startX][startY]==target) return true;
if(matrix[startX][startY]>target){
startY--;
}else startX++;
}
return flag;
}
}