数组相关题目总结

33.搜索旋转排序数组

这题如果数组不进行循环平移那用二分做就很简单,平移后其实也可以用二分做,重点在于二分里面如何check。平移后数组可以分成两段各自升序的数组,并且第一段值大于第二段值。check的时候分两种情况,一种是target >= nums[0],这时候target只可能出现在第一段上,所以二分到第二段时可以直接return true。第二种情况是target < nums[0],这时候target只可能出现在第二段上,所以二分到第一段时可以直接return false。

class Solution {
public:
    int search(vector& nums, int target) {
        int ans = -1, l = 0, r = nums.size()-1;
        while(l <= r){
            int mid = l+r>>1;
            if(target >= nums[0]){
                if(nums[mid] >= target || nums[mid] < nums[0]){
                    ans = mid;
                    r = mid-1;
                }
                else l = mid+1;
            }
            else{
                if(nums[mid] >= target && nums[mid] < nums[0]){
                    ans = mid;
                    r = mid-1;
                }
                else l = mid+1;
            }
        }
        if(ans == -1 || nums[ans] != target) return -1;
        return ans;
    }
};

31.下一个排列

这道题可以找找规律,53421->54123以及54132->54213这两个例子可以看出来每个排列尾部总有一个降序序列,然后这个降序序列左边第一个数字一定会被改变,并且是与降序序列中某个数字进行了交换,交换以后后面的降序序列需要重新排序为升序。

class Solution {
public:
    void nextPermutation(vector& nums) {
        /*
        53421
        54123
        54132
        54213
        54231
        */
        int index = nums.size()-1;
        while(index > 0 && nums[index] <= nums[index-1])
            index--;
        if(index == 0){
            sort(nums.begin(), nums.end());
            return;
        }
        for(int i = nums.size()-1; i >= 0; i--){
            if(nums[i] > nums[index-1]){
                swap(nums[i], nums[index-1]);
                sort(nums.begin()+index, nums.end());
                break;
            }
        }

    }
};

88.合并两个有序数组

简单的归并操作。

class Solution {
public:
    void merge(vector& nums1, int m, vector& nums2, int n) {
        vector a(nums1.size());
        int idx1 = 0, idx2 = 0;
        for(int i = 0; i < a.size(); i++){
            if(idx1 == m) a[i] = nums2[idx2++];
            else if(idx2 == n) a[i] = nums1[idx1++];
            else if(nums1[idx1] > nums2[idx2]) a[i] = nums2[idx2++];
            else a[i] = nums1[idx1++];
        }
        nums1 = a;
    }
};

56.合并区间

比较经典的题目了,按左端点排序,然后遍历一遍区间,维护出当前合并区间的最右端点,如果某个新区间左端点大于当前的最右端点,那就没法合并了,反之可以合并进去。

class Solution {
public:
    vector> merge(vector>& intervals) {
        sort(intervals.begin(), intervals.end());
        vector> res;
        int mxr = -0x3f3f3f3f, mnl = 0x3f3f3f3f;
        for(int i = 0; i < intervals.size(); i++){
            if(intervals[i][0] <= mxr){
                mxr = max(mxr, intervals[i][1]);
            }
            else{
                if(mxr != -0x3f3f3f3f) res.push_back({mnl, mxr});
                mnl = intervals[i][0];
                mxr = intervals[i][1];
            }
        }
        res.push_back({mnl, mxr});
        return res;
    }
};

162.寻找峰值

如果只有一个峰值那就是三分,但这里有多个峰值。其实二分也能做,根据mid左右的值来判断收缩到哪个区间,如果mid-1、mid、mid+1位置的值是递增的,那选择右区间更好一些,如果是递减的那就左区间。

class Solution {
public:
    int findPeakElement(vector& nums) {
        int l = 0, r = nums.size()-1;
        while(l <= r){
            int mid = l+r>>1;
            int lnum = mid?nums[mid-1]:0x80000000;
            int rnum = mid= lnum && nums[mid] >= rnum)
                return mid;
            else if(nums[mid] >= lnum && nums[mid] <= rnum)
                l = mid+1;
            else
                r = mid-1;
        }
        return 0;
    }
};

4.寻找两个正序数组的中位数

这道题直接归并的话复杂度是O(n),但题目要求log级的复杂度,所以这样肯定行不通。正解的话需要先了解怎么求第k小数,如果有两个升序数组,可以比较它们第k/2个元素大小,如果nums1[k/2] < nums2[k/2],那么nums1[0~k/2]一定都不可能是第k小数,因为此时如果把两数组归并起来那nums1[0~k/2]都排在nums2[k/2]前面,都达不到第k小的位置。这样就可以把nums1[0~k/2]这些元素排除掉,由于排除了k/2+1个元素,找第k小就相当于在剩下的元素中找第k-(k/2+1)小。当某个数组中元素全被排除掉或者k = 1时达到终态,此时退出递归。因为每次都排除掉一半的元素,所以时间复杂度是O(logn)。

实现的时候有些细节要注意,比如k/2时可以直接下取整,访问nums[k/2]可能会越界,这时候直接取它最后一个元素,vector的size()方法返回无符号数,运算时需要转为带符号数。

class Solution {
public:
    int findTopK(vector& nums1, int st1, vector& nums2, int st2, int k){
        if(st1 > (int)nums1.size()-1) return nums2[st2+k-1];
        if(st2 > (int)nums2.size()-1) return nums1[st1+k-1];
        if(k == 1) return min(nums1[st1], nums2[st2]);
        int mid = k/2-1;
        int a = st1+mid>nums1.size()-1?nums1.size()-1:st1+mid;
        int b = st2+mid>nums2.size()-1?nums2.size()-1:st2+mid;
        if(nums1[a] > nums2[b])
            return findTopK(nums1, st1, nums2, b+1, k-(b-st2+1));
        else   
            return findTopK(nums1, a+1, nums2, st2, k-(a-st1+1));
    }
    double findMedianSortedArrays(vector& nums1, vector& nums2) {
        if((nums1.size()+nums2.size())&1)
            return findTopK(nums1, 0, nums2, 0, (nums1.size()+nums2.size()+1)/2);
        else{
            int a = findTopK(nums1, 0, nums2, 0, (nums1.size()+nums2.size())/2);
            int b = findTopK(nums1, 0, nums2, 0, (nums1.size()+nums2.size())/2+1);
            return (a+b)/2.0;
        }
               
    }
};

215.数组中的第K个最大元素

类似快速排序的思路,快排中对数组进行一趟操作可以保证第一个元素最终处在正确的位置,也就是其左边的元素均小于等于它,右边的元素均大于等于它。利用这个特性寻找第k大理想情况下可以每趟排除一半的数据量,平均时间复杂度是n+n/2+n/4+n/8+...+1 = O(n)。但这种方法在数据升序时会退化到O(n^2),为了避免退化在每次选取基准元素时应该随机选取。即使加入了随机化,当数组元素值全相同时还是会退化到O(n^2),这时候可以用双路或三路快排。

还有一种方法是用优先队列,维护一个长度为k的升序的优先队列,这种方法时间复杂度为O(nlogk)。

//优先队列做法
class Solution {
public:
    int findKthLargest(vector& nums, int k) {
        priority_queue, greater> q;
        for(int i = 0; i < nums.size(); i++){
            q.push(nums[i]);
            if(q.size() > k) q.pop();
        }
        return q.top();
    }
};
//快排做法
class Solution {
public:
    int findTopK(vector& nums, int l, int r, int k){
        int x = rand()%(r-l+1)+l;
        swap(nums[x], nums[l]);
        int i = l, j = r;
        while(i < j){
            while(nums[i] <= nums[j] && i < j) j--;
            swap(nums[i], nums[j]); 
            while(nums[i] <= nums[j] && i < j) i++;
            swap(nums[i], nums[j]); 
        }
        if(k <= r-i) return findTopK(nums, i+1, r, k);
        else if(k == r-i+1) return nums[i];
        else return findTopK(nums, l, i-1, k-(r-i+1));
    }
    int findKthLargest(vector& nums, int k) {
        srand(time(NULL));
        return findTopK(nums, 0, nums.size()-1, k);
    }
};

34.在排序数组中查找元素的第一个和最后一个位置

两次二分就出来了。

class Solution {
public:
    vector searchRange(vector& nums, int target) {
        vector res;
        int ans = -1, l = 0, r = nums.size()-1;
        while(l <= r){
            int mid = l+r>>1;
            if(nums[mid] >= target){
                ans = mid;
                r = mid-1;
            }
            else l = mid+1;
        }
        if(ans != -1 && nums[ans] != target) res.push_back(-1);
        else res.push_back(ans);
        ans = -1, l = 0, r = nums.size()-1;
        while(l <= r){
            int mid = l+r>>1;
            if(nums[mid] <= target){
                ans = mid;
                l = mid+1;
            }
            else r = mid-1;
        }
        if(ans != -1 && nums[ans] != target) res.push_back(-1);
        else res.push_back(ans);
        return res;
    }
};

283.移动零

遍历一遍数组就行了。

class Solution {
public:
    void moveZeroes(vector& nums) {
        int len = 0;
        for(int i = 0; i < nums.size(); i++)
            if(nums[i]) nums[len++] = nums[i];
        for(int i = len; i < nums.size(); i++)
            nums[i] = 0;
    }
};

153.寻找旋转排序数组中的最小值

分两种情况,一种是旋转完仍然升序,直接返回第一个元素,另一种是分成了两段升序的数组,这时候二分就行了,check条件也很明显。

class Solution {
public:
    int findMin(vector& nums) {
        if(nums[0] <= nums[nums.size()-1]) return nums[0];
        int ans = -1, l = 0, r = nums.size()-1;
        while(l <= r){
            int mid = l+r>>1;
            if(nums[mid] < nums[0]){
                ans = mid;
                r = mid-1;
            }
            else l = mid+1;
        }
        return nums[ans];
    }
};

136.只出现一次的数字

思维题,全部按位异或。

class Solution {
public:
    int singleNumber(vector& nums) {
        int ans = 0;
        for(int i = 0; i < nums.size(); i++)
            ans ^= nums[i];
        return ans;
    }
};

55.跳跃游戏

一开始想的是dp+区间和,过了以后发现还有更简单的方法。直接维护当前能跳到的最远位置,如果走到了最远位置以外那就return false。

//dp+区间和做法
class Solution {
public:
    bool canJump(vector& nums) {
        vector dp(nums.size());
        dp[nums.size()-1] = 1;
        for(int i = nums.size()-2; i >= 0; i--){
            int r = min((int)nums.size()-1, i+nums[i]);
            int sum = dp[i+1]-(r==nums.size()-1?0:dp[r+1]);
            if(sum) dp[i] = dp[i+1]+1;
            else dp[i] = dp[i+1];
        }
        if(nums.size() == 1) return true;
        return dp[0]!=dp[1];
    }
};
//更好的dp做法
class Solution {
public:
    bool canJump(vector& nums) {
        int mx = 0;
        for(int i = 0; i < nums.size(); i++) {
            if(i > mx) return false;
            mx = max(mx, i+nums[i]);
        }
        return true;
    }

};

你可能感兴趣的:(算法,数据结构)