【数据结构】消失的数字+ 轮转数组:踩坑详解

目录

[LeetCode - 消失的数字](#LeetCode - 消失的数字)

解题思路

暴力思路(不推荐)

最优解:异或(XOR)

异或原理详解

扩展:单身狗问题

[LeetCode - 轮转数组](#LeetCode - 轮转数组)

解题思路

暴力:逐个移位(超时)

最优解:三次反转

关键细节

总结


1,LeetCode - 消失的数字

LeetCode - 消失的数字

数组 nums 包含从 0n 的所有整数,但其中缺了一个,请找出那个缺失的整数。要求 O(n) 时间完成。


解题思路

暴力思路(不推荐)

排序后遍历找断点,时间复杂度 O(n log n),不符合要求。

最优解:异或(XOR)

核心观察 :把数组里的数和 0~n 全部异或在一起,相同的数两两抵消,最后剩下的就是缺失的数。

复制代码
nums = [3, 0, 1],n = 3,缺 2
x = 3^0^1 ^ 0^1^2^3
  = (0^0)^(1^1)^(3^3)^2
  = 0^0^0^2 = 2  ✓
cpp 复制代码
int missingNumber(int* nums, int numsSize) {
    int x = 0;
    for (int i = 0; i < numsSize; i++)
        x ^= nums[i];          // 异或数组所有元素
    for (int j = 0; j <= numsSize; j++)
        x ^= j;                // 异或 0~n
    return x;                  // 剩下的就是缺失值
}

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


异或原理详解

异或的本质是逐位做加法 mod 2:9

性质 公式 说明
归零律 a ^ a = 0 相同数异或为 0
恒等律 0 ^ a = a 0 与任何数异或为本身
交换律 a ^ b = b ^ a 加法 mod 2 满足交换律
结合律 (a^b)^c = a^(b^c) 加法 mod 2 满足结合律

交换律和结合律成立,意味着两个循环的异或顺序可以任意调换,相同数字必然在某处相遇并抵消,只有出现奇数次的数字会保留下来。
mod 2 就是除以2取余数,结果只有 0 或 1。

复制代码
0 mod 2 = 0
1 mod 2 = 1
2 mod 2 = 0
3 mod 2 = 1

联系到异或,举个例子,看某一个二进制位:

复制代码
0 ^ 0 = 0    →   (0+0) mod 2 = 0  ✓
0 ^ 1 = 1    →   (0+1) mod 2 = 1  ✓
1 ^ 0 = 1    →   (1+0) mod 2 = 1  ✓
1 ^ 1 = 0    →   (1+1) mod 2 = 0  ✓  (2除以2余0,不进位,直接归零)

异或和普通加法唯一的区别就是 1+1 不进位,直接变成 0,这就是 mod 2 的含义。

所以说"异或 = 加法 mod 2",每个二进制位独立这样算,整个整数就异或完了。



扩展:单身狗问题

同样的技巧可以解决"数组中只出现一次的数字":把所有元素全部异或,成对的数归零,剩下的就是答案。这类问题统称单身狗问题,XOR 是标准解法。


总结

题目 核心思想 时间 空间
消失的数字 异或抵消,相同归零 O(n) O(1)

2,LeetCode - 轮转数组

给定整数数组 nums ,将数组元素向右轮转 **k**个位置。


解题思路

暴力:逐个移位(超时)

每次把最后一个元素移到最前面,执行 k 次,时间复杂度 O(n×k),最坏 O(n²),数据量大时超时。

c

cpp 复制代码
// O(n²) 超时写法,仅作对比
void rotate_slow(int* nums, int numsSize, int k) {
    k %= numsSize;
    while (k--) {
        int last = nums[numsSize - 1];
        for (int i = numsSize - 2; i >= 0; i--)
            nums[i + 1] = nums[i];
        nums[0] = last;
    }
}

最优解:三次反转

规律发现

原数组: 1 2 3 4 5 6 7,k = 3

目标结果:5 6 7 1 2 3 4

步骤:

① 反转前 n-k 个:1 2 3 44 3 2 1 → 4 3 2 1 | 5 6 7

② 反转后 k 个: 5 6 77 6 5 → 4 3 2 1 | 7 6 5

③ 整体反转: 【4 3 2 1 7 6 5】 → 5 6 7 1 2 3 4

cpp 复制代码
void reverse(int* nums, int left, int right) {
    while (left < right) {
        int tmp = nums[left];
        nums[left]  = nums[right];
        nums[right] = tmp;
        left++;
        right--;
    }
}

void rotate(int* nums, int numsSize, int k) {
    k %= numsSize;           // k 可能大于 n,取模防越界
    int n = numsSize;
    reverse(nums, 0, n-k-1); // ① 反转前半段 [0, n-k-1]
    reverse(nums, n-k, n-1); // ② 反转后半段 [n-k, n-1]
    reverse(nums, 0, n-1);   // ③ 整体反转
}

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


关键细节

k %= numsSize 不能省:若 k = 7、n = 7,轮转一整圈等于没转,不取模会导致下标越界或逻辑错误。

三段区间划分

步骤 区间 说明
[0, n-k-1] 不需要移动的前半段
[n-k, n-1] 需要移到前面的后 k 个
[0, n-1] 整体反转得到最终结果

总结

题目 核心思想 时间 空间
轮转数组 三次反转,区间划分准确 O(n) O(1)

是用简单操作的组合达到线性时间,是位运算和双指针的经典入门题,面试高频出现。

相关推荐
leo__5202 分钟前
MATLAB实现牧羊人算法
开发语言·算法·matlab
视觉小萌新6 分钟前
C++利用libmicrohttpd制作交互网页端——C1
java·c++·交互
caimouse8 分钟前
Reactos 第 5 章 进程与线程 — 5.11 线程本地存储 TLS
c语言·windows
Gauss松鼠会8 分钟前
【GaussDB】GaussDB SMP特性调优详解
java·服务器·前端·数据库·sql·算法·gaussdb
Tisfy13 分钟前
LeetCode 3689.最大子数组总值 I:What The Medium
算法·leetcode·题解·贪心·模拟·脑筋急转弯
fpcc13 分钟前
C++编程实践—C++实现类似Qt的信号槽机制
c++·qt
葬送的代码人生13 分钟前
JavaScript 数组完全指南:从入门到实战
前端·javascript·算法
格发许可优化管理系统15 分钟前
Mentor许可证使用规定全解析
java·大数据·c语言·开发语言·c++
郝学胜_神的一滴19 分钟前
Qt 高级开发 030:QListWidget 右键菜单全解,从策略配置到精准删除的优雅实现
c++·qt
春日见28 分钟前
决策规划控制面经汇总
人工智能·深度学习·算法·机器学习·自动驾驶