主页
文章
分类
标签
关于
每日一题(数组篇)
发布于: 2024-9-23   更新于: 2024-10-20   收录于: 编程 , 算法
文章字数: 18658   阅读时间: 38 分钟  

简单题

414. 第三大的数

力扣原题链接 (简单题)

给你一个非空数组,返回此数组中  第三大的数 。如果不存在,则返回数组中最大的数。

示例 1:

  • 输入: [3, 2, 1]
  • 输出: 1
  • 解释: 第三大的数是 1 。

示例 2:

  • 输入: [1, 2]
  • 输出: 2
  • 解释: 第三大的数不存在, 所以返回最大的数 2 。

示例 3:

  • 输入: [2, 2, 3, 1]
  • 输出: 1
  • 解释: 注意,要求返回第三大的数,是指在所有不同数字中排第三大的数。此例中存在两个值为 2 的数,它们都排第二。在所有不同数字中排第三大的数为 1 。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int thirdMax(vector<int> &nums)
    {
        // 初始化最大值、中间值和最小值为最小的长整型值
        long long maxNum = LONG_LONG_MIN, midNum = LONG_LONG_MIN, minNum = LONG_LONG_MIN;
        // 遍历数组中的每一个数字
        for (auto n : nums)
        {
            // 如果当前数字大于最大值,则更新最大值、中间值和最小值
            if (n > maxNum)
            {
                minNum = midNum; // 更新最小值为之前的中间值
                midNum = maxNum; // 更新中间值为之前的最大值
                maxNum = n;      // 更新最大值为当前数字
            }
            // 如果当前数字在最大值和中间值之间,则更新中间值和最小值
            else if (n > midNum && n < maxNum)
            {
                minNum = midNum; // 更新最小值为之前的中间值
                midNum = n;      // 更新中间值为当前数字
            }
            // 如果当前数字在中间值和最小值之间,则更新最小值
            else if (n > minNum && n < midNum)
            {
                minNum = n; // 更新最小值为当前数字
            }
        }
        // 如果最小值仍未更新,返回最大值
        if (minNum == LONG_LONG_MIN)
            return maxNum; // 返回最大值
        else
            return minNum; // 返回第三大的数字
    }

主要逻辑

  1. 初始化:

    • 使用长整型的最小值 LONG_LONG_MIN 初始化三个变量:maxNummidNumminNum,分别代表当前的最大值、中间值和最小值。
  2. 遍历数组:

    • 使用范围基 for 循环遍历 nums 数组中的每一个数字 n
    • 更新条件:
      • 如果当前数字 n 大于 maxNum,则更新最大值、以及中间值和最小值:
        • 中间值 midNum 变为之前的最大值 maxNum
        • 最小值 minNum 变为之前的中间值 midNum
        • 最大值 maxNum 更新为当前数字 n
      • 如果当前数字 n 在最大值和中间值之间,更新中间值和最小值:
        • 最小值 minNum 变为之前的中间值 midNum
        • 中间值 midNum 更新为当前数字 n
      • 如果当前数字 n 在中间值和最小值之间,更新最小值:
        • 最小值 minNum 更新为当前数字 n
  3. 返回结果:

    • 遍历结束后,检查 minNum 是否仍为 LONG_LONG_MIN
      • 如果是,则表示数组中不存在第三大的数,返回 maxNum
      • 否则返回 minNum,即第三大的数字。

605. 种花问题

力扣原题链接 (简单题)

假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false 。

示例 1:

  • 输入: flowerbed = [1,0,0,0,1], n = 1
  • 输出: true

示例 2:

  • 输入: flowerbed = [1,0,0,0,1], n = 2
  • 输出: false

参考代码

第一版

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    bool canPlaceFlowers(vector<int> &flowerbed, int n)
    {
        // 第一版实现
        int rest = 0; // 记录可以种植的花的数量
        // 遍历花坛数组
        for (int i = 0; i < flowerbed.size(); ++i)
        {
            // 如果当前位置已经种植了花,跳过
            if (flowerbed[i] == 1)
            {
                ++i;      // 跳过下一个位置,因为相邻位置不能种花
                continue; // 继续下一个循环
            }
            // 如果当前位置没有种植花
            if (flowerbed[i] == 0)
            {
                // 检查是否是最后一个位置
                if (i == flowerbed.size() - 1)
                {
                    ++rest; // 可以种植一朵花
                    break;  // 结束循环
                }
                // 检查下一个位置是否也为空
                if (flowerbed[i + 1] == 0)
                {
                    flowerbed[i] = 1; // 在当前位置种一朵花
                    ++rest;           // 记录已种植的花的数量
                    ++i;              // 跳过下一个位置
                }
                else
                {
                    continue; // 继续下一个循环
                }
            }
        }
        // 返回是否可以种入n朵花
        return rest >= n ? true : false;
    }
实现思路
  1. 变量定义

    • rest: 用于记录可以种植的花的数量。
  2. 遍历花坛数组

    • 使用一个for循环遍历flowerbed数组。
    • 如果当前位置已经种植了花(flowerbed[i] == 1),则跳过下一个位置(++i),继续下一个循环。
    • 如果当前位置没有种植花(flowerbed[i] == 0),则检测相邻位置:
      • 最后一个位置处理:若是最后一个位置,直接增加rest并退出循环。
      • 相邻位置检查:若下一个位置也是空的(flowerbed[i + 1] == 0),可以在当前位置种植一朵花并记录已种植数量,通过将flowerbed[i]设置为1来进行标记。
    • 如果不满足相邻位置的条件,则继续遍历。
  3. 返回结果

    • 最终,通过比较restn来判断是否能成功种植所需数量的花,如果rest >= n,返回true,否则返回false

第一版修改

第一版代码简化,但是效率低了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    bool canPlaceFlowers(vector<int> &flowerbed, int n)
            int rest = 0;                // 记录可以种植的花的数量
            int size = flowerbed.size(); // 获取花坛的大小
            // 遍历花坛数组
            for (int i = 0; i < size; ++i)
            {
                // 判断当前位置以及相邻的两个位置是否都为空
                if (flowerbed[i] == 0 && (i == 0 || flowerbed[i - 1] == 0) && 
                (i == size - 1 || flowerbed[i + 1] == 0))
                {
                    flowerbed[i] = 1; // 在当前位置种一朵花
                    ++rest;           // 记录已种植的花的数量
                    // 跳过下一个位置,因为相邻位置不能种花
                    ++i;
                }
            }
            // 返回是否可以种入n朵花
            return rest >= n;
    }

第二版

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
      bool canPlaceFlowers(vector<int> &flowerbed, int n)
        int rest = 0; // 记录可以种植的花的数量
        // 在花坛的开头和结尾插入0,以简化判断
        flowerbed.insert(flowerbed.begin(), 0);
        flowerbed.push_back(0);
        // 遍历花坛,从第一块到倒数第二块
        for (int i = 1; i < flowerbed.size() - 1; ++i)
        {
            // 判断当前块以及左右相邻块是否都为空
            if (flowerbed[i] == 0 && flowerbed[i - 1] == 0 &&
            flowerbed[i + 1] == 0)
            {
                flowerbed[i] = 1; // 在当前块种一朵花
                ++rest; // 记录已种植的花的数量
            }
        }
        // 判断是否可以种入n朵花
        return rest >= n ? true : false;
    }
实现步骤
  1. 初始化变量

    • rest: 用于记录可以种植的花的数量,初始值为0。
  2. 简化边界判断

    • 在flowerbed数组的开头插入一个0,表示花坛开始位置。
    • 在数组末尾增加一个0,表示花坛结束位置。这样处理简化了后续的边界判断。
  3. 遍历花坛

    • 从第二个位置(i = 1)开始遍历到倒数第二个位置(i < flowerbed.size() - 1):
    • 检查当前地块、左边地块和右边地块是否都为0。
    • 如果满足条件,则在当前地块种一朵花,更新flowerbed[i]为1,并增加rest的值。
  4. 返回结果

    • 在遍历完成后,判断rest是否大于或等于n。
    • 如果rest >= n,返回true,否则返回false

628. 三个数的最大乘积

力扣原题链接 (简单题)

给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

示例 1:

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

示例 2:

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

示例 3:

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

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    int maximumProduct(vector<int> &nums)
    {
        int len = nums.size() - 1;      // 获取数组长度减一
        sort(nums.begin(), nums.end()); // 对数组进行排序
        // 如果数组的最小值非负,或者最大值为负数,返回三个最大的数的乘积
        if (nums[0] >= 0 || nums[len] < 0)
            return nums[len] * nums[len - 1] * nums[len - 2];
        int firstNum = 0, secondNum = 0, thirdNum = 0, Max = INT_MIN; // 初始化变量
        int temp = 1;                                                 // 临时乘积变量
        int n = 0;          // 统计负数的数量
        while (nums[n] < 0) // 寻找数组中负数的个数
        {
            ++n;
        }
        // 分别存储负数部分和正数部分
        vector<int> fushu(nums.begin(), nums.begin() + n);  // 负数部分
        vector<int> zhengshu(nums.begin() + n, nums.end()); // 正数部分
        // 如果负数部分有两个或更多,计算负数与两个最大的正数的乘积
        if (fushu.size() > 1)
        {
            Max = fushu[0] * nums[1] * zhengshu[zhengshu.size() - 1];
        }
        // 遍历已排序的数组,计算三个最大的数的乘积
        for (int i = len - 1; i > 0; --i)
        {
            firstNum = nums[i + 1];                 // 取最大的数
            secondNum = nums[i];                    // 取第二大的数
            thirdNum = nums[i - 1];                 // 取第三大的数
            temp = firstNum * secondNum * thirdNum; // 计算乘积
            // 更新最大乘积
            if (temp > Max)
            {
                Max = temp;
            }
        }
        return Max; // 返回最大乘积
    }

实现步骤

  1. 参数与返回值

    • 输入:一个整型数组 nums,长度大于等于3。
    • 输出:返回三个数的最大乘积。
  2. 数组排序

    • 使用 sort 函数对数组进行排序,以便后续容易访问最大和最小的元素。
  3. 基本条件判断

    • 如果数组的最小值大于等于0(数组全为非负数),或者数组的最大值为负数,直接返回三个最大的数的乘积。
  4. 初始化变量

    • firstNumsecondNumthirdNum:用于存储当前计算的最大的三个数。
    • Max:初始化为 INT_MIN,用于记录最大乘积。
    • temp:暂时存储当前计算的乘积。
    • n:统计负数的数量。
  5. 统计负数

    • 使用 while 循环遍历数组,统计负数的数量,存储到 n 中。
  6. 分割负数与正数

    • 根据统计结果,将负数和正数分别存储到两个不同的向量 fushu(负数部分)和 zhengshu(正数部分)。
  7. 计算负数与正数组合的乘积

    • 如果负数部分包含两个或更多的负数,则计算两个负数与一个最大正数的乘积,并更新 Max
  8. 遍历计算

    • 从数组末尾开始,遍历计算三个最大的数的乘积,并更新 Max
  9. 返回结果

    • 返回记录的最大乘积 Max

643. 子数组最大平均数 I

力扣原题链接 (简单题)

给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。

请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

任何误差小于 10-5 的答案都将被视为正确答案。

示例 1:

  • 输入: nums = [1,12,-5,-6,50,3], k = 4
  • 输出: 12.75
  • 解释: 最大平均数 (12-5-6+50)/4 = 51/4 = 12.75

示例 2:

  • 输入: nums = [5], k = 1
  • 输出: 5.00000

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    double findMaxAverage(vector<int> &nums, int k)
    {
        // 当数组只有一个元素时,直接返回该元素
        if (nums.size() == 1)
            return nums[0];
        // 当 k 为 1 时,找出数组中的最大元素
        if (k == 1)
        {
            int max = nums[0];  // 初始化最大值为数组的第一个元素
            for (auto n : nums) // 遍历数组
            {
                if (max < n) // 如果找到更大的元素
                    max = n; // 更新最大值
            }
            return max; // 返回最大值
        }
        // 计算初始的 k 个元素的累加和
        double result = static_cast<double>(accumulate(&nums[0], &nums[k], 0));
        double temp = 0; // 临时变量用于存储当前子数组的和
        // 遍历数组以寻找最大平均值
        for (int i = 0, j = k + i - 1; j + 1 < nums.size(); ++i, ++j)
        {
            // 如果下一个元素大于当前的第一个元素
            if (nums[j + 1] > nums[i])
            {
                // 计算新的子数组的和
                temp = static_cast<double>(accumulate(&nums[i + 1], &nums[j + 2], 0));
                // 如果新的子数组和大于当前结果
                if (temp > result)
                    result = temp; // 更新结果
            }
        }
    }

主要逻辑

  1. 特殊情况处理:

    • 当数组只有一个元素时,直接返回该元素的值。
    • k 为 1 时,遍历数组找到最大元素并返回。
  2. 初始和计算:

    • 使用 accumulate 函数计算初始的前 k 个元素的累加和,并将其转换为 double 类型以便进行后续操作。
  3. 滑动窗口遍历:

    • 使用循环检查所有可能的连续子数组。通过滑动窗口的思想,当遇到新的元素时,判断是否需要更新当前的最大和。
    • 如果发现的新子数组的和大于之前计算的最大和,则更新最大和。
  4. 返回最大平均数:

    • 最后返回找到的最大和除以 k,以获得最大平均数。

674. 最长连续递增序列

力扣原题链接 (简单题)

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 l 和 rl < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], …, nums[r - 1], nums[r]] 就是连续递增子序列。

示例 1:

  • 输入: nums = [1,3,5,4,7]
  • 输出: 3
  • 解释: 最长连续递增序列是 [1,3,5] , 长度为3。尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。

示例 2:

  • 输入: nums = [2,2,2,2,2]
  • 输出: 1
  • 解释: 最长连续递增序列是 [2] , 长度为1。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    int findLengthOfLCIS(vector<int> &nums)
    {
        int len = 1;    // 记录最长连续递增子序列的长度
        int temp = len; // 用于临时存储当前递增子序列的长度
        // 遍历数组,寻找连续递增的子序列
        for (int i = 0; i < nums.size() - 1; ++i)
        {
            // 如果当前元素小于下一个元素,说明是递增的
            if (nums[i] < nums[i + 1])
            {
                ++temp; // 当前递增子序列长度加1
                // 更新最长子序列的长度
                if (len < temp)
                {
                    len = temp;
                }
            }
            else
            {
                temp = 1; // 如果不是递增,则重置 temp 为 1
            }
        }
        // 如果最长子序列长度为1,则返回当前的 temp 值
        return len == 1 ? temp : len;
    }

代码逻辑

  1. 初始化变量

    • len:用来记录当前找到的最长连续递增子序列的长度,初始值为1。
    • temp:临时变量,用于存储当前递增子序列的长度,初始值也为1。
  2. 遍历数组

    • 使用 for 循环遍历数组,范围是从 0 到 nums.size() - 1,避免数组越界。
  3. 判断递增关系

    • 在循环中,通过比较当前元素和下一个元素的大小来判断是否为递增关系:
      • 如果当前元素小于下一个元素,则递增长度 temp 加1。
      • 如果 temp 大于 len,则更新 lentemp,记录新的最长长度。
      • 如果当前元素不小于下一个元素,说明递增关系断裂,将 temp 重置为1,表示新的递增子序列开始。
  4. 返回结果

    • 循环结束后,检查 len 是否仍为1(说明没有找到更长的连续递增子序列),如果是,则返回 temp 的值;否则,返回 len 的值。

697. 数组的度

力扣原题链接 (简单题)

给定一个非空且只包含非负数的整数数组 nums,数组的  的定义是指数组里任一元素出现频数的最大值。

你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

示例 1:

  • 输入: nums = [1,2,2,3,1]
  • 输出: 2
  • 解释: 输入数组的度是 2 ,因为元素 1 和 2 的出现频数最大,均为 2 。 连续子数组里面拥有相同度的有如下所示: [1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2] 最短连续子数组 [2, 2] 的长度为 2 ,所以返回 2 。

示例 2:

  • 输入: nums = [1,2,2,3,1,4,2]
  • 输出: 6
  • 解释: 数组的度是 3 ,因为元素 2 重复出现 3 次。 所以 [2,2,3,1,4,2] 是最短子数组,因此返回 6 。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    int findShortestSubArray(vector<int> &nums)
    {
        int arr[50000] = {};  // 用于存储每个数字出现的频率
        int du = -1;          // 数组的度(最大频率)
        int len = 0;          // 当前计算的长度
        int minlen = INT_MAX; // 最短长度,初始化为最大整数
        vector<int> dus;      // 存储出现次数等于数组度的数字
        vector<int> lens;     // 存储对应的连续子数组的长度
        // 统计每个数字的出现频率
        for (auto n : nums)
        {
            ++arr[n]; // 对数字进行计数
        }
        // 迭代频率数组,找到数组的度和对应数字
        for (int i = 0; i < 50000; ++i)
        {
            if (arr[i] == du) // 如果当前次数等于最大次数
            {
                dus.push_back(i); // 将数字添加到候选数组中
            }
            if (arr[i] > du) // 如果当前次数超过最大次数
            {
                du = arr[i];      // 更新最大次数
                dus.clear();      // 清空候选数组
                dus.push_back(i); // 添加当前数字
            }
        }
        // 计算每个候选数字出现的连续子数组的长度
        for (auto m : dus)
        {
            int temp = du;         // 记录要找到的次数
            int i = 0, first = -1; // first用于记录第一次出现的位置
            for (; i < nums.size(); ++i)
            {
                if (nums[i] == m) // 如果当前数字是候选数字
                {
                    if (first == -1) // 第一次出现
                        first = i;   // 记录位置
                    --temp;          // 次数减1
                }
                if (temp == 0) // 当找到足够的次数时,跳出循环
                    break;
            }
            len = i - first + 1; // 计算连续子数组的长度
            lens.push_back(len); // 记录长度
        }
        // 找到最短的长度
        for (auto s : lens)
        {
            if (minlen >= s) // 如果当前长度小于最小长度
                minlen = s;  // 更新最小长度
        }
        return minlen; // 返回最短长度
    }

函数逻辑

  1. 定义变量

    • arr[50000] 用于存储数组中每个数字的出现频率。
    • du 记录最大频率(数组的度)。
    • minlen 初始为最大整数,用于存储最短子数组的长度。
  2. 统计频率

    • 遍历 nums 数组,对每个数字进行计数,更新 arr 数组。
  3. 寻找最大频率和候选数字

    • 遍历频率数组 arr
      • 如果当前出现次数等于 du,则将该数字添加到候选数组 dus
      • 如果当前次数超过 du,更新 du 为当前次数,并清空 dus,将当前数字加入。
  4. 计算候选数字的连续子数组长度

    • 遍历候选数组 dus,对每个候选数字进行以下操作:
      • 初始化 first 为 -1,用于记录该数字第一次出现的位置。
      • 遍历 nums,找到该候选数字的出现次数,当找到足够的次数时停止。
      • 计算当前连续子数组的长度并存储到 lens 数组。
  5. 寻找最短子数组长度

    • 遍历存储的各个长度 lens,找到最小的子数组长度 minlen
  6. 返回结果

    • 返回最短长度 minlen

717. 1 比特与 2 比特字符

力扣原题链接 (简单题)

有两种特殊字符:

  • 第一种字符可以用一比特 0 表示
  • 第二种字符可以用两比特(10 或 11)表示

给你一个以 0 结尾的二进制数组 bits ,如果最后一个字符必须是一个一比特字符,则返回 true 。

示例 1:

  • 输入: bits = [1, 0, 0]
  • 输出: true
  • 解释: 唯一的解码方式是将其解析为一个两比特字符和一个一比特字符。 所以最后一个字符是一比特字符。

示例 2:

  • 输入: bits = [1,1,1,0]
  • 输出: false
  • 解释: 唯一的解码方式是将其解析为两比特字符和两比特字符。 所以最后一个字符不是一比特字符。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    bool isOneBitCharacter(vector<int> &bits)
    {
        // 如果只有一个比特,并且是0,说明最后一个字符是一个一比特字符
        if (bits.size() == 1)
            return true;
        bool flag = true;             // 标志变量,初始为true
        bits.insert(bits.begin(), 0); // 在数组开头插入0,以便简化后续处理
        int len = bits.size();        // 获取数组的长度
        // 从倒数第二个元素开始逆向遍历
        for (int i = len - 2; i >= 0; --i)
        {
            if (bits[i] == 0) // 找到一比特字符的起始点
            {
                // 计算从当前位到最后一位的距离,如果是偶数则最后一个字符是一个一比特字符
                flag = (((len - 1) - i) - 1) % 2 == 0 ? true : false;
                break; // 找到后退出循环
            }
        }
        return flag; // 返回结果
    }

代码详细分析

  1. 基本情况处理:

    • 如果 bits 数组的长度为1且其唯一元素为0,直接返回 true,说明最后一个字符是一个一比特字符。
  2. 准备工作:

    • 在数组的开头插入一个0,这样可以简化后续处理,使得在倒序遍历时方便判断。
  3. 逆向遍历:

    • 从倒数第二个元素开始向前遍历,寻找一比特字符的起始点(即值为0的元素)。
    • 一旦找到0,计算当前元素到最后一位的距离,如果这个距离减1后是偶数,说明最后一个字符是一个一比特字符,标志变量 flag 设置为 true
  4. 循环退出:

    • 找到第一个0后退出循环,避免不必要的遍历。
  5. 返回结果:

    • 最后返回 flag 的值,即判断结果。

724. 寻找数组的中心下标

力扣原题链接 (简单题)

给你一个整数数组 nums ,请计算数组的 中心下标 。

数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。 如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。 如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。

示例 1:

  • 输入: nums = [1, 7, 3, 6, 5, 6]
  • 输出: 3
  • 解释: 中心下标是 3 。左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。

示例 2:

  • 输入: nums = [1, 2, 3]
  • 输出: -1
  • 解释: 数组中不存在满足此条件的中心下标。

示例 3:

  • 输入: nums = [2, 1, -1]
  • 输出: 0
  • 解释: 中心下标是 0 。左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0 。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    int pivotIndex(vector<int> &nums)
    {
        auto beg = nums.begin(); // 数组的起始迭代器
        auto end = nums.end();   // 数组的结束迭代器
        int result = -1;         // 初始化结果为 -1
        int leftSum = 0;         // 左侧元素的和
        int rightSum = 0;        // 右侧元素的和
        // 从后向前遍历数组
        for (int i = nums.size() - 1; i >= 0; --i)
        {
            auto it = nums.begin() + i; // 当前元素的迭代器
            // 计算左侧元素的和
            leftSum = (i == 0) ? 0 : accumulate(beg, it, 0);
            // 计算右侧元素的和
            rightSum = (i == nums.size() - 1) ? 0 : accumulate(it + 1, end, 0);
            // 如果左侧和等于右侧和,则更新结果
            if (leftSum == rightSum)
                result = i; // 找到中心下标
        }
        return result; // 返回找到的中心下标
    }

核心逻辑

  1. 初始化:

    • result 初始化为 -1,表示默认没有找到任何中心下标。
    • leftSumrightSum 分别用于计算左侧和右侧的元素和。
  2. 遍历数组:

    • 使用一个反向循环,从数组的最后一个元素开始向前遍历。
    • 对于每一个元素,计算其左侧和右侧的元素和。
  3. 计算和:

    • 如果当前下标为 0,则左侧元素和视为 0;如果当前下标为数组的最后一个索引,则右侧元素和视为 0。
    • 使用 accumulate 函数计算指定范围内元素的和。
  4. 中心下标判定:

    • 判断左侧和是否等于右侧和,如果相等,则更新 result 为当前下标。
  5. 返回结果:

    • 遍历完成后返回 result,即找到的中心下标或者 -1。

747. 至少是其他数字两倍的最大数

力扣原题链接 (简单题)

给你一个整数数组 nums ,其中总是存在 唯一的 一个最大整数 。

请你找出数组中的最大元素并检查它是否 至少是数组中每个其他数字的两倍 。如果是,则返回 最大元素的下标 ,否则返回 -1 。

示例 1:

  • 输入: nums = [3,6,1,0]
  • 输出: 1
  • 解释: 6是最大的整数,对于数组中的其他整数,6至少是数组中其他元素的两倍。6的下标是1,所以返回 1 。

示例 2:

  • 输入: nums = [1,2,3,4]
  • 输出: -1
  • 解释: 4 没有超过 3 的两倍大,所以返回 -1 。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    int dominantIndex(vector<int> &nums)
    {
        vector<int> temp = nums;        // 创建一个副本以保留原始数组
        sort(nums.begin(), nums.end()); // 对数组进行排序
        // 检查最大元素是否至少是第二大元素的两倍
        if (nums[nums.size() - 1] / 2 >= nums[nums.size() - 2])
        {
            int max = nums[nums.size() - 1]; // 获取最大元素
            // 遍历原始数组查找最大元素的下标
            for (int i = 0; i < temp.size(); ++i)
            {
                if (max == temp[i]) // 如果找到了最大元素
                    return i;       // 返回其下标
            }
        }
        return -1; // 如果条件不满足,返回 -1
    }

实现步骤

  1. 创建副本:使用 vector<int> temp = nums; 创建原始数组的副本,以便后续查找最大元素的下标时不受排序的影响。

  2. 排序:使用 sort(nums.begin(), nums.end()); 对数组进行排序,以便可以快速找到最大元素及其相邻元素(第二大元素)。

  3. 条件判断

    • 检查最大元素是否至少是第二大元素的两倍,使用条件 if (nums[nums.size() - 1] / 2 >= nums[nums.size() - 2]) 实现。
    • 如果条件满足,继续下一步;否则返回 -1。
  4. 查找下标

    • 使用循环遍历副本数组 temp,找到最大元素并返回其下标。
    • 利用条件 if (max == temp[i]) 判断当前元素是否为最大元素。
  5. 返回结果

    • 如果找到了符合条件的最大元素,返回它的下标;如果没有,返回 -1。

830 最大分组的位置

力扣原题链接 (简单题)

在一个由小写字母构成的字符串 s 中,包含由一些连续的相同字符所构成的分组。

例如,在字符串 s = "abbxxxxzyy" 中,就含有 "a""bb""xxxx""z" 和 "yy" 这样的一些分组。

分组可以用区间 [start, end] 表示,其中 start 和 end 分别表示该分组的起始和终止位置的下标。上例中的 "xxxx" 分组用区间表示为 [3,6] 。

我们称所有包含大于或等于三个连续字符的分组为 较大分组 。

找到每一个 较大分组 的区间,按起始位置下标递增顺序排序后,返回结果。

示例 1:

  • 输入: s = “abbxxxxzzy”
  • 输出: [ [3,6] ]
  • 解释: “xxxx” 是一个起始于 3 且终止于 6 的较大分组。

示例 2:

  • 输入: s = “abc”
  • 输出: []
  • 解释: “a”,“b” 和 “c” 均不是符合要求的较大分组。

示例 3:

  • 输入: s = “abcdddeeeeaabbbcd”
  • 输出: [ [3,5],[6,9],[12,14] ]
  • 解释: 较大分组为 “ddd”, “eeee” 和 “bbb”

示例 4:

  • 输入: s = “aba”
  • 输出: []

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    vector<vector<int>> largeGroupPositions(string s)
    {
        // 如果字符串长度小于 3,或者长度为 3 但三个字符不相同,则返回空结果
        if (s.length() < 3 || (s.length() == 3 && s[0] != s[1]))
            return {};
        // 在字符串末尾添加一个不会出现的字符,便于处理最后一个分组
        s.push_back('0');
        vector<int> temp;           // 用于存储当前较大分组的起始和结束位置
        vector<vector<int>> result; // 存储所有较大分组的结果
        // 遍历字符串的每个字符
        for (int i = 0; i < s.length(); ++i)
        {
            // 如果 temp 不为空且当前字符与 temp 开始字符不同,表示一个分组结束
            if (!temp.empty() && s[i] != s[temp[0]])
            {
                temp.push_back(i - 1);  // 添加当前分组的结束位置
                result.push_back(temp); // 将当前分组添加到结果中
                temp.clear();           // 清空 temp,准备下一个分组
            }
            // 如果 temp 不为空且当前字符与 temp 开始字符相同,继续
            if (!temp.empty() && s[i] == s[temp[0]])
            {
                continue;
            }
            // 检查当前字符是否与后面的两个字符相同
            if (s[i] == s[i + 1] && s[i] == s[i + 2])
            {
                temp.push_back(i); // 记录当前分组的起始位置
                i += 2;            // 跳过后两个相同字符
            }
        }
        return result; // 返回所有的较大分组
    }

关键步骤

  1. 参数与返回值:

    • 输入:一个字符串 s
    • 输出:一个二维向量 vector<vector<int>>,其中每个子向量包含一个较大分组的起始和结束位置。
  2. 处理边界条件:

    • 如果字符串长度小于 3,直接返回空的结果。
    • 在字符串末尾附加一个不会与原字符串重复的字符(如 '0'),以便在遍历时能够完整处理最后一个分组。
  3. 变量定义:

    • temp:用于临时存储当前较大分组的起始和结束位置。
    • result:最终存储所有较大分组的结果。
  4. 遍历字符串:

    • 使用 for 循环遍历字符串的每个字符。
    • 如果当前字符与 temp 中的起始字符不相同,表示一个分组结束,更新 tempresult,然后清空 temp
    • 如果当前字符与 temp 中的起始字符相同,则跳过当前迭代。
    • 检查当前字符与其后两个字符是否相同,如果相同,记录起始位置并调整迭代变量 i,以跳过连续字符。
  5. 返回结果:

    • 函数返回所有找到的较大分组的起始和结束位置。

888. 公平的糖果交换

力扣原题链接 (简单题)

爱丽丝和鲍勃拥有不同总数量的糖果。给你两个数组 aliceSizes 和 bobSizes ,aliceSizes[i] 是爱丽丝拥有的第 i 盒糖果中的糖果数量,bobSizes[j] 是鲍勃拥有的第 j 盒糖果中的糖果数量。

两人想要互相交换一盒糖果,这样在交换之后,他们就可以拥有相同总数量的糖果。一个人拥有的糖果总数量是他们每盒糖果数量的总和。

返回一个整数数组 answer,其中 answer[0] 是爱丽丝必须交换的糖果盒中的糖果的数目,answer[1] 是鲍勃必须交换的糖果盒中的糖果的数目。如果存在多个答案,你可以返回其中 任何一个 。题目测试用例保证存在与输入对应的答案。

示例 1:

  • 输入: aliceSizes = [1,1], bobSizes = [2,2]
  • 输出: [1,2]

示例 2:

  • 输入: aliceSizes = [1,2], bobSizes = [2,3]
  • 输出: [1,2]

示例 3:

  • 输入: aliceSizes = [2], bobSizes = [1,3]
  • 输出: [2,3]

示例 4:

  • 输入: aliceSizes = [1,2,5], bobSizes = [2,4]
  • 输出: [5,4]

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    vector<int> fairCandySwap(vector<int> &aliceSizes, vector<int> &bobSizes)
    {
        // 计算爱丽丝和鲍勃的糖果总量
        int sumA = accumulate(aliceSizes.begin(), aliceSizes.end(), 0), // 爱丽丝的糖果总和
            sumB = accumulate(bobSizes.begin(), bobSizes.end(), 0),     // 鲍勃的糖果总和
            // 计算糖果数量的差的一半
            Cab = (sumB - sumA) / 2; // 糖果数量差的一半,用于计算交换数量
        int x = 0;       // 临时变量,用于存储需要交换的糖果数量
        vector<int> res; // 存储答案的数组
        // 遍历爱丽丝的每一盒糖果
        for (const auto &m : aliceSizes)
        {
            x = m - Cab; // 计算需要交换的糖果数量
            // 遍历鲍勃的每一盒糖果
            for (const auto &n : bobSizes)
            {
                // 如果爱丽丝的糖果数量减去 Cab 等于鲍勃的某盒糖果数量,则找到解
                if (x == n)
                {
                    return vector<int>{m, n}; // 返回爱丽丝和鲍勃各自要交换的糖果数量
                }
            }
        }
    }

主要步骤

  1. 计算糖果总量:

    • sumA: 通过 accumulate 函数计算爱丽丝的糖果总和。
    • sumB: 计算鲍勃的糖果总和。
  2. 计算交换所需的糖果数量:

    • Cab: 计算爱丽丝与鲍勃糖果总量的差的一半,公式为 (sumB - sumA) / 2。这是为了找出爱丽丝需要给鲍勃的糖果数量。
  3. 寻找交换的个体:

    • 通过两层循环遍历爱丽丝和鲍勃的糖果盒子。
    • 对于每一盒爱丽丝的糖果,计算 x = m - Cab,表示爱丽丝需要交换的糖果数量。
    • 内层循环遍历鲍勃的糖果盒子,检查爱丽丝要交换的数量是否与鲍勃的某盒糖果相等。
  4. 返回结果:

    • 一旦找到了可以交换的糖果数量,即 x == n,就返回爱丽丝和鲍勃需要交换的糖果数量。

914. 卡牌分组

力扣原题链接 (简单题)

给定一副牌,每张牌上都写着一个整数。

此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组:

  • 每组都有 X 张牌。
  • 组内所有的牌上都写着相同的整数。

仅当你可选的 X >= 2 时返回 true

示例 1:

  • 输入: deck = [1,2,3,4,4,3,2,1]
  • 输出: true
  • 解释: 可行的分组是 [1,1],[2,2],[3,3],[4,4]

示例 2:

  • 输入: deck = [1,1,1,2,2,2,3,3]
  • 输出: false
  • 解释: 没有满足要求的分组。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    bool hasGroupsSizeX(vector<int> &deck)
    {
        array<int, 10001> arr = {}; // 初始化一个数组,用于记录每个数字出现的次数
        for (const auto v : deck)   // 遍历牌组
        {
            ++arr[v]; // 统计每个数字出现的次数
        }
        sort(arr.begin(), arr.end()); // 对出现次数进行排序
        int i = 0;
        while (arr[i] == 0) // 找到第一个非零的出现次数
        {
            ++i;
        }
        int temp = 0, a = 0, b = 0, result = arr[i]; // 初始化变量,用于后续计算
        // 计算所有出现次数的最大公约数
        for (; i < arr.size(); ++i)
        {
            a = arr[i];
            // 利用辗转相除法计算最大公约数
            while (a != 0)
            {
                temp = a;
                a = result % a; // 计算余数
                result = temp;  // 更新 result
            }
        }
        // 判断最大公约数是否大于等于 2
        if (result >= 2)
            return true; // 可以分组
        else
            return false; // 无法分组
    }

实现步骤

  1. 统计出现次数

    • 创建一个大小为10001的数组arr用于记录每个数字在牌组中出现的次数。
    • 遍历deck,对每个数字进行计数。
  2. 排序

    • 对计数后的数组arr进行排序,从小到大。
  3. 找到第一个非零出现次数

    • 通过循环,跳过数组中值为0的元素,找到第一个非零的出现次数。
  4. 计算最大公约数

    • 从第一个非零出现次数开始,利用辗转相除法计算所有出现次数的最大公约数。
    • 初始最大公约数为第一个非零出现次数,从第二个出现次数开始与当前最大公约数进行计算,直到遍历完整个数组。
  5. 检查最大公约数

    • 最后判断计算出的最大公约数result是否大于等于2,返回相应的布尔值。

941. 有效的山脉数组

力扣原题链接 (简单题)

给定一个整数数组 arr,如果它是有效的山脉数组就返回 true,否则返回 false

让我们回顾一下,如果 arr 满足下述条件,那么它是一个山脉数组:

  • arr.length >= 3
  • 在 0 < i < arr.length - 1 条件下,存在 i 使得:
    • arr[0] < arr[1] < … arr[i-1] < arr[i]
    • arr[i] > arr[i+1] > … > arr[arr.length - 1]

示例 1:

  • 输入: arr = [2,1]
  • 输出: false

示例 2:

  • 输入: arr = [3,5,5]
  • 输出: false

示例 3:

  • 输入: arr = [0,3,2,1]
  • 输出: true

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    bool validMountainArray(vector<int> &arr)
    {
        // 如果数组的大小小于 3,无法构成山脉
        if (arr.size() < 3)
            return false;
        int i = 0, j = arr.size() - 1; // 初始化两个指针,i 从前往后,j 从后往前
        // 从前往后查找递增的部分
        while (arr[i + 1] > arr[i])
        {
            ++i; // 增加指针 i
            // 如果 i 到达数组的最后一个元素,跳出循环
            if (i + 1 == arr.size())
                break;
        }
        // 从后往前查找递减的部分
        while (arr[j - 1] > arr[j])
        {
            --j; // 减少指针 j
            // 如果 j 到达数组的第一个元素,跳出循环
            if (j - 1 == -1)
                break;
        }
        // 检查两个指针是否相遇,如果相遇且不是在两端,则说明是有效的山脉数组
        return (i == j ? (i == arr.size() - 1 ? false : (j == 0 ? false : true)) : false);
    }

实现思路

  1. 边界检查

    • 首先检查数组的大小是否小于 3。如果小于,则直接返回 false,因为不能形成山脉。
  2. 指针初始化

    • 使用两个指针 ij
      • i 从数组的开头开始前进。
      • j 从数组的末尾开始后退。
  3. 查找递增部分

    • 通过 while 循环,从前往后查找,直到数组元素不再递增。循环条件为 arr[i + 1] > arr[i],表示当前元素小于下一个元素。
  4. 查找递减部分

    • 通过另一个 while 循环,从后往前查找,直到数组元素不再递减。循环条件为 arr[j - 1] > arr[j]
  5. 指针相遇检查

    • 最后,检查两个指针 ij 是否相遇,并且指针的位置是否在有效范围内(即 i 不能是最后一个元素,j 不能是第一个元素)。

989. 数组形式的整数加法

力扣原题链接 (简单题)

整数的 数组形式  num 是按照从左到右的顺序表示其数字的数组。

  • 例如,对于 num = 1321 ,数组形式是 [1,3,2,1] 。

给定 num ,整数的 数组形式 ,和整数 k ,返回 整数 num + k 的 数组形式 。

示例 1:

  • 输入: num = [1,2,0,0], k = 34
  • 输出: [1,2,3,4]
  • 解释: 1200 + 34 = 1234

示例 2:

  • 输入: num = [2,7,4], k = 181
  • 输出: [4,5,5]
  • 解释: 274 + 181 = 455

示例 3:

  • 输入: num = [2,1,5], k = 806
  • 输出: [1,0,2,1]
  • 解释: 215 + 806 = 1021

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    vector<int> addToArrayForm(vector<int> &num, int k)
    {
        vector<int> res;        // 存储结果的数组
        int digit = 0, sum = 0; // digit 用于存储当前的数字,sum 用于存储当前的和
        // 从 num 的末尾开始遍历
        for (int i = num.size() - 1; i >= 0; --i)
        {
            digit = k % 10;       // 获取 k 的最后一位数字
            sum = digit + num[i]; // 计算当前位的和
            k /= 10;              // 将 k 向右移动一位
            // 检查和是否大于 9
            if (sum > 9)
            {
                ++k;            // 如果和大于 9,进位
                sum = sum % 10; // 只保留当前位的数字
            }
            res.push_back(sum); // 将当前位的和添加到结果数组中
        }
        // 如果 k 还有剩余的数字,则继续处理
        while (k > 0)
        {
            res.push_back(k % 10); // 将 k 的最后一位数字添加到结果中
            k /= 10;               // 将 k 向右移动一位
        }
        reverse(res.begin(), res.end()); // 将结果数组反转,使其符合从左到右的顺序
        return res; // 返回最终的结果
    }

3. 关键步骤解析

  • 初始化:

    • 创建一个空的结果数组 res 用于存储结果。
    • 使用 digit 变量存储当前需要处理的数字,sum 用于存储当前两位相加的结果。
  • 从后向前遍历:

    • 使用一个 for 循环从 num 的末尾开始遍历每一位数字。
    • 在每一次迭代中,获取 k 的最后一位 (digit = k % 10),并与当前位 num[i] 相加得到 sum
    • 更新 k 以去除已经计算的最后一位 (k /= 10)。
  • 处理进位:

    • 如果 sum 大于 9,表示需要进位。进位的处理方式是将 k 加 1,并更新 sum 保留当前位的数字(sum = sum % 10)。
  • 添加结果:

    • 将计算得到的 sum 添加到结果数组 res 中。
  • 处理剩余的 k:

    • 如果在遍历结束后,k 仍然大于 0,则继续处理 k 的剩余位,将每一位依次添加到结果数组中。
  • 反转结果数组:

    • 最后将结果数组反转,以确保返回的数字顺序是正确的。

1128. 等价多米诺骨牌对的数量

力扣原题链接 (简单题)

给你一组多米诺骨牌 dominoes 。

形式上,dominoes[i] = [a, b] 与 dominoes[j] = [c, d] 等价 当且仅当 (a == c 且 b == d) 或者 (a == d 且 b == c) 。即一张骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌。

在 0 <= i < j < dominoes.length 的前提下,找出满足 dominoes[i] 和 dominoes[j] 等价的骨牌对 (i, j) 的数量。

示例 1:

  • 输入: dominoes = [ [1,2],[2,1],[3,4],[5,6] ]
  • 输出: 1

示例 2:

  • 输入: dominoes = [ [1,2],[1,2],[1,1],[1,2],[2,2] ]
  • 输出: 3

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    int numEquivDominoPairs(vector<vector<int>> &dominoes)
    {
        map<vector<int>, int> count; // 用于存储每种骨牌的出现次数
        int sum = 0;                 // 计数相等骨牌对的数量
        for (auto &v : dominoes) // 遍历每一张骨牌
        {
            if (v[0] > v[1])      // 确保骨牌的顺序为小到大
                swap(v[0], v[1]); // 交换骨牌的两个数字
            ++count[v];           // 增加该骨牌的出现次数
        }
        for (const auto &n : count) // 遍历所有骨牌的计数
        {
            sum += n.second * (n.second - 1) / 2; // 根据组合公式计算等价骨牌对的数量
        }
        return sum; // 返回结果
    }

主要思路

  1. 规范化骨牌顺序

    • 对于每一张骨牌 (a, b),我们通过 swap 函数确保骨牌的数字以小到大的顺序存储。这样可以将 [a, b][b, a] 归为同一骨牌。
  2. 计数骨牌出现次数

    • 使用一个 map 来记录每种规范化后的骨牌的出现次数。
  3. 计算等价对的数量

    • 对于每种骨牌,通过组合公式 n * (n - 1) / 2 计算出从 n 张相同骨牌中选出的成对组合。这一公式的意义在于从 n 项中选择两个的组合。


中等题

11. 盛最多水的容器

力扣原题链接 (中等题)

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明: 你不能倾斜容器。

示例 1:

  • 输入: [1,8,6,2,5,4,8,3,7]
  • 输出: 49

示例 2:

  • 输入: height = [1,1]
  • 输出: 1

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    int maxArea(vector<int> &height)
    {
        int left = 0, right = height.size() - 1, h = 0, // 初始化左右指针和高度变量
            capacity = 0, maxCapacity = 0;              // 容量和最大容量初始化
        while (left != right) // 当左右指针相遇时停止
        {
            h = min(height[left], height[right]); // 取较小的高度作为限制
            capacity = (right - left) * h;        // 计算当前容器的容量
            if (capacity > maxCapacity) // 更新最大容量
                maxCapacity = capacity;
            // 移动指针,选择高度较小的一边进行移动
            if (height[left] < height[right])
                ++left; // 向右移动左指针
            else
                --right; // 向左移动右指针
        }
        return maxCapacity; // 返回最大容量
    }

解题思路

  1. 双指针法

    • 使用两个指针分别指向数组的两端,逐步向中间移动指针。这样可以在 O(n) 的时间复杂度内解决问题。
  2. 容量计算

    • 容器的容量由宽度和高度决定。宽度是两个指针之间的距离,高度是两条线中较短的一条。
    • 容量公式为:capacity = (right - left) * h,其中 h = min(height[left], height[right])
  3. 更新最大容量

    • 每次计算当前容量时,跟新记录的最大容量,确保输出的为最大值。
  4. 指针移动策略

    • 每次移动指针时,选择高度较小的一边进行移动,这是因为高度的限制将直接影响容量,移动较小的一侧能够寻找可能的更高线,从而增加容量。

581. 最短无序连续子数组

力扣原题链接 (中等题)

给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。请你找出符合题意的 最短 子数组,并输出它的长度。

示例 1:

  • 输入: nums = [2,6,4,8,10,9,15]
  • 输出: 5
  • 解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。

示例 2:

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

示例 3:

  • 输入: nums = [1]
  • 输出: 0

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    int findUnsortedSubarray(vector<int> &nums)
    {
        // 如果数组只有一个元素,则不需要排序,直接返回0
        if (nums.size() == 1)
            return 0;
        int maxLeft = nums[0], minRight = nums[nums.size() - 1];
        int left = 0, right = nums.size() - 1;
        // 从左到右遍历数组,找出无序子数组的左边界
        for (int i = 0; i < nums.size(); ++i)
        {
            // 如果当前元素大于等于当前最大值,则更新最大值
            if (nums[i] >= maxLeft)
            {
                maxLeft = nums[i];
            }
            // 否则,更新左边界
            else
            {
                left = i;
            }
        }
        // 从右到左遍历数组,找出无序子数组的右边界
        for (int j = nums.size() - 1; j >= 0; --j)
        {
            // 如果当前元素小于等于当前最小值,则更新最小值
            if (nums[j] <= minRight)
            {
                minRight = nums[j];
            }
            // 否则,更新右边界
            else
            {
                right = j;
            }
        }
        // 返回无序子数组的长度,如果没有无序子数组则返回0
        return left > right ? left - right + 1 : 0;
    }

关键步骤

  1. 边界条件判断

    • 首先检查数组是否只有一个元素,如果是,则无需排序,直接返回 0。
  2. 初始化变量

    • maxLeft:从左侧开始遍历时已找到的最大值,初始化为数组的第一个元素。
    • minRight:从右侧开始遍历时已找到的最小值,初始化为数组的最后一个元素。
    • leftright:记录无序子数组的左右边界,初始值分别为 0 和数组长度减 1。
  3. 查找无序子数组的左边界

    • 从左到右遍历数组:
      • 如果当前元素大于等于 maxLeft,更新 maxLeft
      • 否则,发生了无序,因此更新左边界 left
  4. 查找无序子数组的右边界

    • 从右到左遍历数组:
      • 如果当前元素小于等于 minRight,更新 minRight
      • 否则,发生了无序,因此更新右边界 right
  5. 计算并返回无序子数组的长度

    • 如果 left 大于 right,则说明找到了无序子数组,返回其长度 left - right + 1
    • 否则,表示整个数组已经有序,返回 0。

665. 非递减数列

力扣原题链接 (中等题)

给你一个长度为 n 的整数数组 nums ,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]

示例 1:

  • 输入: nums = [4,2,3]
  • 输出: true
  • 解释: 你可以通过把第一个 4 变成 1 来使得它成为一个非递减数列。

示例 2:

  • 输入: nums = [4,2,1]
  • 输出: false
  • 解释: 你不能在只改变一个元素的情况下将其变为非递减数列。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    bool checkPossibility(vector<int> &nums)
    {
        int count = 0;                            // 计数器,用于统计改变的次数
        nums.insert(nums.begin(), INT_MIN);       // 在数组开头插入一个最小值,方便比较
        nums.push_back(INT_MAX);                  // 在数组末尾插入一个最大值,方便比较
        for (int i = 1; i < nums.size() - 1; ++i) // 遍历数组,排除开头和结尾的插入值
        {
            // 如果当前元素小于前一个元素或大于后一个元素,则需要进行处理
            if (nums[i] < nums[i - 1] || nums[i] > nums[i + 1])
            {
                // 如果当前元素小于前一个元素并且大于后一个元素,则无法通过改变一个元素来满足条件
                if (nums[i] < nums[i - 1] && nums[i] > nums[i + 1])
                {
                    return false; // 返回false
                }
                else if (nums[i] < nums[i - 1]) // 如果当前元素小于前一个元素
                {
                    // 如果前一个元素大于后一个元素,改变前一个元素
                    if (nums[i - 1] > nums[i + 1])
                    {
                        nums[i - 1] = nums[i];
                    }
                    else // 否则,改变当前元素
                    {
                        nums[i] = nums[i - 1];
                    }
                    ++count; // 改变次数加1
                    // 检查前两个元素的顺序
                    if (nums[i - 1] < nums[i - 2])
                        return false; // 如果顺序仍然不对,返回false
                }
                else if (nums[i] > nums[i + 1]) // 如果当前元素大于后一个元素
                {
                    // 如果后一个元素大于等于前一个元素,改变当前元素
                    if (nums[i + 1] >= nums[i - 1])
                    {
                        nums[i] = nums[i + 1];
                    }
                    else // 否则,改变后一个元素
                    {
                        nums[i + 1] = nums[i];
                    }
                    ++count; // 改变次数加1
                }
                // 如果改变次数超过1,则返回false
                if (count > 1)
                    return false;
            }
        }
        return true; // 如果变换可以成功返回true
    }

代码分析

  1. 初始化变量

    • 使用一个计数器count来统计改变的次数,初始值为0。
    • 在数组的开头和结尾分别插入最小值和最大值以方便比较。
  2. 遍历数组

    • 从插入的元素后开始遍历至倒数第二个元素。
    • 对于每一个元素,检查其是否满足非递减的条件:   - 如果当前元素小于前一个元素或大于后一个元素,则需要处理。
  3. 处理不满足条件的情况

    • 检查当前元素与其相邻元素的大小关系,如果当前元素<前一个元素且>后一个元素,返回false(无法通过变化一个元素满足条件)。
    • 如果当前元素小于前一个元素:
      • 检查前一个元素与后一个元素的关系,适当改变当前元素或前一个元素,并增加改变计数。
      • 之后再检查前两个元素的顺序是否仍然满足条件,如果不满足,则返回false
    • 如果当前元素大于后一个元素:   - 同样地检查当前元素与前后元素的关系,适当改变元素并增加改变计数。
  4. 改变次数限制

    • 在每次改变后检查count,如果超过1次则返回false,以满足最多改变一个元素的限制。
  5. 返回结果

    • 遍历结束后,如果没有超过1次的改变,则返回true,表示可以通过最少的改变使数组成为非递减数列。

840. 矩阵的幻方

力扣原题链接 (中等题)

3 x 3 的幻方是一个填充有 从 1 到 9  的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。

给定一个由整数组成的row x col 的 grid,其中有多少个 3 × 3 的 “幻方” 子矩阵?

注意:虽然幻方只能包含 1 到 9 的数字,但 grid 可以包含最多15的数字。

示例 1:

  • 输入: grid = [ [4,3,8,4],[9,5,1,9],[2,7,6,2] ]
  • 输出: 1
  • 解释: 下面的子矩阵是一个 3 x 3 的幻方:

而这一个不是:

总的来说,在本示例所给定的矩阵中只有一个 3 x 3 的幻方子矩阵。

示例 2:

  • 输入: grid = [ [8] ]
  • 输出: 0

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    int numMagicSquaresInside(vector<vector<int>> &grid)
    {
        int row = grid.size(); // 获取行数
        int col = grid[0].size(); // 获取列数
        // 如果行数或列数小于3,则无法形成任何3x3的幻方
        if (row < 3 || col < 3)
            return 0;
        // contrast代表每行、列、对角线和应该为15,count用于统计符合条件的幻方个数
        int contrast = 15, count = 0; 
        // 遍历每个可能的3x3子矩阵
        for (int i = 0; i < row - 2; ++i)
        {
            for (int j = 0; j < col - 2; ++j)
            {
                int flag = true; // 标记变量,用于判断当前子矩阵是否合法
                vector<int> nums(10, 0); // 用于记录出现数字的次数(0-15的数字)
                // 遍历3x3子矩阵中的所有元素
                for (int m = i; m < i + 3; ++m)
                {
                    for (int n = j; n < j + 3; ++n)
                    {
                        // 检查数字是否在1-9之间且是否唯一
                        if (grid[m][n] > 9 || nums[grid[m][n]] == 1)
                            flag = false; // 如果不符合条件,设置flag为false
                        else
                            ++nums[grid[m][n]]; // 计数
                        if (flag == false)
                            break; // 提前退出
                    }
                    if (flag == false)
                        break; // 提前退出
                }
                if (flag == false)
                    continue; // 如果当前子矩阵不合法,则跳过
                // 计算3行、3列与2条对角线的和
                int row1 = grid[i][j] + grid[i][j + 1] + grid[i][j + 2],
                    row2 = grid[i + 1][j] + grid[i + 1][j + 1] + grid[i + 1][j + 2],
                    row3 = grid[i + 2][j] + grid[i + 2][j + 1] + grid[i + 2][j + 2],
                    col1 = grid[i][j] + grid[i + 1][j] + grid[i + 2][j],
                    col2 = grid[i][j + 1] + grid[i + 1][j + 1] + grid[i + 2][j + 1],
                    col3 = grid[i][j + 2] + grid[i + 1][j + 2] + grid[i + 2][j + 2],
                    diag1 = grid[i][j] + grid[i + 1][j + 1] + grid[i + 2][j + 2],
                    diag2 = grid[i + 2][j] + grid[i + 1][j + 1] + grid[i][j + 2];
                // 判断是否所有和都等于15
                if (row1 != contrast || row2 != contrast || row3 != contrast ||
                    col1 != contrast || col2 != contrast || col3 != contrast ||
                    diag1 != contrast || diag2 != contrast)
                    continue; // 如果不等于15,则继续
                else
                    ++count; // 如果满足条件,增加幻方计数
            }
        }
        return count; // 返回符合条件的幻方数量
    }

函数步骤

  1. 获取网格尺寸

    • 行数 row 和列数 col 通过 grid.size()grid[0].size()获得。
  2. 边界检查

    • 如果行数或列数小于3,直接返回0,因为无法形成3x3的幻方。
  3. 初始化变量

    • contrast 设置为15,表示每行、列及对角线和应当为15。
    • count 用于计数符合条件的幻方个数。
  4. 遍历子矩阵

    • 使用双重循环遍历所有可能的3x3子矩阵。
    • 创建 flag 标记,初始化为 true,用于判断当前子矩阵是否合法。
    • 创建 nums 数组,用于记录1到15的数字出现次数。
  5. 检查子矩阵合法性

    • 内嵌循环检查当前3x3子矩阵中的数字:
      • 如果数字不在1到9之间或已出现,则标记为不合法(flag = false)。
    • 如果子矩阵不合法,跳过该子矩阵的后续检查。
  6. 计算和

    • 计算3行、3列和两条对角线的和。
    • 检查这些和是否都等于15。
  7. 计数符合条件的幻方

    • 如果所有和都等于15,则将 count 增加1。
  8. 返回结果

    • 最后,返回满足条件的幻方数量 count

849. 到最近的人的最大距离

力扣原题链接 (中等题)

给你一个数组 seats 表示一排座位,其中 seats[i] = 1 代表有人坐在第 i 个座位上,seats[i] = 0 代表座位 i 上是空的(下标从 0 开始)。

至少有一个空座位,且至少有一人已经坐在座位上。

亚历克斯希望坐在一个能够使他与离他最近的人之间的距离达到最大化的座位上。

返回他到离他最近的人的最大距离。

示例 1:

  • 输入: seats = [1,0,0,0,1,0,1]
  • 输出: 2
  • 解释: 如果亚历克斯坐在第二个空位(seats[2])上,他到离他最近的人的距离为 2 。 如果亚历克斯坐在其它任何一个空位上,他到离他最近的人的距离为 1 。 因此,他到离他最近的人的最大距离是 2 。

示例 2:

  • 输入: seats = [1,0,0,0]
  • 输出: 3
  • 解释: 如果亚历克斯坐在最后一个座位上,他离最近的人有 3 个座位远。 这是可能的最大距离,所以答案是 3 。

示例 3:

  • 输入: seats = [0,1]
  • 输出: 1

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
    int maxDistToClosest(vector<int> &seats)
    {
        int index = 0;           // 存储离亚历克斯最近人的位置
        int count = 0;           // 当前空座位的计数
        int leftmaxDistance = 0; // 左侧最大距离
        int maxDistance = 0;     // 最大距离
        // 在数组末尾添加一个人以处理边界情况
        seats.push_back(1);
        for (int i = 0; i < seats.size(); ++i)
        {
            if (seats[i] == 1) // 如果当前座位有人
            {
                // 更新最大距离和索引
                if (count > maxDistance)
                {
                    maxDistance = count; // 更新最大距离
                    index = i;           // 更新索引
                }
                // 如果当前座位的左侧全都没有人,即从左至右遍历的过程中遇到的第一个有人座的位置,就更新左侧最大距离(处理左边界)
                if (i - count == 0)
                {
                    leftmaxDistance = count;
                }
                // 如果当前最大距离的一半小于左侧最大距离,更新最大距离(处理非边界情况)
                if (maxDistance / 2 < leftmaxDistance)
                {
                    maxDistance = leftmaxDistance;
                    index = i; // 更新索引
                }
                // 处理最后一个人坐的位置(处理右边界)
                if (i == seats.size() - 1)
                {
                    if (maxDistance / 2 < count && count > leftmaxDistance)
                    {
                        maxDistance = count; // 更新最大距离
                        index = i;           // 更新索引
                    }
                }
                count = 0; // 重置计数
            }
            else
                ++count; // 如果座位为空,计数加一
        }
        // 根据索引和最大距离返回结果
        if (index == seats.size() - 1 || index - maxDistance == 0)
            return maxDistance; // 如果亚历克斯在最后位置或边缘返回最大距离
        else
            return maxDistance % 2 == 0 ? maxDistance / 2 : (maxDistance + 1) / 2; // 根据最大距离的奇偶性返回结果
    }

主要思路

  1. 初始化变量

    • index: 存储离亚历克斯最近人的位置,用于后续判断。
    • count: 当前连续空座位的计数。
    • leftmaxDistance: 记录左侧最大距离。
    • maxDistance: 记录当前找到的最大距离。
  2. 处理边界情况

    • 在数组末尾添加一个 1,这可以方便地处理最后一个人坐的位置的计算,避免边界条件的问题。
  3. 遍历数组

    • 通过循环遍历每个座位,当遇到有人坐的座位 (seats[i] == 1) 时:
      • 更新最大距离(maxDistance)和已坐位置的索引 index
      • 如果当前座位的左侧没有人,更新左侧的最大距离。
      • 如果当前最大距离与左侧最大距离的关系满足条件,进行更新。
      • 处理最后一个人坐的位置的特殊情况。
    • 若当前座位为空 (seats[i] == 0),则更新空座位计数。
  4. 返回结果

    • 根据 indexmaxDistance 的值来判断如何返回最终结果,考虑了最大距离的奇偶性。

1497. 检查数组对是否可以被 k 整除

力扣原题链接 (中等题)

给你一个整数数组 arr 和一个整数 k ,其中数组长度是偶数,值为 n 。

现在需要把数组恰好分成 n / 2 对,以使每对数字的和都能够被 k 整除。

如果存在这样的分法,请返回 true ;否则,返回 false

示例 1:

  • 输入: arr = [1,2,3,4,5,10,6,7,8,9], k = 5
  • 输出: true
  • 解释: 划分后的数字对为 (1,9),(2,8),(3,7),(4,6) 以及 (5,10) 。

示例 2:

  • 输入: arr = [1,2,3,4,5,6], k = 7
  • 输出: true
  • 解释: 划分后的数字对为 (1,6),(2,5) 以及 (3,4) 。

示例 3:

  • 输入: arr = [1,2,3,4,5,6], k = 10
  • 输出: false
  • 解释: 无法在将数组中的数字分为三对的同时满足每对数字和能够被 10 整除的条件。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    bool canArrange(vector<int> &arr, int k)
    {
        // 用于存储每个余数出现的次数
        map<int, int> map;
        // 统计每个元素与 k 的余数,记录余数出现的次数
        for (const auto &v : arr)
        {
            ++map[v % k >= 0 ? v % k : (v % k) + k];
        }
        // 检查余数的配对情况
        for (const auto &m : map)
        {
            // 处理余数为 0 的情况
            if (m.first == 0)
            {
                // 如果余数为 0 的数量是奇数,则无法配对
                if (m.second % 2 != 0)
                    return false;
                continue;
            }
            // 检查当前余数 m.first 和对应的 k - m.first 数量是否相等
            if (map[m.first] != map[k - m.first])
                return false;
        }
        // 如果所有检查通过,返回 true
        return true;
    }

主要步骤

  1. 余数记录:

    • 使用一个 map<int, int> 记录数组中的每个元素与 k 的余数,并统计每个余数出现的次数。
    • 在统计时,如果余数为负,则通过加 k 将其转换成非负余数以确保映射的正确性。
  2. 配对检查:

    • 遍历余数及其出现次数的映射:
      • 如果余数为 0,检查其数量是否为偶数,因为只能成对存在。
      • 对于其他余数 m.first,检查当前余数的数量是否等于其对应的余数 k - m.first 的数量。
  3. 返回结果:

    • 如果所有条件都满足,返回 true,否则返回 false

LCR 191. 按规则计算统计结果

力扣原题链接 (中等题)

为了深入了解这些生物群体的生态特征,你们进行了大量的实地观察和数据采集。数组 arrayA 记录了各个生物群体数量数据,其中 arrayA[i] 表示第 i 个生物群体的数量。请返回一个数组 arrayB,该数组为基于数组 arrayA 中的数据计算得出的结果,其中 arrayB[i] 表示将第 i 个生物群体的数量从总体中排除后的其他数量的乘积。

示例 1:

  • 输入: arrayA = [2, 4, 6, 8, 10]
  • 输出: [1920, 960, 640, 480, 384]

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    vector<int> statisticalResult(vector<int> &arrayA)
    {
        int product = 1;       // 用于存储所有非零生物群体数量的乘积
        vector<int> res;       // 存储最终结果的数组
        vector<int> zeroindex; // 存储数组中所有零的索引
        // 遍历输入数组,计算非零元素的乘积,并记录零的索引
        for (int i = 0; i < arrayA.size(); ++i)
        {
            if (arrayA[i] == 0) // 如果当前元素为零
            {
                zeroindex.push_back(i);                   // 记录零的索引
                if (zeroindex.size() > 1)                 // 如果零的个数超过一个
                    return vector<int>(arrayA.size(), 0); // 返回全零的数组
            }
            else
                product *= arrayA[i]; // 计算非零元素的乘积
        }
        // 如果存在零,则构造结果数组
        if (!zeroindex.empty())
        {
            res = vector<int>(arrayA.size(), 0); // 初始化结果数组为全零
            res[zeroindex[0]] = product;         // 仅将第一个零的位置赋值为乘积
        }
        else // 如果没有零
        {
            // 计算每个元素排除后的乘积
            for (int j = 0; j < arrayA.size(); ++j)
            {
                res.push_back(product / arrayA[j]); // 将乘积除以当前元素
            }
        }
        return res; // 返回结果数组

    }

关键步骤

  1. 初始化:

    • 创建一个变量 product 用来存储所有非零生物群体数量的乘积。
    • 创建一个结果数组 res 存储最终结果。
    • 创建一个 zeroindex 数组记录所有元素为零的索引。
  2. 遍历输入数组:

    • 如果当前元素为零,将其索引记录到 zeroindex 中,并检查零的数量。如果大于1,则直接返回一个全零的数组。
    • 如果当前元素非零,则更新 product(乘积)为当前元素的乘积。
  3. 构造结果数组:

    • 如果存在零,初始化 res 为全零,并将第一个零的位置设置为 product(非零乘积)。
    • 如果没有零,遍历 arrayA,计算每个元素排除后的乘积,存储在 res 中(通过将总乘积除以当前元素)。