【数据结构C++】数组与矩阵(一)

1. 数组与矩阵

在这里插入图片描述
在这里插入图片描述

  • 把数组中的 0 移到末尾:https://leetcode-cn.com/problems/move-zeroes/

    给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
    示例:
    输入: [0,1,0,3,12]
    输出: [1,3,12,0,0]
    说明:
    必须在原数组上操作,不能拷贝额外的数组。
    尽量减少操作次数。
    
    方法:双指针
    使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。
    右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。
    注意到以下性质:
    左指针左边均为非零数;
    右指针左边直到左指针处均为零。
    因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。
    
    class Solution {
    public:
        void moveZeroes(vector<int>& nums) {
            int n = nums.size(), left = 0, right = 0;
            while (right < n) {
                if (nums[right]) {
                    swap(nums[left], nums[right]);
                    left++;
                }
                right++;
            }
        }
    };
    复杂度分析
    时间复杂度:O(n),其中n为序列长度。每个位置至多被遍历两次。
    空间复杂度:O(1)。只需要常数的空间存放若干变量。
    
  • 改变矩阵维度: https://leetcode-cn.com/problems/reshape-the-matrix/

    在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,
    但保留其原始数据。
    给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c ,分别表示想要的重构的矩阵的行数和列数。
    重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。
    如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。
    

    在这里插入图片描述

    方法:二维数组的一维表示
    

    在这里插入图片描述

    class Solution {
    public:
        // vector<vector<int>>  创建一个二维容器
        vector<vector<int>> matrixReshape(vector<vector<int>>& nums, int r, int c) {
    	    int m = mat.size();
            int n = mat[0].size();
            if (m * n != r * c) {
                return mat;
            }
    
            vector<vector<int>> ans(r, vector<int>(c));
            for (int x = 0; x < m * n; ++x) {
                ans[x / c][x % c] = mat[x / n][x % n];
            }
            return ans;
        }
    };
    复杂度分析:
    时间复杂度:O(rc)O(rc)。这里的时间复杂度是在重塑矩阵成功的前提下的时间复杂度,否则当 mn / rc
    时,C++ 语言中返回的是原数组的一份拷贝,本质上需要的时间复杂度为 O(mn),而其余语言可以直接返回原数	 
    组的对象,需要的时间复杂度仅为 O(1)。
    空间复杂度:O(1)。这里的空间复杂度不包含返回的重塑矩阵需要的空间。
    
  • 找出数组中最长的连续 1: https://leetcode-cn.com/problems/max-consecutive-ones/

    给定一个二进制数组, 计算其中最大连续 1 的个数。
    
    示例:
    输入:[1,1,0,1,1,1]
    输出:3
    解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3.
    
    方法:一次遍历
    为了得到数组中最大连续 11 的个数,需要遍历数组,并记录最大的连续 11 的个数和当前的连续 11 的个数。
    如果当前元素是 11,则将当前的连续 11 的个数加 11,否则,使用之前的连续 11 的个数更新最大的连续 11 的个数,
    并将当前的连续 11 的个数清零。
    
    遍历数组结束之后,需要再次使用当前的连续 11 的个数更新最大的连续 11 的个数,
    因为数组的最后一个元素可能是 11,且最长连续 11 的子数组可能出现在数组的末尾,
    如果遍历数组结束之后不更新最大的连续 11 的个数,则会导致结果错误。
    
    class Solution {
    public:
        // 创建一个一维的数组,并以引用&的方式。
        int findMaxConsecutiveOnes(vector<int>& nums) {
            int maxCount = 0, count = 0;
            int n = nums.size();
            for (int i = 0; i < n; i++) {
                if (nums[i] == 1) {
                    count++;
                } else {
                    maxCount = max(maxCount, count);
                    count = 0;
                }
            }
            maxCount = max(maxCount, count);
            return maxCount;
        }
    };
    复杂度分析:
    时间复杂度:O(n)O(n),其中 nn 是数组的长度。需要遍历数组一次。
    空间复杂度:O(1)O(1)
  • 有序矩阵查找: https://leetcode-cn.com/problems/search-a-2d-matrix-ii/

    编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
    
    每行的元素从左到右升序排列。
    每列的元素从上到下升序排列。
    

    在这里插入图片描述
    在这里插入图片描述

    //方法一:直接查找
    //思路与算法
    //我们直接遍历整个矩阵 matrix,判断target是否出现即可。
    class Solution {
    public:
        bool searchMatrix(vector<vector<int>>& matrix, int target) {
            for (const auto& row: matrix) {
                for (int element: row) {
                    if (element == target) {
                        return true;
                    }
                }
            }
            return false;
        }
    };
    复杂度分析:
    时间复杂度:O(mn)。
    空间复杂度:O(1)
    //方法二:二分查找
    //思路与算法:
    //由于矩阵 matrix中每一行的元素都是升序排列的,因此我们可以对每一行都使用一次二分查找,判断 target 是否在该行中,从而判断 target是否出现。
    class Solution {
    public:
        bool searchMatrix(vector<vector<int>>& matrix, int target) {
            for (const auto& row: matrix) {
                auto it = lower_bound(row.begin(), row.end(), target);
                if (it != row.end() && *it == target) {
                    return true;
                }
            }
            return false;
        }
    };
    复杂度分析
    时间复杂度:O(mlogn)。对一行使用二分查找的时间复杂度为 O(logn),最多需要进行 mm 次二分查找。
    空间复杂度:O(1)
  • 有序矩阵中第 K 小的元素:

    给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
    请注意,它是 排序后 的第 k 小元素,而不是第 k 个不同的元素。
    
    示例 1:
    输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
    输出:13
    解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13
    
    示例 2:
    输入:matrix = [[-5]], k = 1
    输出:-5
    
    //方法一:直接排序
    //思路及算法
    //最直接的做法是将这个二维数组转成一维数组,并对该一维数组进行排序。最后这个一维数组中的第k个数即为答案。
    class Solution {
    public:
        int kthSmallest(vector<vector<int>>& matrix, int k) {
            vector<int> rec;
            for (auto& row : matrix) {
                for (int it : row) {
                    rec.push_back(it);
                }
            }
            sort(rec.begin(), rec.end());
            return rec[k - 1];
        }
    };
    
    复杂度分析
    时间复杂度:O(n^2*logn)对 n^2个数排序。
    空间复杂度:O(n^2),一维数组需要存储这 n^2个数。
    
  • 错误的集合: https://leetcode-cn.com/problems/set-mismatch/

    集合 s 包含从 1 到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个数字复制了成了集合里面的另外一个数字的值,
    导致集合丢失了一个数字并且有一个数字重复 。
    
    给定一个数组 nums 代表了集合 S 发生错误后的结果。
    请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。
    
    示例 1:
    输入:nums = [1,2,2,4]
    输出:[2,3]
    
    示例 2:
    输入:nums = [1,1]
    输出:[1,2]
    
    //方法一:排序
    //将数组排序之后,比较每对相邻的元素,即可找到错误的集合。
    //寻找重复的数字较为简单,如果相邻的两个元素相等,则该元素为重复的数字。
    //寻找丢失的数字相对复杂,可能有以下两种情况:
      //如果丢失的数字大于 1 且小于 n,则一定存在相邻的两个元素的差等于 2,这两个元素之间的值即为丢失的数字;
      //如果丢失的数字是 1 或 n,则需要另外判断。
    //为了寻找丢失的数字,需要在遍历已排序数组的同时记录上一个元素,然后计算当前元素与上一个元素的差。考虑到丢失的数字可能是 1,
    //因此需要将上一个元素初始化为 0。
      //当丢失的数字小于 n 时,通过计算当前元素与上一个元素的差,即可得到丢失的数字;
      //如果 nums[n-1] != n ,则丢失的数字是n。
    
    class Solution {
    public:
        vector<int> findErrorNums(vector<int>& nums) {
            vector<int> errorNums(2);
            int n = nums.size();
            sort(nums.begin(), nums.end());
            int prev = 0;
            for (int i = 0; i < n; i++) {
                int curr = nums[i];
                if (curr == prev) {
                    errorNums[0] = prev;
                } else if (curr - prev > 1) {
                    errorNums[1] = prev + 1;
                }
                prev = curr;
            }
            if (nums[n - 1] != n) {
                errorNums[1] = n;
            }
            return errorNums;
        }
    };
    复杂度分析
    时间复杂度:O(nlogn),其中 n 是数组nums的长度。排序需要O(nlogn) 的时间,遍历数组找到错误的集合需要 O(n) 的时间,因此总时间复杂度是 O(nlogn)。
    空间复杂度:O(logn),其中 n 是数组nums的长度。排序需要O(logn) 的空间。
    
  • 寻找重复数: https://leetcode-cn.com/problems/find-the-duplicate-number/

    给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。
    假设 nums 只有 一个重复的整数 ,找出这个重复的数 。
    你设计的解决方案必须不修改数组 nums 且只用常量级 O(1) 的额外空间。
    示例 1:
    输入:nums = [1,3,4,2,2]
    输出:2
    
    示例 2:
    输入:nums = [3,1,3,4,2]
    输出:3
    
    示例 3:
    输入:nums = [1,1]
    输出:1
    
    示例 4:
    输入:nums = [1,1,2]
    输出:1
    
    方法一:二分查找
    思路和算法
    我们定义 cnt[i] 表示nums 数组中小于等于 i 的数有多少个,假设我们重复的数是target,
    那么[1,target−1]里的所有数满足 cnt[i]≤i,[target,n] 里的所有数满足cnt[i]>i,具有单调性。
    对于所有测试用例,考虑以下两种情况:
    
    如果测试用例的数组中target 出现了两次,其余的数各出现了一次,这个时候肯定满足上文提及的性质,
    因为小于target 的数 i 满足 cnt[i]=i,大于等于 target 的数 j 满足cnt[j]=j+1。
    
    如果测试用例的数组中target 出现了三次及以上,那么必然有一些数不在nums 数组中了,
    这个时候相当于我们用 target 去替换了这些数,我们考虑替换的时候对 cnt[] 数组的影响。
    如果替换的数 i 小于 target ,那么 [i,target−1] 的 cnt 值均减一,其他不变,满足条件。
    如果替换的数 j 大于等于 target,那么 [target,j−1] 的 cnt 值均加一,其他不变,亦满足条件。
    
    class Solution {
    public:
        int findDuplicate(vector<int>& nums) {
            int n = nums.size();
            int l = 1, r = n - 1, ans = -1;
            while (l <= r) {
                int mid = (l + r) >> 1;  //  mid = (l + r) / 2
                int cnt = 0;
                for (int i = 0; i < n; ++i) {
                    cnt += nums[i] <= mid;
                }
                if (cnt <= mid) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                    ans = mid;
                }
            }
            return ans;
        }
    };
    复杂度分析
    时间复杂度:O(nlogn),其中 n 为nums 数组的长度。二分查找最多需要二分 O(logn) 次,
    每次判断的时候需要O(n) 遍历 nums 数组求解小于等于mid 的数的个数,因此总时间复杂度为O(nlogn)。
    空间复杂度:O(1)。我们只需要常数空间存放若干变量。
    
  • 优美的排列 II: https://leetcode-cn.com/problems/beautiful-arrangement-ii/

    给你两个整数 n 和 k ,请你构造一个答案列表 answer ,该列表应当包含从 1 到 n 的 n 个不同正整数,并同时满足下述条件:
    
    假设该列表是 answer = [a1, a2, a3, ... , an] ,那么列表 [|a1 - a2|, |a2 - a3|, |a3 - a4|, ... , |an-1 - an|]
    中应该有且仅有 k 个不同整数。
    返回列表 answer 。如果存在多种答案,只需返回其中任意一种 。
    
    示例 1:
    
    输入:n = 3, k = 1
    输出:[1, 2, 3]
    解释:[1, 2, 3] 包含 3 个范围在 1-3 的不同整数,并且 [1, 1] 中有且仅有 1 个不同整数:1
    
    示例 2:
    输入:n = 3, k = 2
    输出:[1, 3, 2]
    解释:[1, 3, 2] 包含 3 个范围在 1-3 的不同整数,并且 [2, 1] 中有且仅有 2 个不同整数:12
    
    方法:构造
    在前k+1个数构造k个差值的数组,从k+1之后直接全部为递增的序列;
    k个差值的数组为 1、k+12、k、3、k-1……
    
    class Solution {
    public:
        vector<int> constructArray(int n, int k) {
            vector<int>ans(n);
            int num1=1,num2=k+1,i=0;
            while(num1<=num2){
                if(num1==num2){
                    ans[i++]=num1;
                    break;
                }
                ans[i++]=num1;
                ans[i++]=num2;
                num1++;
                num2--;
            }
            while(i<n){
                ans[i++]=i+1;
            }
            return ans;     
        }
    };
    
  • 数组的度:https://leetcode-cn.com/problems/degree-of-an-array/

    给定一个非空且只包含非负数的整数数组 nums,数组的度的定义是指数组里任一元素出现频数的最大值。
    你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。
    
    示例 1:
    输入:nums = [1,2,2,3,1]
    输出:2
    解释:
    输入数组的度是 2 ,因为元素 12 的出现频数最大,均为 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
    方法一:哈希表
    思路及解法
    记原数组中出现次数最多的数为 x,那么和原数组的度相同的最短连续子数组,必然包含了原数组中的全部 x,
    且两端恰为x第一次出现和最后一次出现的位置。
    
    因为符合条件的x可能有多个,即多个不同的数在原数组中出现次数相同。
    所以为了找到这个子数组,我们需要统计每一个数出现的次数,
    同时还需要统计每一个数第一次出现和最后一次出现的位置。
    
    在实际代码中,我们使用哈希表实现该功能,每一个数映射到一个长度为3的数组,
    数组中的三个元素分别代表这个数出现的次数、这个数在原数组中第一次出现的位置和这个数在原数组中最后一次出现的位置。
    当我们记录完所有信息后,我们需要遍历该哈希表,找到元素出现次数最多,且前后位置差最小的数。
    
    class Solution {
    public:
        int findShortestSubArray(vector<int>& nums) {
            unordered_map<int, vector<int>> mp;
            int n = nums.size();
            for (int i = 0; i < n; i++)
            {   
                if (mp.count(nums[i]))    // count统计某个值的个数 
                {
                    mp[nums[i]][0]++;
                    mp[nums[i]][2] = i;
                } 
                else 
                {
                    mp[nums[i]] = {1, i, i};
                }
            }
            int maxNum = 0, minLen = 0;
            for (auto& [_, vec] : mp) 
            {
                if (maxNum < vec[0]) 
                {
                    maxNum = vec[0];
                    minLen = vec[2] - vec[1] + 1;
                } 
                else if (maxNum == vec[0])
                {
                    if (minLen > vec[2] - vec[1] + 1) 
                    {
                        minLen = vec[2] - vec[1] + 1;
                    }
                }
            }
            return minLen;
        }
    };
    注:unordered_map和map类似,都是存储的key-value的值,可以通过key快速索引到value。
    不同的是unordered_map不会根据key的大小进行排序,存储时是根据key的hash值判断元素是否相同,
    即unordered_map内部元素是无序的。unordered_map的底层是一个防冗余的哈希表(开链法避免地址冲突)。
    unordered_map的key需要定义hash_value函数并且重载operator ==。
    哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,时间复杂度为O(1);而代价仅仅是消耗比较多的内存。
    
    复杂度分析
    时间复杂度:O(n),其中 n 是原数组的长度,我们需要遍历原数组和哈希表各一次,它们的大小均为 O(n)。
    空间复杂度:O(n),其中 n 是原数组的长度,最坏情况下,哈希表和原数组等大。
    
  • 托普利茨矩阵: https://leetcode-cn.com/problems/toeplitz-matrix/添加链接描述

    给你一个 m x n 的矩阵 matrix 。如果这个矩阵是托普利茨矩阵,返回 true ;否则,返回 false 。
    如果矩阵上每一条由左上到右下的对角线上的元素都相同,那么这个矩阵是 托普利茨矩阵 。
    

    在这里插入图片描述

    方法一:遍历
    根据定义,当且仅当矩阵中每个元素都与其左上角相邻的元素(如果存在)相等时,该矩阵为托普利茨矩阵。因此,我们遍历该矩阵,将每一个元素和它左上角的元素相比对即可。
    
    class Solution {
    public:
        bool isToeplitzMatrix(vector<vector<int>>& matrix) {
            int m = matrix.size(), n = matrix[0].size();
            for (int i = 1; i < m; i++) {
                for (int j = 1; j < n; j++) {
                    if (matrix[i][j] != matrix[i - 1][j - 1]) {
                        return false;
                    }
                }
            }
            return true;
        }
    };
    
    复杂度分析
    时间复杂度:O(mn),其中 mm 为矩阵的行数,n 为矩阵的列数。矩阵中每个元素至多被访问两次。
    空间复杂度:O(1),我们只需要常数的空间保存若干变量。
    
  • 数组嵌套: https://leetcode-cn.com/problems/array-nesting/

    索引从0开始长度为N的数组A,包含0到N - 1的所有整数。找到最大的集合S并返回其大小,其中 S[i] = {A[i], A[A[i]], A[A[A[i]]], ... }且遵守以下的规则。
    
    假设选择索引为i的元素A[i]为S的第一个元素,S的下一个元素应该是A[A[i]],之后是A[A[A[i]]]... 以此类推,不断添加直到S出现重复的元素。
    
    示例 1:
    输入: A = [5,4,0,3,1,6,2]
    输出: 4
    解释: 
    A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2.
    
    其中一种最长的 S[K]:
    S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0}
    
    方法:
    用一个bool类型数组vector来保存某个位置是否可以访问,初始化全设置为true,访问过后设置为false下一次不可以再访问。
    
    class Solution {
    public:
        int arrayNesting(vector<int>& nums) 
        {
            //初始化res为0,用于存放最终结果
            int res = 0;
            int N = nums.size();//nums的大小
            //定义一个bool类型的vector容器,初始化为N个true
            //表示vector内的相对应nums下标的数组元素没有重复,可以访问
            vector<bool> vistied(N,true);
            for(int i =0; i< N;i++)
            {
                int path = 0;//用于存放当前结果
                //当vector内对应的位置的元素没有访问过时进行循环体内的操作
                while(vistied[i])
                {
                    vistied[i] = false;//i位置的元素已经访问过,标记为false以后不能再访问
                    path += 1;
                    i = nums[i];//下一个i为题中给定的条件nums[i]
                }
                res = max(res,path);//返回当前结果与上一步结果中的最大值
            }
            return res;
        }
    };
    
  • 最多能完成排序的块: https://leetcode-cn.com/problems/max-chunks-to-make-sorted/

    数组arr是[0, 1, ..., arr.length - 1]的一种排列,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。我们最多能将数组分成多少块?
    示例 1:
    
    输入: arr = [4,3,2,1,0]
    输出: 1
    解释:
    将数组分成2块或者更多块,都无法得到所需的结果。
    例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。
    
    示例 2:
    输入: arr = [1,0,2,3,4]
    输出: 4
    解释:
    我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。
    然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。
    
    方法一: 暴力
    思路和算法
    首先找到从左块开始最小块的大小。如果前 k 个元素为 [0, 1, ..., k-1],可以直接把他们分为一个块。当我们需要检查 [0, 1, ..., n-1] 中前 k+1 个元素是不是 [0, 1, ..., k] 的时候,只需要检查其中最大的数是不是 k 就可以了。
    
    class Solution {
    public:
        int maxChunksToSorted(vector<int>& arr) {
            int ans= 0, maxNum = 0;
            int arrSize = arr.size();
            for(int i = 0; i<arrSize; i++)
            {
                maxNum = max(maxNum, arr[i]);
                if(maxNum == i)
                {
                    ans++;
                }
            }
            return ans;
        }
    };
    
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只搬烫手的砖

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值