leetcode-cn | 数组

乘积最大子序列

给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。

示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

使用动态规划的方法,因为数组中由负数的出现,所以要考虑负负得正的情况,要保存最大值和最小值。

class Solution {
public:
    //会出现负数乘负数得正数的情况,使用动态规划的方法
    
    int maxProduct(vector<int>& nums) {
        int len=nums.size();
        vector<vector<int>>dp;
        dp.resize(2);
        dp[0].resize(len);   //dp[0][i]存放以i为结尾的子数组的最大乘积
        dp[1].resize(len);   //dp[1][i]                     最小乘积
        
        int result=nums[0];
        dp[0][0]=nums[0];
        dp[1][0]=nums[0];
        
        for(int i=1;i<len;i++){
            dp[0][i]=max(dp[0][i-1]*nums[i],dp[1][i-1]*nums[i],nums[i]);
            dp[1][i]=min(dp[0][i-1]*nums[i],dp[1][i-1]*nums[i],nums[i]);
            result = max(dp[0][i],dp[1][i],result);
        }
        
        return result;
    }
    
    int max(int a,int b,int c){
        int temp=a;
        if(b>temp)
            temp=b;
        if(c>temp)
            temp=c;
        return temp;
    }
    
    int min(int a,int b,int c){
        int temp=a;
        if(b<temp)
            temp=b;
        if(c<temp)
            temp=c;
        return temp;
    }
};

旋转数组

给定一个数组,将数组中的元素向右移动 个位置,其中 是非负数。

示例 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) 的原地算法。

第一个方法,空间复杂度不为 o(1)

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        vector<int> res(nums.size());
        for(int i=0;i<nums.size();i++){
            int pos=(i+k)%nums.size();
            res[pos]=nums[i];
        }
        nums.assign(res.begin(),res.end());
    }
};

第二种方法就是从网上看到的方法,观察旋转时候的数组会发现最终的结果是先旋转前n-k个元素,然后旋转后n个元素,最后旋转整个数组得到的。

1 2 3 4 5 6 7       k=3

4 3 2 1 5 6 7

4 3 2 1 7 6 5

5 6 7 1 2 3 4

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        //先把前n-k个数翻转一下,再把后k个数翻转一下,再把整个数组翻转一下
        k=k%nums.size();   //可能会出现要旋转的长度比数组要长,例如 nums=[-1],k=2
        reverse(nums.begin(),nums.begin()+(nums.size()-k));
        reverse(nums.begin()+(nums.size()-k),nums.end());
        reverse(nums.begin(),nums.end());
    }
};

第三种方法,旋转相当于是把最前面的元素插入到最后面,最后显示超时了,不知道为什么,可能是插入删除操作太费时了?

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        k=k%nums.size();
        for(int i=0;i<nums.size()-k;i++){
            nums.push_back(nums[0]);
            nums.erase(nums.begin());
        }
    }
};

存在重复元素

给定一个整数数组,判断是否存在重复元素。

如果任何值在数组中出现至少两次,函数返回 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

第一种非常直接的思路,将数组排序,速度还不错

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        if(nums.size()==1||nums.size()==0)
            return false;
        sort(nums.begin(),nums.end());
        for(int i=1;i<nums.size();i++){
            if(nums[i]==nums[i-1])
                return true;
        }
        return false;
    }
};

移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数

最初的暴力的想法是直接将数组遍历,将0向后移动。但是总是觉得有些暴力,记录0的个数,似乎也行不通。还是选择用最暴力的方式。

也很清晰,写算法首先是要把它写出来吧,之后再要求效率之类的。不要将自己陷在一种马上要找出很好的算法的情况下。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        //如果实在是想不出其他方法就先用暴力方法算一下
        int count=0;        
        for(int i=0;i<nums.size();i++){
            if(nums[i]==0){
                for(int j=i+1;;j++){
                    if(j==nums.size())
                        break;
                    if(nums[j]!=0){
                        nums[i]=nums[j];
                        nums[j]=0;
                        break;
                    }
                }
            }
        }
    }
};

看到另一种解法,但实际上感觉操作的次数实际上一样的。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int k = 0;  //数组中索引为[0,k)的均为非零元素
        for(int i = 0; i < nums.size(); i++){
            if(nums[i]  != 0){
                if(k != i)
                    swap(nums[i],nums[k]);
                k++;
            }
        }
    }
};

打乱数组

打乱一个没有重复元素的数组。
示例:
// 以数字集合 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();
class Solution {
private:
    //会出现连续执行很多遍shuffle的情况,所以在每次shuffle之后都需要恢复
    vector<int> numsstatic;
    vector<int> nums2;
public:
    Solution(vector<int>& nums) {
        nums2=nums;
        numsstatic=nums;
    }
    
    int random(int num){
        //定义产生指定范围的随机数的函数
        return rand()%num;
    }
    
    /** Resets the array to its original configuration and return it. */
    vector<int> reset() {
        //printf("reset");
        nums2=numsstatic;
        return numsstatic;
    }
    
    /** Returns a random shuffling of the array. */
    vector<int> shuffle() {
        vector<int> res;
        for(int i=nums2.size();i>0;i--){
            int rand=random(i);
            res.insert(res.begin(),nums2[rand]);
            //printf("%d",&nums2[rand]);
            nums2.erase(nums2.begin()+rand);
        }
        nums2=numsstatic;
        return res;
    }
};

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

看了一下其他的解答,发现自己对这个题目的理解有些偏差,题目要求的是生成的结果要有完全的随机性,(可能需要数学证明吧,菜鸟只能是凭感觉了QAQ),如果应用在抽奖等基数很大的情况下,不是完全随机的话,是很有问题的。

解决这个问题的经典的方法是 Fisher–Yates shuffle 洗牌算法,思路和自己之前写的是相同的,但是直接往对应的位置放就可以了,不需要单独占用空间来存。

参考:https://www.zhihu.com/question/68330851/answer/266506621

class Solution {
private:
    vector<int> nochange;
    vector<int> change;

public:
    Solution(vector<int>& nums) {
        nochange=nums;
        change=nums;
    }
    
    int random(int num){
        //定义产生指定范围的随机数的函数
        //C++中产生随机数的方式是rand(),需要 #include<stdlib.h>
        //产生0 ~ num-1的随机数
        return rand()%num;
    }
    
    /** Resets the array to its original configuration and return it. */
    vector<int> reset() {
        return nochange;
    }
    
    /** Returns a random shuffling of the array. */
    vector<int> shuffle() {
        change=nochange;
        for(int i=change.size()-1;i>0;i--){
            int index=random(i+1);  //需要从最后一个开始,否则还依旧不具有随机性
            int temp=change[i];
            change[i]=change[index];
            change[index]=temp;
        }
        return change;
    }
};

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

两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。
示例 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 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
#include<algorithm>

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        vector<int> res;     
        sort(nums1.begin(),nums1.begin()+nums1.size());
        sort(nums2.begin(),nums2.begin()+nums2.size());
        int i1=0;
        int i2=0;

        while(i1!=nums1.size()&&i2!=nums2.size()){
            if(nums1[i1]==nums2[i2]){
                res.insert(res.begin(),nums1[i1]);
                i1++;
                i2++;
            }
            else if(nums1[i1]<nums2[i2]){
                i1++;
            }
            else if(nums1[i1]>nums2[i2]){
                i2++;
            }
            
        }
        
        return res;        
    }
};

另一种方法,使用map,或许题目中的对顺序没有要求就暗示了使用map呢,在一些元素要/不要同时出现之类的情况中可以考虑以下使用map是否可以。

#include<algorithm>

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        vector<int> res;     
        map<int,int> container;
        for(int i=0;i<nums1.size();i++){
            if(container.find(nums1[i])==container.end()){
                //如果没有这个元素
                container[nums1[i]]=1;
            }
            else{
                container[nums1[i]]+=1;
            }
        }
        
        for(int i=0;i<nums2.size();i++){
            if(container.find(nums2[i])!=container.end()){
                //nums1中也有这个元素出现
                res.insert(res.begin(),nums2[i]);
                container[nums2[i]]-=1;
                if(container[nums2[i]]==0){
                    container.erase(nums2[i]);//用关键字进行删除
                }
            }
        }
        
        return res;
    }
};

递增的三元子序列

给定一个未排序的数组,判断这个数组中是否存在长度为 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

题目的难点在于时间和空间复杂性的限制,同时否定了暴力遍历和动态规划的方式。

设置2个变量,分别记录遍历过程中的第一小和出现在第一小之后的第二小。

感叹一下,解法相当巧妙~

class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        //空间复杂度为1,直接否定掉了动态规划
        //C++取最大值
        int min1=INT_MAX;
        int min2=INT_MAX;
        
        for(int i=0;i<nums.size();i++){
            if(nums[i]<min1)
                min1=nums[i];
                //min1记录最小的值
            if(nums[i]>min1&&nums[i]<min2)
                min2=nums[i];
                //min2记录min1之后比min1小的最小值
                //对min2进行了赋值之后就解决了如果说min2之后又出现了比min2小的值的情况,min2已经记录下了之前的状态,与此同时还能再去继续寻找合适的min1
            if(nums[i]>min2)
                return true;
        }
        
        return false;
    }
};

搜索二维数组 ii

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。
示例:
现有矩阵 matrix 如下:
[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。

看到题目最初的想法是使用递归,但是用递归的话长度大一些就会超时。

在网上看到的方法是:

1.由于数组中是有序的,所以对每一行进行二分查找

class Solution {
public:
    bool find(vector<vector<int>>& matrix,int target,int row){
        printf("find\n");
        int low=0;
        int high=matrix[0].size()-1;  //从0到尺寸减1
        int mid;
        while(low<=high){
            mid=(low+high)/2;
            if(target==matrix[row][mid])
                return true;
            else if(target>matrix[row][mid])
                low=mid+1;
            else
                high=mid-1;
        }
        return false;
    }
    
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if(matrix.size()<1)
            return false;
        if(matrix[0].size()<1)
            return false;
        for(int i=0;i<matrix.size();i++){
            if(find(matrix,target,i))
                return true;
        }
        return false;
    }
    
};

2.从最后一行开始,不断向右上角缩小数组。要充分利用好数组已经排好序的这个条件

class Solution {
public:    
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if(matrix.size()<1)
            return false;
        if(matrix[0].size()<1)
            return false;
        int i=matrix.size()-1;
        int j=0;
        while(i>=0&&j<=matrix[0].size()-1){
        //while(i!=0&&j!=matrix[0].size()-1){  最开始是这样写的,但是如果说只有一个数字的话就是不行的
        //输入为 [[-5]],-5 的时候 结果就是错的,不要偷懒哦
            if(matrix[i][j]==target)
                return true;
            else if(matrix[i][j]>target){
                i--;
            }
            else if(matrix[i][j]<target){
                j++;
            }
        }
        return false;
    }
};

除自身以外数组的乘积

给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)

没有想出来,在网上找的解法,很笨了

联想到了高数里的夹逼定理(虽然他们之间并没有什么关联orz

有了思路之后这道题写起来还是蛮快的

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        ///很有意思哎。从右往左,从左往右,夹了中间的
        vector<int> left(nums.size());
        vector<int> right(nums.size());
        int temp=1;
        for(int i=0;i<nums.size();i++){
            temp=temp*nums[i];
            //left.push_back(temp);
            left[i]=temp;
            printf("left %d:%d\n",i,temp);
        }
        temp=1;
        for(int i=nums.size()-1;i>=0;i--){
            temp=temp*nums[i];
            //right.insert(right.begin(),temp);
            right[i]=temp;
            printf("right %d:%d\n",i,temp);
        }
        
        vector<int>res(nums.size());
        for(int i=0;i<nums.size();i++){
            if(i-1>=0&&i+1<nums.size()){
                res[i]=left[i-1]*right[i+1];
            }
            else if(i-1<0){
                res[i]=right[i+1];
            }
            else if(i+1>=nums.size()){
                res[i]=left[i-1];
            }            
        }
        return res;
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值