leetcode实战篇-数组

前言

hello,小伙伴们大家好~ 我是雨墨,近期开始准备刷 leetcode ~ 故想将自己的刷题笔记以博客的形式分享出来,这是我的第一篇博客,小伙伴们可以在评论区讨论和点赞哟~~
仅以此篇博文记录自己刷数组经典题的过程,日后好复习,持续更新中,每日更新两三题,直到刷完规化好的数组题~
现已将计划刷的数组题刷完了,后续开始刷其他类型的题目~~~

leetcode 1 两数之和

1.1 题目链接

传送门:https://leetcode-cn.com/problems/two-sum/

1.2 我的题解

题目描述

给定一个数组和一个整数目标值,使得两个数之和等于目标值,题目保证一定有解,要求返回数组下标。

样例

给定数组 nums = [2,7,11,15], target = 9
由于 nums[0] + nums[1] = 2 + 7 = 9
所以 return [0, 1]

算法1

(暴力枚举) O ( n 2 ) O(n^2) O(n2)

暴力枚举方法:枚举下标 i , j i, j i,j,然后判断 n u m s [ i ] + n u m s [ j ] = = t a r g e t nums[i] + nums[j] == target nums[i]+nums[j]==target
时间复杂度:由于是两重循环,所以时间复杂度是 O ( n 2 ) O(n^2) O(n2)

C++代码

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = i + 1; j < nums.size(); ++j) {
                if (nums[i] + nums[j] == target) 
                    return {i, j};
            }
        }

        return {};
    }
};

算法2:

(哈希表) O ( n ) O(n) O(n)

使用C++中的哈希表——unrodered_map hash

循环一遍 n u m s nums nums数组,在每一步循环中做两件事:

  1. 判断 t a r g e t − n u m s [ i ] target - nums[i] targetnums[i]是否在哈希表中;
  2. 将会 n u m s [ i ] nums[i] nums[i]插入到哈希表中;

解释:由于数据保证有且仅有一组解,假设是 [ i , j ] ( i < j ) [i, j](i < j) [i,j](i<j),则我们循环到 j j j时, n u m s [ i ] nums[i] nums[i]一定在哈希表中,且有 n u m s [ i ] + n u m s [ j ] = = t a r g e t nums[i] + nums[j] == target nums[i]+nums[j]==target,所以我们一定能找到解。
时间复杂度:由于只扫描一遍,每次哈希表的查找和插入操作的复杂度是 O ( 1 ) O(1) O(1),所以时间复杂度是 O ( n ) O(n) O(n)

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash;
        for (int i = 0; i < nums.size(); ++i) {
            int j = target - nums[i];
            if (hash.count(j)) return {hash[j], i};
            hash[nums[i]] = i;
        } 

        return {};
    }
};

leetcode 54 螺旋矩阵

2.1 题目链接

传送门:https://leetcode-cn.com/problems/spiral-matrix/

2.2 我的题解

题目描述

给定一个mn列的矩阵matrix,按照顺时针螺旋的顺序将它的所有元素用新的一个数组存放再返回出来。

样例

leetcode实战篇-数组_第1张图片

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

算法

(建立坐标系) O ( m n ) O(mn) O(mn)

leetcode实战篇-数组_第2张图片

按照上面的顺序,对数据进行模拟,设置类似于BFS的偏移量dx, dy,总共为4个方向,每个方向遍历完就换另外一个方向,由此往复,就能将所有的元素顺时针螺旋遍历到。
时间复杂度:由于要遍历m*n次,所以时间复杂度是 O ( m n ) O(mn) O(mn)

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> res;
        int m = matrix.size();
        if (!m) return res;
        int n = matrix[0].size();

        int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
        vector<vector<bool>> st(m, vector<bool>(n));	// 初始化一个状态数组

        for (int i = 0, x = 0, y = 0, d = 0; i < m * n; ++i) {
            res.push_back(matrix[x][y]);
            st[x][y] = true;

            int a = x + dx[d], b = y + dy[d];
            if (a < 0 || a >= m || b < 0 || b >= n || st[a][b]) {
                d = (d + 1) % 4;	// d有四个,所以每次更新就模4
                a = x + dx[d], b = y + dy[d];
            }

            x = a, y = b;
        }

        return res;
    }
};

leetcode 59 螺旋矩阵Ⅱ

3.1 题目链接

传送门:https://leetcode-cn.com/problems/spiral-matrix-ii/submissions/

3.2 我的题解

题目描述

给定一个正整数n,让你帮忙生成一个1n^2的螺旋矩阵。

样例

leetcode实战篇-数组_第3张图片

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

算法

(建立坐标系) O ( n 2 ) O(n^2) O(n2)

和上一题方法一摸一样,使用偏移量,当访问不下去了,就换另一条路走;
时间复杂度:由于矩阵大小是n*n,所以时间复杂度是 O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> Matrix(n, vector<int>(n));
        if (!n) return Matrix;

        int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
        vector<vector<bool>> st(n, vector<bool>(n));

        for (int i = 1, x = 0, y = 0, d = 0; i <= n * n; ++i) {
            Matrix[x][y] = i;

            int a = x + dx[d], b = y + dy[d];
            if (a < 0 || a >= n || b < 0 || b >= n || Matrix[a][b]) {
                d = (d + 1) % 4;
                a = x + dx[d], b = y + dy[d];
            }

            x = a, y = b; 
        }

        return Matrix;
    }
};

leetcode 66 加一

4.1 题目链接

传送门:https://leetcode-cn.com/problems/plus-one/

4.2 我的题解

题目描述

给定一个数组,数组中从头到尾的数字串起来表示一个整数。要在该数的基础上加一。然后再将加一过后的整数挨个存储在数组中。除了0之外,数组中的元素都不会以0开头。

样例

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。

算法

(模拟) O ( n ) O(n) O(n)

取一个数出来,进行模拟加一后的结果,发现可能会发生进位,这就类似于高精度加法,也就是应该将原数组中的元素reverse一遍,让进位存在数组的尾部,最后再reverse回来。
时间复杂度:由于需要遍历数组中的每一位一次,所以时间复杂度是 O ( n ) O(n) O(n)

class Solution {
public:
    vector<int> plusOne(vector<int>& digits) {
        reverse(digits.begin(), digits.end());

        int t = 1;  // 由于题目要求在该数的基础上加上一
        for (int i = 0; i < digits.size(); ++i) {
            t += digits[i];     

            digits[i] = t % 10; // 覆盖原来的元素
            t /= 10;
        }

        if (t) digits.push_back(t);

        reverse(digits.begin(), digits.end());

        return digits;
    }
};

剑指offer 3 数组中重复的数字

5.1 题目链接

传送门:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/

5.2 我的题解

题目描述

给定一个长度为n的数组,已知数组的所有数字都在0~n-1的范围内,数组中有的数字会重复,但是不知道重复了多少个,返回任意一个重复的数字。

样例

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 

算法1

(原地交换) O ( n ) O(n) O(n)

下面的算法的主要思想是把每一个元素放在对应的位置上去,即nums[i] = i(如下图所示的效果)。

  • 如果x != i && nums[x] == x,说明了 x x x已经出现了多次,直接返回 x x x
  • 如果nums[x] != x,那就需要将 x x x交换到正确的位置上去,即swap(nums[x], nums[i]),交换完之后如果nums[i] != i,则重复此操作。由于每次交换都会将一个数放在正确的位置上,所以swap操作最多进行 n n n次,不会发生死循环。

循环结束过后,如果没有找到重复的数,则返回-1

时间复杂度:每次swap操作都会将一个数放在正确的位置,最后一次swap会将两个数放在正确的位置,一共只有 n n n个数和 n n n个位置,所以swap最多会进行n-1次操作,所以总的时间复杂度是 O ( n ) O(n) O(n)
空间复杂度:由于是使用原有数组进行操作,所以空间复杂度是 O ( 1 ) O(1) O(1)

leetcode实战篇-数组_第4张图片

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

        for (int i = 0; i < n; ++i) {
            while (nums[i] != nums[nums[i]]) swap(nums[i], nums[nums[i]]);
            if (i != nums[i]) return nums[i];
        }

        return -1;
    }
};

算法2

(哈希存储) O ( n ) O(n) O(n)

下面算法的主要思想是想确认nums[i]是否已经出现过了:

  • 遍历数组nums,如果record[x] == false,表示还没有出现过这个元素值,将其标记为true
  • 如果出现,即record[x] == true,则return x

遍历结束,还没有返回的话,说明没有出现重复的元素,return -1

时间复杂度:最多访问 n n n次元素,所以时间复杂度是 O ( n ) O(n) O(n)
空间复杂度:由于使用额外的哈希表来存储,所以空间复杂度是 O ( n ) O(n) O(n)

因此,第一种算法可能更容易让面试官眼前一亮。

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_map<int, bool> record;

        for (auto& x : nums) {
            if (record[x]) return x;
            record[x] = true;
        }

        return -1;
    }
};

leetcode 75 颜色分类

6.1 题目链接

传送门:https://leetcode-cn.com/problems/sort-colors/

6.2 我的题解

题目描述

给定一个数组,包含红色、白色、蓝色三种颜色,分别用0、1、2表示,原地对数组进行排序,使得排序过后的结果为红色的在一块、白色的在一块、蓝色的在一块。要求仅用常数空间扫描一趟。

样例

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

算法1

(类似于双指针的三指针算法) O ( n ) O(n) O(n)

下面算法是设立分界线来确定不同的颜色该放到哪一个位置:

  • 规定0~j-1的区间存放的是0,j~i-1存放的是1,k~n-1存放的是2;
  • 如果nums[i] == 0,说明应该跟nums[j]做交换,交换完之后,j++
  • 如果nums[i] == 2,说明应该跟nums[k]做交换,此时k--,但是不能确定nums[k]存放的值是0还是1,所有不能对 i i i进行操作;
  • 如果nums[i] == 1,直接++i
  • 因此, i i i k k k 总会在某个时刻相遇。

时间复杂度:由于只需要遍历整个数组一遍,所以时间复杂度是 O ( n ) O(n) O(n)
空间复杂度:由于只是在原地进行交换,所以空间复杂度是 O ( 1 ) O(1) O(1)

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n = nums.size();
        if (!n) return;

        for (int i = 0, j = 0, k = n - 1; i <= k;) {
            if (nums[i] == 0) swap(nums[i++], nums[j++]);
            else if (nums[i] == 2) swap(nums[i], nums[k--]);
            else ++i;
        }
    }
};

算法2

遍历整个数组,分别记录0的个数是多少,1的个数是多少,2的个数是多少,然后再分别插入回去。(水题可以,但是水面试官就不太行)

算法3

直接sort一遍,纯属找死…

leetcode 560 和为k的子数组

7.1 题目链接

传送门:https://leetcode-cn.com/problems/subarray-sum-equals-k/

7.2 我的题解

题目描述

给定一个数组nums和一个整数k,返回数组中和为k的连续子数组的个数。

样例

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

算法

(前缀和+哈希表) O ( n ) O(n) O(n)

算法描述:

  • 题目要求是连续子数组的和,所以可以使用前缀和来表示,即满足 p r e f i x S u m [ i ] − p r e f i x S u m [ j ] = k prefixSum[i] - prefixSum[j] = k prefixSum[i]prefixSum[j]=k的数组个数是多少个,再进行转换就是要求 p r e f i x S u m [ j ] = p r e f i x S u m [ i ] − k prefixSum[j] = prefixSum[i] - k prefixSum[j]=prefixSum[i]k的个数是多少个。
  • 想要直到一个具体的值的个数,可以使用哈希表来存储。

时间复杂度:由于算前缀和的时间复杂度是 O ( n ) O(n) O(n),哈希表取出每个元素的时间复杂度是 O ( 1 ) O(1) O(1),但是需要遍历 n n n次,所以时间复杂度也是 O ( n ) O(n) O(n),综合来看时间复杂度为 O ( n ) O(n) O(n)

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n = nums.size();
        if (!n) return -1;

        vector<int> prefixSum(n + 1);
        // 求 PrefixSum[j-1]
        for (int i = 1; i <= n; ++i) prefixSum[i] = prefixSum[i - 1] + nums[i - 1];

        unordered_map<int, int> hash;
        hash[0] = 1;    // 初始时刻前缀和为0
        int cnt = 0;

        for (int i = 1; i <= n; ++i) {
            cnt += hash[prefixSum[i] - k];
            ++hash[prefixSum[i]];
        }

        return cnt;
    }
};

优化

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

        unordered_map<int, int> hash;
        int cnt = 0, prefixSum = 0;

        hash[0] = 1;    // 初始时刻前缀和为0
        for (auto& x : nums) {
            prefixSum += x;
            cnt += hash[prefixSum - k];
            ++hash[prefixSum];
        }

        return cnt;
    }
};

leetcode 704 二分查找

8.1 题目链接

传送门:https://leetcode-cn.com/problems/binary-search/

8.2 我的题解

题目描述

给定一个大小为n的升序的数组numstarget,查找数组中有无元素等于target,有则返回下标,没有则返回-1,下标从0开始。

样例

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

算法

(二分查找) O ( l o g n ) O(logn) O(logn)

算法描述:

  • 有单调性的一定可以二分,但是二分其实是看存不存在可以二分的条件,即划分两个区间
  • 由于数组中的元素是不重复的,所以二分的条件就找到了nums[mid] >= target,即所找到的元素一定是第一个大于等于target
  • 有了条件,就可以划分区间了,如果满足check函数的条件,则可以想象到答案一定在mid左边,包含mid,如果不满足条件,则答案一定在mid右边,且不包含mid

时间复杂度:由于是折半查找,所以时间复杂度是 O ( l o g n ) O(logn) O(logn)

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1;

        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] >= target) r = mid;   // 一定是左边区间满足
            else l = mid + 1;                   // 如果不满足, 答案肯定在右边区间
        }

        if (nums[l] == target) return l;
        return -1;
    }
};

leetcode 26 删除有序数组中的重复项(经典但很简单)

9.1 题目链接

传送门:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/

9.2 我的题解

题目描述

给定一个数组,要求原地删除重复出现的元素,返回删除后新数组的长度。

样例

输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

算法

(双指针) O ( n ) O(n) O(n)

算法描述:为什么说他经典呢?C++中的unique算法就是这样的思路

  • 使用快指针fastIndex遍历整个数组,慢指针slowIndex确定新的数组
  • 如果快指针fastIndex遍历的数与前一个数相同,则不加入到满指针确定的数组中,如果不相等,则加入到新的数组当中。

时间复杂度:最坏的情况是,整个数组没有重复的元素,此时快慢指针都会遍历n个元素,时间复杂度仍然为 O ( n ) O(n) O(n)

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); ++fastIndex) {
            if (!fastIndex || nums[fastIndex] != nums[fastIndex - 1])
                nums[slowIndex++] = nums[fastIndex];
        }

        return slowIndex;
    }
};

leetcode 27 移除元素

10.1 题目链接

传送门:https://leetcode-cn.com/problems/remove-element/submissions/

10.2 我的题解

题目描述

给定一个数组nums和一个值val,原地移除所有等于val的值,返回新的数组的长度。

样例

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

算法

(双指针算法) O ( n ) O(n) O(n)

跟前面那道题思路一摸一样,不再进行过多阐述。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); ++fastIndex) {
            if (nums[fastIndex] != val) 
                nums[slowIndex++] = nums[fastIndex];
        }

        return slowIndex;
    }
};

leetcode 977 有序数组的平方

11.1 题目链接

传送门:https://leetcode-cn.com/problems/squares-of-a-sorted-array/

11.2 我的题解

题目描述

给定一个非递减序的整数nums,返回一个非递减的由每个数字的平方组成的新数组。

样例

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

算法1

(暴力+快排) O ( l o g n ) O(logn) O(logn)

算法描述:直接将所有元素取平方再sort一遍。
时间复杂度:取平方的时间复杂度是 O ( n ) O(n) O(n),快排的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),所以总的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        for (auto& x : nums) x *= x;

        sort(nums.begin(), nums.end());

        return nums;
    }
};

算法2

(归并双指针) O ( n ) O(n) O(n)

算法描述:

  • 考虑有负数的情况,则平方后的最大值要么在最左边,要么在最右边,不可能在中间
  • 开辟一个存储结果的数组,从原数组两端开始扫描,取出最大值存入新数组的末尾

时间复杂度:由于只扫描了一遍原数组,所以时间复杂度是 O ( n ) O(n) O(n)的。

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

        for (int i = 0, j = n - 1, k = n - 1; i <= j;) {
            if (nums[i] * nums[i] > nums[j] * nums[j]) {
                res[k] = nums[i] * nums[i];
                --k, ++i;
            }else {
                res[k] = nums[j] * nums[j];
                --k, --j;
            }
        }

        return res;
    }
};

leetcode 209 长度最小的子数组

12.1 题目链接

传送门:https://leetcode-cn.com/problems/minimum-size-subarray-sum/

12.2 我的题解

题目描述

给定一个n个正整数的数组和一个正整数target。要求找出满足≥ target的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,则返回 0 0 0

样例

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

算法

(双指针) O ( n ) O(n) O(n)

算法描述:

  • 先讲如何判断序列的单调性的方法(非常实用),先确定快指针fastIndex,且保证的是快指针一定在慢指针之后,然后考虑fastIndex往后移动的时候,slowIndex也会往后移,那就说明具有单调性。确定单调性有什么用,当然是保证双指针算法一定能够被使用。
  • 本题根据上述方法确定了序列满足单调性,所以使用双指针算法来求解。
  • 确切地来说就是,就是不断试探能否移动slowIndex减小连续数组的长度。

时间复杂度:由于是双指针算法,所以时间复杂度是 O ( n ) O(n) O(n)

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int res = INT_MAX;

        for (int fastIndex = 0, slowIndex = 0, sum = 0; fastIndex < nums.size(); ++fastIndex) {
            sum += nums[fastIndex];
            // 不断试探移动slowIndex
            while (sum - nums[slowIndex] >= target) sum -= nums[slowIndex++];
            if (sum >= target) res = min(res, fastIndex - slowIndex + 1);
        }

        if (res == INT_MAX) return 0;
        return res;
    }
};

剑指offer 66 构建乘积数组

13.1 题目链接

传送门:https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/

13.2 我的题解

题目描述

给定一个数组a[0, 1, …, n-1],构建一个b数组,其中b[i]的值是数组a除了下标i以外的元素的乘积,言外之意就是计算a[i]0~n-1的乘积然后再除以特定的a[i]的值。但是本题要求不能使用除法。

思考

能不能使用常数的空间

样例

输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

算法

(拆分遍历) O ( n ) O(n) O(n)

算法描述:

  • 题目要求我们构建这样的一个数组,且需要使用常数的空间,也就是除了新开一个数组,不能再占用其他的空间
  • 可以讲一个问题拆开(曲线解题),先讲i左边的a[i]计算出来,然后再乘上 i i i 右侧的a[i],这样解题思路就有了。
  • 考虑如何将思路复现成代码,定义一个tmp,表示i之前的乘积,然后分别赋给b[i],这样就完成了求i之前乘积的工作了,然后考虑i之后的乘积如何求,从n - 1倒着给b[i] * tmp(这也是本题的绝妙做法)。每次都给tmp初始化为 1 ,因为在i = 0之前或i = n -1之后都没有元素,所以应该将tmp定义为 1 。

时间复杂度:两次遍历,一共 2n 次,所以时间复杂度是 O ( n ) O(n) O(n)

class Solution {
public:
    vector<int> constructArr(vector<int>& a) {
        if (a.empty()) return vector<int> ();
        
        int n = a.size();
        vector<int> b(n);

        for (int i = 0, tmp = 1; i < n; ++i) {
            b[i] = tmp;
            tmp *= a[i];
        }

        for (int i = n - 1, tmp = 1; ~i; --i) {
            b[i] *= tmp;
            tmp *= a[i];
        } 

        return b;
    }
};

leetcode 414 第三大的数

14.1 题目链接

传送门:https://leetcode-cn.com/problems/third-maximum-number/

14.2 我的题解

题目描述

给定一个非空数组,要你返回数组中第三大的数(数不能是重复值),如果不存在这个数,则返回最大的数。

提示

  • 1 <= nums.length <= 10^4
  • -2^31 <= nums[i] <= 2^31 - 1

样例

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

算法1

(分段讨论) O ( n ) O(n) O(n)

一开始我以为是双指针算法,但是那样我没有想出来如何设计出按照降序排列的双指针,故放弃。

算法描述:

  • 由于题目要求返回第三个元素,因此使用firstNumsecondNumthirdNum三个值存放最大值、次大值、第三大值
  • 初始情况上述三个元素都设为-INF,因为看数据范围,最大的数是long long,因此在声明变量的时候应该注意这一点,否则就会出现buffer-overflow的情况。
  • 讨论第一种情况:当x大于firstNum的时候,firstNum应该要移动到x的位置,同理secondNumthirdNum也应该分别移动到firstNumsecondNum之前的位置,所以代码中需要注意交换的次序,同时还需要定义一个cnt来计数。
  • 第二种情况:x只是大于secondNum但不大于firstNum
  • 第三种情况:x只是大于thirdNum但不大于secondNum

时间复杂度:由于只是遍历一次数组,所以时间复杂度为 O ( n ) O(n) O(n)

class Solution {
public:
    int thirdMax(vector<int>& nums) {
        long long INF = 1e10;
        long long firstNum = -INF, secondNum = -INF, thirdNum = -INF, cnt = 0;

        for (auto& x : nums) {
            if (x > firstNum) 
                ++cnt, thirdNum = secondNum, secondNum = firstNum, firstNum = x;
            else if (x > secondNum && x < firstNum) 
                ++cnt, thirdNum = secondNum, secondNum = x;
            else if (x > thirdNum && x < secondNum)
                ++cnt, thirdNum = x;
        }

        if (cnt < 3) return firstNum;
        return thirdNum;
    }
};

算法2

(利用set的自动排序) O ( n ) O(n) O(n)

算法描述:由于set底层是使用红黑树实现的,红黑树能够自动排序,且使用的是unique_insert,所以使用set就能轻而易举的拍好序且不出现重复元素(妙啊~~~)

时间复杂度:插入红黑树需要 O ( l o g n ) O(logn) O(logn)的时间复杂度,遍历需要 O ( n ) O(n) O(n)的时间复杂度,所以总的时间复杂度是 O ( n ) O(n) O(n)

class Solution {
public:
    int thirdMax(vector<int>& nums) {
        set<int> s;

        for (const auto& x : nums) {
            s.insert(x);
            if (s.size() > 3) s.erase(s.begin());
        }

        if (s.size() == 3) return *s.begin();
        return *s.rbegin();
    }
};

leetcode 581 最短无序连续子数组

15.1 题目链接

传送门:https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray/

15.2 我的题解

题目描述

给定一个数组,要求你找到一个连续子数组,使得将这个子数组进行升序排序之后,整个数组都是呈升序排序的,言外之意就是说,这个子数组的两边都是升序排序的且排序子数组之后这两边的序列仍然呈原有顺序排列。要求返回该子数组的长度,若没有这样的数组返回0

样例

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

算法1

(快排+遍历) O ( l o g n ) O(logn) O(logn)

根据上述题目描述,很容易想到,可以将nums数组拷贝到另一个数组中,对另一个数组进行sort一遍,然后从头找到第一个不相等的位置,这就是子数组的起点,再从尾扫一遍,找到第一个不相等的位置,这就是子数组的尾部,最后算子数组的长度即可。

时间复杂度:由于使用了快排,所以总的时间复杂度为 O ( l o g n ) O(logn) O(logn)

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        if (nums.empty()) return -1;

        int n = nums.size();
        vector<int> cop(nums.begin(), nums.end());
        sort(cop.begin(), cop.end());

        int i = 0, j = n - 1;
        for (; i < n; ++i) {
            if (nums[i] != cop[i])
                break;
        }
        for (; ~j; --j) {
            if (nums[j] != cop[j]) 
                break;
        }

        if (j > i) return j - i + 1;
        return 0;
    }
};

算法2

(优化版本,不使用排序) O ( n ) O(n) O(n)

算法描述:

  • 通过进一步思考,可以把整个数组分为三段,第一段是逐渐递增的,但是其最大值(即最靠右的要小于其右边序列的最小值),第二段是题目要求的子数组序列(但此时还不知道其长度是否为0),第三段也是逐渐递增的,但是要求其最小值要大于其左边序列的最大值;
  • 根据以上关系,就可以先确定左边序列有多长,所以需要写一个while loop,即while (nums[left] <= nums[left + 1]),如果left已经走到了整个序列的终点,说明没有题目要求的子数组,所以return 0
  • 否则,停下,确定右边区间的左端点,也是一个while loop,即while (nums[right - 1] <= nums[right]),最终right会停下;
  • 接下来就是判断nums[left]是否满足小于右边序列的最小值,但是不用刻意去求右边序列的最小值,而是挨个和右边序列的每一个点比较,如果大于右边序列某一个值,说明它不属于左边序列;
  • 相同的思路判断右边序列的最小值是否满足大于左边序列的最大值的条件;

时间复杂度:由于遍历都是常数 n 的时间,所以时间复杂度是 O ( n ) O(n) O(n)

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        if (nums.empty()) return -1;

        int left = 0, right = nums.size() - 1, len = nums.size();
        // 首先寻找左边序列
        while (left + 1 < len && nums[left] <= nums[left + 1]) ++left;
        // 判断left是否已经走到了终点
        if (left == right) return 0;
        // 寻找右边序列
        while (right - 1 >= 0 && nums[right - 1] <= nums[right]) --right;
        // 寻找满足左边序列右端点条件的点
        for (int i = left + 1; i < len; ++i) {
            while (left >= 0 && nums[left] > nums[i]) 
                --left;
        }
        // 寻找满足右边序列左端点条件的点
        for (int i = right - 1; ~i; --i) {
            while (right < len && nums[right] < nums[i])
                ++right;
        }
        // 不包含左端点
        return right - left - 1;
    }
};

leetcode 605 种花问题

16.1 题目链接

传送门:https://leetcode-cn.com/problems/can-place-flowers/

16.2 我的题解

题目描述

给定一个01数组,要求每个1之间不能相邻,问:能否插入n1?如何可以则返回true,不行则返回false

提示

0 <= n <= flowerbed.length

样例

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

算法

(分类讨论) O ( n ) O(n) O(n)

能否插入1需要看0的个数,讨论三种情况(需要画图手动模拟):

  • 第一种情况:1 … 1 之间,此时计算 0 个数的公式为 k - 1 / 2
  • 第二种情况:0 0 … 1,只有一边有 1 ,此时计算 0 个数的公式为 k / 2
  • 第三种情况:0 0 0 … 0 0,全部都是 0 ,此时计算 0 个数的公式为 k + 1 / 2

时间复杂度:由于只遍历了一遍数组,所以时间复杂度是 O ( n ) O(n) O(n)

class Solution {
public:
    bool canPlaceFlowers(vector<int>& flowerbed, int n) {
        if (!n) return true;

        int len = flowerbed.size();
        int res = 0;
        // 寻找某一段0的个数
        for (int fastIndex = 0, slowIndex = 0; fastIndex < len; ++fastIndex) {
            if (flowerbed[fastIndex] == 1) continue;
            slowIndex = fastIndex;
            while (slowIndex < len && !flowerbed[slowIndex]) ++slowIndex;
            // 第一种情况:1 ... 1 之间,此时计算 0 个数的公式为 k - 1 / 2
            int cnt = slowIndex - fastIndex - 1;
            // 第二种情况:0 0 ... 1,只有一边有 1 ,此时计算 0 个数的公式为 k / 2
            if (fastIndex == 0) ++cnt;  
            // 第三种情况:0 0 0 ... 0 0,全部都是 0 ,此时计算 0 个数的公式为 k + 1 / 2
            if (slowIndex == len) ++cnt;

            res += cnt / 2;
            if (res >= n) return true; 

            fastIndex = slowIndex;   
        }

        return false;
    }
};

leetcode 628 三个数的最大乘积

17.1 题目链接

传送门:https://leetcode-cn.com/problems/maximum-product-of-three-numbers/

17.2 我的题解

题目描述

给定一个数组,数组元素不小于3,要求输出数组中由三个数组成的最大乘积。

样例

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

算法

(分类讨论+排序) O ( n l o g n ) O(nlogn) O(nlogn)

讨论描述:

  • 结果是三个正数的乘积:取数组最大的三个元素
  • 结果是两个负数和一个正数的乘积:取数组最小的两个负数和最大的正数
  • 结果是一个负数和两个正数的乘积:说明数组一定只有三个元素,否则结果不会是这样的组合
  • 结果是三个负数的乘积:取乘积绝对值最小的三个数的乘积作为最大值,所以选择最大的三个负数
  • 包含0的情况:
    • 负负负负负…0,此时除了选择0外,其余随便选两个数
    • 负0正,说明数组只有这三个元素
    • 负0正正

算法描述:由上述讨论结果,得出结论:只需要在数组最大的三个元素的乘积和数组最小的两个数与最大的数的乘积之间取最大值,结果一定是最终答案。取元素可以通过排序轻而易举取出。

时间复杂度:由于使用了sort,所以时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

class Solution {
public:
    int maximumProduct(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int len = nums.size();

        return max(nums[len - 1] * nums[len - 2] * nums[len - 3], 
            nums[0] * nums[1] * nums[len - 1]);
    }
};

leetcode 643 子数组最大平均数Ⅰ

18.1 题目链接

传送门:https://leetcode-cn.com/problems/maximum-average-subarray-i/

18.2 我的题解

题目描述

给定一个数组nums和一个整数k,要求返回最大的长为k的连续子数组的平均数。

样例

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

算法

(滑动窗口) O ( n ) O(n) O(n)

算法描述:维持一个长度为k的窗口,计算每个窗口的总和的平均值,求最大的返回。

class Solution {
public:
    double findMaxAverage(vector<int>& nums, int k) {
        double res = -1e5;
        
        for (int rh = 0, lh = 0, sum = 0; rh < nums.size(); ++rh) {
            sum += nums[rh];
            if (rh - lh > k - 1) sum -= nums[lh++];
            if (rh >= k - 1) res = max(res, sum / (double)k); 
        }

        return res;
    }
};

leetcode 665 非递减序列

19.1 题目链接

传送门:https://leetcode-cn.com/problems/non-decreasing-array/

19.2 我的题解

题目描述

给定一个数组,要求判断至多改变一次元素就能使整个数组呈升序状态(至于怎么改,那是我的事,想怎么改就怎么改),若能返回true,若不能返回false

样例

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

算法

(遍历+检查) O ( n ) O(n) O(n)

算法描述:本题延外之意就是要寻找是否有逆序对儿,如果有,尝试修改这个逆序对儿,修改之后再check这个数组是否升序排列。

修改方案:由于要使得它升序,就让逆序的两个数都等于第一个数或是都等于第二个数。

class Solution {
public:
    bool check(vector<int>& nums) {
        for (int i = 0; i + 1 < nums.size(); ++i) {
            if (nums[i] > nums[i + 1]) 
                return false;
        }

        return true;
    }

    bool checkPossibility(vector<int>& nums) {
        for (int i = 0; i + 1 < nums.size(); ++i) {
            if (nums[i] <= nums[i + 1]) continue;

            int tmp1 = nums[i], tmp2 = nums[i + 1];

            nums[i] = nums[i + 1] = tmp1;
            if (check(nums)) return true;

            nums[i] = nums[i + 1] = tmp2;
            if (check(nums)) return true;

            return false;
        }

        return true;
    }
};

leetcode 697 数组的度

20.1 题目链接

传送门:https://leetcode-cn.com/problems/degree-of-an-array/

20.2 我的题解

题目描述

给定一个非空且只含非负元素的数组nums,数组的度定义的是数组中任一元素出现频数的最大值(即众数),要求在nums中找到与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.

算法

(哈希表存储) O ( n ) O(n) O(n)

算法描述:

  • 需要解决两个问题:每个数出现的次数怎么找,连续子数组肯定是以众数为起点和终点,起点和终点如何判断?
  • 解决方案:哈希表存储
class Solution {
public:
    int findShortestSubArray(vector<int>& nums) {
        unordered_map<int, int> cnt, firstp, lastp;
        int span = 0, res = INT_MAX;
        int len = nums.size();

        for (int i = 0; i < len; ++i) {
            int x = nums[i];
            if (!cnt[x]) firstp[x] = i;   // 记录第一次出现的位置
            span = max(span, ++cnt[x]); // 找数组的度
            lastp[x] = i;   // 记录最后一次出现的位置
        }

        for (const auto& x : nums) {
            if (cnt[x] == span) {
                int tmp = lastp[x] - firstp[x] + 1;
                res = tmp < res ? tmp : res;
            }
        }

        return res;
    }
};

leetcode 674 最长连续递增序列

21.1 题目链接

传送门:https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/

21.2 我的题解

题目描述

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

样例

输入:nums = [1,3,5,4,7]
输出:3

算法

(双指针算法) O ( n ) O(n) O(n)

这是一道非常经典的双指针算法题目。由于序列具有单调性,所以可以使用双指针。

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int len = 0, r = 0;
        for (int l = 0; l < nums.size(); ++l) {
            r = l + 1;
            while (r < nums.size() && nums[r] > nums[r - 1]) ++r;
            len = max(len, r - l);
            l = r - 1;
        }

        return len;
    }
};

leetcode 717 1比特与2比特字符

22. 1 题目链接

传送门:https://leetcode-cn.com/problems/1-bit-and-2-bit-characters/

22.2 我的题解

题目描述

给定两个特殊字符,分别使用1比特0和2比特1011表示,先取出若干比特组成的字符串,问最后一个字符是否必定为1比特字符,若是则返回true

样例

输入: 
bits = [1, 0, 0]
输出: True
输入: 
bits = [1, 1, 1, 0]
输出: False

算法

(模拟) O ( n ) O(n) O(n)

如果是 1 开始的数,则必定是2比特字符,所以index加 2 ,否则加 1 ,若到达最后位置且尾末元素是 0 ,返回true

class Solution {
public:
    bool isOneBitCharacter(vector<int>& bits) {
        int len = bits.size();

        for (int i = 0; i < len; ++i) {
            if (i == len - 1 && !bits[i]) return true;
            if (bits[i]) ++i;
        }

        return false;
    }
};

leetcode 830 较大分组的位置

23.1 题目链接

传送门:https://leetcode-cn.com/problems/positions-of-large-groups/

23.2 我的题解

题目描述

给定一个由小写字母构成的字符串s,该字符串包含一些连续的相同字符所构成的分组,如果这些分组长度大于或等于三,称之为较大分组,找出题目中较大分组的区间,按起始下标递增排序后返回结果。

样例

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

算法

(双指针) O ( n ) O(n) O(n)

这道题是一道非常 nice 的求区间问题,看到这样的题我首先想到的就是双指针。

class Solution {
public:
    vector<vector<int>> largeGroupPositions(string s) {
        int len = s.size();
        if (len == 1 || len == 2) return vector<vector<int>> ();
        vector<vector<int>> res;

        for (int slowIndex = 0; slowIndex < len; ++slowIndex) {
            int fastIndex = slowIndex + 1;
            while (fastIndex < len && s[fastIndex] == s[fastIndex - 1]) ++fastIndex;
            if (fastIndex - slowIndex >= 3) res.push_back({slowIndex, fastIndex - 1});
            slowIndex = fastIndex - 1;
        }

        return res;
    }
};

leetcode 840 矩阵的幻方

23.1 题目链接

传送门:https://leetcode-cn.com/problems/magic-squares-in-grid/

23.2 我的题解

题目描述

3 x 3的幻方是指一个填充从19的矩阵 3 x 3矩阵,其中每行每列以及两条对角线上的个数之和都相等。问:给定一个多维数组grid,其中有多少个幻方矩阵。

提示

  1. 1 <= grid.length <= 10
  2. 1 <= grid[0].length <= 10
  3. 0 <= grid[i][j] <= 15

样例

输入: [[4,3,8,4],
      [9,5,1,9],
      [2,7,6,2]]
输出: 1 
下面的子矩阵是一个 3 x 3 的幻方:
438
951
276
而这一个不是:
384
519
762

算法

(暴力枚举) O ( n m ) O(nm) O(nm)

由于给定数据较小,直接枚举就完事了。

class Solution {
public:
    bool check(int x, int y, const vector<vector<int>>& grid) {
        vector<bool> st(10, false);
        int tmp = 0;
        // 枚举每一个元素
        for (int r = x; r < x + 3; ++r) {
            for (int c = y; c < y + 3; ++c) {
                tmp = grid[r][c];
                if (tmp < 1 || tmp > 9) return false;
                if (st[tmp]) return false;
                st[tmp] = true;
            }
        }

        // 枚举每一个行每一列
        for (int i = 0; i < 3; ++i) {
            if (grid[x + i][y] + grid[x + i][y + 1] + grid[x + i][y + 2] != 15) return false;
            if (grid[x][y + i] + grid[x + 1][y + i] + grid[x + 2][y + i] != 15) return false;
        }

        // 枚举每一个对角线和反对角线
        if (grid[x][y] + grid[x + 1][y + 1] + grid[x + 2][y + 2] != 15) return false;
        if (grid[x + 2][y] + grid[x + 1][y + 1] + grid[x][y + 2] != 15) return false;
        return true;
    }

    int numMagicSquaresInside(vector<vector<int>>& grid) {
        int row = grid.size(), col = grid[0].size();
        int res = 0;

        for (int x = 0; x + 3 <= row; ++x) {
            for (int y = 0; y + 3 <= col; ++y) {
                if (check(x, y, grid))
                    ++res;
            }
        }

        return res;
    }
};

leeetcode 849 到最近的人的最远距离

24.1 题目链接

传送门:https://leetcode-cn.com/problems/maximize-distance-to-closest-person/

24.2 我的题解

题目描述

给定一个数组seats表示一排座位,0表示该座位没人,1表示该座位有人,下标从0开始。至少有一个空座位且至少有一个人已经在座位上了。Alex 比较喜欢静一静,他希望坐在离他距离最近的人最远的位置。

样例

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

算法

(双指针) O ( n ) O(n) O(n)

这道题没有想到区间的时候,我根本就没觉得会使用双指针算法,但是知道以后发现是真的妙啊~

算法描述:

  • Alex 希望距离最近的人最远,那就需要找到空座位最多的区间,然后取空座位区间的中点,那离两边的人都是最远的
  • 特殊情况:如果是空座位在两边,则有可能这里是离别人最远的位置
class Solution {
public:
    int maxDistToClosest(vector<int>& seats) {
        int len = seats.size();
        int res = 0;

        for (int slowIndex = 0; slowIndex < len; ++slowIndex) {
            // 从第一个空座位开始
            if (seats[slowIndex]) continue; 
            int fastIndex = slowIndex + 1;
            while (fastIndex < len && !seats[fastIndex]) ++fastIndex;
            // 特判,如果是边界没有人(左边界或是右边界)
            if (!slowIndex || fastIndex == len) res = max(res, fastIndex - slowIndex);
            // 否则就取相邻最近的两个人的终点
            else res = max(res, (fastIndex - slowIndex + 1) / 2);
        }

        return res;
    }
};

leetcode 888 公平的糖果棒交换

25.1 题目链接

传送门:https://leetcode-cn.com/problems/fair-candy-swap/

25.2 我的题解

题目描述

给定两个数组,问能否通过交换两个数组中的一个元素,使得两个数组的元素总和一样大。

样例

输入:A = [1,1], B = [2,2]
输出:[1,2]

算法

(哈希表+推导公式) O ( n ) O(n) O(n)

算法描述:

  • 推导公式为 c a n d y o f B o b = c a n d y o f A l i c e + ( s u m A l i c e − s u m B o b ) / 2 candyofBob = candyofAlice + (sumAlice - sumBob) / 2 candyofBob=candyofAlice+(sumAlicesumBob)/2
  • c a n d y o f B o b candyofBob candyofBob存入哈希表中
  • 遍历aliceSizes时计算 c a n d y o f B o b candyofBob candyofBob,看能否寻得哈希表中有此值,有的话返回 c a n d y o f A l i c e , c a n d y o f B o b {candyofAlice, candyofBob} candyofAlice,candyofBob
class Solution {
public:
    vector<int> fairCandySwap(vector<int>& aliceSizes, vector<int>& bobSizes) {
        int sumAlice = accumulate(aliceSizes.begin(), aliceSizes.end(), 0);
        int sumBob = accumulate(bobSizes.begin(), bobSizes.end(), 0);
        unordered_set<int> hash_table;
        int tmp = (sumAlice - sumBob) / 2;

        for (const auto& candy : bobSizes) hash_table.insert(candy);
        for (const auto& candy: aliceSizes) {
            if (hash_table.count(candy - tmp))
                return {candy, candy - tmp};
        }
        
        return {};
    }
};

leetcode 914 卡牌分组

26.1 题目链接

传送门:https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards/

26.2 我的题解

题目描述

给定一副牌,每张牌都有对应的数组,问能否将牌分为一组或多组:

  • 每组都用X张牌
  • 每一组的牌都是一样的

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

样例

输入:[1,2,3,4,4,3,2,1]
输出:true

算法

(最大公约数) O ( n l o g n ) O(nlogn) O(nlogn)

算法描述:

  • 首先计算每种数字对应的牌共有多少张,使用哈希表来存
  • 由题意,可以知道是求每种num对应的牌数sumX之间的最大公约数,使用辗转相除法
class Solution {
public:
    // 辗转相除法求最大公约数
    int gcd(int a, int b) {
        return b ? gcd(b, a % b) : a;
    }

    bool hasGroupsSizeX(vector<int>& deck) {
        unordered_map<int, int> cnt;
        int maxDiv = 0; // 最大公约数

        for (const auto& card : deck) ++cnt[card];
        for (const auto& [num, sum] : cnt) maxDiv = gcd(sum, maxDiv);

        return maxDiv >= 2;
    }
};

leetcode 941 有效的山脉数组

27.1 题目链接

传送门:https://leetcode-cn.com/problems/valid-mountain-array/

27.2 我的题解

题目描述

问给定数组是否满足先单调递增后单调递减。

样例

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

算法

(模拟) O ( n ) O(n) O(n)

先模拟前半段是否单增,如果压根就没有前半段单增或者整个数组都是单增,则return false;再判断后半段是否单调递减,如果是,那么指针应该走到了数组的尾末,判断是否满足即可。

class Solution {
public:
    bool validMountainArray(vector<int>& arr) {
        int i = 0;
        int len = arr.size();

        // 模拟前半段
        while (i + 1 < len && arr[i + 1] > arr[i]) ++i;
        if (i == len - 1 || !i) return false;
        // 模拟后半段
        while (i + 1 < len && arr[i + 1] < arr[i]) ++i;
        return i == len - 1;
    }
};

leetcode 989 数组形式的整数加法

28.1 题目链接

传送门:https://leetcode-cn.com/problems/add-to-array-form-of-integer/

28.2 我的题解

题目描述

给定一个非负整数,将其每位数字存入数组中,给它加上一个数,返回它的数组形式。

样例

输入:A = [1,2,0,0], K = 34
输出:[1,2,3,4]

我的题解

(高精度加法) O ( n ) O(n) O(n)

这题一看就是高精度加法,很简单~

class Solution {
public:
    vector<int> addToArrayForm(vector<int>& num, int k) {
        reverse(num.begin(), num.end());

        for (auto& nu : num) {
            k += nu;
            nu = k % 10;
            k /= 10;
        }
        while (k) {
            num.push_back(k % 10); 
            k /= 10;
        }

        reverse(num.begin(), num.end());

        return num;
    }
};

leetcode 11 盛最多水的容器

29.1 题目链接

传送门:https://leetcode-cn.com/problems/container-with-most-water/

29.2 我的题解

题目描述

给定一个数组,数组中每个元素表示容器的高度,问怎样能够使得容器盛最多的水

样例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OkGcvUy5-1634193532506)(leetcode实战篇-数组.assets/question_11.jpg)]

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

算法

(双指针) O ( n ) O(n) O(n)

这道题比较难想到使用双指针来做。用生活中的道理来说明为何可以使用双指针:木桶容量由短板决定, 移动长板的话, 水面高度不可能再上升, 而宽度变小了, 所以只有通过移动短板, 才有可能使水位上升。

做法:使用两个指针headtail表明容器的头和尾,如果num[head] < num[tail],则说明head所指的是短板,应该寻找长版,故++head,否则–-tail,直至两个指针相遇为止,每次更新指针也要更新容器的最大容量。

反证法证明双指针可行:假设容器容量达到最大时,h为头,t为尾,不妨设head指针先到达h处,此时tail指针还在来的路上,那么一定有tail到达最优解的路上所遇都是比num[t]小的木板。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int res = 0;
        
        for (int head = 0, tail = height.size() - 1; head < tail;) {
            res = max(res, min(height[head], height[tail]) * (tail - head));
            if (height[head] < height[tail]) ++head;
            else --tail;
        }

        return res;
    }
};

你可能感兴趣的:(leetcode刷题笔记,leetcode,算法)