C++ vectorOJ练习题

目录

[136. 只出现一次的数字](#136. 只出现一次的数字)

[118. 杨辉三角](#118. 杨辉三角)

[26. 删除有序数组中的重复项](#26. 删除有序数组中的重复项)

[137. 只出现一次的数字ll](#137. 只出现一次的数字ll)

[260. 只出现一次的数字 III](#260. 只出现一次的数字 III)

[17. 电话号码的字母组合](#17. 电话号码的字母组合)

[JZ39 数组中出现次数超过一半的数字](#JZ39 数组中出现次数超过一半的数字)


136. 只出现一次的数字

采用异或运算的思路

  • 异或运算的特性是,相同的数异或结果为0,而任何数与0异或结果仍为原数。
  • 通过在循环中不断进行异或运算,最终只出现一次的元素将会被保留在变量v中,而其他出现多次的元素则会被抵消掉。
cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int v=0;
        for(auto a:nums)
        {
            v^=a;
        }
        return v;
    }
};

118. 杨辉三角

思路:找出杨辉三角的规律,发现每一行头尾都是1,中间第[j]个数等于上一行[j-1]+[j]。

cpp 复制代码
class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> v;
        v.resize(numRows, vector<int>());
        for (size_t i = 0; i < numRows; i++) {
            v[i].resize(i + 1, 0);
            v[i][0] = v[i][v[i].size() - 1] = 1;
        }
        for (size_t i = 0; i < v.size(); i++) {
            for (size_t j = 0; j < v[i].size(); j++) {
                if (v[i][j] == 0) {
                    v[i][j] = v[i - 1][j] + v[i - 1][j - 1];
                }
            }
        }
        return v;
    }
};
  • 首先,函数定义了一个二维整型数组v,用于存储生成的杨辉三角形。通过调用resize函数,将数组的行数设置为numRows,并将每一行的列数初始化为0。
  • 接下来,使用两个嵌套的for循环来遍历数组v中的每个元素。外层循环控制行数,内层循环控制列数。
  • 在内层循环中,首先通过v[i].resize(i+1,0)将当前行的列数设置为i+1,并将所有元素初始化为0。然后,将当前行的第一个元素和最后一个元素设置为1,即v[i][0]=v[i][v[i].size()-1]=1
  • 接下来,通过判断当前元素是否为0,来确定是否需要计算该元素的值。如果当前元素为0,则表示该元素是由上方两个元素相加得到的。通过v[i-1][j-1]+v[i-1][j]计算出当前元素的值,并将结果赋值给v[i][j]
  • 通过这样的循环嵌套,依次计算出杨辉三角形中每个位置的值。
  • 最后,函数返回生成的杨辉三角形数组v

26. 删除有序数组中的重复项

方法一

使用双指针方法,一个指针用于遍历数组并查找唯一的元素,另一个指针用于跟踪下一个不重复元素应该插入的位置。时间复杂度为O(N).

cpp 复制代码
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if (nums.empty())
            return 0;
        int a = 0, b = 1;
        while (b < nums.size()) {
            if (nums[a] != nums[b]){
                nums[++a] = nums[b];
            }
            ++b;
        }
        return a + 1;
    }
};
  • 如果nums为空,则立即返回0。这是因为空数组没有元素可以移除。函数使用两个指针ab(或者可以认为是索引)来遍历数组。a指向要插入下一个唯一元素的位置,而b遍历数组寻找不重复的元素。
  • b小于nums.size()时,继续遍历数组。
  • 检查nums[a]nums[b]。如果它们不相等,这意味着nums[b]是一个新的唯一元素,它应该被移到a指向的下一个位置。
  • 如果nums[a]nums[b]相等,这意味着nums[b]是重复元素,因此不应移动a
  • 当找到新的唯一元素时(即nums[a]nums[b]不相等),将nums[b]的值复制到nums[a+1]的位置。这样做是为了在数组的前部保留所有唯一的元素。
  • 指针a递增以指向下一个可能的唯一元素的位置。
  • 无论nums[a]nums[b]是否相等,b都会递增,以便继续检查数组的下一个元素。
  • 函数返回a+1,这是因为a是最后一个唯一元素的索引,而数组长度是最后一个元素的索引加1。

方法二

思路: erase删除重复数据,不过会导致数据挪动,时间复杂度为O(N)

  1. 由于数据是排序好的,通过查询每一个数据的重复区间
  2. 将重复区间进行删除,只保留一个数据即可
cpp 复制代码
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if (nums.size() == 1)
            return 1;
        vector<int>::iterator it = nums.begin();
        vector<int>::iterator it1 = it + 1;

        while (it != nums.end()) {
            while (it1 != nums.end() && *it1 == *it){
                it1++;
            }
            it = nums.erase(++it, it1);
            it1 = it;
            it1++;
        }
        return nums.size();
    }
};

137. 只出现一次的数字ll

方法一: 我们可以使用哈希映射统计数组中每个元素的出现次数。对于哈希映射中的每个键值对,键表示一个元素,值表示其出现的次数。在统计完成后,我们遍历哈希映射即可找出只出现一次的元素。

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int, int> count;
        for (auto a : nums) {
            ++count[a];
        }
        for (auto& pair : count) {
            if (pair.second == 1) {
                return pair.first;
            }
        }
        return -1;
    }
};

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int, int> map;
        for (auto n : nums) {
            ++map[n];
        }
        int a = 0;
        for (auto [n, o] : map) {
            if (o == 1) {
                a = n;
                break;
            }
        }
        return a;
    }
};
  1. 创建一个无序映射(unordered_map)count,用于记录每个元素出现的次数。

  2. 遍历数组nums,对于数组中的每个元素a,将其作为键,将其出现的次数加1。

  3. 遍历映射count中的每个键值对,对于每个键值对pair,如果其值等于1,说明该键对应的元素只出现了一次,返回该键。

  4. 如果遍历完映射count后仍未找到只出现一次的元素,则返回-1。

**方法二:**只有一个数字出现一次,其余数字均出现3次,假设数组为{3,5,3,3} 通过分析可知: 3的二进制:0 0 0 0 0 0 1 1 5的二进制:0 0 0 0 0 1 0 1 3的二进制:0 0 0 0 0 0 1 1 3的二进制:0 0 0 0 0 0 1 1 0 0 0 0 0 1 3 4 二进制1的总数 对于出现3次的数字,各位出现的次数都是3的倍数,因此对统计的为1的比特总数%3 0 0 0 0 0 1 0 1 = 5

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 0; i < 32; i++) {
            int sum = 0;
            for (auto n : nums) {
                if (((n >> i) & 1) == 1)
                    sum++;
            }
            sum %= 3;
            if (sum == 1)
                ret |= 1 << i;
        }
        return ret;
    }
};

260. 只出现一次的数字 III

方法一:哈希表

cpp 复制代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        unordered_map<int, int> map;
        for (auto n : nums) {
            map[n]++;
        }
        vector<int> rec;
        for (size_t i = 0; i < nums.size(); ++i) {
            if (map[nums[i]] == 1) {
                rec.push_back(nums[i]);
            }
        }
        return rec;
    }
};

这种方法虽然在代码中实现了,但是它不符合题目要求的线性时间和常数空间的要求。

  • 使用哈希表来记录每个元素出现的次数。
  • 遍历数组,将出现一次的元素添加到结果列表中返回。

这种方法的时间复杂度为O(n),空间复杂度也为O(n),因为需要额外的空间来存储哈希表。

方法二:位操作

cpp 复制代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int sum = 0;
        for (int num : nums) {
            sum ^= num;
        }
        int l = (sum == INT_MIN ? sum : sum & (-sum));
        int num1 = 0, num2 = 0;
        for (int num : nums) {
            if (num & l)
                num1 ^= num;
            else
                num2 ^= num;
        }
        return {num1, num2};
    }
};

这是符合题目要求的方法,利用了XOR运算的性质来解决问题:

  • 异或运算特性:相同为0,相异为1,无进位相加

    • 任何数和0做异或运算,结果仍然是原来的数,即 a⊕0=aa⊕0=a。
    • 任何数和其自身做异或运算,结果是0,即 a⊕a=0a⊕a=0。
    • 异或运算满足交换律和结合律,即 a⊕b=b⊕aa⊕b=b⊕a 和 (a⊕b)⊕c=a⊕(b⊕c)(a⊕b)⊕c=a⊕(b⊕c)。
  • 第一次异或

    • 我们遍历整个数组,将所有元素进行异或运算。由于数组中的大部分元素都是成对出现的,所以这些成对的元素相互抵消,最后得到的结果就是那两个只出现一次的数的异或结果。设这两个数为 xx 和 yy,那么我们得到的异或结果 sum=x⊕ysum=x⊕y。
  • 寻找差异位

    • 因为 xx 和 yy 是不同的数,所以 sumsum 至少有一个比特位是1。我们需要找到这个比特位,这可以通过将 sumsum 和它的补码 sum⊕(−sum)sum⊕(−sum) 进行与运算得到。这个比特位就是 sumsum 中从最低位开始的第一个1。
    • 另一种方法是直接使用 sumsum 本身,因为如果 sumsum 不是最小负数 INT_MIN,那么 sumsum 和它的补码的与运算结果就是 sumsum 的最低位1。如果 sumsum 是 INT_MIN,那么 sumsum 就已经是这个比特位了。
  • 第二次异或

    • 利用找到的差异位,我们可以将数组中的所有元素分为两类:那些在这个比特位上为1的数,以及那些在这个比特位上为0的数。
    • 对这两类数分别进行异或运算。因为每一对重复的数在这个比特位上的值是一样的,它们会在各自的类别中相互抵消,剩下的就是那个类别中只出现一次的那个数。
    • 于是我们得到了 xx 和 yy。

这种方法的时间复杂度为O(n),并且只需要常数级别的额外空间,满足题目的要求。

方法三:排序后比较

cpp 复制代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<int> res;
        int i = 0;
        for (; i < nums.size() - 1;) {
            if (nums[i] == nums[i + 1]) {
                i += 2;
            } else {
                res.push_back(nums[i]);
                i += 1;
            }
        }
        if (i < nums.size()) {
            res.push_back(nums[i]);
        }
        return res;
    }
};

这种方法也不完全符合题目要求的线性时间和常数空间的要求,但可以理解它的逻辑:

  • 先对数组进行排序。
  • 遍历排序后的数组,如果遇到相邻元素不相等的情况,则当前元素一定是只出现一次的元素。
  • 最后处理数组尾部的元素。

这种方法的时间复杂度为O(n log n),因为排序通常需要O(n log n)的时间复杂度。空间复杂度取决于使用的排序算法,如果是原地排序则可以达到O(1),但大多数情况下会大于O(1)。

17. 电话号码的字母组合

回溯算法

cpp 复制代码
class Solution {
public:
    string num_str[10] = {"",    "",    "abc",  "def", "ghi",
                          "jkl", "mno", "pqrs", "tuv", "wxyz"};
    void combination(const string& digits, size_t di, string combStr,
                     vector<string>& strv) {
        if (di == digits.size()) {
            strv.push_back(combStr);
            return;
        }
        int num = digits[di] - '0';
        string str = num_str[num];
        for (auto ch : str) {
            combination(digits, di + 1, combStr + ch, strv);
        }
    }
    vector<string> letterCombinations(string digits) {
        vector<string> str;
        if (digits.size() == 0)
            return str;
        combination(digits, 0, "", str);
        return str;
    }
};
  1. 初始化映射表

    • 创建了一个静态数组 num_str,用于存储数字到字母的映射关系。例如,"2" 映射到 "abc""3" 映射到 "def" 等等。注意索引 01 处的字符串为空,因为我们不考虑这两个数字对应的字母。
  2. 递归函数 combination

    • 这个函数接受四个参数:digits(输入的数字字符串)、di(当前处理的数字索引)、combStr(当前构建的字符串组合)以及strv(用于存储所有可能组合的向量)。
    • 如果当前处理的索引 di 已经等于 digits 的长度,说明已经处理完所有的数字,此时将当前构建的字符串 combStr 加入结果向量 strv
    • 否则,获取当前数字对应的字母字符串 str,然后遍历这个字符串中的每一个字母,并递归调用 combination 函数,同时增加索引 di 并将当前字母加入到 combStr
  3. 主函数 letterCombinations

    • 检查输入的 digits 字符串是否为空,如果为空则直接返回空的字符串向量。
    • 调用 combination 函数开始递归生成所有可能的组合。
    • 最终返回所有可能的字母组合。

示例步骤

假设 digits = "23",则有以下步骤:

  • 开始时,di=0combStr=""num=2,对应的字母是 "abc"
  • 对于每个字母 a, b, c,执行 di+1,即 di=1
  • di=1 时,num=3,对应的字母是 "def"
  • 对于每个字母 d, e, f,执行 di+1,即 di=2
  • di=2 时,已经到达字符串末尾,将当前的 combStr 添加到结果向量中。

结果

对于 "23",结果向量将会包含 "ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"

这种方法的时间复杂度是 O(3^N * 4^M),其中 N 是数字2、3、4、5、6、8出现的次数,M 是数字7、9出现的次数,因为数字7和9对应4个字符,其它数字对应3个字符。空间复杂度主要取决于递归调用栈的深度,最坏情况下为 O(N)。

JZ39 数组中出现次数超过一半的数字

哈希表

cpp 复制代码
class Solution {
  public:
    int MoreThanHalfNum_Solution(vector<int>& numbers) {
        unordered_map<int, int> count;
        int n=numbers.size();
        for (auto a : numbers) {
            ++count[a];
            if (count[a] > n / 2) {
                return a;
            }
        }
        return -1;
    }
};
相关推荐
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
奋斗的小花生4 小时前
c++ 多态性
开发语言·c++
pianmian14 小时前
python数据结构基础(7)
数据结构·算法
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
UestcXiye5 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
好奇龙猫6 小时前
【学习AI-相关路程-mnist手写数字分类-win-硬件:windows-自我学习AI-实验步骤-全连接神经网络(BPnetwork)-操作流程(3) 】
人工智能·算法
霁月风6 小时前
设计模式——适配器模式
c++·适配器模式
sp_fyf_20246 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
香菜大丸7 小时前
链表的归并排序
数据结构·算法·链表
jrrz08287 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表