力扣高频|算法面试题汇总(三):数组

力扣高频|算法面试题汇总(一):开始之前
力扣高频|算法面试题汇总(二):字符串
力扣高频|算法面试题汇总(三):数组
力扣高频|算法面试题汇总(四):堆、栈与队列
力扣高频|算法面试题汇总(五):链表
力扣高频|算法面试题汇总(六):哈希与映射
力扣高频|算法面试题汇总(七):树
力扣高频|算法面试题汇总(八):排序与检索
力扣高频|算法面试题汇总(九):动态规划
力扣高频|算法面试题汇总(十):图论
力扣高频|算法面试题汇总(十一):数学&位运算

力扣高频|算法面试题汇总(三):数组

力扣链接
目录:

  • 1.乘积最大子数组
  • 2.多数元素
  • 3.旋转数组
  • 4.存在重复元素
  • 5.移动零
  • 6.打乱数组
  • 7.两个数组的交集 II
  • 8.递增的三元子序列
  • 9.搜索二维矩阵 II
  • 10.除自身以外数组的乘积

1.乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字)。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组

思路一:暴力,这就不做多介绍了。

思路二:
循环求解,这有点想剑指offer中的《连续子数组的最大和》
同时也参考了思路
需要从前向后遍历数组,

  • 记录当前遍历到的局部最大值全局最大值,然后两者随着遍历的进行不断的比较。
  • 由于负数的出现,需要额外增加一个当前局部最小值变量,在遍历到负数的时候,局部最小值和局部最大值需要做交换,从而解决负数导致的“颠倒”问题。
    以序列[2,3,-2,4]为例:
    力扣高频|算法面试题汇总(三):数组_第1张图片
    C++
class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int Max;
        int imax = 1; 
        int imin = 1;
        for(int i = 0; i < nums.size(); ++i){
            if(nums[i] < 0){
                // 如果是负数,则交换局部最大值和最小值
                int temp = imax;
                imax = imin;
                imin = temp;
            }
            // 局部最大值
            imax = (imax * nums[i] > nums[i]) ? imax * nums[i] : nums[i];
            // 局部最小值
            imin = (imin * nums[i] < nums[i]) ? imin * nums[i] : nums[i];
            // 局部最大值和最大值进行比较
            if(i == 0)
                Max = imax;
            else
                Max = Max > imax ? Max :imax;
        } 
        return Max;
    }
};

Python

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        imax = 1
        imin = 1
        for i in range(len(nums)):
            if nums[i] < 0:
                imax, imin = imin, imax
            imax = max(imax*nums[i], nums[i])
            imin = min(imin*nums[i], nums[i])
            if i == 0:
                Max = imax
            else:
                Max = max(Max, imax)
        return Max

2.多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [3,2,3]
输出: 3
示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2

思路:排序,出现在数组中间的那个数字一定是出现次数大于n/2的元素。
其余解法可参考《数组中出现超过一半的数字》
C++

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        // 排序
        sort(nums.begin(), nums.end());
        return nums[nums.size() / 2];
    }
};

Python

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        nums = sorted(nums)
        return nums[len(nums)//2]

3.旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
说明:
尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
要求使用空间复杂度为 O(1) 的 原地 算法。

思路1:暴力循环,成功超时。

思路2:
直接计算元素移动的最终位置。比如移动k次,那么索引为i的元素会被移动到(i + k)%n的位置上去,其中n是数组的长度。但是这样会存在一个问题:无法全部移动,以输入nums: [1, 2, 3, 4, 5, 6];k: 2为例。挨着交换i%k==0上的元素,是无法交换全部的元素的,所以当完成索引满足i%k==0的元素交换之后,需要对i%k==1上的元素进行交换,一共交换n次。如力扣官方图示:
力扣高频|算法面试题汇总(三):数组_第2张图片
C++

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int temp;
        int counts = 0;
        k = k % nums.size();
        for(int start = 0; counts < nums.size(); ++start){
            int currentID = start;
            int pre = nums[start];
            do{
                int nextID = (currentID + k)% nums.size();
                temp = nums[nextID];
                nums[nextID] = pre;
                pre = temp;
                ++counts;
                currentID = nextID;
            }while(currentID != start);
        }

    }
};

Python:

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        k = k % len(nums)
        start = 0
        counts = 0
        while counts < len(nums):
            currentID = start
            pre = nums[currentID]
            while True:
                nextID = (currentID + k) % len(nums)
                nums[nextID], pre = pre, nums[nextID]
                counts += 1
                currentID = nextID
                if currentID == start:
                    start += 1
                    break

思路3
翻转,这个和剑指offer的《翻转单词顺序》类似。
一下参考力扣官方:

  • 这个方法基于这个事实:当我们旋转数组 k 次, k%nk%n 个尾部元素会被移动到头部,剩下的元素会被向后移动。
  • 在这个方法中,我们首先将所有元素反转。然后反转前 k 个元素,再反转后面 n-kn−k 个元素,就能得到想要的结果。
假设 n=7n=7 且 k=3k=3 。
原始数组                  : 1 2 3 4 5 6 7
反转所有数字后             : 7 6 5 4 3 2 1
反转前 k 个数字后          : 5 6 7 4 3 2 1
反转后 n-k 个数字后        : 5 6 7 1 2 3 4 --> 结果

C++

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int temp;
        k = k % nums.size();
        int pStart = 0;
        int pEnd = nums.size() - 1;
        while(pStart < pEnd){
            temp = nums[pStart];
            nums[pStart] = nums[pEnd];
            nums[pEnd] = temp;
            ++pStart;
            --pEnd;
        }
        pStart = 0;
        pEnd = k - 1;
        while(pStart < pEnd){
            temp = nums[pStart];
            nums[pStart] = nums[pEnd];
            nums[pEnd] = temp;
            ++pStart;
            --pEnd;
        }
        pStart = k;
        pEnd = nums.size() - 1;
        while(pStart < pEnd){
            temp = nums[pStart];
            nums[pStart] = nums[pEnd];
            nums[pEnd] = temp;
            ++pStart;
            --pEnd;
        }
        
    }
};

Python

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        k = k % len(nums)
        start = 0
        counts = 0
        nums.reverse()
        pStart = 0
        pEnd = k-1
        while pStart < pEnd:
            nums[pStart], nums[pEnd] = nums[pEnd], nums[pStart]
            pStart += 1
            pEnd -= 1
        pStart = k
        pEnd = len(nums)-1
        while pStart < pEnd:
            nums[pStart], nums[pEnd] = nums[pEnd], nums[pStart]
            pStart += 1
            pEnd -= 1

4.存在重复元素

给定一个整数数组,判断是否存在重复元素。
如果任意一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。
示例 1:
输入: [1,2,3,1]
输出: true
示例 2:
输入: [1,2,3,4]
输出: false
示例 3:
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true

思路1
使用哈希表记录每个元素出现的次数,如果第二次遇到该数字,直接return true
C++

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        map<int, int> idHash;
        for(auto itear = nums.begin(); itear < nums.end(); ++itear){
            // 第一次遇到数字
            if(idHash.find(*itear) == idHash.end()){
                idHash[*itear] = 1;
            }else{
                // 第二次遇到
                return true;
            }
        }
        return false;
    }
};

Python

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        idHash = {}
        for num in nums:
            if not num in idHash:
                idHash[num] = 1
            else:
                return True
        return False

5.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

思路:
参考这位博主的思路。
把0元素往后移动,实质上等于把非0元素往前移动。
用一个标志记录非0元素,先把非0元素全部移动到前面,剩下的部分再进行填充。
C++

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
       if(nums.size() == 0)
           return;
        // 记录非0的位置
        int k = 0;
        for(int i = 0; i < nums.size(); ++i){
            if(nums[i] != 0){
                // 移动非0元素
                nums[k] = nums[i];
                ++k;
            }
        }
        // 从k开始填补0、
        while(k < nums.size()){
            nums[k] = 0;
            ++k;
        }
    }
};

Python

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        if len(nums) == 0:
            return
        k = 0
        for i in range(len(nums)):
            if nums[i] != 0:
                nums[k] = nums[i]
                k += 1
        while k < len(nums):
            nums[k] = 0
            k = k + 1

6.打乱数组

打乱一个没有重复元素的数组。
示例:
// 以数字集合 1, 2 和 3 初始化数组。
int[] nums = {1,2,3};
Solution solution = new Solution(nums);
// 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。
solution.shuffle();
// 重设数组到它的初始状态[1,2,3]。
solution.reset();
// 随机返回数组[1,2,3]打乱后的结果。
solution.shuffle();

C++

class Solution {
private:
    vector<int> stroeNums;
    vector<int> shuffleNums;
    
public:
    Solution(vector<int>& nums) {
        stroeNums = nums;
    }
    
    /** Resets the array to its original configuration and return it. */
    vector<int> reset() {
        return stroeNums;
    }
    
    /** Returns a random shuffling of the array. */
    vector<int> shuffle() {
        shuffleNums = stroeNums;
        random_shuffle(shuffleNums.begin(),shuffleNums.end());
        return shuffleNums;
    }
};

/**
 * Your Solution object will be instantiated and called as such:
 * Solution* obj = new Solution(nums);
 * vector param_1 = obj->reset();
 * vector param_2 = obj->shuffle();
 */

Python:

class Solution:
    
    def __init__(self, nums: List[int]):
        self.storeNum = nums

    def reset(self) -> List[int]:
        """
        Resets the array to its original configuration and return it.
        """
        return self.storeNum

    def shuffle(self) -> List[int]:
        """
        Returns a random shuffling of the array.
        """
        shuffleNums = self.storeNum.copy()
        random.shuffle(shuffleNums)
        return shuffleNums
# Your Solution object will be instantiated and called as such:
# obj = Solution(nums)
# param_1 = obj.reset()
# param_2 = obj.shuffle()

7.两个数组的交集 II

给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。
进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

思路1:
本题的交集是不考虑顺序的,也就是等同于找出两个数组共同的数字。
那么首先遍历第一个数字,建立哈希表,记录每个数字出现的次数。
再遍历第二个数字,如果哈希表存在这个数字,且这个数字的次数大于0,那么为其中一个答案,且将出现的次数减1。
C++

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        map<int, int> idHash;
        for(auto num : nums1){
            // 第一次遇到字符
            if(idHash.find(num) == idHash.end()){
                idHash[num] = 1;
            }else{
                ++idHash[num];
            }
        }
        vector<int> ans;
        for( auto num : nums2){
            // 如果有字符,且大于0
            if(idHash.find(num) != idHash.end() && idHash[num] > 0){
                --idHash[num];
                ans.push_back(num);
            }
        }
        return ans;
    }
};

Python

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        idHash = {}
        for num in nums1:
            if not num in idHash:
                idHash[num] = 1
            else:
                idHash[num] += 1
        ans = []
        for num in nums2:
            if num in idHash and idHash[num] > 0:
                idHash[num] -= 1
                ans.append(num)
        return ans

思路2:
对两个数字进行排序,小的前,大的在后。依次比较两个数组的值,如果值相等,则为共同元素。如果数组1的值大于数组2的值,则把数组2的索引加1反之对数组1的索引加1(因为排序的缘故)。
C++

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        vector<int> ans;
        // 排序
        sort(nums1.begin(), nums1.end());
        sort(nums2.begin(), nums2.end());
        
        size_t index1 = 0;
        size_t index2 = 0;
        while( index1 < nums1.size() && index2 <nums2.size()){
            if(nums1[index1] == nums2[index2]){
                ans.push_back(nums1[index1]);
                ++index1;
                ++index2;
                    
            }else if(nums1[index1] > nums2[index2]){
                ++index2;
            }else if(nums1[index1] < nums2[index2]){
                ++index1;
            }
        
        }
        return ans;                 
    }
};
class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        ans = []
        nums1 = sorted(nums1)
        nums2 = sorted(nums2)
        index1 = 0
        index2 = 0
        while index1 < len(nums1) and index2 < len(nums2):
            if nums1[index1] == nums2[index2]:
                ans.append(nums1[index1])
                index1 += 1
                index2 += 1
            elif nums1[index1] < nums2[index2]:
                index1 += 1
            else:
                index2 += 1
        return ans

8.递增的三元子序列

给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列。
数学表达式如下:
如果存在这样的 i, j, k, 且满足 0 ≤ i < j < k ≤ n-1,
使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否则返回 false 。
说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1) 。
示例 1:
输入: [1,2,3,4,5]
输出: true
示例 2:
输入: [5,4,3,2,1]
输出: false

思路1:
参考这位大佬的思路。
思路非常的巧妙。
1.使用两个变量smallmid,用来保存序列的最小值和中间值,且满足关系small
2.遍历数组,遇到的值num,如果比small小,则替换small。如果比mid小,则替换mid如果比mid大,则找到递增序列
3.比较巧妙的地方1.:如果遇到比small小的值,则替换。比如序列small和mid为[4,9],当遇到num=1时,则smallmid则变为[1, 9]。如果不替换这个small,那么接下来遇到2,3,其实是存在递增序列的,但却没有找到,所以需要替换。
4.比较巧妙的地方2.:当[4, 9]被替换成[1, 9]之后,虽然1和9不是序列递增的关系,但是当遇到的nummid(即9)大,仍然是存在递增序列的,这是因为即使我们更新了 small ,这个 smallmid 后面,没有严格遵守递增顺序,但它隐含着的真相是,有一个比 small 大比 mid 小的前·最小值出现在 mid 之前。因此,当后续出现比 mid 大的值的时候,我们一样可以通过当前 smallmid 推断的确存在着长度为 3 的递增序列。 所以,这样的替换并不会干扰后续的计算。
C++

class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        int small = INT_MAX;
        int mid = INT_MAX;
        for(auto num : nums){
            if(num <= small){
                small = num;
            }else if(num <= mid){
                mid = num;
            }else{
                return true;
            }
        }
        return false;
    }
};

Python

class Solution:
    def increasingTriplet(self, nums: List[int]) -> bool:
        small = sys.maxsize
        mid = sys.maxsize
        for num in nums:
            if num <= small:
                small = num
            elif num <= mid:
                mid = num
            else:
                return True
        return False

思路2:
动态规划,每次寻找比当前数小的元素个数,当大于等于3时则退出。
C++

class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        int size = nums.size();
        vector<int> dp(size, 1);
        for(int i = 0; i < nums.size(); ++i)
            for(int j = 0; j < i; ++j){
                // 从左边开始计算比i小的数
                if(nums[j] < nums[i]){
                    dp[i] = max(dp[i], dp[j] + 1);
                }
                if(dp[i] >= 3)
                    return true;
            }
        return false;
    }
};

Python

class Solution:
    def increasingTriplet(self, nums: List[int]) -> bool:
        dp = [1] * len(nums)
        for i in range(len(nums)):
            for j in range(0, i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j] + 1)
                if dp[i] >= 3:
                    return True
        return False

9.搜索二维矩阵 II

见:力扣高频|算法面试题汇总(一):开始之前

10.除自身以外数组的乘积

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)

思路1:

首先这题是不能使用除法的。假设有数组 [ a 0 , a 1 , a 2 , a 3 ] [a_0,a_1,a_2,a_3] [a0,a1,a2,a3],那么得到的答案为: [ a 1 a 2 a 3 , a 0 a 2 a 3 , a 0 a 1 a 3 , a 0 a 1 a 2 ] [a_1a_2a_3,a_0a_2a_3,a_0a_1a_3,a_0a_1a_2] [a1a2a3,a0a2a3,a0a1a3,a0a1a2]。拆分答案,可以得到的数组 [ 1 , a 0 , a 0 a 1 , a 0 a 1 a 2 ] [1,a_0,a_0a_1,a_0a_1a_2] [1,a0,a0a1,a0a1a2] [ a 1 a 2 a 3 , a 2 a 3 , a 3 , 1 ] [a_1a_2a_3,a_2a_3,a_3,1] [a1a2a3,a2a3,a3,1],这两个数组分布通过正序和逆序遍历得到。
时间复杂度: O ( N ) O(N) O(N),其中 N 指的是输入数组的大小。
空间复杂度: O ( N ) O(N) O(N),使用了 L 和 R 数组去构造答案
C++

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        vector<int> temp1(nums.size(), 1);
        vector<int> temp2(nums.size(), 1);
        for(int i = 1; i < nums.size(); ++i){
            temp1[i] = temp1[i - 1] * nums[i - 1];
        }
        for(int i = nums.size() - 2; i > -1; --i){
            temp2[i] = temp2[i + 1] * nums[i + 1];
        }
        for(int i = 0; i < nums.size(); ++i){
            temp1[i] *= temp2[i];
        }
        return temp1;
            
    }
};

Python

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        temp1 = [1] * len(nums)
        temp2 = temp1.copy()
        for i in range(1, len(nums)):
            temp1[i] = temp1[i - 1] * nums[i - 1]
        for i in range(len(nums) - 2, -1, -1):
            temp2[i] = temp2[i + 1] * nums[i + 1]
        for i in range(len(nums)):
            temp1[i] *= temp2[i]
        return temp1

思路2:
由于输出数组不算在空间复杂度内,那么可以将 L 或 R 数组在用输出数组来计算,然后再动态构造另一个。也就是首先得到数组得到的数组 [ 1 , a 0 , a 0 a 1 , a 0 a 1 a 2 ] [1,a_0,a_0a_1,a_0a_1a_2] [1,a0,a0a1,a0a1a2],再使用一个中间变量temptemp = temp * nums[i]来从1开始到 a 1 a 2 a 3 a_1a_2a_3 a1a2a3依次赋值。
C++

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        vector<int> ans(nums.size(), 1);
        for(int i = 1; i < nums.size(); ++i)
            ans[i] = ans[i - 1] * nums[i - 1];
        int temp = 1;
        for(int i = nums.size() -1; i > -1; --i){
            ans[i] = temp * ans[i];
            temp *= nums[i]; 
        }
        return ans;
    }
};

Python

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        ans = [1] * len(nums)
        # 第一次构造
        for i in range(1 , len(nums)):
            ans[i] = ans[i - 1] * nums[i - 1]
        # 使用中间变量,动态构造
        temp = 1
        for i in reversed(range(len(nums))):
            ans[i] = temp * ans[i]
            temp = temp * nums[i]
        return ans

你可能感兴趣的:(力扣)