数据结构与算法实战

文章目录

一. 二分查找

一般而言,防止溢出 mid = l + (r-l)/2;其次 为了防止死循环 mid = (l+r+1)/2 left < right 还是 left <= right

0. 二段性:模板题

在这里插入图片描述

1. 山峰数组的顶部

添加链接描述
在这里插入图片描述

  • 求左边红色区域
    条件:arr[i]>arr[i-1]
class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        int l = 0;
        int r = arr.length - 1;
        while(l<r){
            int mid = (l+r+1)/2;
            if(arr[mid]>arr[mid-1]){
                l = mid;
            }else{
                r = mid - 1;
            }
        }
        return l;
    }
}
  • 求右边绿色区域
class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        int l = 0;
        int r = arr.length - 1;
        while(l<r){
            int mid = (l+r)/2;
            if(arr[mid]>arr[mid+1]){
                r = mid;
            }else{
                l = mid + 1;
            }
        }
        return l;
    }
}

2. 狒狒吃香蕉

添加链接描述
在这里插入图片描述
求的是满足条件的最小值,绿色区域

class Solution {
    public int minEatingSpeed(int[] piles, int h) {
        Arrays.sort(piles);
        int len = piles.length;
        if(len==h) return piles[len-1];
        int left = 1;
        int right = piles[len-1];
        //求的是左边界 k最小
        while(left<right){
            int mid = (left+right)>>1;
            if(check(piles,mid,h)){
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;

    }
    public boolean check(int[] piles,int mid,int h){
        int sum = 0;
        for(int num:piles){
            sum += (num-1)/mid + 1;
        }
        if(sum<=h) return true;
        else return false;
    }
}

3. 按权重生成随机数

添加链接描述
在这里插入图片描述

前缀和 + 二分
题意比较难懂,暴力法也可以写,时间复杂度太大

  • 二分
class Solution {
    int[] prev;
    int size;
    private final static Random RND = new Random();
    public Solution(int[] w) {
        prev = new int[w.length];
        prev[0] = w[0];
        for(int i=1;i<prev.length;i++){
            prev[i] = prev[i-1] + w[i];
        }
        this.size = prev[w.length-1];
    }
    
    public int pickIndex() {
        int num = RND.nextInt(size) + 1;
        return binarySearch(num);
    }
    //右边界
    public int binarySearch(int num){
        int l = 0;
        int r = prev.length-1;
        while(l<r){
            int mid = (r-l)/2 + l;
            if(prev[mid]>=num){
                r = mid;
            }else{
               l = mid +1;
            }
        }
        return l;
    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(w);
 * int param_1 = obj.pickIndex();
 */
  • 暴力法
class Solution {
    private int[] w;
    private int size = 0;
    private static final Random RND = new Random();
    public Solution(int[] w) {
        this.w = w;
        for(int num: w){
            size += num;//求和
        }
    }
    
    public int pickIndex() {
        int base = RND.nextInt(size);
        int sum = 0;
        for(int i=0;i<size;i++){
            sum += w[i];
            if(sum>base){
                return i;
            }
        }
        return 0;
    }

}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(w);
 * int param_1 = obj.pickIndex();
 */

4. 查找插入位置

添加链接描述
在这里插入图片描述
分析: 难点在于不存在时,返回什么;
首先,while判断条件必须取等号,left和right可能在相等时,取到target;
其次,若找不到结果,必然会进入left==right循环,mid,left,right三者相等。如果nums[mid]>target,即target会在mid的下一位(mid+1);nums[mid]<target,即target会在mid的位置(mid)。故返回left

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while(left<=right){
            int mid = (right-left)/2+left;
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]>target){
                right = mid-1;
            }else if(nums[mid]<target){
                left = mid + 1;
            }
        }
        return left;
    }
}

5. 求平方根

添加链接描述
在这里插入图片描述

  • 解法一:
    变成二段性问题处理,分类讨论,缩小范围至x/2,避免溢出
class Solution {
    public int mySqrt(int x) {
        if(x<=1) return x;
        if(x<=3) return 1;
        if(x==4) return 2;
        int l = 1;
        int r = x/2;//由数学知识得当x>4时,x/2的平方一定比x大
        while(l<r){//求左区间
            int mid = (l+r+1)/2;
            if((long)mid*mid<=x){
                l = mid;
            }else if((long)mid*mid>x){
                r = mid-1;
            }

        }
        return l;
    }
}
  • 解法二:
class Solution {
    public int mySqrt(int x) {
        if(x==0) return 0;
        int l = 1;
        int r = x;
        while(l<r){
            int mid = l+(r-l)/2+1;//必须写成减 避免溢出
            if((long)mid*mid>x){
                r = mid - 1;
            }else if((long)mid*mid<x){
                l = mid ;
            }else{
                return mid;
            }
        }
        return l;
    }
}
  • 解法三:牛顿迭代
class Solution {
    public int mySqrt(int x) {
        if(x==0) return 0;
        double x0=x,C=x;
        while(true){
            double xi = 0.5 * (x0+C/x0);
            if(Math.abs(xi-x0)<1e-7){
                break;
            }
            x0 = xi;
        }
        return (int)x0;

    }
}

6. 在排序数组中查找数字 I

添加链接描述
在这里插入图片描述

  • 解法1:1次二分查找
class Solution {
    public int search(int[] nums, int target) {
        int count = 0;
        int start = binarySearch(nums,target);
        while(start<nums.length&&nums[start++]==target){
            count++;
        }
        return count;
    }
    public int binarySearch(int[] nums,int target){
        //二段性右边界
        int left = 0;
        int right = nums.length-1;
        while(left<right){
            int mid = (left+right)/2;
            if(nums[mid]>=target){
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }
}
  • 解法2:2次二分查找
class Solution {
    public int search(int[] nums, int target) {
        if(nums.length==0) return 0;
        int start = binarySearch(nums,target);
        int end = binarySearch(nums,target+1);
        if(end==nums.length-1&&nums[end]==target){
            return end-start+1;
        }else{
            return end-start;
        }
    }
    public int binarySearch(int[] nums,int target){
        //二段性右边界
        int left = 0;
        int right = nums.length-1;
        while(left<right){
            int mid = (left+right)/2;
            if(nums[mid]>=target){
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;
    }
}

7. 0~n-1中缺失的数字

添加链接描述
在这里插入图片描述

class Solution {
    //二分法
    //考虑缺少n的情况
    public int missingNumber(int[] nums) {
        int left = 0;
        int right = nums.length-1;
        while(left<right){
            int mid = (left+right) >> 1;
            if(nums[mid]!=mid){
                right = mid ;
            }else{
                left = mid+1;
            }
        }
        return left == nums.length-1&&nums[left]==left?left+1:left;
    }
}

二. 整数

1. 模拟技巧类

整数除法

添加链接描述
在这里插入图片描述

  • 解法思路一: 减数倍增法模拟
    在这里插入图片描述
    考虑特殊情况,最大值或最小值
class Solution {
    public int divide(int a, int b) {
        //设a/b=ans
        //当a取最小
        if(a == Integer.MIN_VALUE){
            if(b==1){
                return Integer.MIN_VALUE;
            }
            if(b == -1){
                return Integer.MAX_VALUE;
            }
        }
        //当b最小
        if(b == Integer.MIN_VALUE){
            return a == Integer.MIN_VALUE ? 1 : 0;
        }
        //当a为0
        if(a == 0){
            return 0;
        }
        //设a/b=ans
        //把正数变负数(处理),负数变正数会越界
        boolean flag = true;
        if(a>0){
            a=-a;
            flag = !flag;
        }
        if(b>0){
            b = -b;
            flag = !flag;
        }
        //进行处理:a/b = ans
        int res = 0;
        while(a<=b){
            int base = 1;//b扩大的倍数
            int val = b;//b扩大后的值
            while(val >= Integer.MIN_VALUE >> 1  && a <= val<<1 ){//防止val + 满足a的条件
                base += base;//b翻倍的倍数
                val += val;//值翻倍
            }
            res += base;
            a -= val;//a经过一轮循环后得到的值
        }
        return flag?res:-res;
    }


}

2. 位运算相关

二进制加法

添加链接描述
在这里插入图片描述
竖式运算,从字符串的最后一位加法开始计算,保留进位carry
小技巧:异或运算 ^ 两个为1,结果为0;否则为1
与运算 & 两个为1,结果才会为1

class Solution {
    //利用与&和异或^运算
    public String addBinary(String a, String b) {        
        int lenA = a.length();
        int lenB = b.length();
        StringBuilder sb = new StringBuilder();
        int n = Math.max(lenA,lenB);//求最大长度
        int temp=0,carry=0;
        for(int i=0;i<n;i++){
            int x = i<lenA ? (a.charAt(lenA-i-1)-'0') : 0;
            int y = i<lenB ? (b.charAt(lenB-i-1)-'0') : 0;
            temp = x ^ y ^ carry;//没有进位的结果
            if(x == 0 || y==0){
                carry = (x^y) & carry;
            }else{
                carry = 1;//两个数为1
            }
            sb.append( (char) (temp + '0'));
        }
        if(carry>0){
            sb.append('1');
        }
        return sb.reverse().toString();//反转 转字符串
    }
}

前 n 个数字二进制中 1 的个数

添加链接描述
在这里插入图片描述

class Solution {
    public int[] countBits(int n) {
        int[] res = new int[n+1];
        for(int i=0;i<=n;i++){
            res[i] = countOne(i);
        }
        return res;
    }
    public int countOne(int num){
        int count = 0;
        while(num>0){
            num = (num-1) & num;
            count++;
        }
        return count;
    }
}

只出现一次的数字

添加链接描述
在这里插入图片描述
利用位运算实现十进制和二进制的转换

class Solution {
    public int singleNumber(int[] nums) {
        //32位数组
        int[] bitsmap = new int[32];
        //每个数变为二进制,范围为32位
        for(int num:nums){
            for(int i=0;i<32;i++){
                if( ( (num >>i ) & 1 )== 1){
                    bitsmap[i]++;
                }
            }
        }
        //对每个值对3取模,要么是0 要么是1
        int res =0;
        for(int i=0;i<32;i++){
            if(bitsmap[i]%3 == 1){
                res += (1<<i);//二进制变为十进制
            }
        }
        return res;
    }
}

单词长度的最大乘积

添加链接描述
在这里插入图片描述

比较字符串是否含有相同元素的技巧:位运算
用十进制数组,用 | 或运算,设置对应的二进制的位数为1.记录保留数组
然后再将两个数进行 与&运算,只有全部不相同才会出现1

class Solution {
    public int maxProduct(String[] words) {
        int res = 0;
        int len = words.length;
        int[] nums = new int[len];
        int index = 0;
        //把字符串变成二进制,比如 acd == 1101
        for(String word:words){
            for(int i=0;i<word.length();i++){
                char c = word.charAt(i);
                nums[index] = nums[index] | (1 << (c-'a'));
            }
            index++;
        }
        //位与运算:两个互不相同意味着不可能出现两个1,也就是结果必须为0
        for(int i=0;i<len;i++){
            for(int j=i;j<len;j++){
                if( (nums[i] & nums[j]) == 0){
                    res = Math.max(words[i].length()*words[j].length(),res);
                }
            }
        }
        return res;
    }

}

3. 大数的加减乘除

添加链接描述

大数相加

添加链接描述

class Solution {
    //竖式运算
    public String addStrings(String num1, String num2) {
        StringBuilder res = new StringBuilder();
        int i = num1.length()-1;
        int j = num2.length()-1;
        int carry = 0;//进位
        while(i >= 0 || j>=0){
            //i或者j存在就取其值 不在就为0
            int n1 = i>=0 ? num1.charAt(i) - '0' : 0;
            int n2 = j>=0 ? num2.charAt(j) - '0' : 0;
            int temp = n1 + n2 + carry;
            carry = temp / 10;
            res.append(temp % 10);
            i--;
            j--;
        }
        //最后一位是否有仅为
        if(carry == 1){res.append(1);}
        return res.reverse().toString();
    }
}

大数相减

  • 保证大数减小数
  • 减法不同于加法需要借位
    num=n1-n2-borrow,若出现num为负数就说明需要借位,num+10,borrow=1
  • 最终结果可能有前置0,需要去掉,特殊情况两数相等不能去掉全部0
/**
 * 大数相减
 */
public class Solution17 {
    public static void main(String[] args) {
        String num1 = "120";
        String num2 = "220";
        System.out.println(substractString(num1,num2));
    }
    public static String substractString(String num1,String num2){
        char sign = '+';
        //保证大数减小数
        if(!compare(num1,num2)){
            sign = '-';
            String temp = num2;
            num2 = num1;
            num1 = temp;
        }
        int len1 = num1.length()-1;
        int len2 = num2.length()-1;
        int borrow = 0;//借位
        StringBuilder res = new StringBuilder();
        while(len1>=0||len2>=0){
            int n1 = len1>=0 ? num1.charAt(len1)-'0' : 0;
            int n2 = len2>=0 ? num2.charAt(len2)-'0' : 0;
            int num = n1-n2-borrow;
            if(num<0){//需要借位
                borrow = 1;
                num += 10;
            }
            res.append(num);
            len1--;
            len2--;
        }
        res.reverse().toString();//反向
        //去前置0
        int index = 0;
        while(index<res.length()&&res.charAt(index)=='0'){
            index++;
        }
        //特殊情况:结果为0
        if(index==res.length()){
            return "0";
        }
        if(sign=='+'){
            return res.substring(index);
        }else{
            return sign+res.substring(index);
        }
    }
    public static boolean compare(String num1,String num2){
        int len1 = num1.length();
        int len2= num2.length();
        if(len1<len2){
            return false;
        }else if(len1>len2){
            return true;
        }else{
            return num1.compareTo(num2)>0;//字符串形式的数字大小比较
        }
    }
}

大数相乘

添加链接描述

  • 解法一
    在这里插入图片描述
  • m位数*n位数最多m+n位,开一个数组保留相乘的结果
  • 依次处理相乘后的结果
  • 去掉前置0
class Solution {
    public String multiply(String num1, String num2) {
        if(num1.equals("0") || num2.equals("0")) return "0";
        char[] c1 = num1.toCharArray();
        char[] c2 = num2.toCharArray();
        int len1 = num1.length();
        int len2 = num2.length();
        int[] value = new int[len1+len2];//开一个数组保存结果
        //计算第一次结果
        for(int i=len1-1;i>=0;i--){
            for(int j=len2-1;j>=0;j--){
                int num = (c1[i]-'0')*(c2[j]-'0');
                value[len1-i-1+len2-j-1] += num;
            }
        }
        //处理进位
        for(int i=0;i<value.length-1;i++){
            value[i+1] += value[i]/10;
            value[i] = value[i]%10;
        }
        //去掉前置0
        int index = value.length-1;
        while(value[index]==0){
            index--;
        }
        StringBuilder res = new StringBuilder();
        while(index>=0){
            res.append(value[index]);
            index--;
        }
        return res.toString();
    }
}

大数相除

处理起来很麻烦,且除法不能整除,模拟除法可以参考下题
添加链接描述

三. 数组

1. 双指针法

数组中和为 0 的三个数 (三数之和)

添加链接描述
在这里插入图片描述双指针,去重

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for(int i=0;i<nums.length;i++){
            if(i>=1 && nums[i] == nums[i-1]) continue;//去重
            int left = i+1;
            int right = nums.length - 1;
            while(left<right){
                int sum = nums[i] + nums[left] + nums[right];
                if(sum == 0){
                    res.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    while(left<right && nums[left] == nums[left+1]) {left++;}
                    while(left<right && nums[right] == nums[right-1]){right--;}
                    left++;
                    right--;
                }else if(sum>0){
                    right--;
                }else if(sum<0){
                    left++;
                }
            }
        }
        return res;
    }
}

和为s的两个数字

添加链接描述
在这里插入图片描述
两数之和升级版,数组排序好,且答案不唯一

在这里插入代码片

移动零

在这里插入图片描述

class Solution {
    public void moveZeroes(int[] nums) {
        if(nums.length==1) return;
        int pointer = 0;
        for(int i=0;i<nums.length;i++){
            if(nums[i]!=0){
                int temp = nums[i];
                nums[i] = nums[pointer];
                nums[pointer] = temp;
                pointer++;
            }
        }
        return;

    }
}

盛最多水的容器

在这里插入图片描述

class Solution {
    public int maxArea(int[] height) {
        int i = 0;
        int j = height.length - 1;
        int res = 0;
        while(i<j){
            res = height[i]<height[j] ? Math.max(res,(j-i)*height[i++]) : 
            Math.max(res,(j-i)*height[j--]);
        }
        return res;
    }
}

2. 滑动窗口

和大于等于 target 的最短子数组 (最小滑动窗口)

添加链接描述
在这里插入图片描述 方法一:最小滑动窗口,时间复杂度o(n)

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int len = nums.length;
        int left = 0;
        int right = 0;
        int sum = 0;
        int res = Integer.MAX_VALUE;
        while(right<len){
            sum += nums[right];
            while(sum>=target){
                res = Math.min(res,right-left+1);
                sum -= nums[left];
                left++;
            }
            right++;
        }
        return res == Integer.MAX_VALUE ? 0:res;
    }
}

方法二:前缀和+二分,时间复杂度o(nlogn)

乘积小于 K 的子数组 (最小滑动窗口)

添加链接描述
在这里插入图片描述
最小滑窗:当满足条件左边压缩
区别于其他的点,在于如何统计数目,固定右端点,开始计数

class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {
        if(k==0||k==1) return 0;
        int left = 0;
        int right = 0;
        int len = nums.length;
        int res = 0;
        int sum = 1;
        while(right<len){
            sum = sum*nums[right];
            while(sum>=k){
                sum /= nums[left];
                left += 1;
            }
            res += right-left+1;//固定右边 有多少个符合条件的数组
            right += 1;
            
        }
        return res;
    }
}

值和下标之差都在给定的范围内 (小于某长度的滑窗)

添加链接描述

3. 前缀和

和为k的子数组

添加链接描述
在这里插入图片描述
区间和,然后求差 : 前缀和+哈希优化(二数之和的思想)

class Solution {
    public int subarraySum(int[] nums, int k) {
        int[] prev = new int[nums.length+1];
        prev[0] = 0;
        for(int i=1;i<nums.length+1;i++){
            prev[i] = prev[i-1] + nums[i-1];
        }
        int res = 0;
        Map<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<nums.length+1;i++){
            if(map.containsKey(prev[i]-k)){
                res += map.get(prev[i]-k);
            }
            map.put(prev[i],map.getOrDefault(prev[i],0)+1);
        }
        return res;
    }
}

0和1个数相同的子数组

添加链接描述
在这里插入图片描述
把0变成-1后,题目转化为求和为0的子数组: 前缀和+哈希表优化

  • 如何求长度:
    求的是最长长度,因此只需要保存第一次出现的索引。
class Solution {
    public int findMaxLength(int[] nums) {
        for(int i=0;i<nums.length;i++){
            if(nums[i]==0){
                nums[i] = -1;
            }
        }
        int res = 0;
        int[] prev = new int[nums.length+1];
        prev[0] = 0;
        for(int i=1;i<nums.length+1;i++){
            prev[i] = prev[i-1] + nums[i-1];
        }
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0,0);
        for(int i=1;i<nums.length+1;i++){
            if(map.containsKey(prev[i])){
                res = Math.max(res,i-map.get(prev[i]));
            }else{
                map.put(prev[i],i);
            }
        }
        return res;
    }
}

左右两边子数组的和相等

添加链接描述
在这里插入图片描述
较简单,前缀和

class Solution {
    public int pivotIndex(int[] nums) {
        int len = nums.length;
        int[] prev = new int[len+1];
        prev[0] = 0;
        for(int i=1;i<len+1;i++){
            prev[i] = nums[i-1] + prev[i-1]; 
        }
        int sum = 0;//数组和
        for(int i=0;i<len;i++){
            sum += nums[i];
        }
        for(int i=0;i<len;i++){
            if(prev[i]*2+nums[i] == sum){
                return i;
            }
        }
        return -1;
    }
}

二维子矩阵的和

添加链接描述
在这里插入图片描述
对每一行分别求前缀和即可

class NumMatrix {
    int[][] preSum;
    public NumMatrix(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        preSum = new int[m][n+1];//i表示行数,
        for(int i=0;i<m;i++){
            Arrays.fill(preSum[i],0);
        }
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                preSum[i][j+1] = preSum[i][j] + matrix[i][j];
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        int res = 0;
        for(int i=row1;i<=row2;i++){
            res += (preSum[i][col2+1]- preSum[i][col1]);
        }
        return res;
    }
}

/**
 * Your NumMatrix object will be instantiated and called as such:
 * NumMatrix obj = new NumMatrix(matrix);
 * int param_1 = obj.sumRegion(row1,col1,row2,col2);
 */

4. 区间问题

合并区间

在这里插入图片描述

class Solution {
    public int[][] merge(int[][] intervals) {
        List<int[]> res = new ArrayList<>();
        Arrays.sort(intervals,(a,b)->(a[0]-b[0]));
        int left = intervals[0][0];
        int right = intervals[0][1];
        for(int i=1;i<intervals.length;i++){
            if(intervals[i][0]>intervals[i-1][1]){//大于前一个右边界,无重叠
                res.add(new int[]{left,right});
                left = intervals[i][0];
                right = intervals[i][1];
            }else{
                intervals[i][1] = Math.max(intervals[i-1][1],intervals[i][1]);
                right = intervals[i][1];
            }
        }
        res.add(new int[]{left,right});
        return res.toArray(new int[res.size()][2]);
    }
}

5. 子串问题

最小覆盖子串

添加链接描述

三. 字符串

1. 变位词 (固定大小滑窗)

固定大小的滑动窗口+两个map

  • 假设只有小写字母的情况下,先创建两个数组,记录子串和目标串(对应子串长度)的所有字符,进行判断;
  • 进行滑窗,同时更新数组对应的左右边界,判断两个数组是否相等。滑窗开始,右指针指向目标串的第i个字符(i为字串的长度)

字符串中的变位词

添加链接描述
在这里插入图片描述

class Solution {
    //固定大小的滑动窗口
    public boolean checkInclusion(String s1, String s2) {
        int[] map1 = new int[26];
        int[] map2 = new int[26];
        int len1 = s1.length();
        int len2 = s2.length();
        if(len1>len2) return false;
        for(int i=0;i<len1;i++){
            map1[s1.charAt(i)-'a']++;
            map2[s2.charAt(i)-'a']++;
        }
        if(Arrays.equals(map1,map2)){
            return true;
        }
        int left = 0;
        int right = len1;
        while(right<len2){
            map2[s2.charAt(left)-'a']--;
            map2[s2.charAt(right)-'a']++;
            if(Arrays.equals(map1,map2)){
                return true;
            }
            left++;
            right++;
        }
        return false;
    }
}

字符串中的所有变位词

添加链接描述
在这里插入图片描述

class Solution {    
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> res = new ArrayList<>();
        int[] map1 = new int[26];
        int[] map2 = new int[26];
        int len1 = s.length();
        int len2 = p.length();
        if(len1<len2) return res;
        for(int i=0;i<len2;i++){
            map1[s.charAt(i)-'a']++;
            map2[p.charAt(i)-'a']++;
        }
        if(Arrays.equals(map1,map2)){
            res.add(0);
        }
        int left = 0;
        int right = len2;
        while(right<len1){
            map1[s.charAt(left)-'a']--;
            map1[s.charAt(right)-'a']++;
            left++;
            right++;
            if(Arrays.equals(map1,map2)){
                res.add(left);
            }

        }
        return res;
    }
}

2. 最长/短字符串 (最大/小滑窗)

最大滑动窗口

不含重复字符的最长子字符串

添加链接描述
在这里插入图片描述

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int left = 0;
        int right = 0;
        int len = s.length();
        Set<Character> set = new HashSet<>();
        int res = Integer.MIN_VALUE;
        while(right<len){
            char c = s.charAt(right);
            while(set.contains(c)){//不满足条件,更新左边界
                set.remove(s.charAt(left));
                left++;
            }
            set.add(s.charAt(right));
            res = Math.max(res,right-left+1);
            right++;
        }
        return res==Integer.MIN_VALUE ? 0 : res;
    }
}

最小滑动窗口

含有所有字符的最短字符串 ***

添加链接描述
在这里插入图片描述
大写小写字母同时出现,初始化数组大小为150,char - ‘A’
变量need记录所需要的字符个数,当字符个数等于0时,满足条件,开始更新

class Solution {
    public String minWindow(String s, String t) {
        int m = s.length();
        int n = t.length();
        int need = n;//记录需要的字符数
        if(m<n) return "";
        int[] cnt = new int[150];//记录每个字母出现的频率,大于0,表明在t出现
        for(int i=0;i<t.length();i++){
            cnt[t.charAt(i)-'A']++;
        }
        int left = 0;
        int right = 0;
        int minLen = Integer.MAX_VALUE;
        int start = 0;
        int end = -1;
        while(right<m){
            char c = s.charAt(right);
            if(cnt[c-'A']>0){//表明c是所需要的
                need--;
            }
            cnt[c-'A']--;//对应字符频率减1
            while(need==0){//满足了要求
                if(right-left+1<minLen){//长度满足要求
                    minLen = right-left+1;
                    start = left;
                    end = right;
                }
                char out = s.charAt(left);//需要滑出的字符
                if(cnt[out-'A']>=0){//表明字符对于target需要
                    need++;
                }
                cnt[out-'A']++;
                left++;
            }
            right++;
        }
        return s.substring(start,end+1);
    }
}

3. 回文字串

有效的回文

添加链接描述
在这里插入图片描述
双指针判断 + 字符串处理常用API

  • 判断字符和数字:
  • 转小写字母
class Solution {
    public boolean isPalindrome(String s) {
        //去空格
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<s.length();i++){
            char c = s.charAt(i);
            if(Character.isLetterOrDigit(c)){
                sb.append(c);
            }
        }
        String target = sb.toString();
        //判断回文串
        int left = 0;
        int right = target.length() -1;
        while(left<right){
            char c1 = target.charAt(left);
            char c2 = target.charAt(right);
            if(Character.toLowerCase(c1) != Character.toLowerCase(c2)){
                return false;
            }else{
                left++;
                right--;
            }
        }
        return true;
    }
}

最多删除一个字符得到回文

添加链接描述
在这里插入图片描述 双指针+递归(贪心策略)
如果字符相同,下一轮;字符不同,则删除左或者右一个字符,进行判断

class Solution {
    public boolean validPalindrome(String s) {
        int left = 0;
        int right = s.length() - 1;
        while(left<right){
            if(s.charAt(left)==s.charAt(right)){
                left++;
                right--;
            }else{
                return isValid(s,left+1,right) || isValid(s,left,right-1);
            }
        }
        return true;
    }
    public boolean isValid(String s,int left,int right){
        while(left<right){
            if(s.charAt(left)==s.charAt(right)){
                left++;
                right--;
            }else{
                return false;
            }
        }
        return true;
    }
}

回文子字符串的个数

添加链接描述
在这里插入图片描述
动态规划求回文串

class Solution {
    public int countSubstrings(String s) {
        int len = s.length();
        boolean[][] dp = new boolean[len][len];
        for(int i=len-1;i>=0;i--){
            for(int j=i;j<len;j++){
                if(s.charAt(i) == s.charAt(j)){
                    if(j-i<=1){
                        dp[i][j] = true;
                    }else{
                        dp[i][j] = dp[i+1][j-1];
                    }
                }
            }
        }
        int res = 0;
        for(int i=0;i<len;i++){
            for(int j=0;j<len;j++){
                if(dp[i][j]){
                    res++;
                }
            }
        }
        return res;
    }
}

4. 字符串与数值

表示数值的字符串

添加链接描述
在这里插入图片描述
难点在于总结出各种情况

class Solution {
    public boolean isNumber(String s) {
        if(s ==null || s.length()==0) return false;
        //去掉首尾空格
        s = s.trim();
        //1. e或E:没有出现e,并且出现过数字,且e后面也要有数字
        //2. +- 第一位或者紧接e后哦面
        //3. . 需要没出现过,且没出现过e
        //4.必须出现数字
        boolean numFlag = false;
        boolean dotFlag = false;
        boolean eFlag = false;
        for(int i=0;i<s.length();i++){
            char c = s.charAt(i);
            if(c>='0'&&c<='9'){
                numFlag = true;//出现过数字
            }else if(c=='.'&&!dotFlag&&!eFlag){
                dotFlag = true;
            }else if( (c=='e'||c=='E') && !eFlag && numFlag){
                eFlag = true;
                numFlag = false;
            }else if ((c=='+'||c=='-') && (i==0||s.charAt(i-1)=='e'||s.charAt(i-1)=='E')){
            continue;
            }else{
            return false;
            }
        }
        return numFlag;//必须出现一个数字
    }
}

把字符串变为整数

添加链接描述
在这里插入图片描述

四. 链表

1. 带环链表

链表中环的入口节点

添加链接描述
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
 //快慢指针
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while(fast!=null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                ListNode cur = slow;
                ListNode prev = head;
                while(cur!=prev){
                    cur = cur.next;
                    prev = prev.next;
                }
                return cur;
            }
        }
        return null;
    }
}

2. 删除节点

删除链表节点

添加链接描述
在这里插入图片描述

删除链表的倒数第n个结点

添加链接描述
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        int len = 0;
        ListNode cur = head;
        while(cur!=null){
            cur = cur.next;
            len++;
        }
        int target = len - n;
        cur = dummy;
        for(int i=0;i<len-n;i++){
            cur = cur.next;
        }
        cur.next = cur.next.next;
        return dummy.next;
    }
}

3. 相交链表

两个链表的第一个重合节点

添加链接描述
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    //相交:a+c+b = b+c+a
    //不相交:a+b = b+a 
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null||headB==null) return null;
        ListNode pointA = headA;
        ListNode pointB = headB;
        while(pointA!=pointB){
            pointA = pointA == null? headB:pointA.next;
            pointB = pointB == null ? headA:pointB.next;
        }
        return pointA;
    }
}

4. 合并链表

合并两个有序链表

添加链接描述
在这里插入图片描述

  • 方法一:虚拟结点
/**
 - Definition for singly-linked list.
 - public class ListNode {
 -     int val;
 -     ListNode next;
 -     ListNode() {}
 -     ListNode(int val) { this.val = val; }
 -     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 - }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        ListNode p1 = list1;
        ListNode p2 = list2;
        while(p1!=null&&p2!=null){
            if(p1.val>p2.val){
                cur.next = p2;
                p2 = p2.next;
                cur = cur.next;
            }else{
                cur.next = p1;
                p1 = p1.next;
                cur = cur.next;
            }
        }
        cur.next = p1==null?p2:p1;
        return dummy.next;
    }
}
  • 递归法
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1==null){
            return list2;
        }else if(list2==null){
            return list1;
        }else if(list1.val<list2.val){
            list1.next = mergeTwoLists(list1.next,list2);
            return list1;
        }else{
            list2.next = mergeTwoLists(list1,list2.next);
            return list2;
        }
    }
}

合并 K 个升序链表

添加链接描述
在这里插入图片描述

  • 暴力法
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
//合并两个
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode res = null;
        for(int i = 0;i<lists.length;i++){
            res = mergeTwoList(res,lists[i]);
        }
        return res;
    }   
    public ListNode mergeTwoList(ListNode a,ListNode b){
        if(a==null||b==null){
            return a!=null ? a:b;//出现空的 就返回非空
        }
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        ListNode pointA = a;
        ListNode pointB = b;
        while(pointA!=null&&pointB!=null){
            if(pointA.val>pointB.val){
                cur.next = pointB;
                pointB = pointB.next;
            }else{
                cur.next = pointA;
                pointA = pointA.next;
            }
            cur = cur.next;
        }
        //可能还有剩下的
        cur.next = pointA != null?pointA:pointB;
        return dummy.next;
    }
}
  • 优先队列
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> pq = new PriorityQueue<>((o1,o2)->(o1.val-o2.val));
        for(ListNode node:lists){
            if(node!=null){
                pq.add(node);
            }
        }
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        while(!pq.isEmpty()){
            cur.next = pq.poll();
            cur = cur.next;
            if(cur.next!=null){
                pq.add(cur.next);
            }
        }
        return dummy.next;
    }
}

链表中的两数相加

添加链接描述

5. 多级链表/循环链表

展平多级双向链表

添加链接描述
在这里插入图片描述

/*
// Definition for a Node.
class Node {
    public int val;
    public Node prev;
    public Node next;
    public Node child;
};
*/
//dfs搜索所有的节点
class Solution {
    List<Node> list = new ArrayList<>();
    public Node flatten(Node head) {
        dfs(head);
        if(list.size()==0) return null;
        for(int i=0;i<list.size()-1;i++){
            Node prev = list.get(i);
            Node next = list.get(i+1);
            prev.next = next;
            next.prev = prev;
            prev.child = null;
        }
        return head;
    }
    public void dfs(Node head){
        if(head == null) return;
        list.add(head);
        dfs(head.child);
        dfs(head.next);
    }
}

排序的循环链表

5. 反转链表

反转链表

添加链接描述
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null) return head;
        ListNode prev = null;
        ListNode cur = head;
        while(cur!=null){
            ListNode temp = cur.next;
            cur.next = prev;
            prev = cur;
            cur = temp;
        }
        return prev;
    }
}

K个一组翻转链表

在这里插入图片描述
分组去 反转链表 难点在于如何模拟过程

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next = head;
        ListNode prev = dummyNode;//指向start的前一个
        ListNode end = dummyNode;//指向反转的最后一个
        while(end.next!=null){
            for(int i=0;i<k&&end!=null;i++){
                end = end.next;
            }
            if(end==null) break;//终止条件
            ListNode start = prev.next;//反转的第一个元素
            ListNode next = end.next;//指向末尾的下一个元素,用于反转后的拼接
            //反转
            end.next = null;//先把该部分断开
            prev.next = reverse(start);
            //反转后的拼接处理
            start.next = next;
            prev = start;
            end = prev;
        }
        return dummyNode.next;
    }
    public ListNode reverse(ListNode head){
        ListNode prev = null;
        ListNode cur = head;
        ListNode temp = null;//保留下一个指向的元素
        while(cur!=null){
            temp = cur.next;
            cur.next = prev;
            prev = cur;
            cur = temp;
        }
        return prev;
    }
}

重排链表

回文链表

五. 哈希表

1. 变位词

有效的变位词

添加链接描述

变位词组

添加链接描述
在这里插入图片描述

  • 利用变位词排序后对应的key相同,构建hashmap
  • 一些字符串,hashmap的处理api
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        List<List<String>> res = new ArrayList<>();
        HashMap<String,ArrayList<String>> map = new HashMap<>();//key:排序后的元素,List<String> 对应的变位词
        for(int i=0;i<strs.length;i++){
            char[] chArray = strs[i].toCharArray();
            Arrays.sort(chArray);
            String key = String.valueOf(chArray);//key
            ArrayList<String> value = map.getOrDefault(key,new ArrayList<String>());
            value.add(strs[i]);//取出并更新 value
            map.put(key,value);
        }
        for(Map.Entry<String,ArrayList<String>> entry:map.entrySet()){
            res.add(entry.getValue());
        }
        return res;

    }
}

2. 设计类

LRU缓存

添加链接描述
在这里插入图片描述

class LRUCache {
    int capacity;
    int size;//记录大小
    Map<Integer,Node> cache;//存储key和value
    DoubleLinkedList dList;//保证key的顺序
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        cache = new HashMap<>();
        dList = new DoubleLinkedList();
    }
    
    public int get(int key) {
        Node node = cache.get(key);
        if(node==null) {
            return -1;
        }else{
            //删除指定节点
            dList.removeNode(node);
            //将节点移动到首部
            dList.addHead(node);
            return node.val;
        }
    }
    
    public void put(int key, int value) {
        Node node = cache.get(key);//判断是否存在
        if(node!=null){
            node.val = value;
            dList.removeNode(node);
            dList.addHead(node);
        }else{
            Node newNode = new Node(key,value);
            //判断容量
            if(size>=capacity){
                Node last = dList.getLast();
                cache.remove(last.key);
                dList.removeNode(last);
                size--;
            }
            cache.put(key,newNode);
            dList.addHead(newNode);
            size++;
        }
    }
}
class Node{
    int key;
    int val;
    Node prev;
    Node next;
    public Node(){}
    public Node(int key,int val){
        this.key = key;
        this.val = val;
    }
}
class DoubleLinkedList{
    Node head;
    Node tail;
    public DoubleLinkedList(){
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.prev = head;
    }
    //插入到头部
    public void addHead(Node node){
        Node temp = head.next;
        node.next = temp;
        temp.prev = node;
        head.next = node;
        node.prev = head;
    }
    //删除节点
    public void removeNode(Node node){
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
    //最后一个节点
    public Node getLast(){
        return tail.prev;
    }
}
/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

LFU缓存

添加链接描述
在这里插入图片描述

class LFUCache {
    int size;
    int capacity;
    Map<Integer,Node> cache;//缓存,key:节点
    Map<Integer,DoubleLinkedList> freqMap;//频率:双向链表
    int minFreq;//最小频率
    public LFUCache(int capacity) {
        this.capacity = capacity;
        size = 0;
        cache = new HashMap<>();
        freqMap = new HashMap<>();
    }
    
    public int get(int key) {
        Node node = cache.get(key);
        if(node==null){
            return -1;
        }
        updateFreq(node);
        return node.val;
    }
    
    public void put(int key, int value) {
        Node node = cache.get(key);
        if(node!=null){
            node.val = value;
            updateFreq(node);
        }else{
            if(size==capacity){
                DoubleLinkedList list = freqMap.get(minFreq);
                cache.remove(list.tail.prev.key);//必须先删缓存后删链表
                list.removeNode(list.tail.prev);
                size--;
            }
            Node newNode = new Node(key,value);
            cache.put(key,newNode);
            DoubleLinkedList minList = freqMap.get(1);
            if(minList==null){
                minList = new DoubleLinkedList();
                freqMap.put(1,minList);
            }
            minList.addNode(newNode);
            size++;
            minFreq = 1;
        }
    }
    //更新频率
    public void updateFreq(Node node){
        int freq = node.freq;
        DoubleLinkedList list = freqMap.get(freq);
        list.removeNode(node);
        if(freq==minFreq && list.head.next == list.tail){
            minFreq += 1;//特殊情况
        }
        node.freq += 1;
        list = freqMap.get(node.freq);//获取新频率的双向链表
        if(list==null){
            list = new DoubleLinkedList();
            freqMap.put(node.freq,list);
        }
        list.addNode(node);
    }
}
class Node{
    int key;
    int val;
    int freq=1;
    Node prev;
    Node next;
    public Node(){}
    public Node(int key,int val){
        this.key = key;
        this.val = val;
    }
}
class DoubleLinkedList{
    Node head;
    Node tail;
    public DoubleLinkedList(){
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.prev = head;
    }
    //增加节点到头部
    public void addNode(Node node){
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
        node.prev = head;
    }
    //删除节点
    public void removeNode(Node node){
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
}

/**
 * Your LFUCache object will be instantiated and called as such:
 * LFUCache obj = new LFUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

六. 栈

1. 栈模拟

2. 单调栈

直方图最大矩形面积

题目
在这里插入图片描述

class Solution {
    public int largestRectangleArea(int[] heights) {
        Deque<Integer> stack = new LinkedList<>();
        stack.push(-1);
        int res = Integer.MIN_VALUE;
        for(int i=0;i<heights.length;i++){
            while(stack.peek()!=-1&&heights[stack.peek()]>=heights[i]){
                //求以每个节点中心的面积;寻找左右两边第一个小于该高度的元素
                //stack.peek()左边第一个小于
                //i右边第一个小于
                res = Math.max(res,heights[stack.pop()]*(i-stack.peek()-1));
            }
            stack.push(i);
        }
        //还剩下元素
        while(stack.peek()!=-1){
            res = Math.max(res,heights[stack.pop()]*(heights.length-stack.peek()-1));
        }
        return res;
    }
}

矩阵中最大的矩形 hard

题目
以上一题为基础,构造每一层柱子,然后遍历求解

七. 队列

1. 树的层序遍历

2. 设计类题目

滑动窗口的平均值

添加链接描述

最近请求次数

添加链接描述

往完全二叉树添加节点

添加链接描述
核心思路:队列只保存左右子树不完整的节点

3. 单调队列

滑动窗口最大值

在这里插入图片描述
双端队列实现单调队列

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 1){return nums;}
        int len = nums.length - k + 1;//滑窗数组的长度
        int[] res = new int[len];//存放结果
        //初始化结果
        MyQueue que = new MyQueue();
        for(int i =0;i<k;i++){
            que.add(nums[i]);
        }
        res[0] = que.peek();
        int num = 1;
        for(int i = k;i<nums.length;i++){//i记录每次滑窗的最右端
            que.pop(nums[i-k]);//移动滑窗,删除第一个元素
            que.add(nums[i]);//把最后的元素加进去队列
            res[num] = que.peek();
            num++;
        }
        return res;
    }
}
//单调队列
//每次用peek取出的元一定是该队列的最大值
class MyQueue{
    Deque<Integer> deque = new LinkedList<>();
    //入队操作:保证单调递减。while循环 如果该值比最后一个元素大,那就弹出最后一个元素,继续比较,知道满足条件
    void add(int val){
        while(!deque.isEmpty() && val > deque.getLast()){
            deque.removeLast();
        }
        deque.add(val);
    }
    //出队操作:并不是针对队列出队,而是需要将队列与数组进行比较。仅仅当peek出来的元素与需要弹出的数据相等时,才将其弹出。因为仅仅peek出来的数据影响取最大值
    void pop(int val){
        if(!deque.isEmpty() && val == deque.peek()){
            deque.poll();
        }
    }
    //返回最大值
    int peek(){
        return deque.peek();
    }
}

八. 树

1. 路径问题

节点之和最大的路径

添加链接描述
在这里插入图片描述

class Solution {
    int res = Integer.MIN_VALUE;
    public int maxPathSum(TreeNode root) {
        dfs(root);
        return res;
    }
    public int dfs(TreeNode node){
        if(node==null) return 0;
        int leftSum = Math.max(dfs(node.left),0);
        int rightSum = Math.max(dfs(node.right),0);
        int sum = leftSum + rightSum + node.val;
        res = Math.max(sum,res);
        return node.val + Math.max(leftSum,rightSum);
    }
}

向下的路径节点之和

添加链接描述
在这里插入图片描述

class Solution {
    Map<Long,Integer> map = new HashMap<>();
    public int pathSum(TreeNode root, int targetSum) {
        map.put(0L,1);
        return dfs(root,0,targetSum);
    }
    public int dfs(TreeNode root,long sum,long targetSum){
        if(root==null) return 0;
        int res = 0;
        sum = root.val + sum;
        res = map.getOrDefault(sum-targetSum,0);
        map.put(sum,map.getOrDefault(sum,0)+1);
        res += dfs(root.left,sum,targetSum);
        res += dfs(root.right,sum,targetSum);
        map.put(sum,map.getOrDefault(sum,0)-1);
        return res;
    }
}

2. 二叉搜索树

二叉搜索树中的中序后继

添加链接描述

所有大于等于节点的值之和

添加链接描述
关键在于选对遍历顺序

二叉搜索树迭代器

添加链接描述

3. 设计树解决问题

值和下标之差都在给定的范围内

添加链接描述

日程表

添加链接描述

4. 树的修改与改造

二叉树的镜像

添加链接描述
在这里插入图片描述

对称的二叉树

添加链接描述
在这里插入图片描述

5.深度/高度问题

二叉树的最大深度

在这里插入图片描述

  • dfs:
class Solution {
    int res = 0;
    public int maxDepth(TreeNode root) {
        if(root==null) return 0;
        int depth = 1;
        dfs(root,depth);
        return res;
    }
    public void dfs(TreeNode root,int depth){
        res = Math.max(res,depth);
        if(root.left==null&&root.right==null){
            return;
        }
        if(root.left!=null){
            depth++;
            dfs(root.left,depth);
            depth--;
        }
        if(root.right!=null){
            depth++;
            dfs(root.right,depth);
            depth--;
        }
    }
}
  • 层序遍历
class Solution {

    public int maxDepth(TreeNode root) {
        if(root==null) return 0;
        int res = 0;
        Queue<TreeNode> que = new LinkedList<>();
        que.offer(root);
        while(!que.isEmpty()){
            int size = que.size();
            while(size>0){
                size--;
                TreeNode node = que.poll();
                if(node.left!=null){
                    que.offer(node.left);
                }
                if(node.right!=null){
                    que.offer(node.right);
                }
            }
            res++;
        }
        return res;
    }

}

九. 堆

数据流的第 K 大数值

添加链接描述
如何保证每次取出第k个大的元素:维护一个大小为k的小顶堆,每次取出来的元素为所求

出现频率最高的 k 个数字

添加链接描述
大顶堆

和最小的 k 个数对

添加链接描述
小顶堆(语法)+优化思路,如何减少搜索次数

十. 前缀树

添加链接描述

实现前缀树

添加链接描述
在这里插入图片描述

class Trie {
    Trie[] children;
    boolean isEnd;//是否为单词末尾
    /** Initialize your data structure here. */
    public Trie() {
        children = new Trie[26];//26个字母
        isEnd = false;
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        Trie node = this;
        for(int i=0;i<word.length();i++){
            char ch = word.charAt(i);//取出字符
            int index = ch - 'a';
            if(node.children[index]==null){//该字符为空
                node.children[index] = new Trie();
            }
            node = node.children[index];
        }
        node.isEnd = true;   
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        Trie node = searchPrefix(word);//搜索前缀

        return node!=null&&node.isEnd;//节点非空且为结束
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        return searchPrefix(prefix) != null;
    }
    //找前缀
    public Trie searchPrefix(String prefix){
        Trie node = this;
        for(int i=0;i<prefix.length();i++){
            char c = prefix.charAt(i);
            int index = c - 'a';
            if(node.children[index] == null){
                return null;
            }
            node = node.children[index];
        }
        return node;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */

最短的单词编码(后缀树)

添加链接描述
在这里插入图片描述
记录每个单词的后缀,构建后缀树。后缀树的特点是只有每一个分支的最后一个字母的end标记为true,因此遍历每个单词判断其end标记

class Solution {
    Trie trie = new Trie();
    public int minimumLengthEncoding(String[] words) {
        for(String word:words){
            trie.insert(word);//构建后缀树
        }
        int res = 0;
        Set<String> set = new HashSet<>();//去重
        for(String word:words){
            if(set.contains(word)) continue;
            set.add(word);
            //判断当前单词end是否为true,end为true就加长度
            if(trie.isLastEnd(word)){
                res += word.length() + 1;
            }
        }
        return res;
    }
}

class Trie{
    Trie[] children;
    boolean isEnd;//该分支的最后一个才设置为true
    public Trie(){
        children = new Trie[26];
        isEnd = false;
    }
    public void insert(String word){
        Trie node = this;
        boolean newBranch = false;//是否开辟新分支
        for(int i=word.length()-1;i>=0;i--){
            char c = word.charAt(i);
            int index = c - 'a';
            if(node.children[index]==null){
                node.children[index] = new Trie();
                newBranch = true;
            }
            node = node.children[index];
            if(node.isEnd&&i!=0) node.isEnd = false;//撤销之前的end标记
        }
        if(newBranch){
            node.isEnd = true;
        }
    }
    public boolean isLastEnd(String word){
        Trie node = this;
        for(int i=word.length()-1;i>=0;i--){
            char c = word.charAt(i);
            int index = c -'a';
            node = node.children[index];
        }
        return node.isEnd;
    }
}

十一. 排序

1. 归并排序

链表排序

添加链接描述

2. 排序应用

把数组排成最小的数

添加链接描述
在这里插入图片描述
解法参考

  • 内置排序
class Solution {
    public String minNumber(int[] nums) {
        String[] numsStr = new String[nums.length];
        for(int i=0;i<nums.length;i++){
            numsStr[i] = String.valueOf(nums[i]);
        }
        //30,3 
        //303<330 -30在3前面
        Arrays.sort(numsStr,(x,y)->(x+y).compareTo(y+x));
        String res = new String();
        for(String str:numsStr){
            res += str;
        }
        return res;
    }
}

合并K个升序链表 (分治) TODO

添加链接描述

十二. 回溯

1. 全排列

含有重复元素集合的全排列 TODO

添加链接描述

2. 括号问题

生成匹配的括号

添加链接描述
在这里插入图片描述

class Solution {
    List<String> res = new ArrayList<>();
    StringBuilder path = new StringBuilder();
    public List<String> generateParenthesis(int n) {
        backTracking(n,0,0);
        return res;
    }
    public void backTracking(int n,int left,int right){
        if(path.length()==2*n){
            res.add(path.toString());
            return;
        }
        if(left<n){
            path.append('(');
            backTracking(n,left+1,right);
            path.deleteCharAt(path.length()-1);
        }
        if(left>right){
            path.append(')');
            backTracking(n,left,right+1);
            path.deleteCharAt(path.length()-1);
        }
    }
}

十三. 动态规划

1. 二维动态规划问题

最长公共子序列 TODO

添加链接描述

2. 背包问题

排列的数目 TODO

添加链接描述

3. 取不取问题

环形房屋偷盗

添加链接描述
在这里插入图片描述

class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        if(len==1){return nums[0];}
        if(len==2){
            return Math.max(nums[0],nums[1]);
        }
        return Math.max(robHelper(nums,0,len-2),robHelper(nums,1,len-1));
    }
    public int robHelper(int[] nums,int start,int end){
        int[] dp = new int[end-start+1];
        dp[0] = nums[start];
        dp[1] = Math.max(nums[start],nums[start+1]);
        for(int i=2;i<=end-start;i++){
            if(start==0){
                dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
            }
            if(start==1){
                dp[i] = Math.max(dp[i-2]+nums[i+1],dp[i-1]);
            }
        }
        return dp[end-start];
    }

}

粉刷房子

添加链接描述
在这里插入图片描述

class Solution {
    public int minCost(int[][] costs) {
        int len = costs.length;
        int[][] dp = new int[len][3];
        dp[0][0] = costs[0][0];
        dp[0][1] = costs[0][1];
        dp[0][2] = costs[0][2];
        for(int i=1;i<len;i++){
            dp[i][0] = Math.min(dp[i-1][1],dp[i-1][2]) + costs[i][0];
            dp[i][1] = Math.min(dp[i-1][0],dp[i-1][2]) + costs[i][1];
            dp[i][2] = Math.min(dp[i-1][1],dp[i-1][0]) + costs[i][2];
        }
        return Math.min(Math.min(dp[len-1][0],dp[len-1][1]),dp[len-1][2]);
    }
}

4. 回文串

最少回文分割

添加链接描述

5. 字符串操作

字符串交织 TODO

添加链接描述

6. 其他

n个骰子的点数

添加链接描述
在这里插入图片描述

class Solution {
    public double[] dicesProbability(int n) {
        //概率=投出和为x的方法数/总方法数 (看成排列数,1,2和2,1算两种情况)
        double[] res = new double[6*n-n+1];
        //dp[i][j] i个骰子投出和为j的方法数 
        //i-1个骰子投出 (j-k) 的方法数之和(k为1,2,3,4,5,6)
        int[][] dp = new int[n+1][n*6+1];
        for(int i=1;i<=6;i++){
            dp[1][i] = 1;
        }
        for(int i=2;i<=n;i++){
            for(int j=i;j<=i*6;j++){
                for(int k=1;k<=6;k++){
                    if(j<k) break;
                    dp[i][j] += dp[i-1][j-k];
                }
            }
        }
        //计算最后结果
        double total = Math.pow(6,n);
        for(int i=0;i<=5*n;i++){
            res[i] = dp[n][i+n]/total;
        }
        return res;
    }
}

十四. 图论

1. 二分图

二分图 TODO

添加链接描述

2. BFS最短路

开密码锁(板子题) TODO

添加链接描述

单词接龙(双向BFS减少时间复杂度) TODO

添加链接描述

矩阵中的距离

添加链接描述
在这里插入图片描述

  • 解法一:板子,从0反向搜索
class Solution {
    public int[][] updateMatrix(int[][] mat) {
        int m = mat.length;
        int n = mat[0].length;
        int[][] res = new int[m][n];
        boolean[][] visited  = new boolean[m][n];
        Queue<int[]> que = new LinkedList<>();
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(mat[i][j] == 0){
                	visited[i][j] = true;
                    que.offer(new int[]{i,j});
                }
            }
        }
        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};
        while(!que.isEmpty()){
            int[] temp = new int[2];
            temp[0] = que.peek()[0];
            temp[1] = que.poll()[1];
            for(int[] dir:dirs){
                int nx = temp[0] + dir[0];
                int ny = temp[1] + dir[1];
                if(nx<0||ny<0||nx>=m||ny>=n||visited[nx][ny]||mat[nx][ny]!=1){
                    continue;
                }
                visited[nx][ny] = true;
                res[nx][ny] = res[temp[0]][temp[1]] + 1;
                que.offer(new int[]{nx,ny});
            }
        }
        return res;
    }
}
  • 解法二:动态规划

机器人的运动范围

添加链接描述
在这里插入图片描述

class Solution {
    public int movingCount(int m, int n, int k) {
        if(k==0) return 1;
        Queue<int[]> que = new LinkedList<>();
        boolean[][] visited = new boolean[m][n];
        visited[0][0] = true;
        que.offer(new int[]{0,0});
        int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
        int res = 1;
        while(!que.isEmpty()){
            int[] temp = que.poll();
            int x = temp[0];
            int y = temp[1];
            for(int[] dir:dirs){
                int nx = x + dir[0];
                int ny = y + dir[1];
                if(nx<0||ny<0||nx>=m||ny>=n||visited[nx][ny]||!isValid(nx,ny,k)){
                    continue;
                }
                que.offer(new int[]{nx,ny});
                visited[nx][ny] = true;
                res++;
            }
        }
        return res;
    }
    public boolean isValid(int m,int n,int k){
        int res = 0;
        while(m>0||n>0){
            res += m%10+n%10;
            m /= 10;
            n /= 10;
        }
        if(res>k){
            return false;
        }else{
            return true;
        }
    }
}

3. 岛屿问题

岛屿最大面积 TODO

添加链接描述

岛屿数量

在这里插入图片描述

class Solution {
    int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
    public int numIslands(char[][] grid) {
        int res = 0;
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j]=='1'){
                    dfs(grid,i,j);
                    res++;
                }
            }
        }
        return res;
    }

    public void dfs(char[][] grid,int x,int y){ 
        grid[x][y] = '0';
        for(int[] dir:dirs){
            int nx = x + dir[0];
            int ny = y + dir[1];
            if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length||grid[nx][ny]!='1'){
                continue;
            }
            dfs(grid,nx,ny);
        }
    }
}

4. 拓扑排序 (无向图找环)

构建入度表,邻接表
度最小的入队
出队,遍历对应的邻接表,更新状态

课程顺序 TODO

添加链接描述

多余的边

添加链接描述
在这里插入图片描述

class Solution {
    public int[] findRedundantConnection(int[][] edges) {
        //先找环,然后从后往前遍历,删除边
        int len = edges.length;
        int[] indegree = new int[len];
        List<List<Integer>> joinList = new ArrayList<>();
        for(int i=0;i<len;i++){
            joinList.add(new ArrayList<>());
        }
        for(int[] edge:edges){
            indegree[edge[0]-1]++;
            indegree[edge[1]-1]++;
            joinList.get(edge[0]-1).add(edge[1]-1);
            joinList.get(edge[1]-1).add(edge[0]-1);
        }
        //入度为1的入队
        Queue<Integer> que = new LinkedList<>();
        for(int i=0;i<len;i++){
            if(indegree[i]==1){
                que.offer(i);
            }
        }
        while(!que.isEmpty()){
            int item = que.poll();
            for(int index:joinList.get(item)){
                indegree[index]--;
                if(indegree[index]==1){
                    que.offer(index);
                }
            }
        }
        //处理 度不为1 说明是环 删除
        for(int i=len-1;i>=0;i--){
            if(indegree[edges[i][0]-1]>1&&indegree[edges[i][1]-1]>1){
                return edges[i];
            }
        }
        return new int[0];
    }
}

5. 有向无环图

所有路径 TODO

6. 并查集

添加链接描述

最长连续序列 (高频题)

添加链接描述
在这里插入图片描述

class Solution {
    public int longestConsecutive(int[] nums) {
        UnionFind uf = new UnionFind(nums);//初始化
        int res = 0;
        for(int num:nums){//合并
            if(uf.findRoot(num+1) != null){
                uf.union(num,num+1);
            }
        }
        for(int num:nums){
            int end = uf.findRoot(num);
            res = Math.max(res,end-num+1);
        }
        return res;
    }
}
//并查集
class UnionFind{
    Map<Integer,Integer> parent = new HashMap<>();//节点:父节点
    //初始化
    public UnionFind(int[] nums){
        for(int num:nums){
            parent.put(num,num);//节点本身是自己的父节点
        }
    }
    //找父节点
    public Integer findRoot(int num){
        if(!parent.containsKey(num)){//假设不存在该数
            return null;
        }
        while(num!=parent.get(num)){
            parent.put(num,parent.get(parent.get(num)));
            num = parent.get(num);
        }
        return num;
    }
    //合并两个连通分量: num并入到num+1
    public void union(int x,int y){
        int rootX = findRoot(x);
        int rootY = findRoot(y);
        if(rootX==rootY){
            return;
        }
        parent.put(rootX,rootY);
    }
}
  • 方法二:哈希 o(n) o(n)

省份数量

添加链接描述
在这里插入图片描述

class Solution {
    int[] parent ;
    public int findCircleNum(int[][] isConnected) {
        int len = isConnected.length;
        parent = new int[len];
        for(int i=0;i<len;i++){
            parent[i] = i;
        }
        for(int i=0;i<len;i++){
            for(int j=i+1;j<len;j++){
                if(isConnected[i][j]==1){
                    union(j,i);
                }
            }
        }
        int res = 0;
        for(int i=0;i<parent.length;i++){
            if(parent[i]==i){
                res++;
            }
        }
        return res;
    }
    public int findRoot(int num){
        if(parent[num]!=num){
            parent[num] = findRoot(parent[num]);
        }
        return parent[num];
    }
    //x合并到y
    public void union(int x,int y){
        int rootX = findRoot(x);
        int rootY = findRoot(y);
        parent[rootX] = rootY;
    }
}

十五. 查找算法

1. 找重复数字

数组中重复的数字 (任意次数)

添加链接描述
在这里插入图片描述

class Solution {
    public int findRepeatNumber(int[] nums) {
        int index = 0;
        while(index<nums.length){
            if(nums[index]==index){
                index++;
                continue;
            }
            if(nums[nums[index]]==nums[index]){
                return nums[index];
            }
            int temp = nums[index];
            nums[index] = nums[temp];
            nums[temp] = temp;
        }
        return -1;
    }
}

只出现一次的数字(3次中找1次) TODO

添加链接描述
在这里插入图片描述

只出现一次的数字(2次中找1次) TODO

添加链接描述
在这里插入图片描述

class Solution {
    public int singleNumber(int[] nums) {
        int res = nums[0];
        for(int i=1;i<nums.length;i++){
            res ^= nums[i];
        }
        return res;
    }
}

十六. 数学

数组中出现次数超过一半的数字

添加链接描述
在这里插入图片描述

  • 方法一:排序取中 o(nlogn) o(logn)
  • 方法二:选票法 o(n) o(1)

十七. 矩阵

矩阵置零

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值