LeetCode特殊思路题解(数据结构/中等)

这篇博客详细解析了LeetCode中涉及动态规划和贪心算法的10道经典题目,包括递增三元子序列、搜索二维矩阵、旋转图像、无重叠区间、螺旋矩阵、设计哈希映射、合并区间、颜色分类、除自身以外数组的乘积和和为k的子数组。每道题目的思路、代码实现和优化技巧都有详尽阐述,适合提升算法思维和编程能力。
摘要由CSDN通过智能技术生成

1.递增的三元子序列

题目链接:https://leetcode.cn/problems/increasing-triplet-subsequence/

思路:先确定一个t,将之后的数与t进行比较,找到第一个比t大的数并将其设为t1,之后的数若大于t1则返回为真若大于t则将其赋值给t1,若以上条件都不满足则将其赋值给t(相当于重新开始找递增子序列)

代码:

class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        int n=nums.size();
        if(n<3) return false;
        int t=nums[0],t1=INT_MAX;
        for(int i=1;i<n;i++){
            if(nums[i]>t1) return true;
            else if(nums[i]>t){
                t1=nums[i];
            }else{
                t=nums[i];
            }
        }
        return false;
    }
};

总结:这种解法用到了贪心的思想,即为了找到递增的三元子序列,nums[i]和nums[j]应该尽可能地小,这样找到nums[k]的可能性更大。最有意思的地方在于判断条件的顺序和 t1的初始值设置,当t1初始值设为INT_MAX之后,刚开始就不会触发第一个if条件语句,而第二个条件语句限制了t1的取值必须大于t,第三个条件语句下只会更改t更是带来了一个极大的好处:那就是之后即使存在一个小于t和t1的值也不会漏解(比如是4516这种)
举例:以4516为例,按照我之前的思路,当出现小于t和t1的数时,要重新开始计算这个序列,即从t=1开始,但这样的结果显然是错误的。而当出现小于t和t1的数时,若只改变t,相当于保留了前一个序列的两个解(因为t1不变),此时只要后面的数大于t1,依旧返回为真。

2.搜索二维矩阵Ⅱ

题目链接:https://leetcode.cn/problems/search-a-2d-matrix-ii/

说来惭愧,这题我做过Ⅰ,结果做Ⅱ一开始还tle了…言归正传

思路:观察矩阵不难发现,右上角的元素都大于这一行的元素,小于这一列的元素,利用这一特性,搜索target可以从右上角往左下角搜,如果target<右上角数,则列数-1(即向左搜);如果target>右上角数,则行数+1(即向下搜),理解不难,这题如果暴力也能过,但做题遇到矩阵记得总结特性,更高效

代码:

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m=matrix.size();
        int n=matrix[0].size();
        int i=0,j=n-1;
        while(i<m&&j>=0){
            if(matrix[i][j]>target){
                j--;
            }else if(matrix[i][j]<target){
                i++;
            }else{
                return true;
            }
        }
        return false;
    }
};

3.旋转图像

题目链接:https://leetcode.cn/problems/rotate-image/

思路:这题一开始我的思路是找出旋转前和旋转后的位置关系,然后直接两重循环进行交换,不过这个思路过程比较复杂,在此讲一个简单适用性强的思路。题目要求旋转90度,看图可得出,可以先对称上下翻转,再对角线翻转,即可得到所要求图像。

代码:

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n=matrix.size();
        for(int i=0;i<n/2;i++){
            matrix[i].swap(matrix[n-1-i]);
        }
        for(int i=0;i<n;i++){
            for(int j=i;j<n;j++){
                swap(matrix[i][j],matrix[j][i]);
            }
        }
    }
};

4.无重叠区间

题目链接:https://leetcode.cn/problems/non-overlapping-intervals/

思路:题目要求移除区间的最小数量,那么反向思考就是我们要尽可能多的找无重叠的区间,按照这个思路考虑,那么每个区间的范围最好越小越好(因为这样能使后面产生的区间可能性更多),想到这里是不是又感受到了贪心的影子哈哈哈。所以,我们先对每个区间的范围进行排序(注意,每个区间范围都是一对数,这俩是不能拆开的,所以我们可以按照左边界进行排序,也可以按照右边界进行排序)。若按照右边界排序(升序),则从左往右遍历,若下一个左边界>=右边界,则无重叠区间+1;若按照左边界排序(升序),则从右往左遍历(因为比较的是左边界和前一个的右边界),若前一个右边界<=左边界,则无重叠区间+1.最后返回区间总数-无重叠区间。

代码:

class Solution {
public:
    static bool cmp(const vector<int>& a,const vector<int>& b){
        return a[1]<b[1];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        int n=intervals.size();
        sort(intervals.begin(),intervals.end(),cmp);
        int ans=1;
        int bj=intervals[0][1];
        for(int i=1;i<n;i++){
            if(intervals[i][0]>=bj){
                ans++;
                bj=intervals[i][1];
            }
        }
        return n-ans;
    }
};

这道题代码还包括了如何对一个二维vector数组进行自定义排序,cmp中返回值若是a[0]<b[0],则是对二维数组中的i值进行排序。

5.螺旋矩阵Ⅱ

题目链接:https://leetcode.cn/problems/spiral-matrix-ii/

思路:一道模拟题,模拟将值螺旋式存入数组,我们可以人为设置矩阵边界,当数值存储到边界时,调整方向向右转!(具体可以脑补我们玩贪吃蛇,要碰到墙的时候就调整方向),这题的代码可以说是完美模拟了这个情况,而矩阵边界是不断缩小的,一直到将最后一个值存入数组中。这个思路和代码我只能说妙!

代码:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>>ans(n,vector<int>(n));
        int cnt = 1, tar = n * n;
        int l=0,r=n-1,b=n-1,t=0;
        while(cnt<= tar){
            for(int i = l; i <= r; i++) ans[t][i] = cnt++; // left to right.
            t++;
            for(int i = t; i <= b; i++) ans[i][r] = cnt++; // top to bottom.
            r--;
            for(int i = r; i >= l; i--) ans[b][i] = cnt++; // right to left.
            b--;
            for(int i = b; i >= t; i--) ans[i][l] = cnt++; // bottom to top.
            l++;
        }
        return ans;
    }
};

好喜欢这个思路啊哈哈哈哈

6.设计哈希映射

题目链接:https://leetcode.cn/problems/design-hashmap/

思路:这题是简单题,但哈希表原理值得记录

代码:

class MyHashMap {
public:
    vector<list<pair<int, int>>> data;
    static const int base = 111;//任意取值
    static int hash(int key) {
        return key % base;
    }
    MyHashMap():data(base) {

    }
    
    void put(int key, int value) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); it++) {
            if ((*it).first == key) {
                (*it).second = value;
                return;
            }
        }
        data[h].push_back(make_pair(key, value));
    }
    
    int get(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); it++) {
            if ((*it).first == key) {
                return (*it).second;
            }
        }
        return -1;
    }
    
    void remove(int key) {
        int h = hash(key);
        for (auto it = data[h].begin(); it != data[h].end(); it++) {
            if ((*it).first == key) {
                data[h].erase(it);
                return;
            }
        }
    }
};
/**
 * Your MyHashMap object will be instantiated and called as such:
 * MyHashMap* obj = new MyHashMap();
 * obj->put(key,value);
 * int param_2 = obj->get(key);
 * obj->remove(key);
 */

7.合并区间(算是4的镜像问题了)

题目链接:https://leetcode.cn/problems/merge-intervals/

思路:双指针…不过我还不太明白为啥这里可以对区间左右边界分别排序

代码:

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        int n=intervals.size();
        vector<int> start,end;
        vector<vector<int>>ans;
        for(int i=0;i<n;i++){
            start.push_back(intervals[i][0]);
            end.push_back(intervals[i][1]);
        }
        sort(start.begin(),start.end());
        sort(end.begin(),end.end());
        for(int i=0,j=0;i<n;i++){
            if(i==n-1||start[i+1]>end[i]){
                ans.push_back({start[j],end[i]});
                j=i+1;
            }
        }
        return ans;
    }
};

在这里补充一下关于合并区间的模板(来自一个大佬的博客,大佬用Java写的,但思路会了什么语言的代码都能写)

class Solution {
    public int[][] merge(int[][] intervals) {
        // 先按照区间起始位置排序
        Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);
        // 遍历区间
        int[][] res = new int[intervals.length][2];
        int idx = -1;
        for (int[] interval: intervals) {
            // 如果结果数组是空的,或者当前区间的起始位置 > 结果数组中最后区间的终止位置,
            // 则不合并,直接将当前区间加入结果数组。
            if (idx == -1 || interval[0] > res[idx][1]) {
                res[++idx] = interval;
            } else {
                // 反之将当前区间合并至结果数组的最后区间
                res[idx][1] = Math.max(res[idx][1], interval[1]);
            }
        }
        return Arrays.copyOf(res, idx + 1);
    }
}

作者:sweetiee
链接:https://leetcode.cn/problems/merge-intervals/solution/chi-jing-ran-yi-yan-miao-dong-by-sweetiee/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

8.颜色分类

题目链接:https://leetcode.cn/problems/sort-colors/

思路:当然是直接快排啦 由题目可以将我们要得到的数组分为三个区间,第一个区间全为0,第二个全为1,第三个全为2,设l1为区间一的右边界,l2为区间三的左边界,i则表示区间二的右边界(设为i也意味着这个值是不断变化的),我们要确保l1指针左边的每一个数都为0,l2指针右边的每一个数都为2

代码:

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int l1=0,l2=nums.size()-1;//l1代表数值为0的右边界,l2代表数值为2的左边界
        int i=0;//i代表数值为1的右边界
        while(i<=l2){//当1的右边界和2的左边界重合跳出循环
            if(nums[i]==0){
                nums[i]=nums[l1];//确保l1指针左边的每一个数都为0
                nums[l1]=0;
                l1++,i++;
            }else if(nums[i]==1){//无需和自己人交换
                i++;
            }else{
                nums[i]=nums[l2];//确保l2指针右边的每一个数都为2
                nums[l2]=2;
                l2--;
            }
        }
    }
};

这个思路真的很妙,而且一次循环就能在不开额外空间的情况下排序所有数

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

题目链接:https://leetcode.cn/problems/product-of-array-except-self/

思路:如果按照常规思维用双重循环,这题数据量很大一定会超时,所以我们应该尽量将时间复杂度降低到O(n)。
首先,我们可以设两个数组left和right,其中left[i]则表示在nums[i]左边所有数的乘积(包括nums[i]),即left[i]=left[i-1]*nums[i],而right[i]同理,一次遍历nums就可以得到这两个数组,而ans[i]=left[i-1]*right[i+1].
在此思路下,我们不难想到第二种优化方法。即只设置一个数组(可以是left也可以是right),得到left数组之后,逆序遍历nums,ans[i]=left[i-1]*r, r的取值随着循环更新, 即r=r *nums[i],r 为 nums[i]右侧数乘积。
做到这里,既然right数组可以以一个数表示,那么left数组是不是也可以呢?所以我们进一步优化后得到最终解法。

代码:

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

10.和为k的子数组

题目链接:https://leetcode.cn/problems/subarray-sum-equals-k/

思路:这题一定要注意是子数组而不是子序列!(子数组必须是相邻元素构成的,子序列则可以不相邻,一开始没注意到我还排序了…)还有一点就是即使nums[i]==k也不代表就这一个答案了!因为很可能相邻数为0,这样又可以生产一个新的子数组…(是的我这里踩坑了两次)
最终具体思想是前缀和+哈希,我还需要斟酌一下表达,贴一个大佬的题解:https://leetcode.cn/problems/subarray-sum-equals-k/solution/dai-ni-da-tong-qian-zhui-he-cong-zui-ben-fang-fa-y/

代码:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n=nums.size();
        int ans=0;
        int cnt=0;
        unordered_map<int,int> m;
        m[0]=1;//和为0的子序列有1个
        for(int i=0;i<n;i++){
            cnt+=nums[i];
            if(m.count(cnt-k)) ans+=m[cnt-k];
            m[cnt]++;
        }
        return ans;
    }
};

End
就先十题一篇好啦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值