922.按奇偶排序数组II

目标

给定一个非负整数数组 nums, nums 中一半整数是 奇数 ,一半整数是 偶数 。

对数组进行排序,以便当 nums[i] 为奇数时,i 也是 奇数 ;当 nums[i] 为偶数时, i 也是 偶数 。

你可以返回 任何满足上述条件的数组作为答案 。

示例 1:

输入:nums = [4,2,5,7]
输出:[4,5,2,7]
解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。

示例 2:

输入:nums = [2,3]
输出:[2,3]

说明:

  • 2 <= nums.length <= 2 * 10^4
  • nums.length 是偶数
  • nums 中一半是偶数
  • 0 <= nums[i] <= 1000

进阶:可以不使用额外空间解决问题吗?

思路

数组 nums 长度为偶数,其元素一半为偶数,另一半为奇数。调整数组元素的顺序,使得奇数下标中的元素为奇数,偶数下标的元素为偶数。

使用奇偶两个指针,步长为2,找到不满足条件的元素并交换。

代码


/**
 * @date 2025-02-04 19:04
 */
public class SortArrayByParityII922 {

    public int[] sortArrayByParityII(int[] nums) {
        int n = nums.length;
        int even = 0, odd = 1;
        while (even < n && odd < n) {
            while (even < n && nums[even] % 2 == 0) {
                even += 2;
            }
            if (even >= n) {
                break;
            }
            while (odd < n && nums[odd] % 2 == 1) {
                odd += 2;
            }
            int tmp = nums[even];
            nums[even] = nums[odd];
            nums[odd] = tmp;
        }
        return nums;
    }

}

性能

680.验证回文串II

目标

给你一个字符串 s,最多 可以从中删除一个字符。

请你判断 s 是否能成为回文字符串:如果能,返回 true ;否则,返回 false 。

示例 1:

输入:s = "aba"
输出:true

示例 2:

输入:s = "abca"
输出:true
解释:你可以删除字符 'c' 。

示例 3:

输入:s = "abc"
输出:false

说明:

  • 1 <= s.length <= 10^5
  • s 由小写英文字母组成

思路

判断给定字符串是否是回文,如果不是,能否删除任意一个字符使之变成回文。

当不满足回文条件时,分别考虑删掉其中一个字符,判断剩余子串是否是回文即可。

代码


/**
 * @date 2025-02-03 18:24
 */
public class ValidPalindrome680 {

    public boolean validPalindrome(String s) {
        int n = s.length();
        int i = 0;
        for (; i < n / 2; i++) {
            // 找到第一个不满足回文的字符下标
            if (s.charAt(i) != s.charAt(n - 1 - i)) {
                break;
            }
        }
        if (i == n / 2) {
            return true;
        }
        // 尝试删掉左边/右边字符判断剩余字符是否是回文
        boolean res = true;
        for (int j = i; j < n / 2; j++) {
            // 删掉 n - 1 - i,即 n - 2 - j
            if (s.charAt(j) != s.charAt(n - 2 - j)) {
                res = false;
            }
        }
        if (res) {
            return true;
        }
        // 这里是 j <= n /2,例如 abc,i + 1 指向 b
        for (int j = i + 1; j <= n / 2; j++) {
            // 这里是 n - 1 - i, 即 n - j,相当于删除了 i,但是右指针是不变的
            if (s.charAt(j) != s.charAt(n - j)) {
                return false;
            }
        }
        return true;
    }

}

性能

598.区间加法II

目标

给你一个 m x n 的矩阵 M 和一个操作数组 op 。矩阵初始化时所有的单元格都为 0 。ops[i] = [ai, bi] 意味着当所有的 0 <= x < ai 和 0 <= y < bi 时, M[x][y] 应该加 1。

在 执行完所有操作后 ,计算并返回 矩阵中最大整数的个数 。

示例 1:

输入: m = 3, n = 3,ops = [[2,2],[3,3]]
输出: 4
解释: M 中最大的整数是 2, 而且 M 中有4个值为2的元素。因此返回 4。

示例 2:

输入: m = 3, n = 3, ops = [[2,2],[3,3],[3,3],[3,3],[2,2],[3,3],[3,3],[3,3],[2,2],[3,3],[3,3],[3,3]]
输出: 4

示例 3:

输入: m = 3, n = 3, ops = []
输出: 9

说明:

  • 1 <= m, n <= 4 * 10^4
  • 0 <= ops.length <= 10^4
  • ops[i].length == 2
  • 1 <= ai <= m
  • 1 <= bi <= n

思路

有一个 m x n 矩阵,其所有元素初值为0。另有一个二维数组 ops,每一个操作 ops[i] = [ai, bi] 表示将矩阵中 x ∈ [0, ai], y ∈ [0, bi] 的元素加 1。求执行完所有操作后,矩阵中最大元素的个数。

这其实是一个脑筋急转弯,就是求操作数组中 x 与 y 的最小值,[0, 0][x, y] 的矩阵是所有操作都重合的,所以元素值最大,其个数为 x * y。注意考虑操作为空的情况,这时返回 m * n,所有元素均为 0

代码


/**
 * @author jd95288
 * @date 2025-02-02 1:23
 */
public class MaxCount598 {

    public int maxCount(int m, int n, int[][] ops) {
        int x = m;
        int y = n;
        for (int[] op : ops) {
            x = Math.min(x, op[0]);
            y = Math.min(y, op[1]);
        }
        return x * y;
    }

}

性能

541.反转字符串II

目标

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例 1:

输入:s = "abcdefg", k = 2
输出:"bacdfeg"

示例 2:

输入:s = "abcd", k = 2
输出:"bacd"

说明:

  • 1 <= s.length <= 10^4
  • s 仅由小写英文组成
  • 1 <= k <= 10^4

思路

将字符串从左至右划分为若干个长度为 2 * k 的子串,将每个子串的前 k 个字符反转,返回反转后的子串拼接成的字符串(保持子串之间的顺序不变)。

代码


/**
 * @date 2025-01-31 14:45
 */
public class ReverseStr541 {

    public String reverseStr(String s, int k) {
        char[] chars = s.toCharArray();
        int k2 = 2 * k;
        int n = chars.length;
        for (int i = 0; i < n; i += k2) {
            int end = Math.min(i + k, n) - 1;
            reverse(chars, i, end);
        }
        return new String(chars);
    }

    private void reverse(char[] chars, int i, int end) {
        for (int j = i; j < end; j++) {
            char tmp = chars[j];
            chars[j] = chars[end];
            chars[end--] = tmp;
        }
    }

}

性能

350.两个数组的交集II

目标

给你两个整数数组 nums1 和 nums2,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

说明:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

进阶:

  • 如果给定的数组已经排好序呢?你将如何优化你的算法?
  • 如果 nums1 的大小比 nums2 小,哪种方法更优?
  • 如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

思路

求两个数组 nums1nums2 的交集(不考虑元素顺序),比如 a b c be b d 的交集为 b

使用哈希表对数组 nums1 的元素值计数,遍历 nums2 获取对应元素在 nums1 中的数量,如果大于0,将元素加入列表,并将数量减一。

如果已经排好序可以直接使用双指针,每次比较移动元素值小的指针。

交集的最大元素不会超过两个数组长度的最小值,因此初始化时可以取长度较小的数组进行计数。

如果 nums2 存储在磁盘上,根据上面的讨论,我们对 nums1 计数,每次从磁盘读取一部分数据进行判断即可。

代码


/**
 * @date 2025-01-30 21:37
 */
public class Intersect350 {

    public int[] intersect_v2(int[] nums1, int[] nums2) {
        if (nums1.length > nums2.length) {
            intersect_v2(nums2, nums1);
        }
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int[] res = new int[nums1.length];
        int j = 0;
        int index = 0;
        for (int i = 0; i < nums2.length && j < nums1.length; i++) {
            while (j < nums1.length && nums2[i] > nums1[j]) {
                j++;
            }
            if (j == nums1.length) {
                break;
            }
            if (nums2[i] == nums1[j]) {
                res[index++] = nums1[j];
                j++;
            }
        }

        return Arrays.copyOfRange(res, 0, index);
    }

    public int[] intersect_v1(int[] nums1, int[] nums2) {
        Map<Integer, Integer> cnt = new HashMap<>();
        for (int i : nums1) {
            cnt.merge(i, 1, Integer::sum);
        }
        List<Integer> list = new ArrayList<>();
        for (int i : nums2) {
            int value = cnt.getOrDefault(i, 0) - 1;
            if (value >= 0) {
                list.add(i);
                cnt.put(i, value);
            }
        }
        return list.stream().mapToInt(i -> i).toArray();
    }

}

性能

219.存在重复元素II

目标

给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。

示例 1:

输入:nums = [1,2,3,1], k = 3
输出:true

示例 2:

输入:nums = [1,0,1,1], k = 1
输出:true

示例 3:

输入:nums = [1,2,3,1,2,3], k = 2
输出:false

说明:

  • 1 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • 0 <= k <= 10^5

思路

判断下标距离小于等于 k 的窗口内是否存在重复元素,窗口内的元素个数为 k + 1

代码


/**
 * @date 2024-07-04 22:36
 */
public class ContainsNearbyDuplicate219 {

    public boolean containsNearbyDuplicate(int[] nums, int k) {
        int n = nums.length;
        Set<Integer> set = new HashSet<>(k);
        int left = 0;
        for (int right = 0; right < n; right++) {
            if (!set.add(nums[right])) {
                return true;
            }
            if (right - left == k) {
                set.remove(nums[left++]);
            }
        }
        return false;
    }

}

性能

119.杨辉三角II

目标

给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

示例 1:

输入: rowIndex = 3
输出: [1,3,3,1]

示例 2:

输入: rowIndex = 0
输出: [1]

示例 3:

输入: rowIndex = 1
输出: [1,1]

说明:

  • 0 <= rowIndex <= 33

进阶:

你可以优化你的算法到 O(rowIndex) 空间复杂度吗?

思路

返回杨辉三角第 rowIndex 行的元素。

代码


/**
 * @date 2025-01-28 1:06
 */
public class GetRow119 {

    public List<Integer> getRow(int rowIndex) {
        List<Integer> res = new ArrayList<>();
        res.add(1);
        if (rowIndex == 0) {
            return res;
        }
        for (int i = 1; i <= rowIndex; i++) {
            int prev = res.get(0);
            for (int j = 1; j < res.size(); j++) {
                int cur = prev + res.get(j);
                prev = res.get(j);
                res.set(j, cur);
            }
            res.add(1);
        }
        return res;
    }

}

性能

2239.找到最接近0的数字

目标

给你一个长度为 n 的整数数组 nums ,请你返回 nums 中最 接近 0 的数字。如果有多个答案,请你返回它们中的 最大值 。

示例 1:

输入:nums = [-4,-2,1,4,8]
输出:1
解释:
-4 到 0 的距离为 |-4| = 4 。
-2 到 0 的距离为 |-2| = 2 。
1 到 0 的距离为 |1| = 1 。
4 到 0 的距离为 |4| = 4 。
8 到 0 的距离为 |8| = 8 。
所以,数组中距离 0 最近的数字为 1 。

示例 2:

输入:nums = [2,-1,1]
输出:1
解释:1 和 -1 都是距离 0 最近的数字,所以返回较大值 1 。

说明:

  • 1 <= n <= 1000
  • -10^5 <= nums[i] <= 10^5

思路

返回数组中最接近 0 的数字,如果有多个结果返回最大的那个。

代码


/**
 * @date 2025-01-20 8:43
 */
public class FindClosestNumber2239 {

    public int findClosestNumber(int[] nums) {
        int res = nums[0];
        int min = Math.abs(res);
        for (int num : nums) {
            int b = Math.abs(num);
            if (min > b) {
                res = num;
                min = b;
            } else if (min == b) {
                res = num > 0 ? num : res;
            }
        }
        return res;
    }

}

性能

3095.或值至少K的最短子数组I

目标

给你一个 非负 整数数组 nums 和一个整数 k 。

如果一个数组中所有元素的按位或运算 OR 的值 至少 为 k ,那么我们称这个数组是 特别的 。

请你返回 nums 中 最短特别非空 子数组 的长度,如果特别子数组不存在,那么返回 -1 。

示例 1:

输入:nums = [1,2,3], k = 2
输出:1
解释:
子数组 [3] 的按位 OR 值为 3 ,所以我们返回 1 。
注意,[2] 也是一个特别子数组。

示例 2:

输入:nums = [2,1,8], k = 10
输出:3
解释:
子数组 [2,1,8] 的按位 OR 值为 11 ,所以我们返回 3 。

示例 3:

输入:nums = [1,2], k = 0
输出:1
解释:
子数组 [1] 的按位 OR 值为 1 ,所以我们返回 1 。

说明:

  • 1 <= nums.length <= 50
  • 0 <= nums[i] <= 50
  • 0 <= k < 64

思路

求数组 nums 的最短特别子数组长度,特别子数组的所有元素按位与的结果大于等于 k。

枚举子数组,暴力求解每个子数组的或值。

代码


/**
 * @date 2025-01-16 22:22
 */
public class MinimumSubarrayLength3095 {
    /**
     * 枚举起点 O(n^2)
     */
    public int minimumSubarrayLength_v1(int[] nums, int k) {
        int n = nums.length;
        int res = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            int or = 0;
            for (int j = i; j < n; j++) {
                or |= nums[j];
                if (or >= k) {
                    res = Math.min(res, j - i + 1);
                    break;
                }
            }
        }
        return res == Integer.MAX_VALUE ? -1 : res;
    }

    /**
     * 枚举终点 O(n^3)
     */
    public int minimumSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        int res = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= i; j++) {
                int or = 0;
                for (int x = j; x <= i; x++) {
                    or |= nums[x];
                }
                if (or >= k) {
                    res = Math.min(res, i - j + 1);
                }
            }
        }
        return res == Integer.MAX_VALUE ? -1 : res;
    }
}

性能

3065.超过阈值的最少操作数I

目标

给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。

一次操作中,你可以删除 nums 中的最小元素。

你需要使数组中的所有元素都大于或等于 k ,请你返回需要的 最少 操作次数。

示例 1:

输入:nums = [2,11,10,1,3], k = 10
输出:3
解释:第一次操作后,nums 变为 [2, 11, 10, 3] 。
第二次操作后,nums 变为 [11, 10, 3] 。
第三次操作后,nums 变为 [11, 10] 。
此时,数组中的所有元素都大于等于 10 ,所以我们停止操作。
使数组中所有元素都大于等于 10 需要的最少操作次数为 3 。

示例 2:

输入:nums = [1,1,2,4,9], k = 1
输出:0
解释:数组中的所有元素都大于等于 1 ,所以不需要对 nums 做任何操作。

示例 3:

输入:nums = [1,1,2,4,9], k = 9
输出:4
解释:nums 中只有一个元素大于等于 9 ,所以需要执行 4 次操作。

说明:

  • 1 <= nums.length <= 50
  • 1 <= nums[i] <= 10^9
  • 1 <= k <= 10^9
  • 输入保证至少有一个满足 nums[i] >= k 的下标 i 存在。

思路

求数组中小于 k 的元素个数。

代码


/**
 * @date 2025-01-14 8:41
 */
public class MinOperations3065 {

    public int minOperations(int[] nums, int k) {
        int res = 0;
        for (int num : nums) {
            if (num < k){
                res++;
            }
        }
        return res;
    }
}

性能