2、数组问题最常见

内容来自刘宇波老师玩转算法面试

1、二分查找法

二分查找法(Java 实现)

template<typename T>
int binarySearch1(T arr[], int n, T target) {
    // 在 [l ... r] 的范围里寻找 target
    int l = 0;
    int r = n - 1;
    int mid;

    // 当 l == r 时, 区间 [l ... r] 依然是有效的
    while (l <= r) {
        mid = l + (r - l) / 2;
        if (arr[mid] == target) return mid;
        if (arr[mid] < target) l = mid + 1;
        else r = mid - 1;
    }

    return -1;
}

template<typename T>
int binarySearch2(T arr[], int n, T target) {
    // 在 [l ... r) 的范围里寻找 target
    int l = 0;
    int r = n;
    int mid;

    // 当 l < r 时, 区间 [l ... r) 就是有效的
    while (l < r) {
        mid = l + (r - l) / 2;
        if (arr[mid] == target) return mid;
        if (arr[mid] < target) l = mid + 1;
        else r = mid;
    }

    return -1;
}

2、移动零

283 - 移动零

/**
 * 时间复杂度: O(N)
 * 空间复杂度: O(N)
 */
public static void moveZeroes1(int[] nums) {
    int[] temp = new int[nums.length];
    int index = 0;
    for (int num : nums) {
        if (num != 0) temp[index++] = num;
    }
    System.arraycopy(temp, 0, nums, 0, temp.length);
}

/**
 * 时间复杂度: O(N)
 * 空间复杂度: O(1)
 */
public static void moveZeroes2(int[] nums) {
    // [0 ... p) 存放的都是非 0 元素
    int p = 0;
    for (int num : nums) {
        if (num != 0) nums[p++] = num;
    }
    Arrays.fill(nums, p, nums.length, 0);
}

/**
 * 时间复杂度: O(N)
 * 空间复杂度: O(1)
 */
public static void moveZeroes3(int[] nums) {
    // [0 ... p) 存放的都是非 0 元素
    int p = 0;
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != 0) {
            if (p != i) swap(nums, p++, i);
            else p++;
        }
    }
}

private static void swap(int[] nums, int a, int b) {
    int k = nums[a];
    nums[a] = nums[b];
    nums[b] = k;
}

更多问题
27 - 移除元素
26 - 删除有序数组中的重复项
80 - 删除有序数组中的重复项 II

3、颜色分类

75 - 颜色分类

/**
 * 计数排序思路
 * 时间复杂度: O(N)
 * 空间复杂度: O(R), R 为元素的取值范围, 在这里 R = 3
 */
public static void sortColors1(int[] nums) {
    // nums 中的元素固定为 0, 1, 2
    // 处理元素取值范围是 [0, R) 的计数排序
    int R = 3;
    int[] cnt = new int[R];
    for (int num : nums) cnt[num]++;

    // [index[i], index[i + 1]) 区间的值为 i
    int[] index = new int[R + 1];
    for (int i = 0; i < R; i++) {
        // 右边界 = 左边界 + cnt[i]
        index[i + 1] = index[i] + cnt[i];
    }

    for (int i = 0; i + 1 < index.length; i++) {
        // [index[i], index[i + 1]) 区间的值为 i
        for (int j = index[i]; j < index[i + 1]; j++) nums[j] = i;
    }
}

/**
 * 三路快速排思路
 * 时间复杂度: O(N)
 * 空间复杂度: O(1)
 */
public static void sortColors2(int[] nums) {
    int p1 = -1;
    int p2 = nums.length;

    int i = 0;
    while (i < p2) {
        if (nums[i] == 0) swap(nums, ++p1, i++);
        else if (nums[i] == 2) swap(nums, --p2, i);
        else i++;
    }
}

private static void swap(int[] nums, int a, int b) {
    int k = nums[a];
    nums[a] = nums[b];
    nums[b] = k;
}

更多问题
88 - 合并两个有序数组
215 - 数组中的第K个最大元素

4、两数之和 II - 输入有序数组

167 - 两数之和 II - 输入有序数组

更多问题
125 - 验证回文串
344 - 反转字符串
345 - 反转字符串中的元音字母
11 - 盛最多水的容器

4.1、二分搜索思路

image

public static int[] twoSum(int[] numbers, int target) {
    for (int i = 0; i < numbers.length; i++) {
        int complement = target - numbers[i];
        int res = binarySearch(numbers, i + 1, numbers.length - 1, complement);
        if (res != -1) return new int[]{i + 1, res + 1};
    }
    return new int[]{-1, -1};
}

private static int binarySearch(int[] arr, int l, int r, int target) {
    int mid;

    while (l <= r) {
        mid = l + (r - l) / 2;
        if (arr[mid] == target) return mid;
        else if (arr[mid] > target) r = mid - 1;
        else l = mid + 1;
    }

    return -1;
}

4.2、对撞指针思路

image

public static int[] twoSum(int[] numbers, int target) {
    int p1 = 0;
    int p2 = numbers.length - 1;
    while (p1 < p2) {
        int sum = numbers[p1] + numbers[p2];
        if (sum == target) return new int[]{p1 + 1, p2 + 1};
        else if (sum < target) p1++;
        else p2--;
    }
    return new int[]{-1, -1};
}

5、滑动窗口

滑动窗口算法通常使用双指针来维护窗口的左右边界,右指针用于扩展窗口,左指针用于缩小窗口
在每一次移动窗口时,我们都可以根据窗口的状态来更新答案

5.1、长度最小的子数组

209 - 长度最小的子数组
minSubArrayLen1 和 minSubArrayLen2 均可

image

public static int minSubArrayLen1(int target, int[] nums) {
    // nums[l ... r] 是滑动窗口
    int l = 0;
    int r = -1;
    int sum = 0;
    int res = nums.length + 1;

    // 窗口的左边界在数组范围内, 则循环继续
    while (l < nums.length) {
        if (r + 1 < nums.length && sum < target) sum += nums[++r];
        else sum -= nums[l++];

        if (sum >= target) res = Math.min(res, r - l + 1);
    }

    return res != nums.length + 1 ? res : 0;
}

public static int minSubArrayLen2(int target, int[] nums) {
    // nums[l ... r] 是滑动窗口
    int l = 0;
    int r = -1;
    int sum = 0;
    int res = nums.length + 1;

    // 窗口的右边界可以继续扩展, 则循环继续
    while (r + 1 < nums.length) {
        // r 用于扩展窗口
        while (r + 1 < nums.length && sum < target) sum += nums[++r];
        if (sum >= target) res = Math.min(res, r - l + 1);

        // l 用于缩小窗口
        while (l <= r && sum >= target) {
            sum -= nums[l++];
            if (sum >= target) res = Math.min(res, r - l + 1);
        }
    }

    return res != nums.length + 1 ? res : 0;
}

5.2、无重复字符的最长子串

3 - 无重复字符的最长子串
推荐 lengthOfLongestSubstring3

image

public static int lengthOfLongestSubstring1(String s) {
    // s[l ... r] 是滑动窗口
    int l = 0;
    int r = -1;
    int[] freq = new int[256]; // freq[i] 代表字符 i 出现的频率
    int res = 0;

    // 窗口的左边界在字符串范围内, 则循环继续
    while (l < s.length()) {
        if (r + 1 < s.length() && freq[s.charAt(r + 1)] == 0) freq[s.charAt(++r)]++;
        else freq[s.charAt(l++)]--;

        res = Math.max(res, r - l + 1);
    }

    return res;
}

public static int lengthOfLongestSubstring2(String s) {
    // s[l ... r] 是滑动窗口
    int l = 0;
    int r = -1;
    int[] index = new int[256]; // index[i] 代表字符 i 在 s 中的索引
    Arrays.fill(index, -1);
    int res = 0;

    // 窗口的左边界在字符串范围内, 则循环继续
    while (l < s.length()) {
        if (r + 1 < s.length() && index[s.charAt(r + 1)] == -1) index[s.charAt(++r)] = r;
        else index[s.charAt(l++)] = -1;

        res = Math.max(res, r - l + 1);
    }

    return res;
}

public static int lengthOfLongestSubstring3(String s) {
    // s[l ... r] 是滑动窗口
    int l = 0;
    int r = -1;
    int[] index = new int[256]; // index[i] 代表字符 i 在 s 中的索引
    Arrays.fill(index, -1);
    int res = 0;

    // 窗口的右边界可以继续扩展, 则循环继续
    while (r + 1 < s.length()) {
        // r 用于扩展窗口
        while (r + 1 < s.length() && index[s.charAt(r + 1)] == -1) index[s.charAt(++r)] = r;
        res = Math.max(res, r - l + 1);

        // l 用于缩小窗口
        if (r + 1 < s.length()) {
            int i;
            for (i = l; i <= index[s.charAt(r + 1)]; i++) index[s.charAt(i)] = -1;
            l = i;
        }
    }

    return res;
}

6、更多题目

6.1、找到字符串中所有字母异位词

438 - 找到字符串中所有字母异位词
推荐 findAnagrams1 和 findAnagrams3

public static List<Integer> findAnagrams1(String s, String p) {
    List<Integer> list = new ArrayList<>();
    if (s.length() < p.length()) return list;

    int[] freq_p = new int[26];
    for (char c : p.toCharArray()) freq_p[c - 'a']++;

    // s[l ... r] 是滑动窗口
    int l = 0;
    int r = l + p.length() - 1;
    int[] freq_s = new int[26];
    for (int i = l; i <= r; i++) freq_s[s.charAt(i) - 'a']++;
    if (same(freq_s, freq_p)) list.add(l);

    // 窗口的右边界可以继续扩展, 则循环继续
    while (r + 1 < s.length()) {
        freq_s[s.charAt(++r) - 'a']++;
        freq_s[s.charAt(l++) - 'a']--;
        if (same(freq_s, freq_p)) list.add(l);
    }

    return list;
}

public static List<Integer> findAnagrams2(String s, String p) {
    List<Integer> list = new ArrayList<>();
    if (s.length() < p.length()) return list;

    int[] freq_p = new int[26];
    for (char c : p.toCharArray()) freq_p[c - 'a']++;

    // s[l ... r] 是滑动窗口
    int l = 0;
    int r = -1;
    int[] freq_s = new int[26];

    // 窗口的右边界可以继续扩展, 则循环继续
    while (r + 1 < s.length()) {
        if (r - l + 1 != p.length()) {
            freq_s[s.charAt(++r) - 'a']++;
            if (r - l + 1 == p.length() && same(freq_s, freq_p)) list.add(l);
        } else {
            freq_s[s.charAt(++r) - 'a']++;
            freq_s[s.charAt(l++) - 'a']--;
            if (same(freq_s, freq_p)) list.add(l);
        }
    }

    return list;
}

public static List<Integer> findAnagrams3(String s, String p) {
    List<Integer> list = new ArrayList<>();
    if (s.length() < p.length()) return list;

    int[] freq_p = new int[26];
    for (char c : p.toCharArray()) freq_p[c - 'a']++;

    int[] freq_s = new int[26];
    for (int i = 0; i < s.length(); i++) {
        freq_s[s.charAt(i) - 'a']++;

        // s[i - len + 1 ... i]
        if (i >= p.length() - 1) {
            if (same(freq_s, freq_p)) list.add(i - p.length() + 1);
            freq_s[s.charAt(i - p.length() + 1) - 'a']--;
        }
    }

    return list;
}

private static boolean same(int[] freq_s, int[] freq_p) {
    for (int i = 0; i < 26; i++) {
        if (freq_s[i] != freq_p[i]) return false;
    }
    return true;
}

6.2、最小覆盖子串

76 - 最小覆盖子串
minWindow1 和 minWindow2 均可

public static String minWindow1(String s, String t) {
    int[] freq_t = new int[256];
    for (char c : t.toCharArray()) freq_t[c]++;

    int startIndex = -1;
    int minLength = s.length() + 1;

    // s[l ... r] 是滑动窗口
    int l = 0;
    int r = -1;
    int[] freq_s = new int[256];
    int sCnt = 0; // sCnt 代表窗口中包含的 t 中字符的个数

    // 窗口的左边界在字符串范围内, 则循环继续
    while (l < s.length()) {
        if (r + 1 < s.length() && sCnt < t.length()) {
            char c = s.charAt(r + 1);
            freq_s[c]++;
            if (freq_s[c] <= freq_t[c]) sCnt++;
            r++;
        } else {
            if (sCnt == t.length() && r - l + 1 < minLength) {
                minLength = r - l + 1;
                startIndex = l;
            }

            char c = s.charAt(l);
            freq_s[c]--;
            if (freq_s[c] < freq_t[c]) sCnt--;
            l++;
        }
    }

    if (startIndex != -1) return s.substring(startIndex, startIndex + minLength);
    return "";
}

public static String minWindow2(String s, String t) {
    int[] freq_t = new int[256];
    for (char c : t.toCharArray()) freq_t[c]++;

    int startIndex = -1;
    int minLength = s.length() + 1;

    // s[l ... r] 是滑动窗口
    int l = 0;
    int r = -1;
    int sCnt = 0; // sCnt 代表窗口中包含的 t 中字符的个数
    int[] freq_s = new int[256];

    // 窗口的右边界可以继续扩展, 则循环继续
    while (r + 1 < s.length()) {
        while (r + 1 < s.length() && sCnt < t.length()) {
            char c = s.charAt(r + 1);
            freq_s[c]++;
            if (freq_s[c] <= freq_t[c]) sCnt++;
            r++;
        }

        while (sCnt == t.length()) {
            if (r - l + 1 < minLength) {
                startIndex = l;
                minLength = r - l + 1;
            }

            char c = s.charAt(l);
            freq_s[c]--;
            if (freq_s[c] < freq_t[c]) sCnt--;
            l++;
        }
    }

    if (startIndex != -1) return s.substring(startIndex, startIndex + minLength);
    return "";
}
posted @ 2023-04-27 21:08  lidongdongdong~  阅读(11)  评论(0编辑  收藏  举报