[LeetCode]-Fenêtre coulissante

Préface

Enregistrer les problèmes liés à la fenêtre coulissante rencontrés lors du brossage de LeetCode

209.Sous-tableau avec longueur minimale

Les fenêtres coulissantes ne conviennent pas aux tableaux contenant des valeurs négatives

/**
 *劣质的 滑动窗口 ,由于把考虑到的特殊情况都用if-else单独拎出来处理,导致过多的if-else分支,一方面代码不够简洁,一方面执行效率也降低了
 * =============下面做了四个改进===============================
 * ==========改进过程可以明显发现影响时间最大的就是count函数========================================================
 * ====由于滑动窗口是O(n)算法且是一个元素一个元素进行操作,所以用变量维护的方法维护每一个滑动窗口(子数组)的和是可取的,=======
 * ====完全不需要每次都调用循环的方法去求和=================================================================
 * ===================================================================================================
**/
public int minSubArrayLen(int target, int[] nums) {
    
    
    int length = nums.length;
    /* 改进一:
        首先这两个if-else是完全没必要的
        if(length == 0){
            return 0;
        }
        if(length == 1){
            return nums[0] >= target ? 1 : 0;
        }*/
    int result = length + 1;
    int left = 0;
    int right = 0;
    /*改进二:
     *      这段是用来初始化窗口的,改掉这一段后,时间明显提升,可能是因为每一次都调用了count方法,而count方法本身是个循环,应该用变量维护的方法求
     * 当前子数组的和而不是每次变换窗口都计算子数组的和
     * while (right < length && count(nums, left, right) < target){
     *             right++;
     *}*/
    int count1 = 0;
    while (right < length){
    
    
        count1 += nums[right];
        if(count1 >= target){
    
    
            break;
        }else {
    
    
            right++;
        }
    }
    /* 注意点1
     * 此时right可能已经超出数组长度了·  即特例 :所有数组元素加起来都不等于target,也就是说 result 可能是没有被修改的,
     * 所以最后不应该直接返回result,而是返回 result == length + 1 ? 0 : result*/
    while (right < length){
    
    
        if(count1 >= target){
    
    
            result = Math.min(right - left + 1, result);
            /*改进三:就算left加一后与right重合了也无所谓,此时子数组和为0,到下一次循环时right会加一,不用担心left比right大的
            if(left + 1 <= right) {
                count1 -= nums[left];
                left++;
            }else {
                right++;
                left++;
                if(right < length){
                    count1 = nums[left];
                }
            }*/
            count1 -= nums[left++];
        }else {
    
    
            right++;
            if(right < length){
    
    
                count1 += nums[right];
            }
        }
    }
    /* 注意点2 当所有数组元素加起来都不等于target时应返回0*/
    return result == length + 1 ? 0 : result;
}
/*public int count(int[] nums, int left, int right){
    if(left == right){
        return nums[left];
    }
    int count = 0;
    for(int i = left;i <= right && i < nums.length;i++){
        count += nums[i];
    }
    return count;
}*/
//整理后是:
public int minSubArrayLen(int target, int[] nums) {
    
    
    int length = nums.length;
    int result = length + 1;
    int left = 0;
    int right = 0;
    int count1 = 0;
    while (right < length){
    
    
        count1 += nums[right];
        if(count1 >= target){
    
    
            break;
        }else {
    
    
            right++;
        }
    }
    while (right < length){
    
    
        if(count1 >= target){
    
    
            result = Math.min(right - left + 1, result);
            count1 -= nums[left++];
        }else {
    
    
            right++;
            if(right < length){
    
    
                count1 += nums[right];
            }
        }
    }
    return result == length + 1 ? 0 : result;
}

904. Corbeille de fruits

public int totalFruit(int[] fruits) {
    
    
    int length = fruits.length;
    /* 不必要的特判:
    if(length == 1){
        return 1;
    }
    if(length == 2){
        return 2;
    }*/
    int left = 0;
    int right = -1;
    //找到与首元素不同的元素,对应情况:前面好几个都一样
    for(int i = 1;i < length;i++){
    
    
        if(fruits[i] != fruits[left]){
    
    
            right = i;
            break;
        }
    }
    //特例 : 所有数都是同一个
    if(right == -1){
    
    
        return length;
    }
    int record1;
    int record2;
    int maxCount = right - left + 1;
    while (right < length - 1){
    
    
        //更新记录的两个数 
        record1 = fruits[left];
        record2 = fruits[right];
        while (right < length - 1 && (fruits[right + 1] == record1 || fruits[right + 1] == record2)){
    
    
            right++;
        }
        maxCount = Math.max(maxCount, (right - left + 1));
        int temp = right;
        while (fruits[temp] == fruits[right]){
    
    
            temp--;
        }
        left = temp + 1;
        //此时要么right的下一个是不同的数,要么right >= length - 1然后跳出循环,所以right++是完全买毛病的
        right++;
    }
    return maxCount;
}

76. Sous-chaîne de couverture minimale (dur)

class Solution {
    
    
    private Map<Character,Integer> tMap = new HashMap<>();
    private Map<Character,Integer> resMap = new HashMap<>();
    public String minWindow(String s, String t) {
    
    
        if(s.length() == 0){
    
    
            return "";
        }
        int tLength = t.length();
        int sLength = s.length();
        for (int i = 0; i < tLength; i++) {
    
    
            char c = t.charAt(i);
            tMap.put(c,tMap.getOrDefault(c,0) + 1);
        }
        int l = 0;
        while (l < sLength && !tMap.containsKey(s.charAt(l))){
    
    
            l++;
        }
        int r = sLength - 1;
        while (r > l && !tMap.containsKey(s.charAt(r))){
    
    
            r--;
        }
        if(l > r){
    
    
            return "";
        }
        //以上代码是在把s两端不在t中出现的字符全部剔除,再在剩下的字符串newS中查找符合条件的字符串
        String newS = s.substring(l,r + 1);
        //len记录运算过程中当前记录到的最小窗口的长度
        int len = 1000000000;
        int resl = 0;
        int resr = -1;
        //p1是窗口左边界,收缩窗口;p2是窗口右边界,扩大窗口
        int p1 = 0;
        //p2从负一开始是为了应对newS长度为一的情况,如样例s:"a",t:"a"
        int p2 = -1;
        //p2<newS.length() - 1很容易理解,窗口的右边界达到字符串末端时是滑动的终止条件
        //不过可能会出现p2到达字符串末端时,p1还可以再向右继续缩小窗口,因此还要加上一个p1<p2的条件
        while (p1 < p2 || p2 < newS.length() - 1){
    
    
            //只要当前窗口不符合条件p2都要不断右移
            while (p2 < newS.length() - 1 && !check()){
    
    
                p2++;
                char c = newS.charAt(p2);
                resMap.put(c,resMap.getOrDefault(c,0) + 1);
            }
            //当前窗口符合条件,就要更新len以及resl,resr,保证此时的len是最小的,resl和resr是相对应的字符串下标
            if(check() && p2 - p1 +  1 < len){
    
    
                len = p2 - p1 +  1;
                resl = p1;
                resr = p2;
            }
            //此时得到的窗口是符合条件的,所以可以开始缩小窗口往右遍历寻找更优解
            if(p1<p2){
    
    
            char c = newS.charAt(p1++);
            //p1一开始指向的肯定是t中有的字符,收缩的思路是找到下一个t中有的字符
            resMap.put(c,resMap.get(c) - 1);
            while (!tMap.containsKey(newS.charAt(p1))){
    
    
                p1++;
            }}
        }
        return (resr == -1) ? "" : newS.substring(resl,resr + 1);
    }
    public boolean check(){
    
    
        for (Map.Entry<Character, Integer> entry : tMap.entrySet()) {
    
    
            Character key = entry.getKey();
            Integer val = entry.getValue();
            if (resMap.getOrDefault(key, 0) < val) {
    
    
                return false;
            }
        }
        return true;
    }
}

674. La séquence croissante continue la plus longue (traitement en ligne/fenêtre glissante)

Il est nécessaire que les sous-séquences soient contiguës. La variable l maintient la longueur de la séquence continue du nombre actuellement parcouru. Ensuite, lors du passage vers nums[i], si nums[i] > nums[i - 1], l augmentera de un, sinon nums[i] will doit être recalculé comme le début d'une nouvelle séquence croissante, avec l défini sur 1

public int findLengthOfLCIS(int[] nums) {
    
    
    int len = nums.length;
    int l = 1,maxL = 1; //maxL维护最大长度。l初始化为1,对应 nums[0] 
    for(int i = 1;i < len;i++){
    
    
        if(nums[i] > nums[i - 1]){
    
    
            l++;
            maxL = Math.max(l,maxL);
        }
        else l = 1;
    }
    return maxL;
}

53. Somme maximale du sous-tableau (traitement en ligne/fenêtre coulissante)

public int maxSubArray(int[] nums) {
    
    
    int sum = 0,max = Integer.MIN_VALUE;
    for (int num : nums) {
    
    
        if (sum < 0) {
    
    
            sum = 0;
        }
        sum += num;
        max = Math.max(max, sum);
    }
    return max;
}

Parcourez le tableau depuis le début et utilisez sum pour enregistrer la somme des éléments de la sous-séquence parcourue à ce moment. Si la somme est inférieure à 0, cela signifie que la somme des sous-séquences parcourues est inférieure à 0, car la question nécessite une sous-séquence. Alors cette sous-séquence dont la somme des éléments est égale à 0 est un fardeau pour les sous-séquences suivantes, car quelle que soit la somme des séquences suivantes, si vous ajoutez cette sous-séquence avec une somme de 0, la somme de la séquence entière deviendra petite, donc si la somme actuelle est inférieure à 0, la sous-séquence actuellement parcourue doit être ignorée.

Question d'entretien 17.24.Sous-matrice maximale

L'idée de résoudre le problème vient de la solution
. Pour chaque ligne i, "pressez" toutes les lignes entre la i-ème ligne et la j-ème ligne (j = i, i + 1,..., ligne - 1) en un nombre, puis effectuez la solution [somme maximale du sous-tableau] pour un tableau unidimensionnel

public int[] getMaxMatrix(int[][] matrix) {
    
    
    int row = matrix.length;
    int column = matrix[0].length;
    int maxSum = Integer.MIN_VALUE,r1 = 0,c1 = 0,r2 = 0,c2 = 0,sum,r1Tmp = 0,c1Tmp = 0;
    int[] rowSum = new int[column]; //当前被“压榨”的所有行中每一列的和
    for(int i = 0;i < row;i++){
    
      //对于每一行 i
        Arrays.fill(rowSum,0);
        for(int j = i;j < row;j++){
    
      //枚举 j
            sum = 0;
            for(int k = 0;k < column;k++){
    
     
                rowSum[k] += matrix[j][k];  //求i 到 j 行间所有行中每一列的和
                if(sum > 0) sum += rowSum[k]; 
                else{
    
        //前面的和小于0,那就弃掉,让当前列的和作为新的 sum,同时更新左上角的行列坐标
                    sum = rowSum[k];
                    r1Tmp = i;
                    c1Tmp = k;
                }
                if(sum > maxSum){
    
      //如果得到了比维护的子矩阵最大和还大的和,就更新维护答案的四个坐标以及maxSum
                    maxSum = sum;
                    r1 = r1Tmp;
                    c1 = c1Tmp;
                    r2 = j;
                    c2 = k;
                }
            }
        }
    }
    return new int[]{
    
    r1,c1,r2,c2};
}

363. L'aire rectangulaire ne dépasse pas la plus grande somme numérique de K

La solution à cette question n'est en fait pas très similaire à une fenêtre coulissante, mais l'idée est très similaire aux deux questions ci-dessus, je les ai donc rassemblées. Il est recommandé de comparer les trois questions ensemble
. Tout d'abord, selon la signification de la question, vous souhaitez énumérer certaines zones rectangulaires dans la matrice d'origine, c'est-à-dire les sous-matrices. Ensuite, comme pour la question [Sous-matrice maximale] ci-dessus, lorsque énumérant une sous-matrice, chacune d'elles Une ligne est "compressée" en un nombre pour obtenir un tableau unidimensionnel. Chaque nombre de ce tableau unidimensionnel représente la somme des éléments d'une ligne. Calculez ensuite la somme avec la plus grande somme de éléments parmi les matrices formées par ces lignes. Bien sûr, c'est le plus grand Le besoin total n'est pas supérieur à k

class Solution {
    
    
    public int maxSumSubmatrix(int[][] matrix, int k) {
    
    
        int rows = matrix.length, cols = matrix[0].length, max = Integer.MIN_VALUE;
        for (int l = 0; l < cols; l++) {
    
     // 枚举左边界
            int[] rowSum = new int[rows]; 
            for (int r = l; r < cols; r++) {
    
     // 枚举右边界
                for (int i = 0; i < rows; i++) {
    
    
                    rowSum[i] += matrix[i][r]; //累加计算左右边界之间每一行的元素总和
                }
                max = Math.max(max, findMax(rowSum, k)); //找到左右边界之间这些行可以组成的矩阵中,元素总和最大的那个总和
                if(max == k) return k; //找到等于k的答案直接返回,节约后续的运算,也算个剪枝
            }
        }
        return max;
    }
    private int findMax(int[] arr, int k) {
    
    
        int max = Integer.MIN_VALUE,sum;
        for (int i = 0;i < arr.length;i++) {
    
     //第i行可以和第i+1,...,第arr.length-1行之间连续的行组成矩阵
            sum = 0;
            for (int j = i; j < arr.length; j++) {
    
    
                sum += arr[j];
                if (sum > max && sum <= k) max = sum;
                if(max == k) return k;
            }
        }
        return max;

    }
}

On peut voir que la fonction findMax() recherche en fait la somme maximale du sous-tableau du tableau arr, mais elle nécessite que la somme maximale du sous-tableau ne dépasse pas k. Ensuite, similaire à [somme maximale du sous-tableau], l'optimisation suivante peut être fait:

class Solution {
    
    
    public int maxSumSubmatrix(int[][] matrix, int k) {
    
    
    	//...
        int[] rowSum = new int[rows];  //将rowSum的重新new操作改为全填充为0
        for (int l = 0; l < cols; l++) {
    
     // 枚举左边界
            Arrays.fill(rowSum,0);
            //...
        }
        return max;
    }
    private int findMax(int[] arr, int k) {
    
    
        int sum = 0,max = Integer.MIN_VALUE;
        for(int i : arr){
    
    
            if(sum < 0) sum = 0;
            sum += i;
            max = Math.max(max,sum);
        }
        if(max <= k) return max; //最大子数组和不超过k就满足,可以直接返回,否则就还是要按照原来那样算
        max = Integer.MIN_VALUE;
        sum = 0;
        for (int i = 0;i < arr.length;i++) {
    
    
            //...
        }
        return max;
    }
}

3. La sous-chaîne la plus longue sans caractères répétés

Utilisez un algorithme de fenêtre glissante. Les limites initiales gauche et droite se trouvent à la position d'indice 0, puis la limite droite commence à s'étendre vers la droite, tout en enregistrant chaque caractère traversé et son indice le plus récent. Étendez la bordure droite jusqu'à ce qu'un caractère répété soit rencontré, réduisez la bordure gauche jusqu'à la position à côté de la dernière occurrence du caractère répété, puis continuez à étendre la bordure droite, et ainsi de suite. Conserver la longueur de la fenêtre tout en la déplaçant

public int lengthOfLongestSubstring(String s) {
    
    
    int len = s.length();
    if (len == 0) return 0;
    char[] c = s.toCharArray();
    //map记录每个字符最近一次出现的下标
    HashMap<Character, Integer> map = new HashMap<Character, Integer>();
    int max = 0;
    int left = 0;
    for(int i = 0; i < len; i++){
    
    
        if(map.containsKey(c[i])){
    
    
            //出现一个重复字符,就将窗口左边界移到重复字符上一次出现位置的下一个位置
            //由于任何字符上一次出现的位置一定不会超过右边界(i),所以更新后的左边界一定不大于右边界
            left = Math.max(left,map.get(c[i]) + 1);
        }
        map.put(c[i],i);
        //维护最大长度
        max = Math.max(max,i - left + 1);
    }
    return max;
}

187. Séquences d'ADN répétées

Chaque fois que 10 caractères consécutifs de la chaîne sont interceptés pour obtenir une sous-chaîne, une table de hachage est utilisée pour enregistrer le nombre d'occurrences de toutes les sous-chaînes, et si le nombre d'occurrences est supérieur à 1, il est ajouté à ans.

public List<String> findRepeatedDnaSequences(String s) {
    
    
    List<String> ans = new ArrayList<String>(); //保存答案
    Map<String, Integer> countMap = new HashMap<String, Integer>(); //记录子串出现次数
    int len = s.length();
    for (int i = 0; i <= len - 10;i++) {
    
    
        String sub = s.substring(i, i + 10); //截取子串
        int num = countMap.getOrDefault(sub,0);
        if(num == 1) ans.add(sub); //判断是否需要添加到ans中
        countMap.put(sub,num + 1);
    }
    return ans;
}

La deuxième méthode de la solution officielle est également très intéressante, il suffit de la marquer ici.

Je suppose que tu aimes

Origine blog.csdn.net/Pacifica_/article/details/125247246
conseillé
Classement