有何相见恨晚的算法答题套路

有何相见恨晚的算法答题套路/

有什么相见恨晚🐂的算法答题套路?

有🐂什么相见恨晚的算🐻法答题套路?

前言

众🐻所周知,算法题主要🐻有两大难点,一是「实🐕现」,即算法本身的难🐒度;二是「思路」,即你能🐂否想到使用这个算🐂法来解决题目。

并且🎲对于有一定刷题基🐖础的同学来说,力扣🎲上大部分简单、中等👅题所涉及的算法都🎲是非常常见的算法,💙即算法本身不存在🐻难度,最大的难点在⭐于「思路」,即如何想到👅适合本题的算法。

而:satisfied:解决「思路」问题,除了🐕大量刷题积累经验💙之外,还可以采用一⭐定的「巧劲」,从时间复💙杂度这个角度入手🐒筛选出合适的算法。🐒而本文的主要目的🔥就是向大家介绍这🔥种「巧劲」是如何在具🐒体解题过程中发挥🔥作用的。

本文一共分🐂成三个部分,具体内🐖容框架如下所示:

有🐕什么相见恨晚的算🐂法答题套路?

​数据范🎲围的隐含信息

如何🐒确定一道题合适的🐕时间复杂度?最简单、🎲快捷的方式就是通🐕过观察题目中的数🔥据范围来确定。

不过💙你可能马上会反驳,🐒力扣上并不是所有🐻题目都有数据范围,👅那又该如何确定呢?

🐂不要急噢,没有数据👅范围也是可以采用🐒这种方式来思考的,:satisfied:我们会在「练习」部分🔥进行详细说明。

言归🐻正传,如何通过数据🐕范围来确定合适的⭐时间复杂度呢?

通常🐒来说,在力扣上,Python 可以🐂支持到 10^7 的时间复杂⭐度;C++ 会稍微多一点,大🐂概 10^7 – 10^8
之间。因此我们可🐕以得到如下表所示👅的,数据范围与算法👅大致时间复杂度的🎲对应表。

有什么相见🐒恨晚的算法答题套🎲路?

算法时间复杂度🐻总结

通过数据范围⭐得到时间复杂度后,👅我们需要对照下图👅筛选出适合的算法🐻进行求解。

有什么相💙见恨晚的算法答题💙套路?

此处有两点需🎲要注意:

1. 上图仅列出🔥了时间复杂度较为🐂固定的常见算法,而🐂类似于动态规划、贪🐕心、暴力等时间复杂🎲度百变多样的算法🐂并未列出。

2. O(logn) 的算法通🐻常与 O(n) 的算法组合在🎲一起,用于实现 O(nlogn) 要求🐂的题目。

练习

在讲解⭐具体题目之前,我们💙先明确一下根据时⭐间复杂度做题的具🐒体流程:

1. 根据数据范👅围选择时间复杂度

2. 🔥根据时间复杂度选🐖择对应的常见算法🐕集合

3. 思考题目特征,💙从集合中选出合适🐻的算法

4. 根据选出的🐖算法求解题目

** 力扣 1248. 🐒统计「优美** ** ** ** 数组」**

题🐒目描述

给你一个整🔥数数组 nums 和一个整数 k。

:satisfied:如果某个连续子数👅组中恰好有 k 个奇数⭐数字,我们就认为这🐻个子数组是「优美子🐖数组」。

请返回这个数💙组中「优美子数组」的:satisfied:数目。

示例 1:

输入:nums = [1,1,2,1,1], k = 3
输出:2
🐂解释:包含 3 个奇数的⭐子数组是 [1,1,2,1] 和 [1,2,1,1] 。

示例 2:

输:satisfied:入:nums = [2,4,6], k = 1
输出:0
解释:数列中:satisfied:不包含任何奇数,所🐂以不存在优美子数🐒组。

示例 3:

输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16

数⭐据范围

1 <= nums.length <= 50000
1 <= nums[i] <= 10^5
1 <= k <= nums.length

解决过程

  • 数👅据范围 => 时间复杂度

🐻本题的数据范围到:satisfied:达了 50000,因此我们将时🔥间复杂度划定在 O(n) 的🔥范围内。

  • 时间复杂度 => 🐂常见算法集合

根据🐖上述「常见算法 & 时间🐒复杂度」图,我们可以🐻划定本题的算法集🐕合。由于此题明显是:satisfied:数组上操作的问题,:satisfied:因此我们仅列出 O(n) 范🐻围内关于数组的算🐕法。

差分、前缀和、双指🐖针、桶排序、单调栈、单🐻调队列
  • 思考题目特🔥征 => 从集合中选出合🐖适算法

仔细观察题💙干,可以发现本题有💙两大关键特征:

1. 连续🐂子数组

2. 子数组内恰⭐好有 k 个奇数数字

如:satisfied:果对「前缀和」算法有🐒所掌握的话,凭借这🐂两大特征不难确定🐕此题可以用「前缀和」🐕求解。

令 sum[i] 表示数组第 0 ⭐个数到第 i 个数中奇🐻数的个数,因此区间 [l, r] 🐒符合题意,当且仅当👅下式成立:

有什么相:satisfied:见恨晚的算法答题👅套路?

​由此我们可以🐒令 mp[x] 表示有多少个节:satisfied:点 i 满足 sum[i] = x。然后从左向🐕右枚举,当求得第 i 个👅点的 sum 值后,更新
mp[sum[i]] 数组,🎲并计算有多少个 l 满🐒足区间 [l, i] 符合题意。累🐂加答案即可得到最🐒终结果,具体实现可🐖参看下述代码。

C++ 代码🐕实现

class Solution {
    vector mp;
public:
    int numberOfSubarrays(vector& nums, int k) {
        int sum = 0, ans = 0, n = nums.size();
        mp.resize(n + 2, 0);
        mp[0] = 1;
        for(auto y:nums){
            if(y % 2) sum++;
            mp[sum]++;
            if(sum-k >= 0) ans += mp[sum-k];
        }
        return ans;
    }
};

** 力扣 面试题 08.11. 硬🐕币**

题目描述

硬币。给🐻定数量不限的硬币,🐂币值为 25 分、10 分、5 分和 1 分,🎲编写代码计算 n 分有🐂几种表示法。(结果可👅能会很大,你需要将🐕结果模上
1000000007)

示例 1:

输入: n = 5
💙输出:2
解释: 有两种方:satisfied:式可以凑成总金额:
5=5
5=1+1+1+1+1

🐻示例 2:

输入: n = 10
输出:4
解释: 👅有四种方式可以凑🐒成总金额:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1

数据范围

​0 <= n (🐂总金额) <= 1000000

解决过程

  • 数🐖据范围 => 时间复杂度

​🐕本题的数据范围到⭐达了 1000000,因此我们将时🐂间复杂度划定在 O(n) 的🐒范围内。再仔细观察🔥一下「常见算法 & 时间:satisfied:复杂度」图,可以发现👅由于只有 4
种面值的🔥硬币,因此 O(nm) 的背包也💙是可行的。

  • 时间复杂🔥度 => 常见算法集合

根⭐据时间复杂度与「常🐖见算法 & 时间复杂度」🐕图,我们可以划定本🐒题的算法集合。由于💙此题明显与图论、字🎲符串等算法无关,因⭐此我们仅列出 O(n) 、O(nm)
范围🐕内有一定可能性的🐂算法。

差分、前缀和、双🎲指针、桶排序、单调栈、💙单调队列、背包问题
  • 🐻思考题目特征 => 从集🐻合中选出合适算法

🐻仔细观察此题,可以💙发现如下几个特征:

1. 🐒四类硬币

2. 每类硬币:satisfied:数量不限

3. 求组成 n 的🐒方案数

如果对「动态⭐规划」有一定熟悉度🐖的话,基本可以确定💙此题就是「动态规划👅问题」,因此本题具有⭐很明显的「子结构」性💙质。

然后再根据之前🐒确定的时间复杂度 O(n)、O(nm),👅以及我们选出的算🎲法,基本可以确定该🐻动态规划问题的状💙态,只有如下两种:

1. f[i] 表🐻示用四种硬币组成 i 🐕分的方案数,属于典👅型线性 DP

2. f[i][j] 表示用前 i 种🐂硬币组成 j 分的方案🐖数,属于背包问题

再🐻仔细思考两种状态🐂的转移方程,可以发🎲现第二种采用背包👅思路的 DP 状态更适合🐒解决本题,且由于硬💙币个数不限,因此是🐒经典的「完全背包」问🎲题。

所以我们可以直:satisfied:接列出如下的转移🎲方程(coin[i] 表示第 i 类硬币🐂的面值):

f[i][j] = f[i-1][j]
f[i][j] = f[i][j] + f[i][j-coin[i]]

可以发现,f[i][j] 的🐒数值主要由 f[i - 1][j] 与 f[i][j - coin[j]]
得到,🐒因此我们可以压缩🔥掉第一维,即采用滚⭐动数组的方法,得到🐖如下方程:

f[j] = f[j] + f[j-coin[i]]

由于「完全🐖背包」是背包问题中🐖的经典模型,因此更🔥具体的细节,大家可⭐以参考下述代码。

C++ 代👅码实现

class Solution {
    vector f;
    int coin[4] = {25, 10, 5, 1}, mod = 1e9+7;
public:
    int waysToChange(int n) {
        f.resize(n + 2, 0);
        f[0] = 1;
        for(int i = 0; i < 4; i++)
            for(int j = coin[i]; j <= n; j++)
                f[j] = (f[j] + f[j-coin[i]]) % mod;
        return f[n];
    }
};

力扣 56. 合并区🐻间

题目描述

​给出一🐂个区间的集合,请合⭐并所有重叠的区间。

🐕示例 1:

输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 🔥区间 [1,3] 和 [2,6] 重叠, 将它们🎲合并为 [1,6].

示例 2:

输入: [[1,4],[4,5]]
输💙出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被👅视为重叠区间。

​解决🐕过程

  • 数据范围 => 时间🔥复杂度

现在我们来:satisfied:解决最开头提到的🐕那个问题,「力扣上并🐕不是所有题目都有🐖数据范围,那又该如🐂何确定呢?」。此题就属🐒于没有数据范围的🐒题目,并且在很多面🐖试题中,也都是没有🐒数据范围的,这时应🐖该怎么办呢?

根据经🔥验,对于此类没有数🐒据范围的题目,我们🐖通常需要自行从小🐒到大枚举数据范围,🐕一般从 O(n) 开始枚举,并🐕且大部分的题枚举🎲到 O(nlogn)
时就能找到合适🐒的算法。

因此对于此🔥题,我们暂且将时间💙复杂度限制在 O(n) - O(nlogn) 之间。

  • 🐻时间复杂度 => 常见算:satisfied:法集合

有了时间复🐖杂度之后,我们就可🐻以根据「常见算法 & 时🐂间复杂度」图划定算⭐法集合。由于此题明⭐显与图、计算几何、字🐖符串无关,因此我们🎲可以大致确定如下💙的算法集合。

​差分、前🐒缀和、双指针、桶排序、🐕单调栈、单调队列、堆、ST 🐒表、线段树、树状数组、⭐排序
  • ​思考题目特征 => :satisfied:从集合中选出合适🎲算法

仔细观察此题,🐕可以发现本题题意🎲很明确,就是合并重:satisfied:叠区间,那怎样的区🐻间算重叠呢?

现有两👅个区间,分别为 [l1, r1]、[l2, r2],假设 l1 <= l2,⭐则当 l2 <= r1 时,两个区间发🎲生重叠。

此时再根据👅上述选出的算法集:satisfied:合,一一排除、筛选,不:satisfied:难发现本题可用「排:satisfied:序」解决。即对于所有🐖区间,按照左端点排🐒序,然后从左到右枚⭐举所有区间,对于区🔥间 i
来说,l[i-1] <= l[i],则我们只需🐻判断 l[i] <= r[i-1] 是否成立。如果🎲成立,则合并两个区🐒间,否则不合并。

由此🎲我们便可以通过「排🐻序」算法解决此题,具🎲体的代码细节如下🐂所示。

C++ 代码实现

class Solution {
    vector> ans;
public:
    vector> merge(vector>& intervals) {
        sort(intervals.begin(), intervals.end());
        for (auto y:intervals) {
            int l = y[0], r = y[1];
            if (!ans.size() || ans.back()[1] < l)
                ans.push_back({l, r});
            else
                ans.back()[1] = max(ans.back()[1], r);
        }
        return ans;
    }
};

总结

​🐻最后,我们来总结一:satisfied:下「数据范围」=> 「最终算⭐法」的总体过程,如下🐻图所示。

有什么相见🎲恨晚的算法答题套💙路?

除此之外,还需注⭐意,从「数据范围」入手🐒思考「最终算法」只是🐻获取题目思路的手🐕段之一,并且在上述🐖流程图中,「根据题目🐖特征,筛选算法」是最💙为关键的步骤,这不:satisfied:仅要求做题者具有「🔥挖掘题目特征」的能🐻力,更要求做题者对🐕于「常见算法」要有一🐖定的熟悉度。

也正是🐕因为这个原因,「常见💙算法 & 时间复杂度」对🎲应图是具有个人特🐖征的。每个人由于掌🎲握的算法不同,「常见:satisfied:算法 &
时间复杂度」图🐖也各不相同,因此希🐻望大家能够有意识🐻地构建属于自己的「🎲常见算法 &
时间复杂🐻度」图,并在刷题的过👅程中,不断更新,不断👅完善。力求能够在遇🐒到自己掌握范围内💙的算法题时,一举击⭐破。

最后的最后,祝大🐂家刷题愉快,能力蹭⭐蹭蹭往上涨。

ps:力扣 六🐕月「每日一题」打卡活🎲动继续 ,希望大家在⭐接下来的打卡活动🐒中继续保持刷题好🔥习惯,不断提升技术👅实力!

除。

赞(0)
未经允许不得转载:青年文摘 » 有何相见恨晚的算法答题套路