
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~

解题思路 由于数组是非严格递增的,重复的元素必然是相邻的。我们可以用两个指针:

结合 示例 1(输入nums = [1,1,2]) 拆解解题过程: 步骤 1:初始化指针
步骤 2:第一次循环(curr = 1) 此时 nums[curr] = 1,nums[prev] = 1,两者相等。
步骤 3:第二次循环(curr = 2) 此时 nums[curr] = 2,nums[prev] = 1,两者不相等。
步骤 4:返回结果最终 prev = 1,无重复元素的数量为 prev + 1 = 2,与示例输出一致。数组的前 2 个元素为 [1, 2],满足题目要求。
尤其注意:慢指针 prev 最终指向的是 “无重复元素区域” 的最后一个元素的索引,需要返回的是无重复元素的数量
通过这样的步骤,双指针法在原地完成了重复元素的删除,同时保证了时间复杂度为O(n)、空间复杂度为 O(1),是该问题的最优解法。

写这道题要清楚异或运算的特性: 异或运算(^)是基于二进制位的操作,规则是:对应二进制位相同则为 0,不同则为 1
3 的二进制:011(8 位表示为 00000011)
5 的二进制:101(8 位表示为 00000101)
合并结果为 00000110,即十进制的 6。所以 3 ^ 5 = 6性质 1:相同的数异或,结果为 0。例如 2 ^ 2 = 0,5 ^ 5 = 0。 性质 2:0 和任意数异或,结果为这个数本身。例如 0 ^ 3 = 3,0 ^ 99 = 99
异或元素还满足: 交换律:a ^ b = b ^ a(异或的顺序不影响结果)。 结合律:(a ^ b) ^ c = a ^ (b ^ c)(可以任意调整异或的组合顺序)
4 ^ 1 ^ 2 ^ 1 ^ 2 = (1 ^ 1) ^ (2 ^ 2) ^ 4 = 0 ^ 0 ^ 4 = 4基于以上特性得出结论:只要元素是 “成对出现” 的,无论其数值是多少,异或后都会相互抵消(结果为 0);而唯一不成对的元素,会在最终异或中被保留下来


思路分析 数组中除一个元素外,其余元素都恰好出现三次。对于二进制的每一位来说,出现三次的元素在该位上的 1 的个数是 3 的倍数,而只出现一次的元素在该位上的 1 会 “打破” 这个倍数关系。因此,我们可以:


代码解释
详细处理:



补充:符号位的作用是区分存储数据的正负 如果忽略符号位,只统计低 31 位,当目标元素是负数时,就会丢失符号位的信息,导致结果错误。
举个例子:假设目标元素是 -1,它的 32 位补码是 11111111 11111111 11111111 11111111(32 个1)。如果不统计符号位(第 31 位),统计结果会忽略最高位的1,最终得到的结果就会是一个正数(而非-1),显然错误。
因此,为了正确处理所有可能的整数(包括负数),必须统计全部 32 位(包含符号位),这样才能通过每一位的 “次数对 3 取余” 逻辑,准确还原出目标元素的二进制表示(包括符号位)。

思路分析

代码解释
下面是结合样例的详细分析:


再说一下最后为什么可以直接返回{3, 4}; 在 C++ 中,return {a, b} 会返回一个 vector< int > 类型的对象(可以理解为动态数组),其内部包含两个元素 a 和 b。
具体原因: 函数的返回类型是 vector(题目要求返回 “两个只出现一次的数字组成的数组”),而 C++ 支持用初始化列表(即 {} 包裹的元素)来构造 vector 对象。
当执行 return {a, b} 时,编译器会自动将初始化列表 {a, b} 转换为一个 vector< int > 实例,其中第一个元素是 a,第二个元素是 b,符合函数的返回类型要求。
结合样例: 在示例 nums = [1,2,1,3,2,5] 中,最终 a=3、b=5,return {a, b} 会返回一个 vector< int >,内容为 [3, 5],这正是题目所需的结果。
但是也可以看到上面的代码只通过了8个测试用例: 要解决这个运行时错误,我们需要处理int 类型最小值的取反溢出问题。以下是原因分析和修正方案: 错误原因 -2147483648 是 32 位有符号 int 的最小值(即 -231)。当对它取反时,-(-2147483648) = 2147483648,但 32 位有符号 int 的最大值是 2147483647(即 231-1),因此这个值无法用有符号 int 表示,导致溢出和未定义行为。 解决方案 将 xorSum 转换为无符号整数(unsigned int/size_t)后再计算 mask,因为无符号整数的范围更大(0 ~ 232-1),可以避免溢出。



思路分析 摩尔投票法的核心思想是:

样例输入: 假设输入数组为 [1, 2, 3, 2, 2, 2, 5],目标是找到出现次数超过数组长度一半的数字(数组长度为 7,超过一半即至少出现 4 次)。
执行步骤拆解
结果分析 遍历结束后,候选数为 2。在原数组中,2 共出现 4 次,确实超过数组长度(7)的一半(3.5),因此结果正确。
这里最重要的原理就是: 目标数字出现次数超过数组长度的一半,意味着它与其他所有数字的总出现次数相比,至少多 1 次。在遍历过程中,目标数字会 “抵消” 掉所有不同的数字,最终剩余的候选数必然是它。

二、解题思路

下面重点说一下这道题目的核心“回溯算法”
在编程中,回溯算法通常用 递归 来实现。它非常适合解决需要生成所有可能组合、排列的问题,比如:
所以,“23” 的所有组合是:ad, ae, af, bd, be, bf, cd, ce, cf
核心思想:我们需要为每个数字选择一个字母,然后将这些字母组合起来。但问题是,数字的长度可能不固定(例如输入可能是 “234”、“2” 等),所以我们需要一种灵活的方式来处理这种动态的选择过程。
(空)
/ | \
a b c (选择数字2的字母)
/|\ /|\ /|\
d e f d e f d e f (选择数字3的字母)
/ | \ ... ... ...
ad ae af ... ... cf (最终组合)回溯算法的执行步骤:
string s = "abc";
s.pop_back(); // 现在 s 变成 "ab"
s.pop_back(); // 现在 s 变成 "a"
s.pop_back(); // 现在 s 变成空字符串为什么需要 pop_back() 在回溯算法中,我们需要:
pop_back() 就是用来 撤销选择 的关键操作。
回溯算法执行流程(以 “23” 为例)




class Solution {
private:
//数字到字母的映射表,索引为数字,值为对应字母
vector<string> phoneMap = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
vector<string> result;//存储最终结果
string path; //存储当前字母组合
public:
vector<string> letterCombinations(string digits) {
//处理输入为空的情况
if(digits.empty())
{
return result;
}
backtrack(digits, 0);
return result;
}
//回溯函数:index表示当前处理到digits的第几个字符
void backtrack(const string& digits, int index)
{
//所有数字已处理完毕
if(index == digits.size())
{
result.push_back(path);
return;
}
//当前处理的数字
char digit = digits[index];
//数字对应的字母合集
string letters = phoneMap[digit - '0'];
//遍历每个字符
for(char c : letters)
{
//选择当前字母
path.push_back(c);
//递归处理下一个数字
backtrack(digits, index + 1);
//回溯,撤销选择
path.pop_back();
}
}
};补充:这里若digits为空直接返回result,result里面不会是随机值: 在 C++ 中,vector result; 这种成员变量的定义,会调用 vector 的默认构造函数,此时result会被初始化为空容器(即里面没有任何元素)