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^5
  • 0 <= nums[i] <= 10^9

解题思路

这道题的关键在于理解按位与操作的性质:如果一个子序列的按位与非零,说明存在至少一个二进制位在所有元素中都为1。

核心思路:

  1. 按位枚举:对于0到30的每一位(32位整数),如果某一位在最终结果中为1,那么这一位必须在所有选中的元素中都为1
  2. 位过滤:对于每一位b,筛选出第b位为1的所有元素,保持原始顺序
  3. LIS计算:在过滤后的序列上计算最长递增子序列(LIS)
  4. 取最大值:返回所有位上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 数组。