力扣刷题攻略:数组篇(更新中)

文章目录

      • 一、数组的改变、移动
        • 453. 最小操作次数使数组元素相等
        • 665. 非递减数列
        • 283. 移动零
      • 二、数组的旋转
        • 189. 轮转数组
        • 396. 旋转函数
      • 三、统计数组中的元素
        • 645. 错误的集合
        • 697. 数组的度
        • 448. 找到所有数组中消失的数字
        • 442. 数组中重复的数据
        • 41. 缺失的第一个正数
      • 四、数组的遍历
        • 485. 最大连续 1 的个数
        • 495. 提莫攻击
        • 414. 第三大的数
        • 628. 三个数的最大乘积

一、数组的改变、移动

453. 最小操作次数使数组元素相等

题目链接

解题思路:

  • 由于每次操作将会使 n - 1 个元素增加 1,等价于每次操作使 1 个元素减小 1,最终让数组中的所有元素相等;
  • 将每个元素都减小为最小值,即可使得所有元素相等;
  • 先分别计算每个元素与最小值的差值即为对每个元素的操作次数,最终得到总共的操作次数。

C++代码:

class Solution {
public:
    int minMoves(vector<int>& nums) {
        int minv = nums[0];
        for (auto x: nums) minv = min(minv, x);
        int res = 0;
        for (auto x: nums) res += x - minv;
        return res;
    }
};
665. 非递减数列

题目链接

C++代码:

class Solution {
public:
    bool checkPossibility(vector<int>& nums) {
        if (nums.size() < 3) return true;
        for (int i = 1, j = 0; i < nums.size(); i++, j++) {
            if (nums[i] < nums[j]) {
                if (i + 1 < nums.size() && nums[i + 1] <= nums[j]) nums[j] = nums[i];
                //else if (i + 1 < nums.size() && nums[i + 1] > nums[j]) nums[i] = nums[j];
                else nums[i] = nums[j];
                break;
            }
        }
        for (int i = 1, j = 0; i < nums.size(); i++, j++)
            if (nums[i] < nums[j])
                return false;
        return true;
    }
};
283. 移动零

题目链接

解题思路:
利用双指针,将非0元素全部移动到数组前端,然后将慢指针指向的元素到数组末尾全部赋为0

C++代码:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int j = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i]) nums[j++] = nums[i];
        }
        for (int i = j; i < nums.size(); i++) nums[i] = 0;
    }
};

二、数组的旋转

189. 轮转数组

题目链接

解题思路:
通过观察轮转后的数组特点,一般可通过reverse()函数实现轮转。

C++代码:

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        k %= nums.size();
        reverse(nums.begin(), nums.end());
        reverse(nums.begin(), nums.begin() + k);
        reverse(nums.begin() + k, nums.end());
    }
};
396. 旋转函数

题目链接

解题思路:
由题意可知:
F(0) = 0 * a[0] + 1 * a[1] + 2 * a[2] + ... + (n - 2) * a[n - 2] + (n - 1) * a[n - 1]
F(1) = 1 * a[0] + 2 * a[1] + 3 * a[2] + ... + (n - 1) * a[n - 2] + 0 * a[n - 1]
F(2) = 2 * a[0] + 3 * a[1] + 4 * a[2] + ... + 0 * a[n - 2] + 1 * a[n - 1]
F(3) = 3 * a[0] + 4 * a[1] + 5 * a[2] + ... + 1 * a[n - 2] + 2 * a[n - 1]
...
由以上式子可得:
F(1) - F(0) = a[0] + a[1] + a[2] + ... + a[n - 2] + a[n - 1] - n * a[n - 1]
F(2) - F(1) = a[0] + a[1] + a[2] + ... + a[n - 2] + a[n - 1] - n * a[n - 2]
F(3) - F(2) = a[0] + a[1] + a[2] + ... + a[n - 2] + a[n - 1] - n * a[n - 3]
...
所以,
sum = a[0] + a[1] + a[2] + ... + a[n - 2] + a[n - 1],有:
F(1) = F(0) + sum - n * a[n - 1]
F(2) = F(1) + sum - n * a[n - 2]
F(3) = F(2) + sum - n * a[n - 3]
...
最终可得到F(0), F(1), ... , F(n - 1)中的最大值。

C++代码:

class Solution {
public:
    int maxRotateFunction(vector<int>& A) {
        typedef long long LL;
        LL sum = 0, cur = 0; //cur用于保存当前的F(x);
        for (auto c: A) sum += c;
        int n = A.size();
        for (int i = 0; i < n; i ++ ) cur += i * A[i];
        LL res = cur;
        for (int i = n - 1; i >= 0; i -- ) {
            cur += sum - (LL)n * A[i];
            res = max(res, cur);
        }
        return res;
    }
};

三、统计数组中的元素

645. 错误的集合

题目链接

解题思路:
可通过哈希函数统计每个数字出现的次数,当遍历到某个元素出现的次数大于1时,此时说明该元素出现了重复,将该元素加入结果数组中,统计完所有数字出现的次数后,对1 ~ n的每一个数进行遍历,当遍历到某个元素出现的次数为0时,表明该元素丢失,将该元素加入结果数组。

C++代码:

class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        unordered_map<int, int> hash;
        vector<int> res;
        for (int i = 0; i < nums.size(); i++) {
            hash[nums[i]]++;
            if (hash[nums[i]] > 1) 
                res.push_back(nums[i]);
        }
        for (int i = 1; i <= nums.size(); i++) {
            if (!hash.count(i))
                res.push_back(i);
        }
        return res;
    }
};
697. 数组的度

题目链接

解题思路:
这道题的题意是找到数组中出现次数最多的数的最短连续子数组的长度。主要通过两次循环遍历完成以上操作,

  • 第一遍循环:统计每个元素出现的次数,将元素出现次数的最大值记录下来;并且记录每个元素出现的下标最小值,以及不断更新每个元素出现的下标最大值。
  • 第二遍循环:遍历整个数组,如果当前元素出现的次数等于元素出现次数的最大值,则说明当前元素的首末之间的子数组可能是我们要找的最短连续子数组,我们更新结果。我们通过寻找出现次数最多的元素来不断更新结果,最终得到的就是数组中出现次数最多的数的最短连续子数组的长度。

C++代码:

class Solution {
public:
    int findShortestSubArray(vector<int>& nums) {
        unordered_map<int, int> cnt, minp, maxp;
        int d = 0;
        for (int i = 0; i < nums.size(); i++) {
            int x = nums[i];
            d = max(d, ++cnt[x]);
            if (!minp.count(x)) minp[x] = i;
            maxp[x] = i;
        }
        int res = INT_MAX;
        for (auto x: nums) {
            if (cnt[x] == d)
                res = min(res, maxp[x] - minp[x] + 1);
        }
        return res;
    }
};
448. 找到所有数组中消失的数字

题目链接

解题思路:
题目给出了一个数组,元素的取值在[1, n],要求从数组中找出[1, n]的元素取值中所有没有出现的数字。
这道题可使用负号标记当前元素是否出现过,这里会用到元素值和下标的映射关系,具体做法如下:

  • 遍历数组中的每个元素,得到当前元素作为下标索引的元素值,将该元素值标记为负数,表示当前元素的下标出现过;
  • 再一次遍历数组,若当前元素的值大于0,表明当前元素所对应的下标没有出现过,即找到一个没有出现的数字。依次遍历每个元素,即可得到结果数组。

C++代码:

class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        for (auto x: nums) {
            x = abs(x);
            if (nums[x - 1] > 0) nums[x - 1] *= -1;
        }
        vector<int> res;
        for (int i = 0; i < nums.size(); i++)
            if (nums[i] > 0)
                res.push_back(i + 1);
        return res;
    }
};
442. 数组中重复的数据

题目链接

C++代码 1:

class Solution {
public:
    vector<int> findDuplicates(vector<int>& nums) {
        vector<int> res;
        for (auto x: nums) {
            x = abs(x);
            if (nums[x - 1] > 0) nums[x - 1] *= -1;
            else res.push_back(x);
        }
        return res;
    }
};

C++代码 2:

class Solution {
public:
    vector<int> findDuplicates(vector<int>& nums) {
        vector<int> res;
        vector<int> cnt(nums.size(), 0);
        for (int i = 0; i < nums.size(); i++) {
            cnt[nums[i] - 1]++;
            if (cnt[nums[i] - 1] == 2) 
                res.push_back(nums[i]);
        }
        return res;
    }
};
41. 缺失的第一个正数

题目链接

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

示例 1:

输入:nums = [1,2,0]
输出:3

示例 2:

输入:nums = [3,4,-1,1]
输出:2

示例 3:

输入:nums = [7,8,9,11,12]
输出:1

提示:

  • 1 <= nums.length <= 5 * 105
  • -231 <= nums[i] <= 231 - 1

解题思路:
题目给出了一个未排序的数组,要求我们找出其中没有出现的最小的正整数。简单来说,由于数组中总共有n个元素,若1~n都存在数组中,那么没有出现的最小正整数则为n + 1;反之,没有出现的最小正整数位于1~n

(桶排序) 时间复杂度O(n), 空间复杂度O(1)

  • 不用额外空间的桶排序:保证1出现在nums[0]的位置上,2出现在nums[1]的位置上,…,n出现在nums[n-1]的位置上,其他的数字不管。例如[3,4,-1,1]将被排序为[1,-1,3,4]
  • 遍历nums,找到第一个不在对应位置上的1n的数。例如,排序后的[1,-1,3,4]中第一个 nums[i] != i + 1 的是数字2(注意此时i=1)。

时间复杂度分析:swap元素到相应位置过程中,每个元素最多被换O(1)次,时间复杂度O(n);遍历数组时间复杂度为O(n);所以总的时间复杂度还是O(n)

C++代码:

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        int n = nums.size();

        for (int i = 0; i < n; i++) 
            while (nums[i] <= n && nums[i] > 0 && nums[nums[i] - 1] != nums[i]) 
                swap(nums[i], nums[nums[i] - 1]) ;

        for (int i = 0; i < n; i++) 
            if (nums[i] != i + 1)
                return i + 1;

        return n + 1;
    }
};

四、数组的遍历

485. 最大连续 1 的个数

题目链接

给定一个二进制数组 nums , 计算其中最大连续 1 的个数。

示例 1:

输入:nums = [1,1,0,1,1,1]
输出:3
解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3.

示例 2:

输入:nums = [1,0,1,1,0,1]
输出:2

提示:

  • 1 <= nums.length <= 105
  • nums[i] 不是 0 就是 1.

解题思路:
双指针记录连续1的起始位置和终止位置,不断更新连续1的最大长度。

C++代码:

class Solution {
public:
    int findMaxConsecutiveOnes(vector<int>& nums) {
        int res = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == 0) continue;
            int j = i + 1;
            while (j < nums.size() && nums[i] == nums[j]) j++;
            res = max(res, j - i);
            i = j;
        }
        return res;
    }
};
495. 提莫攻击

题目链接

解题思路:
观察相邻两次攻击的时间间隔

  • 若相邻两次攻击的时间差大于等于中毒的持续时间,说明在这两次攻击的时间间隔中,中毒时间为duration秒;
  • 若相邻两次攻击的时间差小于中毒的持续时间,说明在这两次攻击的时间间隔中,中毒时间为这两次攻击的时间差。

因此可遍历timeSeries数组,将每两次攻击的时间差与中毒持续时间进行比较,将这两者中的较小时间加到结果中。

C++代码:

class Solution {
public:
    int findPoisonedDuration(vector<int>& timeSeries, int duration) {
        int res = 0;
        for (int i = 1; i < timeSeries.size(); i++) 
            res += min(timeSeries[i] - timeSeries[i - 1], duration);
        if (timeSeries.size()) res += duration;
        return res;
    }
};
414. 第三大的数

题目链接

解题思路:
对数组先排序在去重,最终得到一个从小到大排序,元素不重复的数组。
若元素个数小于3,表明第三大数不存在,则返回数组最后一个元素;否则,返回倒数第三个元素。

C++代码:

class Solution {
public:
    int thirdMax(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        nums.erase(unique(nums.begin(), nums.end()), nums.end());
        if (nums.size() < 3) return nums.back();
        return *(nums.end() - 3);
    }
};
628. 三个数的最大乘积

题目链接

解题思路:
首先对数组进行排序,有两种情况可能会出现三个数的最大乘积:

  • 数组前两个元素相乘,再将其结果与最后一个元素相乘;
  • 数组的最后三个元素相乘;

当数组中存在两个或两个以上的负数时,取前两个元素相乘,即是让最小的两个负数相乘,然后相乘的结果再与最大的元素相乘,所产生的结果可能为最大乘积。

C++代码:

class Solution {
public:
    int maximumProduct(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        return max(nums[n - 1] * nums[n - 2] * nums[n - 3], nums[0] * nums[1] * nums[n - 1]);
    }
};

你可能感兴趣的:(力扣刷题攻略,leetcode,算法,数据结构)