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       : [email protected]
 ***********************************************************/
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使得区间内的元素满足题目要求
2023-04-12 面试中常见的数组题目_第1张图片

给定一个整型数组和一个数字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       : [email protected]
 ***********************************************************/
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?
  大小写是否敏感?

2023-04-12 面试中常见的数组题目_第2张图片

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       : [email protected]
 ***********************************************************/
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));
    }
}

你可能感兴趣的:(算法,Java,面试,算法,java)