算法分析课程博客分享 3

算法分析课程博客分享 3


169. 求众数(Majority Element)

给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在众数。

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

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


解题思路:
这道题属于简单题,因此,我打算在这里提供多种做法。
解法 1: 哈希法
使用 std::unordered_map 容器,将数组里的元素与出现的次数关联在一起,然后返回出现次数大于 len/2 的值。时间复杂度为 O(n)。

int majorityElement(vector<int>& nums) {
    std::unordered_map<int, int> count;

    int len = nums.size();
    for (int i = 0; i < len; ++i)
        ++count[nums[i]];

    len /= 2.0f;
    for (auto it = count.begin(); it != count.end(); it++)
        if (it->second > len)
            return it->first;
}


解法 2: 位运算法
使用一个桶来记录 32 个 bits 在数组中出现的次数。对于返回值,将在桶中出现次数大于 len/2 的 bit 置为 1。时间复杂度为 O(n),但是会慢于哈希法。

int majorityElement(vector<int>& nums) {
    int count[32] = { 0 };

    int len = nums.size();
    for (int i = 0; i < len; ++i) {
        for (int j = 0; j < 32; ++j) {
            count[j] += ((nums[i] >> j) & 1);
        }
    }

    int ans = 0;
    for (int i = 0; i < 32; ++i) {
        if (count[i] > len/2) {
            ans |= (1 << i);
        }
    }

    return ans;
}


解法 3: 数组法
由于题目限定了众数在数组中出现必然大于 len/2。因此,众数出现的次数必然大于其他所有数字出现次数的和。我们使用动态规划的思想,先记录当前数字与当前数字的出现频率,若下一个数字与当前数字一致则频率加 1,否则频率减 1。当前数字的频率为 0 时,我们将更新当前数字。时间复杂度为 O(n)。

int majorityElement(vector<int>& nums) {
    int num = nums[0], count = 1;
    for (int i = 1; i < nums.size(); ++i) {
        if (nums[i] == num) {
            ++count;
        } else {
            --count;
        }
        if (count == 0) {
            num = nums[i];
            count = 1;
        }
    }
    return num;
}


解法 4: 分治法
此题的重点解法,具体思想与数组法一致,不同点在于运用了二分的思想来统计当前数字与出现频率。时间复杂度为 O(n)。

int majorityElement(vector<int>& nums) {
    return majorityElement(nums, 0, nums.size()-1).first;
}

pair<int, int> majorityElement(vector<int>& nums, int start, int end) {
    if (start == end)
        return make_pair(nums[start], 1);

    int middle = (start + end) / 2;
    pair<int, int> left = majorityElement(nums, start, middle);
    pair<int, int> right = majorityElement(nums, middle+1, end);

    if (left.first == right.first)
        return make_pair(left.first, left.second+right.second);
    else if (left.second < right.second)
        return make_pair(right.first, right.second-left.second);
    else if (left.second > right.second)
        return make_pair(left.first, left.second-right.second);
    else
        return make_pair(0, 0);
}



74. 搜索二维矩阵(Search a 2D Matrix)

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

  • 每行中的整数从左到右按升序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

示例 1:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true

示例 2:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
输出: false


解题思路:
由于最近比较忙,所以选了一些不太难的题目来解。在这道题中,我们首先通过二分查找确定目标数字所在的行,然后再在该行中用二分查找目标元素。在原理上是非常简单的,这里就特别提一下一些细节的地方。首先,我们第一次用二分查找的是第一个元素小于等于目标元素的行,因此二分查找的条件应该设为 start < end,这能保证跳出循环后 start 的值不会导致数组越界。而为了满足该行第一个元素小于等于目标元素,我们还需要判定是否需要对 start 进行减 1。但是为了保证 start-1 的值大于等于 0,所以在程序最开头需要加上 matrix[0][0] > target 的判断。算法的时间复杂度为 O((m+n)/2),具体代码如下:

bool searchMatrix(vector<vector<int>>& matrix, int target) {
    if (matrix.empty() || matrix[0].empty())
        return false;
    if (matrix[0][0] > target)
        return false;

    int start = 0, end = matrix.size() - 1, middle = 0;
    while (start < end) {
        middle = (start + end) / 2;
        if (matrix[middle][0] == target) {
            return true;
        } else if (matrix[middle][0] < target) {
            start = middle + 1;
        } else {
            end = middle - 1;
        }
    }

    int entry = (matrix[start][0] > target) ? start - 1 : start;
    start = 0, end = matrix[entry].size() - 1;
    while (start <= end) {
        middle = (start + end) / 2;
        if (matrix[entry][middle] == target) {
            return true;
        } else if (matrix[entry][middle] < target) {
            start = middle + 1;
        } else {
            end = middle - 1;
        }
    }

    return false;
}


另外,也分享一种参考得来的 O(m+n) 的算法,该算法的优点是代码比较简洁:

bool searchMatrix(vector<vector<int>>& matrix, int target) {
    int rows = matrix.size();
    if (rows <= 0)
        return false;

    int cols = matrix[0].size();
    int row = 0, col = cols-1; 
    while (row >= 0 && row < rows && col >= 0 && col < cols) {
        int val = matrix[row][col];
        if (val == target)
            return true;
        else if (val < target)
            ++row;
        else
            --col;
    }

    return false;
}



240. 搜索二维矩阵 II(Search a 2D Matrix II)

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。


解题思路:
这道题是上一题的一个升级版,虽然题目的标签里有分治算法,但是我并没有找到比较好的分治算法来解决这个问题。其实这道题的解法可以参考上一道题,其中,时间复杂度为 O(m+n) 具体代码如下:

bool searchMatrix(vector<vector<int>>& matrix, int target) {
    if (matrix.empty() || matrix[0].empty())
        return false;

    int rows = matrix.size(), cols = matrix[0].size();
    int r = 0, c = cols - 1;
    while (r < rows && c >= 0) {
        if (matrix[r][c] == target)
            return true;
        else if (matrix[r][c] < target)
            ++r;
        else
            --c;
    }

    return false;
}


根据题目的定义,使用以下的例子来解释我们的算法:

现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 13。

首先,我们从矩阵的右上角开始遍历,我们发现当前元素 15 的值大于目标值 13,且外,我们可以确保与 15 处于同一列的其他元素都大于 13。因此,我们可以将最后一列从矩阵中删去。

[
[1, 4, 7, 11],
[2, 5, 8, 12],
[3, 6, 9, 16],
[10, 13, 14, 17],
[18, 21, 23, 26]
]

之后,我们的当前元素变为 11,我们发现 11 小于目标值,且外,我们可以确保与 11 处于同一行的其他元素都小于 13,因此,我们可以将第一列从矩阵中删去。

[
[2, 5, 8, 12],
[3, 6, 9, 16],
[10, 13, 14, 17],
[18, 21, 23, 26]
]

当前元素变为 12,由于 12 小于目标值,我们采用与第二步相同的策略。

[
[3, 6, 9, 16],
[10, 13, 14, 17],
[18, 21, 23, 26]
]

当前元素变为 16,由于 16 大于目标值,我们采用与第一步相同的策略。

[
[3, 6, 9],
[10, 13, 14],
[18, 21, 23]
]

当前元素变为 9,由于 9 小于目标值,我们采用与第二步相同的策略。

[
[10, 13, 14],
[18, 21, 23]
]

当前元素变为 14,由于 14 大于目标值,我们采用与第一步相同的策略。

[
[10, 13],
[18, 21]
]

当前元素变为 13,我们成功查找到了目标值,返回 true。