题目描述

大餐 是指 恰好包含两道不同餐品 的一餐,其美味程度之和等于 2 的幂。

你可以搭配 任意 两道餐品做一顿大餐。

给你一个整数数组 deliciousness ,其中 deliciousness[i] 是第 i 道餐品的美味程度,返回你可以用数组中的餐品做出的不同 大餐 的数量。结果需要对 10^9 + 7 取余。

注意,只要餐品下标不同,就可以认为是不同的餐品,即便它们的美味程度相同。

示例 1:

输入:deliciousness = [1,3,5,7,9]
输出:4
解释:大餐的美味程度组合为 (1,3) 、(1,7) 、(3,5) 和 (7,9) 。
它们各自的美味程度之和分别为 4 、8 、8 和 16 ,都是 2 的幂。
示例 2:

输入:deliciousness = [1,1,1,3,3,3,7]
输出:15
解释:大餐的美味程度组合为 3 种 (1,1) ,9 种 (1,3) ,和 3 种 (1,7) 。

LeetCode之1711大餐计数(相关话题:哈希表+位运算)_思维题

思路分析

正常逻辑是要写两个for遍历,但是数组长度是10^5,直接遍历会超时。于是引入了hash表,来减少内层循环的次数。

代码如下

	/**
	 * 两数之和的思路
	 * 
	 * @param ds
	 * @return
	 */

	public int countPairs2(int[] ds) {
	    int mod = (int) 1e9 + 7;
		int n = ds.length;
		long ans = 0;
        //hashMap用来记录每个数据出现的次数
		Map<Integer, Integer> map = new HashMap<>();
		for (int i = 0; i < n; i++) {
			int x = ds[i];
			for (int other : map.keySet()) {
				int num = other + x;
				//(num & num - 1) == 0代表num为2的若干次幂
				if (num>0 && (num & num - 1) == 0) {
					ans += map.get(other);
					 
				}
			}
			map.put(x, map.getOrDefault(x, 0) + 1);
		}
		return (int) (ans % mod);
	}

有个小技巧是可以通过(num & num - 1) == 0来判断num是否为2的若干次幂。

但是上述代码在数据量大时依然会超时,而且有个坑点就是一般人都会忽略num>0 这个条件导致无法通过力扣后台的第5个测试用例。

那么究竟要用什么思路来解决这个问题那?

观察提示 0<=deliciousness[i]<=10^20 (解题时看下提示的数据量再摸索解题套路很重要),也就是所任意两个数相加都不会超过10^22,那么我们是否可以尝试用位移来每次执行内侧for循环呢?这样内层for循环每次只执行22次,这是一个新的技巧点。模板如下:

		int max = 1 << 22;		
        for (int x : ds) {
			for (int i = 1; i < max; i <<= 1) {
 
			}
 
		}

代码实现

	public int countPairs3(int[] ds) {
	    int mod = (int) 1e9 + 7;
		int max = 1 << 22;
		Map<Integer, Integer> map = new HashMap<>();
		int ans = 0;
		for (int x : ds) {
			for (int i = 1; i < max; i <<= 1) {
				int t = i - x;
				if (map.containsKey(t)) {
					ans += map.get(t);
					if (ans >= mod)
						ans -= mod;
				}
			}
			map.put(x, map.getOrDefault(x, 0) + 1);
		}
		return ans;
	}

知识点拓展

判断是否为2的整数次幂还有如下方法,但是博主觉得并没有num >0 && (num & num - 1) == 0

简洁

	boolean check(long x) {
		// 方法一
		// long cur = 1;
		// while (cur < x) {
		// cur = cur * 2;
		// }
		// return cur == x;

		// 方法二
		return getVal(x) == x;
	}

	// 主要功能是返回一个比给定整数大且最接近的2的幂次方整数
	// 如给定10,返回2的4次方16.
	long getVal(long x) {
		long n = x - 1;
		n |= n >>> 1;
		n |= n >>> 2;
		n |= n >>> 4;
		n |= n >>> 8;
		n |= n >>> 16;
		return n < 0 ? 1 : n + 1;
	}

解题总结

刷够了经典题型套路之后,需要有实战机会才能提高。要提高短时间内的解题思考密度和思考深度,才能应付考场上的高强度面试。这需要一个逐步提高的过程