Medium
题目描述
给定一个整数数组 nums。
返回 nums 中最长严格递增子序列的长度,该子序列的按位与结果非零。如果不存在这样的子序列,返回 0。
示例 1:
输入:nums = [5,4,7]
输出:2
解释:
一个最长严格递增子序列是 [5, 7]。按位与结果是 5 AND 7 = 5,非零。
示例 2:
输入:nums = [2,3,6]
输出:3
解释:
最长严格递增子序列是 [2, 3, 6]。按位与结果是 2 AND 3 AND 6 = 2,非零。
示例 3:
输入:nums = [0,1]
输出:1
解释:
一个最长严格递增子序列是 [1]。按位与结果是 1,非零。
约束条件:
1 <= nums.length <= 10^50 <= nums[i] <= 10^9
解题思路
这道题的关键在于理解按位与操作的性质:如果一个子序列的按位与非零,说明存在至少一个二进制位在所有元素中都为1。
核心思路:
- 按位枚举:对于0到30的每一位(32位整数),如果某一位在最终结果中为1,那么这一位必须在所有选中的元素中都为1
- 位过滤:对于每一位b,筛选出第b位为1的所有元素,保持原始顺序
- LIS计算:在过滤后的序列上计算最长递增子序列(LIS)
- 取最大值:返回所有位上LIS的最大值
算法步骤:
- 遍历每一位(0-30位)
- 对于当前位,提取所有在该位上为1的元素
- 使用二分查找优化的LIS算法计算最长递增子序列长度
- 维护全局最大值
这种方法的精妙之处在于将复杂的"按位与非零"约束转化为简单的"某一位全为1"约束,然后在每个约束下求解经典的LIS问题。
时间复杂度优化: 使用二分查找的LIS算法,每个位的复杂度为O(n log n),总体为O(31 * n log n)。
代码实现
class Solution {
public:
int longestSubsequence(vector<int>& nums) {
int n = nums.size();
int maxLen = 0;
// 对每一位进行枚举
for (int bit = 0; bit < 31; bit++) {
vector<int> filtered;
// 筛选出第bit位为1的元素
for (int num : nums) {
if (num & (1 << bit)) {
filtered.push_back(num);
}
}
if (filtered.empty()) continue;
// 计算LIS
vector<int> lis;
for (int num : filtered) {
auto it = lower_bound(lis.begin(), lis.end(), num);
if (it == lis.end()) {
lis.push_back(num);
} else {
*it = num;
}
}
maxLen = max(maxLen, (int)lis.size());
}
return maxLen;
}
};
class Solution:
def longestSubsequence(self, nums: List[int]) -> int:
max_len = 0
# 对每一位进行枚举
for bit in range(31):
# 筛选出第bit位为1的元素
filtered = [num for num in nums if num & (1 << bit)]
if not filtered:
continue
# 计算LIS
from bisect import bisect_left
lis = []
for num in filtered:
pos = bisect_left(lis, num)
if pos == len(lis):
lis.append(num)
else:
lis[pos] = num
max_len = max(max_len, len(lis))
return max_len
public class Solution {
public int LongestSubsequence(int[] nums) {
int maxLen = 0;
// 对每一位进行枚举
for (int bit = 0; bit < 31; bit++) {
var filtered = new List<int>();
// 筛选出第bit位为1的元素
foreach (int num in nums) {
if ((num & (1 << bit)) != 0) {
filtered.Add(num);
}
}
if (filtered.Count == 0) continue;
// 计算LIS
var lis = new List<int>();
foreach (int num in filtered) {
int pos = BinarySearch(lis, num);
if (pos == lis.Count) {
lis.Add(num);
} else {
lis[pos] = num;
}
}
maxLen = Math.Max(maxLen, lis.Count);
}
return maxLen;
}
private int BinarySearch(List<int> list, int target) {
int left = 0, right = list.Count;
while (left < right) {
int mid = left + (right - left) / 2;
if (list[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
var longestSubsequence = function(nums) {
const n = nums.length;
const memo = new Map();
function dp(i, mask) {
if (i === n) return 0;
const key = `${i},${mask}`;
if (memo.has(key)) return memo.get(key);
let result = dp(i + 1, mask);
if (mask === -1 || nums[i] > nums[mask]) {
const newMask = mask === -1 ? 0 : memo.get(`and,${mask},${i}`) || (memo.get(`and,${mask},${i}`) = nums[mask] & nums[i]);
const andValue = mask === -1 ? nums[i] : newMask;
if (andValue !== 0) {
result = Math.max(result, 1 + dp(i + 1, i));
}
}
memo.set(key, result);
return result;
}
let maxLen = 0;
const dp2 = new Map();
for (let i = 0; i < n; i++) {
const states = new Map();
for (let [prevAnd, len] of dp2) {
const newAnd = prevAnd & nums[i];
if (newAnd !== 0) {
states.set(newAnd, Math.max(states.get(newAnd) || 0, len + 1));
}
}
if (nums[i] !== 0) {
states.set(nums[i], Math.max(states.get(nums[i]) || 0, 1));
}
for (let [and, len] of states) {
dp2.set(and, Math.max(dp2.get(and) || 0, len));
maxLen = Math.max(maxLen, len);
}
}
return maxLen;
};
复杂度分析
| 复杂度类型 | 值 |
|---|---|
| 时间复杂度 | O(31 × n log n) = O(n log n) |
| 空间复杂度 | O(n) |
其中 n 为数组长度。对于每一位,我们需要 O(n) 时间筛选元素,O(n log n) 时间计算 LIS,总共 31 位。空间复杂度主要用于存储过滤后的数组和 LIS 数组。