位运算 & 数学 & 高频进阶九题通关(Python + C++)

位运算 & 数学 & 高频进阶九题通关(Python + C++)

位运算和数学技巧常常能以极低的复杂度解决问题;而堆、Trie、快速选择等高级数据结构则是面试中的加分项。本文整理9道经典题目,每道题包含:题目描述、解题思路、图解(文本示意)、Python代码、C++代码、复杂度分析


📌 题目清单(位运算 & 数学)

题号 题目 核心考点
136 只出现一次的数字 异或(a^a=0)
169 多数元素 摩尔投票法 / 排序 / 分治
7 整数反转 数学(溢出判断)
50 Pow(x, n) 快速幂(递归 / 迭代)
461 汉明距离 异或 + 位计数(x&(x-1))

📌 题目清单(其他高频)

题号 题目 核心考点
215 数组中的第 K 个最大元素 快速选择 / 堆
23 合并 K 个升序链表 优先队列 / 分治归并
208 实现 Trie (前缀树) 字典树(插入/搜索/前缀)
295 数据流中的中位数 两个堆(大根堆 + 小根堆)

1. 只出现一次的数字(LeetCode 136)

题目描述

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现一次的元素。要求 O(n) 时间,O(1) 空间。

示例

输入:[2,2,1] → 输出:1

解题思路

  • 利用异或性质:a ^ a = 0a ^ 0 = a,且异或满足交换律、结合律。
  • 将所有数字异或起来,成对的数字抵消为 0,最终结果就是只出现一次的数字。

图解

复制代码
数组 [2,2,1]
2 ^ 2 = 0
0 ^ 1 = 1
结果 1

Python代码

python 复制代码
def singleNumber(nums):
    res = 0
    for num in nums:
        res ^= num
    return res

C++代码

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;
        for (int num : nums) res ^= num;
        return res;
    }
};

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2. 多数元素(LeetCode 169)

题目描述

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊n/2⌋ 的元素。假设数组非空且多数元素一定存在。

示例

输入:[3,2,3] → 输出:3

解题思路

  • 摩尔投票法 :维护候选元素 candidate 和计数 count。遍历数组:
    • count == 0,将当前元素设为候选。
    • 若当前元素 == candidate,count++,否则 count--
    • 由于多数元素数量超过一半,最终 candidate 即为答案。

图解

复制代码
[3,2,3]
i=0: candidate=3, count=1
i=1: 2 != 3 → count=0
i=2: count=0 → candidate=3, count=1
结束 candidate=3

Python代码

python 复制代码
def majorityElement(nums):
    candidate = None
    count = 0
    for num in nums:
        if count == 0:
            candidate = num
        count += 1 if num == candidate else -1
    return candidate

C++代码

cpp 复制代码
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int candidate = 0, count = 0;
        for (int num : nums) {
            if (count == 0) candidate = num;
            count += (num == candidate) ? 1 : -1;
        }
        return candidate;
    }
};

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

3. 整数反转(LeetCode 7)

题目描述

给你一个 32 位有符号整数 x,返回反转后的整数。如果反转后超出 32 位有符号整数范围 [−2³¹, 2³¹−1],则返回 0。

示例

输入:123 → 输出:321

输入:-123 → 输出:-321

解题思路

  • 逐位取出:pop = x % 10x //= 10
  • 构建结果:rev = rev * 10 + pop
  • 溢出判断:在加之前判断 rev > INT_MAX/10rev < INT_MIN/10 时返回 0。

图解

复制代码
x = 123
rev=0, pop=3 → rev=3, x=12
pop=2 → rev=32, x=1
pop=1 → rev=321, x=0
返回 321

Python代码

python 复制代码
def reverse(x):
    INT_MAX = 2**31 - 1
    INT_MIN = -2**31
    rev = 0
    sign = 1 if x >= 0 else -1
    x = abs(x)
    while x:
        pop = x % 10
        x //= 10
        # 溢出检查
        if rev > INT_MAX // 10 or (rev == INT_MAX // 10 and pop > 7):
            return 0
        rev = rev * 10 + pop
    return sign * rev

C++代码

cpp 复制代码
class Solution {
public:
    int reverse(int x) {
        int rev = 0;
        while (x != 0) {
            int pop = x % 10;
            x /= 10;
            if (rev > INT_MAX/10 || (rev == INT_MAX/10 && pop > 7)) return 0;
            if (rev < INT_MIN/10 || (rev == INT_MIN/10 && pop < -8)) return 0;
            rev = rev * 10 + pop;
        }
        return rev;
    }
};

复杂度分析

  • 时间复杂度:O(log₁₀ x)(位数)
  • 空间复杂度:O(1)

4. Pow(x, n)(LeetCode 50)

题目描述

实现 pow(x, n),即计算 x 的整数 n 次幂。

示例

输入:x = 2.00000, n = 10 → 输出:1024.00000

解题思路

  • 快速幂(递归/迭代)
    • n 为负数,转为 1 / pow(x, -n)
    • 利用 x^n = (x^(n/2))^2,当 n 为奇数时再乘一个 x。
  • 迭代写法:result = 1,将 n 视为二进制,从低位向高位累乘。

图解(n=10,二进制 1010)

复制代码
x=2, n=10
result=1, base=2
n=10 (1010): n%2=0 → base=4, n=5
n=5 (101): n%2=1 → result=1*4=4, base=16, n=2
n=2: n%2=0 → base=256, n=1
n=1: n%2=1 → result=4*256=1024, base=65536, n=0
返回 1024

Python代码(递归)

python 复制代码
def myPow(x, n):
    if n == 0:
        return 1
    if n < 0:
        return 1 / myPow(x, -n)
    half = myPow(x, n // 2)
    if n % 2 == 0:
        return half * half
    else:
        return half * half * x

C++代码(迭代)

cpp 复制代码
class Solution {
public:
    double myPow(double x, int n) {
        double res = 1.0;
        long long N = n;  // 防止 -2147483648 取反溢出
        if (N < 0) {
            x = 1 / x;
            N = -N;
        }
        while (N) {
            if (N & 1) res *= x;
            x *= x;
            N >>= 1;
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:O(log n)
  • 空间复杂度:O(log n)(递归) / O(1)(迭代)

5. 汉明距离(LeetCode 461)

题目描述

两个整数之间的汉明距离是指这两个数字对应二进制位不同的位置的数目。给出两个整数 xy,计算汉明距离。

示例

输入:x = 1, y = 4 → 输出:2

(1: 001,4: 100,不同位数为2)

解题思路

  • 先计算 xor = x ^ y,得到所有不同位为 1 的数字。
  • 统计 xor 中 1 的个数:可用 while (xor) { count++; xor &= (xor - 1); }(每次消除最低位的1)。

图解

复制代码
x=1 (001), y=4 (100)
xor = 101 (5)
count=0
xor=5 (101) → xor & (xor-1)=101 & 100=100, count=1
xor=4 (100) → 100 & 011=000, count=2
结束 count=2

Python代码

python 复制代码
def hammingDistance(x, y):
    xor = x ^ y
    count = 0
    while xor:
        count += 1
        xor &= xor - 1
    return count

C++代码

cpp 复制代码
class Solution {
public:
    int hammingDistance(int x, int y) {
        int xorVal = x ^ y;
        int count = 0;
        while (xorVal) {
            count++;
            xorVal &= xorVal - 1;
        }
        return count;
    }
};

复杂度分析

  • 时间复杂度:O(bit_count),最坏 O(32)
  • 空间复杂度:O(1)

6. 数组中的第 K 个最大元素(LeetCode 215)

题目描述

给定整数数组 nums 和整数 k,返回数组中第 k 个最大的元素(即排序后的第 k 个最大元素,而非第 k 个不同元素)。

示例

输入:[3,2,1,5,6,4], k=2 → 输出:5

解题思路

  • 快速选择(Quick Select):基于快排的 partition,每次将枢轴放到正确位置,若枢轴索引正好是第 k 大对应的下标(降序)则返回,否则只在一边递归。
  • :使用最小堆维护前 k 大的元素,堆顶即为答案。

图解(快速选择)

复制代码
nums=[3,2,1,5,6,4], k=2(第2大即升序第5小,索引 len-k=4)
选枢轴4,partition后可能变成 [3,2,1,4,6,5],枢轴索引3 < 4,找右边
...
最终找到索引4的值5

Python代码(快速选择)

python 复制代码
import random
def findKthLargest(nums, k):
    def quick_select(l, r, k_smallest):
        if l == r:
            return nums[l]
        pivot_idx = random.randint(l, r)
        pivot_val = nums[pivot_idx]
        nums[pivot_idx], nums[r] = nums[r], nums[pivot_idx]
        store = l
        for i in range(l, r):
            if nums[i] < pivot_val:
                nums[store], nums[i] = nums[i], nums[store]
                store += 1
        nums[store], nums[r] = nums[r], nums[store]
        if k_smallest == store:
            return nums[store]
        elif k_smallest < store:
            return quick_select(l, store - 1, k_smallest)
        else:
            return quick_select(store + 1, r, k_smallest)
    n = len(nums)
    return quick_select(0, n-1, n - k)   # 第 k 大是升序第 n-k 小

C++代码(堆)

cpp 复制代码
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<int>> minHeap;
        for (int num : nums) {
            minHeap.push(num);
            if (minHeap.size() > k) minHeap.pop();
        }
        return minHeap.top();
    }
};

复杂度分析

  • 快速选择:平均 O(n),最坏 O(n²);空间 O(log n)(递归栈)
  • :O(n log k);空间 O(k)

7. 合并 K 个升序链表(LeetCode 23)

题目描述

给你一个链表数组,每个链表都已按升序排列。请将所有链表合并成一个升序链表并返回。

示例

输入:[[1,4,5],[1,3,4],[2,6]] → 输出:[1,1,2,3,4,4,5,6]

解题思路

  • 方法一:优先队列(小根堆):将每个链表的头节点放入堆中,每次弹出最小值节点,将其下一节点入堆。
  • 方法二:分治归并:两两合并,重复直到只剩一个。

图解(优先队列)

复制代码
lists: [1->4->5, 1->3->4, 2->6]
堆初始: (1,list0), (1,list1), (2,list2)
弹出1(list0) → result:1, 加入list0.next(4)
堆: (1,list1), (2,list2), (4,list0)
弹出1(list1) → result:1->1, 加入list1.next(3)
......
最终合并完成

Python代码(优先队列)

python 复制代码
import heapq
def mergeKLists(lists):
    heap = []
    for i, node in enumerate(lists):
        if node:
            heapq.heappush(heap, (node.val, i, node))
    dummy = ListNode()
    cur = dummy
    while heap:
        val, i, node = heapq.heappop(heap)
        cur.next = node
        cur = cur.next
        if node.next:
            heapq.heappush(heap, (node.next.val, i, node.next))
    return dummy.next

C++代码(分治归并)

cpp 复制代码
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if (lists.empty()) return nullptr;
        int step = 1;
        while (step < lists.size()) {
            for (int i = 0; i + step < lists.size(); i += 2*step) {
                lists[i] = mergeTwo(lists[i], lists[i+step]);
            }
            step *= 2;
        }
        return lists[0];
    }
    ListNode* mergeTwo(ListNode* l1, ListNode* l2) {
        ListNode dummy(0), *cur = &dummy;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                cur->next = l1; l1 = l1->next;
            } else {
                cur->next = l2; l2 = l2->next;
            }
            cur = cur->next;
        }
        cur->next = l1 ? l1 : l2;
        return dummy.next;
    }
};

复杂度分析

  • 优先队列:O(N log k),其中 N 为总节点数,k 为链表数;空间 O(k)
  • 分治归并:O(N log k),空间 O(1)(不计递归栈)

8. 实现 Trie (前缀树)(LeetCode 208)

题目描述

实现一个 Trie(前缀树),包含 insertsearchstartsWith 三个操作。

示例

python 复制代码
trie = Trie()
trie.insert("apple")
trie.search("apple")   # True
trie.search("app")     # False
trie.startsWith("app") # True
trie.insert("app")
trie.search("app")     # True

解题思路

  • 每个节点包含一个子节点数组(26个字母)和一个布尔标志 isEnd
  • insert:遍历单词的每个字符,若子节点不存在则创建,最后标记 isEnd = True
  • search:遍历字符,若某字符缺失返回 False,最后检查 isEnd
  • startsWith:类似但不检查 isEnd

图解

复制代码
插入 "apple":
root -> a -> p -> p -> l -> e (isEnd)
插入 "app":
root -> a -> p -> p (isEnd)

Python代码

python 复制代码
class TrieNode:
    def __init__(self):
        self.children = [None] * 26
        self.isEnd = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word: str) -> None:
        node = self.root
        for ch in word:
            idx = ord(ch) - ord('a')
            if not node.children[idx]:
                node.children[idx] = TrieNode()
            node = node.children[idx]
        node.isEnd = True

    def search(self, word: str) -> bool:
        node = self._find(word)
        return node is not None and node.isEnd

    def startsWith(self, prefix: str) -> bool:
        return self._find(prefix) is not None

    def _find(self, prefix):
        node = self.root
        for ch in prefix:
            idx = ord(ch) - ord('a')
            if not node.children[idx]:
                return None
            node = node.children[idx]
        return node

C++代码

cpp 复制代码
class Trie {
    struct TrieNode {
        TrieNode* children[26];
        bool isEnd;
        TrieNode() : isEnd(false) {
            for (int i = 0; i < 26; ++i) children[i] = nullptr;
        }
    };
    TrieNode* root;
public:
    Trie() { root = new TrieNode(); }
    
    void insert(string word) {
        TrieNode* node = root;
        for (char c : word) {
            int idx = c - 'a';
            if (!node->children[idx]) node->children[idx] = new TrieNode();
            node = node->children[idx];
        }
        node->isEnd = true;
    }
    
    bool search(string word) {
        TrieNode* node = find(word);
        return node && node->isEnd;
    }
    
    bool startsWith(string prefix) {
        return find(prefix) != nullptr;
    }
private:
    TrieNode* find(string& s) {
        TrieNode* node = root;
        for (char c : s) {
            int idx = c - 'a';
            if (!node->children[idx]) return nullptr;
            node = node->children[idx];
        }
        return node;
    }
};

复杂度分析

  • 时间复杂度:插入/查询均为 O(L),L 为单词长度
  • 空间复杂度:O(总字符数 × 26)

9. 数据流中的中位数(LeetCode 295)

题目描述

设计一个支持以下两种操作的数据结构:

  • addNum(num):从数据流中添加一个整数到数据结构中。
  • findMedian():返回目前所有元素的中位数。

示例

复制代码
addNum(1), addNum(2), findMedian() → 1.5
addNum(3), findMedian() → 2

解题思路

  • 使用两个堆:大根堆 left 存储较小的一半(允许比 right 多一个元素),小根堆 right 存储较大的一半。
  • 保证 len(left) >= len(right) 且差值 ≤ 1。
  • 添加元素时,先插入 left(取负),再平衡到 right,最后调整大小。
  • 中位数:若两堆大小相等,取 (left.top + right.top)/2;否则取 left.top。

图解

复制代码
add 1: left=[1], right=[] -> median=1
add 2: 先放left: left=[2,1], 弹出最大2放入right: left=[1], right=[2] median=(1+2)/2=1.5
add 3: 3>left.top(1), 放入right: right=[2,3]? 实际小根堆,[2,3] min=2,但需保证left大小>=right,将right最小移到left: left=[2,1], right=[3] median=2

Python代码

python 复制代码
import heapq
class MedianFinder:
    def __init__(self):
        self.left = []   # 大根堆(存负数)
        self.right = []  # 小根堆

    def addNum(self, num: int) -> None:
        heapq.heappush(self.left, -num)
        heapq.heappush(self.right, -heapq.heappop(self.left))
        if len(self.left) < len(self.right):
            heapq.heappush(self.left, -heapq.heappop(self.right))

    def findMedian(self) -> float:
        if len(self.left) > len(self.right):
            return -self.left[0]
        else:
            return (-self.left[0] + self.right[0]) / 2

C++代码

cpp 复制代码
class MedianFinder {
    priority_queue<int> left; // 大根堆
    priority_queue<int, vector<int>, greater<int>> right; // 小根堆
public:
    void addNum(int num) {
        left.push(num);
        right.push(left.top());
        left.pop();
        if (left.size() < right.size()) {
            left.push(right.top());
            right.pop();
        }
    }
    double findMedian() {
        if (left.size() > right.size()) return left.top();
        else return (left.top() + right.top()) / 2.0;
    }
};

复杂度分析

  • 时间复杂度:addNum O(log n),findMedian O(1)
  • 空间复杂度:O(n)

🎯 总结

题目 核心技巧 时间复杂度 空间复杂度
136. 只出现一次的数字 异或 O(n) O(1)
169. 多数元素 摩尔投票法 O(n) O(1)
7. 整数反转 数学 + 溢出判断 O(log x) O(1)
50. Pow(x, n) 快速幂 O(log n) O(1)
461. 汉明距离 异或 + 位计数 O(1) O(1)
215. 第K大元素 快速选择 / 堆 O(n) 平均 / O(n log k) O(log n) / O(k)
23. 合并K个链表 优先队列 / 分治 O(N log k) O(k) / O(1)
208. 前缀树 字典树 O(L) O(总字符数×26)
295. 数据流中位数 两个堆 add O(log n), find O(1) O(n)

位运算题需要熟悉异或、与、或、移位等基础;数学题注意溢出;堆和分治是处理 Top K、多路归并的利器;前缀树适合字符串匹配问题。多练习,形成肌肉记忆。

相关推荐
jerryinwuhan1 小时前
hello算法,简单讲(1)
算法·排序算法
y = xⁿ1 小时前
20天速通LeetCodeday15:BFS广度优先搜索
算法·宽度优先
2303_821287381 小时前
如何清洗SQL输入数据_使用框架内置的ORM处理数据交互
jvm·数据库·python
400分1 小时前
吃透RAG核心-----语义检索与关键字检索底层原理
算法·架构
go不是csgo1 小时前
s01 搭建第一个对话智能体
服务器·网络·python·ai
用户8356290780511 小时前
使用 Python 在 PowerPoint 中生成并自定义饼图与环形图
后端·python
棉猴1 小时前
python海龟绘图之倾转
python·turtle·海龟绘图·titlangle·tilt
目黑live +wacyltd1 小时前
算法备案:常见驳回原因与应对策略
人工智能·算法
磊 子2 小时前
多态类原理+四种类型转换+异常处理
开发语言·c++·算法