LC数组

170 阅读5分钟

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. 矩阵二分法

    //每次比较能明确的排除一行/一列(一维数组可以直接分一半)
    //所以左上角右下角不可以用
    //两边条件相同,没有限制,无法明确的排除

74. 搜索二维矩阵 - 力扣(LeetCode)

240. 搜索二维矩阵 II - 力扣(LeetCode)

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;
    }
}