Skip to content

数组

如何对角线遍历数组?

https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals/description/

  1. 暴力

for循环遍历,每遍历一个数,计算它所在对角线,需要遍历很多遍对角线

以下为个人写的暴力代码

java
class Solution {
    public int[][] differenceOfDistinctValues(int[][] grid) {
       int row = grid.length;
       int col = grid[0].length;
       int[][] res = new int[row][col];
       for(int i = 0;i<row;i++){
        for(int j = 0;j<col;j++){
            int temi = i+1;
            int temj = j+1;
            Set<Integer> set1 = new HashSet<>();
            while(temi < row && temj < col){
                set1.add(grid[temi][temj]);
                temi++;
                temj++;
            }
            temi = i-1;
            temj = j-1;
            Set<Integer> set2 = new HashSet<>();
            while(temi >= 0 && temj >= 0){
                set2.add(grid[temi][temj]);
                temi--;
                temj--;
            }
            res[i][j] = Math.abs(set1.size() - set2.size());
        }
       }
       return res;
    }
}
  1. 前后缀分解

对于同一条对角线,方法一会多次遍历。比如计算 ans[0][0] 的时候我们会遍历主对角线,计算 ans[1][1] 的时候我们又会遍历主对角线。怎么减少遍历次数?

我们按从右上到左下的对角线来遍历,可以发现在同一条对角线上,j - i 为一个固定值即 j - i = c

当我们按从右上到左下的对角线来遍历时,我们令右上角的对角线为第k条,k = n - (j - i),就可以得出k的范围为[1,n+m - 1](n为列数,m为行数),所有我们遍历k就是在从右上到左下来遍历对角线,那如何得到对角线上的点的坐标呢?不要忘记** j - i = c 是常数**,由k = n - (j - i) 可以得出 j = n - k + i ,i 属于 [0,m-1]。

所以

  • 当 i=0 的时候 j 取到最小值 n−k,但这个数不能是负数,所以最小的 j 是 max(n−k,0)。
  • 当 i=m−1 的时候 j 取到最大值 m+n−1−k,但这个数不能超过 n−1,所以最大的 j 是 min(m+n−1−k,n−1)。

当得到 j 的范围后,就可以遍历 j 了,在遍历每个 j 时,我们可以算出对应的 i 的值,并且这个遍历是沿着对角线进行的。

下述为灵神的代码

java
class Solution {
    public int[][] differenceOfDistinctValues(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] ans = new int[m][n];
        Set<Integer> set = new HashSet<>();

        // 第一排在右上,最后一排在左下
        // 每排从左上到右下
        // 令 k=i-j+n,那么右上角 k=1,左下角 k=m+n-1
        for (int k = 1; k < m + n; k++) {
            // 核心:计算 j 的最小值和最大值
            int minJ = Math.max(n - k, 0); // i=0 的时候,j=n-k,但不能是负数
            int maxJ = Math.min(m + n - 1 - k, n - 1); // i=m-1 的时候,j=m+n-1-k,但不能超过 n-1

            set.clear();
            for (int j = minJ; j <= maxJ; j++) {
                int i = k + j - n;
                ans[i][j] = set.size(); // topLeft[i][j] == set.size()
                set.add(grid[i][j]);
            }

            set.clear();
            for (int j = maxJ; j >= minJ; j--) {
                int i = k + j - n;
                ans[i][j] = Math.abs(ans[i][j] - set.size()); // bottomRight[i][j] == set.size()
                set.add(grid[i][j]);
            }
        }
        return ans;
    }
}

有序三元组的最大值I

2873. 有序三元组中的最大值 I - 力扣(LeetCode)

一. 直接暴力

三层for循环暴力求解

**注意:**一定要预防整型溢出的的情况(踩坑)

java
class Solution {
    public long maximumTripletValue(int[] nums) {
        long res = 0;
        int len = nums.length;
        for(int i = 0; i < len;i++){
            for(int j = i+1; j < len;j++){
                for(int k = j + 1; k < len;k++){
                    long temp = (nums[i] - nums[j]) * nums[k] * 1l;
                    res = res > temp ? res:temp;
                }
            }
        }
        return res;
    }
}

long temp = (nums[i] - nums[j]) * nums[k] * 1l;

上述这句代码看似进行了类型转化,但其实是无效转化

原因:(nums[i] - nums[j])算出来的是一个整型,所以(nums[i] - nums[j]) * nums[k]算出来的也是一个整型,如果数字很大的话,在这一步就已经溢出了,在将其 * 1l 只是将溢出后的值进行转化,无效!!!!

GPT解释:

** 这里 **(nums[i] - nums[j]) * nums[k]** 先进行运算,然后再乘以 **1l**
****由于 **nums[i] - nums[j]****nums[k]** 都是 **int** 类型,**(nums[i] - nums[j]) * nums[k]** 的计算仍然是 **int** 类型。如果结果超出了 **int** 的范围(即大于 **2^31-1** 或小于 **-2^31**),会发生 整数溢出,然后再将溢出的 **int** 转换为 **long**,但溢出的信息已经丢失了,导致结果错误。 **

java
class Solution {
    public long maximumTripletValue(int[] nums) {
        long res = 0;
        int len = nums.length;
        for(int i = 0; i < len;i++){
            for(int j = i+1; j < len;j++){
                for(int k = j + 1; k < len;k++){
                    long temp = 1l * (nums[i] - nums[j]) * nums[k];
                    res = res > temp ? res:temp;
                }
            }
        }
        return res;
    }
}

上述代码中,我们在(nums[i] - nums[j]) 后就将其转化为long类型,所以后面进行(nums[i] - nums[j]) * nums[k] 算出来的还是long类型,这里其实也有溢出风险,因为当nums[i]很大时(nums[i] - nums[j])直接就溢出了,但在这不可能,因为nums是一个整型数组

GPT解释

由于 1llong 类型,**1l * (nums[i] - nums[j])**** 立即提升为 **long** 类型**,因此整个计算过程都是 long 类型,不会发生 int 溢出。

总结

  • 第一段代码可能发生 **int** 溢出,因为 (nums[i] - nums[j]) * nums[k] 仍然是 int 运算,只有最后才转换为 long
  • 第二段代码避免了溢出,因为 1l * (nums[i] - nums[j]) 让整个计算在 long 里进行。

二. 贪心

我们固定 k,那么当 nums[i]−nums[j] 取最大值时,三元组的值最大。我们可以用 imax 维护 nums[i] 的最大值,dmax 维护 nums[i]−nums[j] 的最大值,在枚举 k 的过程中,更新 dmax 和 imax。

java
class Solution {
    public long maximumTripletValue(int[] nums) {
        long res = 0;
        int len = nums.length;
        int imax = 0;
        int detaMax = 0;
        for(int i = 0; i < len;i++){
            res = Math.max(res,(long)detaMax*nums[i]);
            detaMax = Math.max(detaMax,imax-nums[i]);
            imax = Math.max(imax,nums[i]);
        }
        return res;
    }
}