常见算法技巧

1、给定一个正数int型数组arr,和一个正数目标值target,随意从数组中取出任意个数字(不能重复取)求和,请问有多少种取法可以使总和刚好等于target

(1)普通动态规划

设置dp[m][n]数组,其中m为数组长度,n为target+1。dp[i][j]表示,使用从下标0~i这些位置上的数字,可以获得sum=j的取法。则有状态转移方程:

dp[i][j] = dp[i-1][j] + dp[i-1][j-arr[i]]

根据我们是否使用第i位置上的数字,dp[i][j]的状态由以下两种状态组成:

  • 当不使用i位置上的数字时,获得sum=j的取法就等使用0~i-1这些位置上的数字,即dp[i-1][j]
  • 当使用i位置上的数字时,把第i位置去掉,目标数字也减去第i位置上的值,则这种情况转化为使用第0~i-1位置上的数组,获得sum=j-arr[i]的取法数量,即dp[i-1][j-arr[i]]。
class Solution{
    public int solve(int[] arr, int target) {
        int[][] dp = new int[arr.length][target+1];
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j <= target; j++) {
                if (i == 0) {
                    if (arr[i] == j) {
                        dp[i][j] = 1;
                    }
                }else {
                    if (j - arr[i] >= 0) {
                        dp[i][j] = dp[i - 1][j] + dp[i - 1][j - arr[i]];
                    }else {
                        dp[i][j] = dp[i - 1][j];
                    }
                }
            }
        }
        return dp[arr.length - 1][target];
    }
}

(2)空间压缩型动态规划

从上面的动态转移方程来看,每个外层数组dp[i]都用到了上一个数组dp[i-1]的值,最终获取结果也是最后一个数组的最后一个数字,因此可以用一个数组来表示动态规划表,通过修改这张表来避免使用更多的空间。

class Solution{
    public int solve(int[] arr, int target) {
        int[] dp = new int[target + 1];
        dp[0] = 1;
        for (int n: arr) {
            for (int i = target; i >= n; i--) {
                dp[i] += dp[i - n];
            }
        }
        return dp[target];
    }
}

2、用位运算表示中位数的两个位置

class Solution{
    public void solve(int[] arr) {
        int len = arr.length;
        int leftPosition = (len - 1) >> 1
        int rightPosition = len >> 1;
        // 中位数就等于 (arr[leftPosition] + arr[rightPosition]) / 2
    }
}

3、使用位运算求两个数字的均值并取floor,其实就是中间位置的下标

class Solution{
    public int solve(int n1, int n2) {
        if (n1 > n2) {
            return solve(n2, n1);
        }
        return n1 + ((n2 - n1) >> 1); // 等价于 Math.floor((n1 + n2) / 2)
    }
}

4、前缀和(二维)

求随机某个区间或者某个块的和,可以通过前缀和预处理,来降低时间复杂度。

参见:https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/xia-ci-ru-he-zai-30-miao-nei-zuo-chu-lai-ptlo/

public class NumMatrix {
    int[][] sum;
    public NumMatrix(int[][] matrix) {
        int n = matrix.length, m = n == 0 ? 0 : matrix[0].length;
        // 与「一维前缀和」一样,前缀和数组下标从 1 开始,因此设定矩阵形状为 [n + 1][m + 1](模板部分)
        sum = new int[n + 1][m + 1];
        // 预处理除前缀和数组(模板部分)
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + matrix[i - 1][j - 1];
            }
        }
    }

    public int sumRegion(int x1, int y1, int x2, int y2) {
        // 求某一段区域和 [i, j] 的模板是 sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];(模板部分)
        // 但由于我们源数组下标从 0 开始,因此要在模板的基础上进行 + 1
        x1++; y1++; x2++; y2++;
        return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
    }
}

5、数组,单点修改,区间求和问题

涉及数据结构和算法:树状数组、线段树。参考:https://leetcode-cn.com/problems/range-sum-query-mutable/

posted @ 2022-03-15 13:31  yury757  阅读(55)  评论(0)    收藏  举报