【经典专题】和为K问题——双重for循环的哈希降维

两数之和

我们先从一个极其简单 的问题开始吧!

给定一个整数数组nums和一个目标值target,数组中是否存在两个数,它们的和为target?

举个例子,nums=[5, 3, 1, 2, 6],target=9,那么存在这样的两个数(3,6),它们的和为9。

你立即有了思路,然后写出代码一气呵成:

class Solution {
    public boolean twoSum(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
			for (int j = i + 1; j < nums.length; j++) {
				if (nums[i] + nums[j] == target) {
					return true;
				}
			}
		}
		return false;
    }
}

思路很明确,双重for循环嵌套,O(n²)的时间复杂度。

接着思考,如何对其进行优化呢?

思考ing…

思考ing…

思考ing…

没错,空间换时间。看看下面的代码,你会恍然大悟:

class Solution {
    public boolean twoSum2(int[] nums, int target) {
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            if (set.containsKey(target - nums[i])) {
                return true;
            }
            set.add(nums[i]);
        }
        return false;
    }
}

这有点奇怪,但是这的确将时间复杂度降低到了O(n)。

玄机在于,在第二层中,O(n)的遍历变成了O(1)的哈希。

 
 

和为K的子数组

给定一个整数数组nums和一个目标值target,你需要找到该数组中和为 k 的连续的子数组的个数。

举个例子,nums=[5, 3, 1, 2, 2, 4],target=8,[5, 3]、[3, 1, 2, 2]、[2, 2, 4]都满足条件,一共有3个。

似乎很简单,前缀和优化模板题。

但我们是否可以沿用上面的哈希降维思路呢?请看下面的思路转化过程:

在两数之和问题中:
num1 + num2 = target ----> num2 = target - num1
因此我们可以通过哈希去寻找num2,而无需进行遍历。

我们借助前缀和将本题转化为两数之差问题!

在本题中:
preSum1 - preSum2 = target ----> preSum2 = preSum1 - target
和上面一样,我们可以通过哈希去寻找preSum2,而无需进行遍历。

class Solution {
    public int subarraySum(int[] nums, int target) {
		
		// 前缀和
        int len = nums.length;
        int[] preSum = new int[len + 1];
        for (int i = 0; i < len; i++) {
            preSum[i + 1] = preSum[i] + nums[i];
        }

		// 计数
        int count = 0;
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        for (int i = 0; i < len; i++) {
            if (map.containsKey(preSum[i + 1] - target)) {
                count += map.get(preSum[i + 1] - target);
            }
            map.put(preSum[i + 1], map.getOrDefault(preSum[i + 1], 0) + 1);
        }
        return count;
    }
}

更加优雅的写法:

class Solution {
    public int subarraySum(int[] nums, int target) {
    	Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    	map.put(0, 1);
    	int count = 0;
    	int preSum = 0;
    	for(int n : nums) {
    		preSum += n;
    		if (map.containsKey(preSum - target)) {
                count += map.get(preSum - target);
            }
            map.put(preSum, map.getOrDefault(preSum, 0) + 1);
    	}
    	return count;
    }
}

 
 

和为K的子矩阵

给出矩阵matrix[][]和目标值target,返回元素总和等于target的子矩阵的数量。

读本篇文章之前,你的思路可能是这样的:

计算二维前缀和,for遍历出所有的子矩阵,计算出子矩阵和,与target比较并计数——O(m²n²)。

读本篇文章之后,你的思路就会是这样的:

先将二维问题转化为多个一维子问题,对于每个一维子问题,使用哈希优化的算法——O(m²n)。

在这里插入图片描述

看看代码吧:

class Solution {
    public int numSubmatrixSumTarget(int[][] matrix, int target) {
        int m = matrix.length;
        int n = matrix[0].length;

        // 构造列前缀和
        int[][] preColSum = new int[m + 1][n + 1];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                preColSum[i + 1][j] = preColSum[i][j] + matrix[i][j];
            }
        }

        // 合并列
        int count = 0;
        for (int top = 0; top < m; top++) {
            for (int bottom = top; bottom < m; bottom++) {
                // 构造一维矩阵
                int[] nums = new int[n];
                for (int j = 0; j < n; j++) {
                    nums[j] = preColSum[bottom + 1][j] - preColSum[top][j];
                }
                // 子问题
                count += subarraySum(nums, target);
            }
        }
        return count;
    }

    private int subarraySum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        int count = 0;
        int preSum = 0;
        for(int n : nums) {
            preSum += n;
            if (map.containsKey(preSum - target)) {
                count += map.get(preSum - target);
            }
            map.put(preSum, map.getOrDefault(preSum, 0) + 1);
        }
        return count;
    }
}

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

E N D END END

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值