刷题心得体会
自己在刷题的时候,总结出的一些适用于自己的小心得体会。
- 空间换时间
要求时间复杂度为O(N)时,一个很普遍的思路是使用HashMap或HashSet来进行存储,利用哈希表的特性来进行存储。 - 前缀和
对于求区间和的问题,我们很容易想到利用前缀和数组来优化其查询速度前缀和wiki:https://oi-wiki.org/basic/prefix-sum/
- 双指针
-
快慢指针
-
左右指针
4.回溯法
-
LinkedList result = new LinkedList();
public void backtrack(路径,选择列表){
if(满足结束条件){
result.add(结果);
}
for(选择:选择列表){
做出选择;
backtrack(路径,选择列表);
撤销选择;
}
}
-
快速幂
快速幂是一种简单而有效的小算法,它可以以[公式]的时间复杂度计算乘方。快速幂不仅本身非常常见,而且后续很多算法也都会用到快速幂。
无非是一个二分的思路。我们很自然地可以得到一个递归方程
- 非递归快速幂
//递归快速幂
int qpow(int a, int n)
{
if (n == 0)
return 1;
else if (n % 2 == 1)
return qpow(a, n - 1) * a;
else
{
int temp = qpow(a, n / 2);
return temp * temp;
}
}
- 递归快速幂
我们换一个角度来引入非递归的快速幂。还是7的10次方,但这次,我们把10写成二进制的形式,也就是 [公式] 。
现在我们要计算 7^(1010)2
,可以怎么做?我们很自然地想到可以把它拆分为 7^(1000)2*7^(10)2
. 实际上,对于任意的整数,我们都可以把它拆成若干个 [公式] 的形式相乘。而这些[公式],恰好就是 7^1
、7^2
、7^4
……我们只需不断把底数平方就可以算出它们。
//非递归快速幂
int qpow(int a, int n){
int ans = 1;
while(n){
if(n&1) //如果n的当前末位为1
ans *= a; //ans乘上当前的a
a *= a; //a自乘
n >>= 1; //n往右移一位
}
return ans;
}
最初ans为1,然后我们一位一位算:
1010的最后一位是0,所以a^1
这一位不要。然后1010变为101,a变为a^2
。
101的最后一位是1,所以a^2这一位是需要的,乘入ans。101变为10,a再自乘。
10的最后一位是0,跳过,右移,自乘。
然后1的最后一位是1,ans再乘上a^8
。循环结束,返回结果。
这里的位运算符,>>是右移,表示把二进制数往右移一位,相当于/2;&是按位与,&1可以理解为取出二进制数的最后一位,相当于%2==1。这么一等价,是不是看出了递归和非递归的快速幂的关系了?虽然非递归快速幂因为牵扯到二进制理解起来稍微复杂一点,但基本思路其实和递归快速幂没有太大的出入。
节选自快速幂专栏:https://zhuanlan.zhihu.com/p/95902286,原文写的非常优秀!
50. Pow(x, n)