算法分析课程博客分享 12

算法分析课程博客分享 12


46. 全排列(Permutations)

给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

解题思路:
我们采用递归的方式来解决问题,此处,我们将 INT_MAX 视为已访问过的数据。我们先顺序扫描数组,若检测到不等于 INT_MAX 的元素 n,我们将其放入 seq 中,并将其的值修改为 INT_MAX,表示已被访问过。其中,seq 代表全排列生成的某一个序列。将 n 放入 seq 后,若 seq 的长度等于 nums 的长度,说明 seq 已经构成了全排列中的某一个序列,我们将其放入结果集 res 中;若 seq 的长度小于 nums 的长度,说明我们还需往 seq 中放入元素才能令其构成全排列中的某一个序列,此时我们调用 permute() 来往 seq 中继续放入元素。在每一次更新完 res 集后,我们已经计算完元素 n 处于 seq 中的当前位置时可以生成的所有序列,因此,我们还原 seq 的状态,把 n 从 seq 中移除,然后将 n 的状态恢复回未访问。当我们扫描到数组末尾时,说明我们已经计算出了所有的情况,此时返回结果集 res。具体代码如下:

public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] != INT_MAX) {
                int n = nums[i];
                nums[i] = INT_MAX;
                seq.push_back(n);
                if (seq.size() == nums.size()) {
                    res.push_back(seq);
                } else {
                    vector<vector<int>> subset = permute(nums);
                    res.insert(res.end(), subset.begin(), subset.end());
                }
                seq.pop_back();
                nums[i] = n;
            }
        }
        return res;
    }
    
private:
    vector<int> seq;



189. 旋转数组(Rotate Array)

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

示例 1:
输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]

示例 2:
输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]

说明:

  • 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
  • 要求使用空间复杂度为 O(1) 的原地算法。

解题思路:
首先,我们可以发现,数组长度小于 2 时,无论 k 的取值如何,数组元素的位置都不会发生改变,故当 nums.size() < 2 时,我们可以直接返回,无需进行任何操作。而当 k >= nums.size() 时,数组元素移动了一个或以上的周期,我们实际只需计算数组元素移动 k % size 位后的结果即可。所谓将数组中的元素向右移动 k 个位置,即将数组的末尾 k 位移动到数组的开头。我们先把整个数组反转,令末尾元素处于数组的开头,然后,我们对此时数组的前 n 位与后 size - n 位分别执行反转操作来还原他们在原数组中的位置关系。此时,我们就得到了将数组末尾 k 位移动到数组开头的结果,具体代码如下:

void rotate(vector<int>& nums, int k) {
    int size = nums.size();
    if (size < 2)
        return;

    k %= size;
    reverse(nums.begin(), nums.end());
    reverse(nums.begin(), nums.begin()+k);
    reverse(nums.begin()+k, nums.end());   
}



477. 汉明距离总和(Total Hamming Distance)

两个整数的 汉明距离 指的是这两个数字的二进制数对应位不同的数量。

计算一个数组中,任意两个数之间汉明距离的总和。

示例:
输入: 4, 14, 2
输出: 6
解释: 在二进制表示中,4表示为0100,14表示为1110,2表示为0010。(这样表示是为了体现后四位之间关系)
所以答案为:
HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6.

注意:

  • 数组中元素的范围为从 0到 10^9。
  • 数组的长度不超过 10^4。

解题思路:
为了方便理解,我们先以 bit 为单位来进行分析。假设我们有以下三个 bits:1、1、0,我们要计算这三个 bits 的汉明距离,我们得到的结果是 2(即 HammingDistance(1, 1) + HammingDistance(1, 0) + HammingDistance(1, 0) = 0 + 1 + 1)。我们可以观察到,所谓汉明距离即所有不同 (0, 1) 对的数量,根据排列组合,我们可以得到对于 bit 来说,汉明距离等于所有 0 的数量乘所有 1 的数量。我们将这个结论推广到 32 位的整型数,每一位的汉明距离都等于当前位 1 的数量乘 0 的数量,则汉明距离总和等于这 32 个位的汉明距离的和,具体代码如下:

int totalHammingDistance(vector<int>& nums) {
    int count = 0;
    for (int i = 0; i < 32; ++i) {
        int ones = 0;
        for (int n : nums) {
            ones += ((n >> i) & 1);
        }
        count += ones * (nums.size()-ones);
    }
    return count;
}