关于数组的笔试、面试题

以下题目出自leetcode.
我们先来回顾一下最基础的二分查找算法。

class Solution1
{
public:
    int Search(int A[], int n, int target)
    {
        int frist = 0, last = n;
        while (frist != last)
        {
            const int mid = frist + (last - frist) / 2;
            if (A[mid] == target)
                return mid;
            else if (A[mid] > target)
            {
                last = mid;
            }
            else if (A[mid] < target)
            {
                frist = mid + 1;
            }
        }
        return -1;
    }
};

现在有这样有一道题目:在一个旋转的已排序数组中找到某个数。
比如:0 1 2 3 4 5 6 7,旋转后为5 6 7 8 1 2 3 4
同样使用二分查找:

/Search in Rotesd Sorted Array
//4 5 6 1 2 3
class Solution2
{
public:
    int Search(int A[], int n, int target)
    {
        int first = 0, last = n;
        while (first != last)
        {
            const int mid = first + (last - first) / 2;
            if (A[mid] == target)
                return mid;
            if (A[first] <= A[mid])
            {
                if (A[first] <= target && target < A[mid])
                {
                    last = mid;
                }
                else
                    first = mid + 1;
            }
            else
            {
                if (A[mid] < target && target <= A[last] - 1)
                {
                    first = mid + 1;
                }
                else
                    last = mid;
            }
        }
        return -1;
    }
};

那么如果上述题目中允许出现重复元素呢?
比如 旋转后的数组时 1 3 1 1 1
那上述算法中的当A[1] <= A[m]时就不能确定一个递增区间了,那么我们将它分为两个区间:
(1)当A[m] > A[1],则区间[1,m]一定递增
(2)当A[m]=A[1],则不能确定一个递增区间,我们需要对左区间进行++操作,再进一步进行计算。

//允许相同元素
//1 3 1 1 1
class Solution2
{
public:
    bool Search(int A[], int n, int target)
    {
        int first = 0, last = n;
        while (first != last)
        {
            const int mid = first + (last - first) / 2;
            if (A[mid] == target)
                return mid;
            if (A[first] < A[mid])
            {
                if (A[first] <= A[mid] && A[mid] < target)
                {
                    last = mid;
                }
                else
                    first = mid + 1;
            }
            else if (A[first] > A[mid])
            {
                if (A[mid] > target && target <= A[last-1])
                {
                    first = mid + 1;
                }
                else
                    last = mid;
            }
            else
                first++;
        }
    }
};

求两个已排序数组中所有元素的中位数。
这道题可以转换为一道经典的算法题:求两个已排序数组所有元素中的第k大元素。
我们可以从k入手,如果我们每次都能删除一个一定在第k大元素之前的元素,那么我们需要进行k次。但是如果每次我们都删除一半呢,类似于二分查找。
假设A和B的元素个数偶大于k/2,我们将A的第k/2个元素(即A[k/2-1])和B的k/2个元素(即B[k/2-1]),有以下三种情况:
(1)A[k/2-1] == B[k/2-1];
(2)A[k/2-1] > B[k/2-1];
(3)A[k/2-1] < B[k/2-1];
如果 A[k/2-1] < B[k/2-1],意味着A[0]到A[k/2-1]肯定在AUB的topk元素的范围内,换句话说,A[k/2-1]不可能大于AUB的第k大元素。
因此,我们可以放心的删除A数组的这k/2个元素。同理,当A[k/2-1]>B[k/2-1]时,可以删除B数组的k/2个元素。
当A[k/2-1]=B[k/2-1]时,说明找到了第k大的元素,直接返回A[k/2-1]或B[k/2-1]即可。
因此,我们可以写一个地柜函数。那么函数什么时候终止呢?
(1)当A或B是空时,直接返回A[k-1]或B[k-1]
(2)当k=1时,返回min(A[0],B[0])
(3)当A[k/2-1] == B[k/2-1]时,返回A[k/2-1]或B[k/2-1]

//找到两个已排序数组的中位数
class Solution3
{
public:
    double findkSortenArr(int A[], int m, int B[], int n)
    {
        int total = m + n;
        if (total & 0x1)//奇数
        {
            return Find_kPath(A, m, B, n,total/2);
        }
        else //偶数
        {
            return Find_kPath(A, m, B, n, total / 2) + Find_kPath(A, m, B, n, total / 2 + 1)/2.0;
        }
    }
private:
    //寻找两个已排序数组的第k大元素
    static int Find_kPath(int A[], int m, int B[], int n, int k)
    {
        if (m > n)
        {
            return Find_kPath(B, n, A, m, k);
        }
        if (m == 0)
        {
            return B[k - 1];
        }
        if (k == 1)
        {
            return min(A[0], B[0]);
        }

        int ia = min(k / 2, m), ib = k - ia;
        if (A[ia - 1] < B[ib - 1])
        {
            return Find_kPath(A + ia, m - ia, B, n, k - ia);
        }
        else if (A[ia - 1] > B[ib - 1])
        {
            return Find_kPath(A, m, B + ib, n - ib, k - ib);
        }
        else
            return A[ia - 1];
    }
};

在一个未排序的数组中找出最长的连续序列的长度,比如:[100,4,200,1,3,2],其中最长连续序列是[1,2,3,4],长度为4
要求时间复杂度为O(N)

由于序列里的元素是无序的,又要求时间复杂度为O(N),首先应该想到用哈希表。我们可以用一个哈希表unordered_map

class Solution4
{
public:
    int LongestConsecutive(const vector<int> & num)
    {
        unordered_map<int, bool> used;
        for (auto i : num)
            used[i] = false;
        int longest = 0;
        for (auto i : num)
        {
            if (used[i])
                continue;
            int length = 1;
            used[i] = true;
            for (int j = i + 1; used.find(j) != used.end(); ++j)
            {
                used[j] = true;
                ++length;
            }
            for (int j = i - 1; used.find(j) != used.end(); --j)
            {
                used[j] = true;
                ++length;
            }
            longest = max(longest, length);
        }
        return longest;
    }
};

求数组中和为0的三个数。
比如:S = {-1 0 1 2 -1 -4}
则结果是:(-1,0,1)(-1,-1,2)
先排序,再左右夹逼,时间复杂度O(N^2),空间复杂度O(1).注意要跳过重复的数。

//数组中和为0的三个数
class Solution5
{
public:
    vector<vector<int>> threeSum(vector<int>& num)
    {
        vector<vector<int>> result;
        if (num.size() < 3)
            return result;
        sort(num.begin(), num.end());
        const int target = 0;

        auto last = num.end();
        for (auto i = num.begin(); i < last - 2; i++)
        {
            auto j = i + 1;
            //跳过重复的数
            if (i > num.begin() && *i == *(i - 1))
                continue;
            auto k = last - 1;
            while (j < k)
            {
                if (*i + *j + *k < target)
                {
                    ++j;
                    //跳过重复的数
                    while (*j == *(j - 1) && j < k)
                        ++j;
                }
                else if (*i + *j + *k > target)
                {
                    --k;
                    //跳过重复的数
                    while (*k == *(k - 1) && j < k)
                        --k;
                }
                else
                {
                    result.push_back({ *i, *j, *k });
                    ++j;
                    --k;
                    while (*j == *(j - 1) && *k == *(k + 1) && j < k)
                        ++j;
                }
            }
        }
        return result;
    }
};

90度旋转矩阵,如图:
这里写图片描述

class Solution
{
public:
    //顺时针
    void rotate1(vector<vector<int>>& matrix)
    {
        const int n = matrix.size();//求行数
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < n - i; j++)//沿着副角线旋转
            {
                swap(matrix[i][j], matrix[n - 1 - j][n - 1 - i]);
            }
        }
        for (int i = 0; i < n / 2; ++i)//沿着水平线旋转
        {
            for (int j = 0; j < n; ++j)
            {
                swap(matrix[i][j], matrix[n - 1 - i][j]);
            }
        }
    }

    //逆时针
    void rotate2(vector<vector<int>>& matrix)
    {
        const int n = matrix.size();
        for (int i = 0; i < n; i++)
        {
            for (int j = i + 1; j < n; j++)
            {
                swap(matrix[i][j], matrix[j][i]);
            }
        }
        for (int i = 0; i < n / 2; i++)
        {
            for (int j = 0; j < n; j++)
            {
                swap(matrix[i][j], matrix[n - i - 1][j]);
            }
        }
    }
};

给定一个数组表示非负整数,其高位在数组的前面,对这个整数加1。
解决方案:遍历数组每一位并处理进位,如果最后还有进位,则在最前面插入1即可。

class Solution
{
public:
    vector<int> plusOne(vector<int> &digits)
    {
        add(digits, 1);
        return digits;
    }
private:
    void add(vector<int>& digits, int digit)
    {
        int c = digit; //c表示进位
        for (auto it = digits.rbegin(); it != digits.rend(); it++)
        {
            *it += c;
            c = *it / 10;
            *it %= 10;
        }
        if (c > 0)
        {
            digits.insert(digits.begin(), 1);
        }
    }
};

矩阵赋0
如果一个矩阵中的某个元素为0,则将这个元素所在的行和列的所有元素都赋0。
这里写图片描述
我们可以设置两个bool数组,记录每行每列是否存在0。若存在0,则置为true,若不存在,则置为false,再遍历矩阵,将bool数组中为true的行和列的所有元素都置0。

class Solution
{
public:
    void setZeroes(vector <vector<int>>&matrix)
    {
        const size_t m = matrix.size();//行数
        const size_t n = matrix[0].size();//列数
        vector<bool> row(m, false);//标记该行是否存在0
        vector<bool> col(n, false);//标记该列是否存在0
        for (size_t i = 0; i < m; ++i)
        {
            for (size_t j = 0; i < n; ++j)
            {
                if (matrix[i][j] == 0)
                    row[i] = col[j] = true;
            }
        }
        for (size_t i = 0; i < m; i++)
        {
            if (row[i])
                fill(&matrix[i][0], &matrix[i][0] + n,0);
        }
        for (size_t j = 0; j < n; j++)
        {
            if (col[j])
            {
                for (size_t i = 0; i < m; ++i)
                    matrix[i][j] = 0;
            }
        }
    }
};

上述代码的时间复杂度为O(m*n),空间复杂度为O(m+n).
但这道题目的要求是空间复杂度为O(1),所以我们可以重复使用第一行和第一列。
步骤如下:
(1)先扫描第一行第一列,如果有0,则将各自的标志位置为true
(2)然后扫描除去第一行第一列的整个数组,如果有0,则将对应的第一行第一列的数字赋0
(3)再次遍历除去第一行第一列的整个数组,如果对应的第一行或第一列的数字有一个为0,则将当前赋0
(4)最后根据第一行第一列的标志位更新第一行第一列

class Solution
{
public:
    void SetZeroes(vector<vector<int>>& matrix)
    {
        const size_t m = matrix.size();//行数
        const size_t n = matrix[0].size();//列数
        bool row_has_zero = false;//第一行是否存在0
        bool col_has_zero = false;//第一列是否存在0
        for (size_t i = 0; i < n; i++)
        {
            if (matrix[0][i] == 0)
            {
                row_has_zero = true;
                break;
            }
        }
        for (size_t i = 0; i < n; i++)
        {
            if (matrix[i][0] == 0)
            {
                col_has_zero = true;
                break;
            }
        }
        for (size_t i = 1; i < m; i++)
        {
            for (size_t j = 1; j < n; j++)
            {
                //将其对应的第一行第一列置1
                if (matrix[i][j] == 0)
                {
                    matrix[0][j] = 0;
                    matrix[i][0] = 0;
                }
            }
        }
        for (size_t i = 1; i < m; i++)
        {
            for (size_t j = 1; j < n; j++)
            {
                if (matrix[i][0] == 0 || matrix[0][j] == 0)
                {
                    //如果第一行或第一列中此位置为0,则赋0
                    matrix[i][j] = 0;
                }
            }
        }
        if (row_has_zero)
        {
            for (size_t i = 0; i < n; i++)
                matrix[0][i] = 0;
        }
        if (col_has_zero)
        {
            for (size_t i = 0; i < m; i++)
                matrix[i][0] = 0;
        }
    }
};

环形加油站问题
城市的环形路有n个加油站,第i个加油站的油量用gas[i]来表示,你有如下的一辆车:
它的油缸是无限量的,初始是空的
它从第i个加油站到第i+1个加油站消耗油量为cost[i]
现在你可以从任意加油站开始,路过加油站可以不断的加油,问是否能够走完环形路。如果可以返回开始加油站的编号,如果不可以返回-1。

要保证能走完环形路,不仅要保证走完之后的剩余油量大于等于0,还要保证从当前站到下一站的剩余油量也要大于等于0。我们设置两个变量,sum判断当前指针的有效性,total判断整个数组是否有解,有就返回通过sum得到的下标,没有就返回-1。

class Solution
{
public:
    int canCompleteCircuit(vector<int> &gas, vector<int>& cost)
    {
        int total = 0;
        int start = -1;
        for (int i = 0, sum = 0; i < gas.size(); i++)
        {
            sum += gas[i] - cost[i];
            total += gas[i] - cost[i];
            if (sum < 0)
            {
                start = i;
                sum = 0;
            }
        }
        return total >= 0 ? start + 1 : -1;
    }
};

分糖果问题
多个小朋友站成一排,根据他们的得分分发糖果,得分高的小朋友要比旁边得分低的小朋友得到的糖果多,每个小朋友至少得到一枚糖果,问最少要准备多少糖果?
1、与前面的邻居比较,前向遍历权重数组ratings,如果ratings[i]>ratings[i-1],则A[i]=A[i-1]+1;
2、与后面的邻居比较,后向遍历权重数组ratings,如果ratings[i] > ratings[i+1]且A[i] < A[i+1]+1,则更新A,A[i]=A[i+1]+1;
3、对A求和即为最少需要的糖果。
时间复杂度:O(n)
空间复杂度:O(n)

class Solution
{
public:
    int candy(vector<int> &ratings)
    {
        int n = ratings.size();
        int sum;

        vector<int> nums(n, 1);
        for (int i = 1; i < n; i++)
        {
            if (ratings[i] > ratings[i + 1])
                nums[i + 1] = nums[i] + 1;
        }

        sum = nums[n - 1];
        for (int i = n - 2; i >= 0; i++)
        {
            if (ratings[i + 1] > ratings[i] && nums[i + 1] + 1 > nums[i])
                nums[i] = nums[i + 1] + 1;
            sum += nums[i];
        }
        return sum;
    }
};

给定一个整数数组,除了一个整数出现一次之外,其余的每一个整数均出现三次,请找出这个出现一次的整数。
之前我们做过类似的题,比如:一个数组中只有一个数字出现一次,其余数字都出现两次,求这个出现一次的数,或者是,一个数组中有两个数出现一次,其余数都是成对出现,求这两个数。
同样的,这道题还是对位运算的考察。
将每一个整数都看成一个长度位32的数组,然后统计32位中每一位出现1的次数。如果一个数出现3次,则其出现1的位肯定也是3次,这时如果某位出现4次,则意味着出现一次的数在该位也为1。通过分析所有的位,我们即可以找到这个出现一次的数。

class Solution
{
public:
    int SingleNumber(int A[], int n)
    {
        int count[32], result = 0;
        memset(count, 0, sizeof(int)* 32);

        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < 32; j++)
            {
                count[j] += A[i] >> j & 0x1;
            }
        }
        for (int i = 0; i<32; i++)
        {
            result |= (count[i] % 3 << i);
        }
        return result;
    }
};
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值