2023-04-12 面试中常见的数组题目

数组中的问题其实最常见

  • 通过基础问题,掌握写出正确算法的“秘诀”
  • 巧妙使用双索引技术,解决复杂问题
  • 对撞指针- 滑动窗口

1 从二分查找法看如何写出正确的程序

本节学习重点:处理边界问题!

  • 1.确定边界范围方法,先用区间表示,即明确范围的数学定义,后用代码表示;
  • 2.在循环里维护循环不变量,能保证算法的正确性,即这里的“在[l…r]的范围里寻找target”;
  • 3.重新理解一些基础算法当中的循环不变量。截图代码中:while(l<=r)、l=mid+1、r=mid-1都是在维护循环不变量“在[l…r]的范围里寻找target”。

2 写程序与调试程序

写程序:

  • 1、明确变量的含义,包含的范围区间
  • 2、循环不变量(比如Java的list的length的获取),保证变量和区间在循环时保持正确

调程序:

  • 1、小数据量测试:边界条件,特殊情况,错误例子。
  • 2、大数据量测试:性能和大数据量测试。

3~4 283号问题Move Zeros

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:
  输入: [0,1,0,3,12]
  输出: [1,3,12,0,0]

说明:
  必须在原数组上操作,不能拷贝额外的数组。
  尽量减少操作次数。

解法1:最简单解法,常规思维

package Chapter03Array.MoveZeros;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/***********************************************************
 * @note      : 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
 *              解决思路:遍历整个数组,把非零地先存起来,然后在赋值回nums,把剩下的位置补零即可            
 * @author    : l00379880 梁山广
 * @version   : V1.0 at 2019/8/19 18:53
 ***********************************************************/
class Solution {
    public void moveZeroes(int[] nums) {
        // list初始化最好指定大小
        List<Integer> numList = new ArrayList<>(nums.length);
        for (int num : nums) {
            if (num != 0) {
                numList.add(num);
            }
        }
        for (int i = numList.size(); i < nums.length; i++) {
            numList.add(0);
        }
        for (int i = 0; i < nums.length; i++) {
            nums[i] = numList.get(i);
        }
    }

    public static void main(String[] args) {
        int[] nums = {0,1,0,3,12};
        new Solution().moveZeroes(nums);
        System.out.println(Arrays.toString(nums));
    }
}

解法2:双指针法,不用额外空间

package Chapter03Array.MoveZeros;

import java.util.Arrays;

/***********************************************************
 * @note      : 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
 *              简化解法:双指针法,把非零元素不断前移,把剩下的补零。没有使用任何辅助空间
 * @author    : l00379880 梁山广
 * @version   : V1.0 at 2019/8/19 18:53
 ***********************************************************/
class Solution {
    /**
     * 简化解法:双指针法,把非零元素不断前移,把剩下的补零。没有使用任何辅助空间
     */
    public void moveZeroes(int[] nums) {
        // k代表nums中,[0...k)的元素均为非0元素
        int k = 0;
        // 遍历到第i个元素后,保证[0...i)中所有非0元素都按照顺序排列在[0...k)中
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                nums[k++] = nums[i];
            }
        }
        // 将nums中的剩余位置补零
        for (int i = k; i < nums.length; i++) {
            nums[i] = 0;
        }
    }

    public static void main(String[] args) {
        int[] nums = {0, 1, 0, 3, 12};
        new Solution2().moveZeroes(nums);
        System.out.println(Arrays.toString(nums));
    }
}

解法3:对换法,非零元素和零元素对换位置,不需要额外的空间和时间,最优

package Chapter03Array.MoveZeros;

import java.util.Arrays;

/***********************************************************
 * @note      : 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
 * @author    : l00379880 梁山广
 * @version   : V1.0 at 2019/8/19 18:53
 ***********************************************************/
class Solution3 {
    /**
     * 简化解法:对外交换法,零和非零元素对位交换,不需要额外空间和时间,最为简单
     */
    public void moveZeroes(int[] nums) {
        // k代表nums中,[0...k)的元素均为非0元素
        int k = 0;
        // 遍历到第i个元素后,保证[0...i)中所有非0元素都按照顺序排列在[0...k)中
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                // 零和非零元素对位交换
                int tmp = nums[k];
                nums[k] = nums[i];
                nums[i] = tmp;
                k++;
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = {0, 1, 0, 3, 12};
        new Solution3().moveZeroes(nums);
        System.out.println(Arrays.toString(nums));
    }
}

LeetCode上更多同类型问题

  • 27 Remove Element

    给定一个数组nums和一个数值val,将数组中所有等于val的元素删除,并返回剩余的元素个数。
    如nums = [3, 2, 2, 3], val = 3;
    返回2,且nums中前两个元素为2
    

    注意事项:

    • 如何定义删除?从数组中去除?还是放在数组末尾?
    • 剩余元素的排列是否要保证原有的相对顺序?
    • 是否有空间复杂度的要求? O(1)
      class Solution {
          public int removeElement(int[] nums, int val) {
              int k = 0;
              for(int i = 0; i < nums.length; i++){
                  if(nums[i] != val){
                      nums[k++] = nums[i];
                  }
              }
              return k;
          }
      }
    
  • 26 Remove Duplicated from Sorted Array

    给定一个有序数组,对数组中的元素去重,使得原数组的每个元素只有一个。返回去重后数组的长度值
    如 nums = [1, 1, 2],
    结果应返回2,且nums的前两个元素为12
    

    注意事项:

    • 如何定义删除?从数组中去除?还是放在数组末尾?
    • 剩余元素的排列是否要保证原有的相对顺序?
    • 是否有空间复杂度的要求? O(1)
      class Solution {
          public int removeDuplicates(int[] nums) {
              int k = 0;
              for(int i = 1; i< nums.length; i++){
                  if(nums[i] != nums[k]){ // 有序数组,所以直接和前面的比较就知道有没有排序了
                      nums[++k] = nums[i];
                  }
              }
              return k + 1; // k从0开始,所以数组长度最后要+1
          }
      }
      
  • 80 Remove Duplicated from Sorted Array II

    给定一个有序数组,对数组中的元素去重,使得原数组的每个元素最多保留两个。返回去重后数组的长度值
    如 nums = [1, 1, 1, 2, 2, 3],
    结果应返回5,且nums的前五个元素为1, 1, 2, 2, 3
    
    class Solution {
        public int removeDuplicates(int[] nums) {
            int k = 1;
            // 前2位无论如何都是要加入地,所以直接跳过接口
            for(int i = 2; i < nums.length; i++){
                if(nums[i] != nums[k]){
                    nums[++k] = nums[i];
                }else {
                    // 相邻的相等,还要再往前判断一位
                    if(nums[i] != nums[k - 1]){
                        nums[++k] = nums[i];
                    }
                }
            }
            return k + 1;
        }
    }
    

5 三路快排partition思路的应用 Sort Color

LeetCode75号问题 颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 012 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

示例:

输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:

一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出012 元素的个数,然后按照012的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?

解法1:系统自带的Arrays.sort()方法直接排好了

解法2:使用计数排序的两趟扫描算法

package Chapter03Array.SortColors;

import java.util.Arrays;

/***********************************************************
 * 题目:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
 * 思路:使用计数排序的两趟扫描算法
 *
 * @author    : l00379880 梁山广
 * @version   : V1.0 at 2019/8/19 20:05
 ***********************************************************/
class Solution {
    public void sortColors(int[] nums) {
        int[] count = {0, 0, 0};
        for (int i = 0; i < nums.length; i++) {
            count[nums[i]]++;
        }
        Arrays.fill(nums, 0, count[0], 0);
        Arrays.fill(nums, count[0], count[0] + count[1], 1);
        Arrays.fill(nums, count[0] + count[1], nums.length, 2);
    }

    public static void main(String[] args) {
        int[] nums = {2, 0, 2, 1, 1, 0};
        new Solution().sortColors(nums);
        System.out.println(Arrays.toString(nums));
    }
}

解法3:三路快排法

package Chapter03Array.SortColors;

import java.util.Arrays;

/***********************************************************
 * 题目:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
 * 思路:三路快速排序,按照0、1、2把数据分成三份,通过数据交换即可一次循环完成排序
 *
 * @author    : l00379880 梁山广
 * @version   : V1.0 at 2019/8/19 20:05
 ***********************************************************/
class Solution2 {
    void swap(int[] nums, int i, int j) {
        int tmp = nums[j];
        nums[j] = nums[i];
        nums[i] = tmp;
    }

    public void sortColors(int[] nums) {
        // nums[0...zero] == 0
        int zero = -1;
        // nums[two...n-1] == 2
        int two = nums.length;
        for (int i = 0; i < two; ) {
            if (nums[i] == 1) {
                i++;
            } else if (nums[i] == 2) {
                // 等于2时i不需要继续前移
                swap(nums, i, --two);
            } else {
                // nums[i] ==0
                swap(nums, ++zero, i++);
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = {2, 0, 2, 1, 1, 0};
        new Solution2().sortColors(nums);
        System.out.println(Arrays.toString(nums));
    }
}

更多题目

  • 88 Merge Sorted Array:给定两个有序整型数组nums1, nums2,将nums2的元素归并到nums1中

    class Solution {
        public void merge(int[] nums1, int m, int[] nums2, int n) {
            // 1.递归终止条件
            if(n == 0){
                // nums2为空时,nums1还有元素,调整完毕,nums1既是想要的
                return;
            }
            if(m == 0){
                // nums1为空时,nums2还有元素,直接把nums2剩下的元素全都覆盖到nums1中即可
                for(int i = 0; i < n; i++){
                    nums1[i] = nums2[i];
                }
                return;
            }
    
            // 2.递归的具体逻辑
            // nums2和nums1都不为空时,需要比较最后一个元素,决定nums1最后插入哪个元素
            if(nums2[n - 1] > nums1[m - 1]){
                // nums2的最后一个元素大于nums1的最后一个元素,则nums1最后的元素取nums2的
                nums1[m + n -1] = nums2[n - 1];
                // nums2少了一个元素,继续往下递归
                merge(nums1, m, nums2, n-1);
            }else {
                // nums2的最后一个元素小于nums1的最后一个元素,则nums1最后的元素取nums1的
                nums1[m + n -1] = nums1[m - 1];
                // nums1少了一个元素,继续往下递归
                merge(nums1, m-1, nums2, n);
            }
        }
    }
    
  • 215 Kth Largest Element in an Array

    在一个整数序列中寻找第k大的元素
    如给定数组 [3, 2, 1, 5, 6, 4], k = 2, 结果为5
    利用快排partition中,将pivot放置在了其正确的位置上的性质
    

    解题思路:维护一个大小为k的最小堆,遍历一遍数组,最后的最小堆堆顶元素就是第k大的元素

    class Solution {
       public int findKthLargest(int[] nums, int k) {
            PriorityQueue<Integer> pq = new PriorityQueue<>(k);
            for (int num : nums) {
                if (pq.size() < k) {
                    pq.offer(num);
                } else {
                    pq.offer(num);
                    pq.poll();
                }
            }
            return pq.poll();
        }
    }
    

6 Two Sum问题

LeetCode第1号问题 两数之和

给定一个有序整型数组和一个整数target,在其中寻找两个元素,使其和为target。返回这两个数的索引。
如numbers = [2, 7, 11, 15], target = 9
返回数字27的索引1, 2 (索引从1开始计算)

注意:
  如果没有解怎样?保证有解
  如果有多个解怎样?返回任意解

解题思路:
0.暴力解法,两层for循环,列出所有的可能,总能找出来符合条件地,不过这种方法性能太低,容易超时
1.因为是有序地,所以一个确定一个元素后,另一个元素在数组中用二分法查找
2.也可以用使用两个指针,从两边向中间靠近,直到获取nums[i] + nums[j] = target,这个方法又称对撞指针法,下面有实现
/***********************************************************
 * @Description : 给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
 * 可以用使用两个指针,从两边向中间靠近,直到获取nums[i] + nums[j] = target,这个方法又称对撞指针法,下面有实现
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2019/8/19 23:48
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter03.TwoSum;

import java.util.Arrays;

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int l = 0, r = numbers.length - 1;
        while (l < r) {
            if (numbers[l] + numbers[r] == target) {
                // 注意返回地是下标,而且是从1开始
                return new int[]{l + 1, r + 1};
            } else if (numbers[l] + numbers[r] < target) {
                l++;
            } else {
                r--;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        int[] numbers = {2, 7, 11, 15};
        int target = 9;
        int[] result = new Solution().twoSum(numbers, target);
        System.out.println(Arrays.toString(result));
    }
}

扩展问题

  • 125 Valid Palindrome

    // 125 Valid Palindrome
    给定一个字符串,只看其中的数字和字母,忽略大小写,判断这个字符串是否为回文串?
    “A man, a plan, a canal; Panama- 是回文串
    “race a car” - 不是回文串
    
    注意事项:
      空字符串如何看?
      字符的定义?
      大小写问题
    
    class Solution {
        public boolean isPalindrome(String s) {
            s = s.toLowerCase();
            int l = 0;
            int r = s.length() - 1;
            while(l < r){
                // 只要还没相遇就接着往下走
                if(!Character.isLetterOrDigit(s.charAt(l))){
                    // 左边的字符不是字母或数字
                    l++;
                    continue;
                }
                if(!Character.isLetterOrDigit(s.charAt(r))){
                    // 右边的字符不是字母或数字
                    r--;
                    continue;
                }
                // 左右两边都是字母或数字,只要不相等就说明不是回文
                if(s.charAt(l) != s.charAt(r)){
                    return false;
                }
                // 相等地话,继续向中间靠拢
                l++;
                r--;
            }
            return true;
        }
    }
    
  • 344 Reverse String

    给定一个字符串,返回这个字符串的倒序字符串。
    - 如“hello”,返回”olleh”
    - 类似:翻转一个数组
    
    class Solution {
        public void reverseString(char[] s) {
            int l = 0;
            int r = s.length - 1;
            while(l < r){
                char tmp = s[l];
                s[l] = s[r];
                s[r] = tmp;
                l++;
                r--;
            }
        }
    }
    
  • 345 Reverse Vowels of a String

    给定一个字符串,将该字符串中的元音字母翻转
    如:给出 ”hello”,返回”holle”
    如:给出“leetcode”,返回“leotcede”
    元音不包含y
    
    class Solution {
        public String reverseVowels(String s) {
            String vowels = "aeiouAEIOU"; // 注意大小写
            char[] chs = s.toCharArray();
            int l = 0;
            int r = s.length() - 1;
            while(l < r){
                // 左边不是元音字符
                if(!vowels.contains(String.valueOf(chs[l]))){
                    l++;
                    continue;
                }
                // 右边不是元音字符
                if(!vowels.contains(String.valueOf(chs[r]))){
                    r--;
                    continue;
                }
                // 左右都为元音字符
                char tmp = chs[l];
                chs[l] = chs[r];
                chs[r] = tmp;
                l++;
                r--;
            }
            return String.valueOf(chs);
        }
    }
    
  • 11 Container With Most Water

    给出一个非负整数数组 a1,a2,a3,,an;每一个整数表示一个竖立在坐标轴x位置的一堵高度为ai的“墙”,选择两堵墙,和x轴构成的容器可以容纳最多的水。
    
    /**
    * 基本的表达式: area = min(height[i], height[j]) * (j - i) 使用两个指针,值小的指针向内移动,这样就减小了搜索空间 
    * 因为面积取决于指针的距离与值小的值乘积,如果值大的值向内移动,距离一定减小,而求面积的另外一个乘数一定小于等于值小的值,因此面积一定减小;
    * 而我们要求最大的面积,因此值大的指针不动,而值小的指针向内移动遍历
    */
    class Solution {
        public int maxArea(int[] height) {
            int maxArea = 0;
            int l = 0;
            int r = height.length - 1;
            while (l < r) {
                int area = Math.min(height[l], height[r]) * (r - l);
                maxArea = Math.max(area, maxArea); // 更新最大值
                // height小地向内移动,贪心算法。如果求最小值就是height大地向内移动
                if (height[l] < height[r]) {
                    l++;
                } else {
                    r--;
                }
            }
            return maxArea;
        }
    }
    

7 滑动窗口问题

LeetCode第209号问题 长度最小的子数组

核心思想:用于求解符合条件的连续子数组问题,方法是维护一个范围[l, r),l和r均在数组范围内,不断调整l和r使得区间内的元素满足题目要求
滑动窗口图示

给定一个整型数组和一个数字s,找到数组中最短的一个连续子数组,使得连续子数组的数字和sum >= s,返回这个最短的连续子数组的长度值
如,给定数组[2, 3, 1, 2, 4, 3], s = 7
答案为[4, 3],返回2

注意:
  什么叫子数组
  如果没有解怎么办?返回0

方法1:暴力解

遍历所有的连续子数组[i…j],计算其和sum,验证sum >= s,时间复杂度O(n^3)

方法2:滑动窗口

不算调整窗口长度来看窗口数组是否满足条件,过程中记录窗口数组的长度,不断取最小值

/***********************************************************
 * @Description : 滑动窗口问题
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2019/8/20 07:49
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter03.MinSubArrayLen;

/**
 * 时间复杂度O(n),空间复杂度O(1)
 */
class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        // nums[l, r)为我们的滑动窗口,左闭右开区间,初始化应该不包含任何元素
        int l = 0, r = -1;
        int sum = 0;
        // 后面是求最小值,所以开始要设置成最大值
        int minLen = nums.length + 1;
        while (l < nums.length) {
            // 因为要r++,所以务必判断r的范围
            if ((r + 1) < nums.length && sum < s) {
                // 小于要求的目标和s了,右边元素下标右移,然后计算新的数组和sum
                r++;
                sum = sum + nums[r];
            } else {
                // 大于要求地目标和s了,先减去l位置的元素,然后左边元素下标右移
                sum = sum - nums[l];
                l++;
            }

            if (sum >= s) {
                // 满足要求了就取出窗口长度,
                minLen = Math.min(minLen, r - l + 1);
            }
        }
        if (minLen == nums.length + 1) {
            // minLen从未更新过,说明数组中不存在子数组满足其和大于等于s
            return 0;
        }
        return minLen;
    }

    public static void main(String[] args) {
        int s = 11;
        int[] nums = {1, 2, 3, 4, 5};
        int minLen = new Solution().minSubArrayLen(s, nums);
        System.out.println(minLen);
    }
}

8 在滑动窗口中做记录 Longest Substring Without Repeating Characters

LeetCode第3号问题 无重复字符的最长子串,此题中引入了频率数组用于统计各个字符的出现次数

在一个字符串中寻找没有重复字母的最长子串,返回其长度。
如”abcabcbb”,则结果为”abc”,长度为3
如”bbbbb”,则结果为”b”,长度为1
如”pwwkew”,则结果为”wke”,长度为3

注意:
  字符集?只有字母?数字+字母?ASCII?
  大小写是否敏感?

滑动窗口+记录ascc码的频率数组

package Chapter03Array.LongestSubString;

import java.util.Arrays;

/***********************************************************
 * @note      : 在一个字符串中寻找没有重复字母的最长子串
 * @author    : l00379880 梁山广
 * @version   : V1.0 at 2019/8/20 9:32
 ***********************************************************/
public class Solution {
    public int lengthOfLongestSubstring(String s) {
        // freq表示所有的ASCII码字符对应的频率
        int[] freq = new int[256];
        Arrays.fill(freq, 0);
        // 滑动窗口为s[l...r)左闭右开区间
        int l = 0, r = -1;
        // 要求最大值,就得初始化为最小值
        int maxLen = 0;
        while (l < s.length()) {
            if (r + 1 < s.length() && freq[s.charAt(r + 1)] == 0) {
                // 最右侧的字符还没统计过频率,那么就把最右侧的字符放入统计数组freq
                r++;
                freq[s.charAt(r)]++;
            } else {
                // 已经到最右侧或者最右侧的字符已经统计过频率了非0了,那么就把最左侧的字符右移,右边界不进行扩展
                freq[s.charAt(l)]--;
                l++;
            }
            maxLen = Math.max(maxLen, r - l + 1);
        }
        return maxLen;
    }

    public static void main(String[] args) {
        String s = "abcabcbb";
        System.out.println(new Solution().lengthOfLongestSubstring(s));
    }
}

滑动窗口的其他相关问题

  • 438.找到字符串中所有字母异位词
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    int[] freq = new int[256];

    /**
     * 判断[l, r)的区间是否满足要求:即使anagrams
     *
     * @param l 左边界
     * @param r 右边界
     * @param s 原字符串
     * @return [l, r)是否满足要求
     */
    boolean isAnagrams(int l, int r, String s, String p) {
        int[] freqTmp = new int[256];
        Arrays.fill(freqTmp, 0);
        for (int i = l; i < r; i++) {
            freqTmp[s.charAt(i)] += 1;
        }
        for (int i = 0; i < p.length(); i++) {
            if (freqTmp[p.charAt(i)] != freq[p.charAt(i)]) {
                return false;
            }
        }
        return true;
    }

    public List<Integer> findAnagrams(String s, String p) {
        // 初始化为-1,-1表示时不需要查找的字符
        Arrays.fill(freq, 0);
        // 更新在p中的字符
        for (int i = 0; i < p.length(); i++) {
            freq[p.charAt(i)] += 1;
        }
        // 存储符合条件的起始索引的列表
        List<Integer> indexList = new ArrayList<>();
        int l = 0;
        int r = p.length();
        // 右边界还没滑动到末尾时就一直往后滑动
        while (r <= s.length()) {
            if (isAnagrams(l, r, s, p)) {
                indexList.add(l);
            }
            l++;
            r++;
        }
        return indexList;
    }

    public static void main(String[] args) {
        String s = "baa";
        String p = "aa";
        List<Integer> anagrams = new Solution().findAnagrams(s, p);
        System.out.println(anagrams);
    }
}

优化1:

class Solution {
   public List<Integer> findAnagrams(String s, String p) {
        // 存储符合条件的起始索引的列表
        List<Integer> indexList = new ArrayList<>();
        if ("".equals(s) || "".equals(p) || s.length() < p.length()) {
            return indexList;
        }
        int[] freq = new int[256];
        int[] freqTmp = new int[256];
        Arrays.fill(freq, 0);
        Arrays.fill(freqTmp, 0);
        // 更新在p中的字符
        for (int i = 0; i < p.length(); i++) {
            freq[p.charAt(i)] += 1;
            // 初始化窗口内的字符频率
            freqTmp[s.charAt(i)] += 1;
        }
        int l = 0;
        int r = p.length();
        // 右边界还没滑动到末尾时就一直往后滑动
        while (r <= s.length()) {
            boolean find = true;
            for (int i = l; i < r; i++) {
                if (freqTmp[s.charAt(i)] != freq[s.charAt(i)]) {
                    find = false;
                    break;
                }
            }
            if (find) {
                indexList.add(l);
            }
            if (r + 1 > s.length()) {
                break;
            }
            // 窗口向后移动,注意区间是左闭右开[l...r)
            freqTmp[s.charAt(l)] -= 1;
            freqTmp[s.charAt(r)] += 1;
            l++;
            r++;
        }
        return indexList;
    }
}
  • 76.最小覆盖子串
/***********************************************************
 * @Description : LeetCode 76号问题:最小覆盖字符串
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2020/1/15 23:34
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter03.LeetCode76;

class Solution {
    public String minWindow(String s, String p) {
        int sLen = s.length();
        int pLen = p.length();
        if ("".equals(s) || "".equals(p) || sLen < pLen) {
            return "";
        }
        // s="ab" p="A
        if (pLen == 1) {
            if (s.contains(p)) {
                return p;
            } else {
                return "";
            }
        }
        int[] freq = new int[256];
        int[] freqTmp = new int[256];

        // 更新在p中的字符
        for (int i = 0; i < pLen; i++) {
            freq[p.charAt(i)] += 1;
            // 初始化窗口内的字符频率
            freqTmp[s.charAt(i)] += 1;
        }
        int l = 0;
        int r = pLen;
        int lMin = 0;
        int rMin = pLen;
        // 找最小时,初始应为最大值
        int min = Integer.MAX_VALUE;
        // 右边界还没滑动到末尾时就一直往后滑动
        while (r <= sLen) {
            boolean find = true;
            for (int i = 0; i < pLen; i++) {
                if (freqTmp[p.charAt(i)] == 0) {
                    // 有一个p中的字母在s中找不到,直接跳出
                    find = false;
                    break;
                } else {
                    // 能找到但是频率小,出现频率大于等于可以
                    if (freqTmp[p.charAt(i)] < freq[p.charAt(i)]) {
                        find = false;
                        break;
                    }
                }
            }
            if (find) {
                // 找到了符合条件的字符串
                if ((r - l) < min) {
                    // 新的区间小于初始化地最小区间才需要更新
                    min = r - l;
                    lMin = l;
                    rMin = r;
                }
            }

            if (freq[s.charAt(l)] == 0) {
                // 最左边的字母不在p中,此时窗口宽度必须必p的长度大,这样左边的l才可以右移
                if (r - l > pLen) {
                    // 在维持窗口宽度的前提下,左边界l可以向右移动地
                    freqTmp[s.charAt(l)] -= 1;
                    l++;
                } else {
                    // 长度不够,r必须先往右侧移动
                    // 必须放在这里~~否则会提前退出地
                    if (r + 1 > sLen) {
                        break;
                    }
                    freqTmp[s.charAt(r)] += 1;
                    r++;
                }

            } else {
                // 最左边的元素在p中,同时它的频率大于在p中的频率了,左边界l一定是可以向右移动地,需要更新freqTmp
                if (freqTmp[s.charAt(l)] > freq[s.charAt(l)]) {
                    freqTmp[s.charAt(l)] -= 1;
                    l++;
                } else {
                    // 必须放在这里~~否则会提前退出地
                    if (r + 1 > sLen) {
                        break;
                    }
                    // 其他情况下只能右侧元素往右移动,并更新频率
                    freqTmp[s.charAt(r)] += 1;
                    r++;
                }
            }
        }
        if (min == Integer.MAX_VALUE) {
            return "";
        } else {
            return s.substring(lMin, rMin);
        }
    }

    /**
     * 想到的异常用例:
     * S = "aa", T = "a"  ==> "a"
     * S = "abc", T = "acd"  ==> ""
     * S = "abc", T = "cba"  ==> abc
     * S = "bbaa", T = "aba"  ==>baa
     * S = "aaaaaaaaaaaabbbbbcdd", T = "abcdd"  ==> abbbbbcdd
     * S = "aabAA", T = "BB"
     */
    public static void main(String[] args) {
        String S = "aabAA";
        // cba、acd都测测
        String T = "BB";
        System.out.println(new Solution().minWindow(S, T));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/10427.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务架构-服务网关(Gateway)-权限认证(分布式session替代方案)

权限认证-分布式session替代方案 前面我们了解了Gateway组件的过滤器&#xff0c;这一节我们就探讨一下Gateway在分布式环境中的一个具体用例-用户鉴权。 1、传统单应用的用户鉴权 从我们开始学JavaEE的时候&#xff0c;就被洗脑式灌输了一种权限验证的标准做法&#xff0c;…

Elasticsearch:集群管理的一些建议

在之前的文章 “Elasticsearch&#xff1a;集群管理” &#xff0c;我们对集群管理做了一些介绍。在今天的文章中&#xff0c;我们接着来聊一下有关配置的方面的问题。这在很大程度上取决于你的用例&#xff0c;是索引还是搜索繁重。 我们将在这里讨论在集群设置方面我们需要关…

Leetcode.130 被围绕的区域

题目链接 Leetcode.130 被围绕的区域 mid 题目描述 给你一个 m x n的矩阵 board&#xff0c;由若干字符 X和 O&#xff0c;找到所有被 X围绕的区域&#xff0c;并将这些区域里所有的 O用 X填充。 示例 1&#xff1a; 输入&#xff1a;board [[“X”,“X”,“X”,“X”],[“X…

「Cpolar」使用Typecho搭建个人博客网站【内网穿透实现公网访问】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后端的开发语言A…

在 Python 中计算两个数字之间的百分比

要计算两个数字之间的百分比&#xff0c;请将一个数字除以另一个数字&#xff0c;然后将结果乘以 100&#xff0c;例如 (30 / 75) * 100。这显示第一个数字占第二个数字的百分比。 在示例中&#xff0c;30 是 75 的 40%。 def is_what_percent_of(num_a, num_b):return (num_a…

基于SVG的HMI组件

人机界面是自动化领域不可或缺重要组成部分。人机界面系统的设计看上去并没有太大的技术门槛&#xff0c;但是设计一个HMI系统的工作量是巨大的&#xff0c;如果你没有足够的耐心和精力是难以完成一个通用HMI系统的。构建UI控件库就是一个似乎永远完不成的事情&#xff0c;用户…

Halo博客建站实战以及问题汇总

目录 简介 特性 快速开始 安装步骤 环境准备 Docker-compose方式部署 问题汇总 mac端无法访问页面 页面登录提示账号密码错误 重装注意点 资料 官方文档 简介 Halo 强大易用的开源建站工具 特性 代码开源 我们的所有代码开源在 GitHub 上且处于积极维护状态&…

《分解因数》:质因数分解

目录 一、题目&#xff1a; 二、思路&#xff1a; 三、代码&#xff1a; 一、题目&#xff1a; 分解因数 《分解因数》题目链接 所谓因子分解&#xff0c;就是把给定的正整数a&#xff0c;分解成若干个素数的乘积&#xff0c;即 a a1 a2 a3 ... an,并且 1 < a1…

HCIA第二次笔记

目录 OSI/RM七层参考模型——开放式的系统互联参考模型 核心——分层 TCP/IP模型——TCP/IP协议簇 应用层 应用层协议 封装与解封装 传输层 TCP协议和UDP协议的区别 TCP的报文 TCP的三次握手 TCP的四次挥手 TCP的四种可靠传输机制 OSI/RM七层参考模型——开放式的系…

[目标识别-论文笔记]Object Detection in Videos by Short and Long Range Object Linking

文章标题&#xff1a;2018_Cite13_Tang——Object Detection in Videos by Short and Long Range Object Linking 这篇论文也被叫做“2019_Cite91_TPAMI_Tang——Object Detection in Videos by High Quality Object Linking” 如果这篇博客对你有帮助&#xff0c;希望你 点赞…

学生信息管理系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87673902 更多系统资源库…

【设计模式】如何在业务开发中使用适配器模式?

文章目录前言适配器模式定义通用代码实现适用场景案例场景分析一坨坨代码实现适配器模式重构总结前言 适配器模式&#xff08;Adapter Pattern&#xff09;&#xff1a;将一个类的接口变换成客户端所期待的另一种接口&#xff0c;从而使原本因接口不匹配而无法在一起工作的两个…

高速Serdes技术(FPGA领域应用)

目录引入一、Serdes&#xff08;概念-历程&#xff09;1、概念2、技术现状3、发展历程二、Serdes结构三、在FPGA领域中的运用四、Serdes跟Lvds的关系五、Xilinx 有关 serdes的文档六、参考文献引入 回顾接口技术发展历史&#xff0c;其实数据的传输最开始是低速的串行接口&…

OSI七层网络模型与TCP/IP四层模型

一、OSI七层网络模型 OSI 七层模型 是国际标准化组织提出一个网络分层模型&#xff0c;其大体结构以及每一层提供的功能如下图所示&#xff1a; 但由于各方面原因&#xff0c;OSI 七层模型并没有被广泛应用&#xff0c;更多的是作为网络分层的一种基础理论模型。 二、TCP/IP…

NumPy 基础知识 :1~5

原文&#xff1a;Numpy Essentials 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 一、NumPy 简介 “我宁愿使用通用语言进行数学运算&#xff0c;也不愿尝试使用数学语言进行通用编程。” – John D Cook 在过去的十年中&#xff0c;Python 已成为科学计算中最受欢迎…

MVCC

MVCC基本概念 当前读 当前读 : 读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁. 对于我们日常的操作. 如 : select....lock in share mode(共享锁) , select * for update , update ,insert,delete(排他锁) 都是一种当前读. 快…

Java对象模型

介绍 Java是一种面向对象的语言&#xff0c;而Java对象在JVM中存储是由一定结构的。而这个 Java对象自身的存储模型称之为Java对象模型HotSpot虚拟机中&#xff0c;设计了一个OOP-Klass Model.OOP指的是普通对象指针&#xff0c;而Klass用来描述对象的具体类型。如下图所示是一…

文章生成器写出来的原创文章

文章生成机器人 文章生成机器人是一种基于人工智能技术和自然语言处理算法的程序&#xff0c;可以自动地生成高质量、原创的文章。 文章生成机器人的优点如下&#xff1a; 提高工作效率&#xff1a;文章生成机器人能够在较短的时间内自动帮助用户生成大量的文章&#xff0c;提…

Python 小型项目大全 21~25

二十一、DNA 可视化 原文&#xff1a;http://inventwithpython.com/bigbookpython/project21.html 脱氧核糖核酸是一种微小的分子&#xff0c;存在于我们身体的每个细胞中&#xff0c;包含着我们身体如何生长的蓝图。它看起来像一对核苷酸分子的双螺旋结构&#xff1a;鸟嘌呤、…

计算机网络微课堂1-3节

目录 1. TCP/TP协议​编辑 2. 3.调制解调器 4.因特网的组成 5.电路交换 6.分组交换 重要常用 7.报文交换 8.总结电路交换 报文交换和分组交换 9. 1. TCP/TP协议 2. ISP 网络提供商 ISP的三层 国际 国家 和本地 3.调制解调器 什么是调制解调器&#xff0c;它存在的…