LeetCode-数组

1.题号628. 三个数的最大乘积

最大三数之积为,三个最大值相乘,或最小两个负数与最大正数相乘的最大值
可以只遍历一次,记录下这五个数,达到O(N)时间复杂度

int maximumProduct(vector<int>& nums) {
        int max1=INT_MIN,max2=INT_MIN,max3=INT_MIN;
        int min1=INT_MAX,min2=INT_MAX,n=nums.size();
        for(int i=0;i<n;i++){
            if(nums[i]>max1){
                max3=max2;
                max2=max1;
                max1=nums[i];
            }else if(nums[i]>max2){
                max3=max2;
                max2=nums[i];
            }else if(nums[i]>max3){
                max3=nums[i];
            }
            if(nums[i]<min1){
                min2=min1;
                min1=nums[i];
            }else if(nums[i]<min2){
                min2=nums[i];
            }
        }
        return max(max1*max2*max3,min1*min2*max1);
    }

2.题号56. 合并区间

要先排序,这样可能合并的区间都相邻了
二维数组默认根据每个数组首元素排序
当右区间大于等于下个左区间时,区间就需要合并,右值为两个右区间最大值(可能出现[0,6],[0,3]的情况),左值还为开始的左区间
因为j会变化,所以用pre和t记录下初始的左右区间

vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end());
        vector<vector<int>> ans;
        int n=intervals.size(),j=0;
        while(j<n){
            int t=intervals[j][1],pre=intervals[j][0];
            j++;
            while(j<n && t>=intervals[j][0]){
                t=max(t,intervals[j][1]);
                j++;
            }
            ans.push_back({pre,t});
        }
        return ans;
    }

3. 题号961. 重复 N 次的元素

占数组一半长度的元素,最大间隔长度为3,如[9,5,6,9],已经是间隔最大的情况了
所以只要判断在他之后的三个字符内有和他一样的就可确定就是他

int repeatedNTimes(vector<int>& A) {
        for (int k = 1; k <= 2; ++k){
            for (int i = 0; i < A.size() - k; ++i){
                if (A[i] == A[i+k]){
                    return A[i];
                }
            }
        }
        return 0;
    }

4. 题号228. 汇总区间

当数组为最后一个元素或与下一个元素不连续时需要进行汇总区间
需要用left记录左区间,左右区间相同让字符串等于当前元素,否则就进行左右区间的拼接

vector<string> summaryRanges(vector<int>& nums) {
        vector<string> ans;
        int left=0,n=nums.size();
        for(int i=0;i<n;i++){
            if(i==n-1 || nums[i]+1<nums[i+1]){
                string s="";
                if(left==i){
                    s=to_string(nums[left]);
                }else{
                    s=to_string(nums[left])+"->"+to_string(nums[i]);
                }
                left=i+1;
                ans.push_back(s);
            }
        }
        return ans;
    }

5. 题号448. 找到所有数组中消失的数字

把所有元素放到正确的位置上,索引就是隐形的桶
正确位置上的 num[i] 应该等于 i+1
注意交换完之后还要再判断交换过来元素的正确位置,所以 i–保持索引不变

vector<int> findDisappearedNumbers(vector<int>& nums) {
        vector<int> ans;
        int n=nums.size();
        for(int i=0;i<n;i++){
            if(nums[nums[i]-1]!=nums[i]){	当对应位置元素不正确时交换
                swap(nums[i],nums[nums[i]-1]);
                --i;
            }    
        }
        for(int i=0;i<n;i++){
            if(nums[i]!=(i+1)){
                ans.push_back(i+1);
            }
        }
        return ans;
    }

6. 题号442. 数组中重复的数据

负数标记法找重复元素,遍历数组,把元素对应索引(nums[i]-1),变为负数,再次遇到相同元素,检测到该位置为负数,就将元素num[i]添加到答案数组
注意数组元素会变为负数,要用数组元素绝对值来访问

还有一种加数组最大值(n)标记法,只有重复元素数值会大于2n

vector<int> findDuplicates(vector<int>& nums) {
        vector<int> ans;
        int len=nums.size(),n;
        for(int i=0;i<len;i++){
            n=abs(nums[i]);
            if(nums[n-1]>0){
                nums[n-1]*= -1;
            }else{
                ans.push_back(n);
            }
        }
        return ans;
    }

7. 128. 最长连续序列

要求n时间复杂度,用set快速判断元素是否存在
只有当数字为整个序列最小数时才进行操作,避免重复计数
向后查询元素,直到没有连续数字为止

int longestConsecutive(vector<int>& nums) {
        unordered_set<int> set;
        for(int num:nums){
            set.insert(num);
        }
        int maxLength=0;
        for(int num:nums){
            if(!set.count(num-1)){	//当数字为序列开始时
                int length=1;
                int curNum=num;
                while(set.count(curNum+1)){	//找下一个数
                    curNum++;
                    length++;
                }
                maxLength=max(maxLength,length);
            }
        }
        return maxLength;
    }

8. 724. 寻找数组的中心索引

爽啊,自己做出来了
先求出总和,中心索引相当于一个天平
pre记录左边之和,没有数据相当于0,当两边总和一样时,返回当前索引

int pivotIndex(vector<int>& nums) {
        if(nums.size()==0) ret,urn -1;
        int sum=0;
        for(int num:nums){
            sum+=num;
        }
        int pre=0;
        for(int i=0;i<nums.size();i++){
            if(pre*2+nums[i]==sum){
                return i;
            }
            pre+=nums[i];
        }
        return -1;
    }

9. 765. 情侣牵手

我的第两百道题,好像有点东西,明明是困难题却又不那么难,765,情侣牵手
每个人的情侣是他与1的异或(最后一位1变0,0变1)
划分成n/2个座位,每次找到他的情侣交换 ,并记录次数+1
也可以用哈希表或数组(索引代表自身,值代表情侣),记录情侣索引,来用时间换空间(不用遍历寻找了)

int minSwapsCouples(vector<int>& row) {
        int ans=0;
        for(int i=0;i<row.size();i+=2){
            int cur=row[i];
            int cp=row[i]^1;
            if(row[i+1]==cp){
                continue;
            }
            int index=i+2;
            while(row[index]!=cp){
                index++;
            }
            swap(row[index],row[i+1]);
            ans++;
        }
        return ans;
    }

注意交换位置后,cp的位置与row[i+1]交换,所以此时couple【row【i+1】】装的是cp的位置

int minSwapsCouples(vector<int>& row) {
        int ans=0;
        vector<int> couple(row.size());
        for(int i=0;i<row.size();i++){
            couple[row[i]]=i;
        }
        for(int i=0;i<row.size()-1;i+=2){
            int cp=row[i]^1;
            if(row[i+1]!=cp){
            	换位置,此时row【i+1】装的是cp的位置
                swap(couple[row[i+1]],couple[cp]);
                所以换i+1和row【i+1】的位置(此时装的就是cp的位置)
                swap(row[i+1],row[couple[row[i+1]]]);
                ans++;
            }
        }
        return ans;
    }

10. 977. 有序数组的平方

双指针法,平方最大值由最小负数或最大正数产生(最左索引与最右索引中产生),每次从二者中选取最大值放到答案数组尾,直到数组遍历完全
也可以找到最后一个负数,这样前后分为两个递增的两个数组,合并这两个数组即可

vector<int> sortedSquares(vector<int>& nums) {
        vector<int> ans(nums.size());
        int left=0,right=nums.size()-1,index=nums.size()-1;
        while(left<=right){
            if(nums[left]*nums[left]>nums[right]*nums[right]){
                ans[index]=nums[left]*nums[left];
                left++;
            }
            else{
                ans[index]=nums[right]*nums[right];
                right--;
            }
            index--;
        }
        return ans;
    }

11. 面试题 17.10. 主要元素

摩尔投票法,让一个数当选,遇到相同的票数+1,不同的票数-1,最后只有大于一半的元素最后才能当选(如果存在)
最后验证选出来的数是否满足条件(1,2,3),3会上位但他不满足条件

int majorityElement(vector<int>& nums) {
        int count=0,cur=-1;
        for(int num:nums){
            if(num==cur){
                count++;
            }
            else if(--count<0){
                cur=num;
                count=1;
            }
        }
        count=0;
        for(int num:nums){
            if(num==cur){
                count++;
            }
        }
        return count>nums.size()/2 ? cur:-1;
    }

12. 229. 求众数 II

两个众数:因为两个众数之间是不进行消耗的,其他数都小于1/3,不可能消耗众数到0
一个众数:众数消耗一次,其他数消耗两次(如{3,1,2,3},遍历到二时123的数量都为0),所以众数也不可能被消耗到0
所以众数就存在与cand1和cand2之中,最后判断是否满足条件即可
计算数量时一定要加else,否则可能会把相同的数加入数组,如{-1,-1,-1},会加入两个-1进答案数组

vector<int> majorityElement(vector<int>& nums) {
        int cand1=-1,cand2=-1,count1=0,count2=0;
        vector<int> ans;
        for(int num:nums){
            if(num==cand1){
                count1++;
            }
            else if(num==cand2){
                count2++;
            }
            else if(count1==0){
                cand1=num;
                count1=1;
            }
            else if(count2==0){
                cand2=num;
                count2=1;
            }
            else{
                count1--;
                count2--;
            }
        }
        count1=0,count2=0;
        for(int num:nums){
            if(num==cand1){
                count1++;
            }
            else if(num==cand2){	一定要加
                count2++;
            }
        }
        if(count1>nums.size()/3) ans.push_back(cand1);
        if(count2>nums.size()/3) ans.push_back(cand2);
        return ans;
    }

13. 1588. 所有奇数长度子数组的和

暴力解法n^3,找规律O(n)
计算这个数被多少个奇数长度数组所包含
一个数组长度要想为奇数,不算他自己,他左右两边数量要为偶数(即左奇右奇,左偶右偶),计算左右两边偶数个奇数个长度次数,进行组合即可
odd奇数,even偶数
左边可能出现元素数量(包含0):i+1
右边可能出现元素数量(包含0):n-i
{0,1,2},偶数次为(left+1)/2
奇数次为left/2,直接向下取整就好

int sumOddLengthSubarrays(vector<int>& arr) {
        int n=arr.size(),sum=0;
        for(int i=0;i<arr.size();i++){
            int left=i+1,right=n-i;
            int lefteven=(left+1)/2,righteven=(right+1)/2;
            int leftood=left/2,rightood=right/2;
            sum+=arr[i]*(lefteven*righteven+leftood*rightood);
        }
        return sum;
    }

14. 832. 翻转图像

只要沾上修改就不用foreach!
只要沾上修改就不用foreach!
只要沾上修改就不用foreach!
修改元素并交换首尾元素
0、1互换用异或

vector<vector<int>> flipAndInvertImage(vector<vector<int>>& A) {
        int n=A[0].size();
        for (int i = 0; i < A.size();i++) {
            for (int j = 0; j < (n + 1) / 2; j++) {
                int temp = (A[i][j] ^ 1);
                A[i][j] = (A[i][n - j - 1] ^ 1);
                A[i][n - j - 1] = temp;
            }
	    }
        return A;
    }

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

不需要与每个元素都进行比较,只需要与第二大的数进行比较,比第二大就意味着比所有数都大
额外添加一个idx变量维护索引,比max直接记录最大索引方便很多

 int dominantIndex(vector<int>& nums) {
        if(nums.size()<2) return 0;
        int max1=-1,max2=-1,idx=0;
        for(int i=0;i<nums.size();i++){
            if(nums[i]>max1){
                max2=max1;
                max1=nums[i];
                idx=i;
            }
            else if(nums[i]>max2){
                max2=nums[i];
            }
        }
        return max1>=2*max2 ? idx:-1;
    }

16. 1304. 和为零的N个唯一整数

如果数量为奇数,先加入0,
然后填入一对一对的相反数
一月结束,210道题,二月明天继续努力!

vector<int> sumZero(int n) {
        int idx=0;
        vector<int> ans(n);
        if(n&1){
            ans[idx++]=0;
        }
        for(int i=1;i<=n/2;i++){
            ans[idx++]=i;
            ans[idx++]=-i;
        }
        return ans;
    }

17. 560. 和为K的子数组

思路好棒,计算前缀和,记录到哈希表中【前缀和,出现次数】,因为有负数所以相同的前缀和可能出现多次
当pre-k存在记录中,说明该位置有map【pre-k】个和为k的子数组
子数组索引区间为【pre-k】索引+1,到当前索引
要先添加map【0】=1,代表前缀和为0的出现1次,当首元素为k时需要用到

    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> map;
        map[0]=1;
        int pre=0,count=0;
        for(int num:nums){
            pre+=num;
            if(map.count(pre-k)){
                count+=map[pre-k];
            }
            map[pre]++;
        }
        return count;
    }

18. 1248. 统计「优美子数组」

前缀和法,记录i个奇数出现次数,sum表示当前奇数个数
当奇数个数差值为k时,证明有preCnt【sum-k】个子数组满足条件
别忘了preCnt[0]=1,防止头元素就满足条件

int numberOfSubarrays(vector<int>& nums, int k) {
        vector<int> preCnt(nums.size()+1);
        preCnt[0]=1;
        int ans=0,sum=0;
        for(int num:nums){
            sum+=num&1;
            preCnt[sum]++;
            if(sum>=k){
                ans+=preCnt[sum-k];
            }
        }
        return ans;
    }

数学法也很妙,记录下每个奇数元素的位置,就可以很方便的求出连续偶数的个数
k个奇数的左右两边可以有若干个连续偶数,计算左右两边连续偶数个数+1(包括0个)之积,就是这一段子数组的个数
…1,1…1…
注意边界条件,相当于-1和n处也为奇数,相当于连续偶数碰到奇数或边界时停止
奇数与边界都是在偶数的左边或右边,所以具有相同的性质

int numberOfSubarrays(vector<int>& nums, int k) {
        int n = (int)nums.size();
        int odd[n + 2], ans = 0, cnt = 0;
        for (int i = 0; i < n; ++i) {
            if (nums[i] & 1) odd[++cnt] = i;
        }
        odd[0] = -1, odd[++cnt] = n;
        for (int i = 1; i + k <= cnt; ++i) {
            ans += (odd[i] - odd[i - 1]) * (odd[i + k] - odd[i + k - 1]); 
        }
        return ans;
    }

19. 41. 缺失的第一个正数

把所有小于n大于0的数都放到正确位置(1放到索引0处),从头遍历,找第一个不等于i+1的数,没找到返回n+1
注意需要交换元素而不是覆盖,也要注意循环条件是(0,n ],毕竟并且交换的两个值不相同

int firstMissingPositive(vector<int>& nums) {
        int n=nums.size();
        for(int i=0;i<n;i++){
            while(nums[i]<=n && nums[i]>0 && nums[nums[i]-1]!=nums[i]){
                swap(nums[nums[i]-1],nums[i]);
            }
        }
        for(int i=0;i<nums.size();i++){
            if(nums[i]!=i+1){
                return i+1;
            }
        }
        return n+1;
    }

20. 66. 加一

等于9有进位,小于9没有进位,当没有进位时代表已经计算完毕,直接返回数组
当数组最高位仍有进位时,在数组首部添加最高位1
不需要额外设置carry变量,只需要判断当前为余10是否为0即可

vector<int> plusOne(vector<int>& digits) {
        int n = digits.size();
        for(int i=n-1;i>=0;i--){
            digits[i]++;
            digits[i] = digits[i] % 10; 
            if(digits[i]>0) return digits;
        }
        digits.insert(digits.begin(),1);
        return digits;
    }

21. 189. 旋转数组

可以构造一个新数组,新位置索引为(i+k)%n

newArr[(i + k) % n] = nums[i];

旋转数组就是把前 k 个数放到数组最前头,第k+1个元素依次后退
例子1234567,k = 3,目标5671234
三次反转,第一次整体翻转7.6.5.4.3.2.1
第二次翻转【0,k-1】达到567在最前头的效果
第三次翻转【k,n-1】达到1234依次后退的效果

void rotate(vector<int>& nums, int k) {
        k%=nums.size();
        rerverse(nums,0,nums.size()-1);
        rerverse(nums,0,k-1);
        rerverse(nums,k,nums.size()-1);
    }
    void rerverse(vector<int>& nums,int left,int right){
        while(left<right){
            swap(nums[left++],nums[right--]);
        }
    }

22. 896. 单调数列

单调数列就是,大于和小于不能同时存在

bool isMonotonic(vector<int>& A) {
        bool a = true,b = true;
        for(int i=0;i<A.size()-1;i++){
            if(A[i]>A[i+1]){
                a = false;
            }
            if(A[i]<A[i+1]){
                b = false;
            }
            if(!a && !b){
                return false;
            }
        }
        return true;
    }

23. 1089. 复写零

第一边遍历找到最后一个需要复写的数,然后从后到前复写数组,就不需要移动元素了
注意最后一个需要复写的元素是否是0,如果是0只能复写一个进去(长度限制)
根据count>n判断,最后一个复写数大于0,count会等于n
i 即为最后一个需要复写数的下标,太妙了太妙了!

void duplicateZeros(vector<int>& arr) {
        int count = 0,n = arr.size(),i = 0;
        for(i = 0; i < n; i++){
            count++;
            if(arr[i] == 0){
                count++;
            }
            if(count >= n){
                break;
            }
        }
        for(int j = n - 1; j >= 0; j--){
            if(arr[i] != 0){
                arr[j] = arr[i];
            }
            else if(arr[i] == 0 && count > n && j == n - 1){
                arr[j] = arr[i];
            }
            else{
                arr[j--] = arr[i];
                arr[j] = arr[i];
            }
            i--;
        }
    }

24. 1275. 找出井字棋的获胜者

太妙了,只需要关注最后一个人,和最后一个棋子就可以,一旦有人赢就会结束比赛,所以最后一个棋子就是致胜棋子,获胜者是最后一个人
通过棋子奇偶数判断是否是最后一个人下的棋子
判断是否在同一斜线的方法:
主对角线 \:横竖坐标之差相同
副对角线 /:横竖坐标之和相同
遍历棋盘后记录与最后一颗棋子同列行斜线的数量判断是否有人获胜

string tictactoe(vector<vector<int>>& moves) {
        vector<vector<int>> vec(3,vector<int>(3,0));
        int rowCount = 0,colCount = 0;
        int mainCount = 0,subCount = 0;
        int last = moves.size() - 1;
        int sum = moves[last][0] + moves[last][1];
        int dif = moves[last][0] - moves[last][1];
        int key = last % 2;
        for(int i = 0; i < moves.size(); i++){
            if(i % 2 == key){
                if(moves[last][0] == moves[i][0]){
                    rowCount++;
                }
                if(moves[last][1] == moves[i][1]){
                    colCount++;
                }
                if(moves[i][0] - moves[i][1] == dif){
                    mainCount++;
                }
                if(moves[i][0] + moves[i][1] == sum){
                    subCount++;
                }
            } 
        }
        if(rowCount == 3 || colCount == 3 || mainCount == 3 ||subCount == 3){
            return key == 0 ? "A" : "B";
        }
        if(moves.size() < 9){
            return "Pending";
        }
        return "Draw";
    }

25. 1260. 二维网格迁移

相当于转换为一维数组,迁移后再转换为二维数组
用双向队列容易操作,把最后 k 个元素放到队列头,

vector<vector<int>> shiftGrid(vector<vector<int>>& grid, int k) {
        deque<int> dque;
        for(int i = 0; i < grid.size(); i++){
            for(int j = 0; j < grid[0].size(); j++){
                dque.push_back(grid[i][j]);
            }
        }
        int index = 0;
        while(index++ < k){
            int temp = dque.back();
            dque.pop_back();
            dque.push_front(temp);
        }
        for(int i = 0; i < grid.size(); i++){
            for(int j = 0; j < grid[0].size(); j++){
                grid[i][j] = dque.front();
                dque.pop_front();
            }
        }
        return grid;
    }

26. 274. H 指数

从小到大排序
至少有size - i篇论文引用量大于等于c[i],所以c[i] >= (size - i)(累计引用数大于论文数)就可得出此时的h

int hIndex(vector<int>& citations) {
        sort(citations.begin(), citations.end());
        for(int i = 0; i < citations.size(); i++){
            int h = citations.size() - i;
            if(h <= citations[i]){
                return h;
            }
        }
        return 0;
    }

得出的是正方形,受限于引用论文数量和论文被引用次数的次数两条边长
计数排序,只需要记录0到n的论文引用数即可,因为n最大也就为论文数量,超过引用次数n也算为n
倒叙遍历桶数组,sum累计(意义为超过当前引用量的次数)与论文数量进行比较,引用次数次数大于论文数量就返回(n每次减一)

int hIndex(vector<int>& citations) {
        int n = citations.size();
        vector<int> bucket(n + 1, 0);
        for(int i = 0; i < n; i++){
            int index = min(citations[i], n);
            bucket[index]++;
        }
        int sum = 0;
        for(int i = n; i >= 0; i--){
            sum += bucket[i];
            if(sum >= n){
                break;
            }
            n--;
        }
        return n;
    }

收获与体会

  1. 注意交换完之后还要再判断交换过来元素是否满足条件
  2. 当数组索引与元素有联系时可以考虑当做隐形的桶使用,节省空间
  3. 负数标记法找重复元素时,注意数组元素会变为负数,要用数组元素绝对值来访问
  4. 如果知道数据的总和,可以用总和-数组当前和,剩下的就是丢失的数字
  5. 只要有修改就不能用foreach!foreach不能修改
  6. 交换首尾元素,遍历到{1,2,3}的1,要想把2也遍历到用i<(n+1)/2
	for(int i=0;i<n/2;i++){	
 	    swap(row[i],row[n-1-i]);
	}
  1. 判断二维数组中元素是否在同一斜线的方法:
    主对角线 \:横竖坐标之差相同
    副对角线 /:横竖坐标之和相同
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值