在leetcode练习也练了一段时间了,在解决问题的过程中,我们经常会用到那些算法思想呢,今天就来总结一下,我遇到的算法思想的总结
递归:反复利用自身以解决问题
递归就是某个函数直接或间接调用自身的问题求解过程。
通过将自身问题划分成相同性质的子问题的求解过程,这些小问题的求解过程较容易,小问题的解就构成了原问题的解。
递归使用的优缺点
- 递归是在过程或函数中调用自身的过程,因此它的逻辑简单。
- 在使用递归策略时,必须有一个明确的递归结束条件,这称为递归出口。
- 在递归调用过程中,系统用栈来存储每一层的返回点和局部量。如果递归次数过多,则容易造成栈溢出,所以一般不提倡用递归算法设计程序。
递归的应用:
我们通过一个leetcode例题来分析:
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
解题思路
首先要求两边的高度差不超过1,所以我们选择数组中最中间元素作为根结点。然后当我们输出时是按照前序遍历输出的。也就是根—左---右的顺序
我们使用切片将该数组从中间分开,分别赋予两个不同的数列lchild和rchild。然后这两个新的数列分别重复刚才的步骤,直到将所有元素排列完。
我们使用Go语言实现其功能
func sortedArrayToBST(nums []int) *TreeNode{
if len(nums) == 0{
return nil;
}
mid := len(nums) / 2
lchild := nums[:mid]
rchild := nums[mid+1:]
tree := &TreeNode{nums[mid], sortedArrayToBST(lchild), sortedArrayToBST(rchild)}
return tree
}
通过这个实例我们发现,递归更倾向于是一种技巧,通过反复执行自身以达到代码最简。它在所谓的把大问题化小的角度上,其实采用的是分治的思想。以我的理解,递归其实是分治的实现途径。那么什么是分治呢?
分治:解决子问题从而解决大问题
分治法,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治法的应用
分治算法体现在归并排序中,首先是分,将一个无序数组分成若干个小部分,然后一一排序最后合并成一个大的有序数组
代码如下:
void mergeSort(int arr[], int reg[], int start, int end) {
if (start >= end)
return;
int len = end - start, mid = (len >> 1) + start;
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
mergeSort(arr, reg, start1, end1);
mergeSort(arr, reg, start2, end2);
int k = start;
while (start1 <= end1 && start2 <= end2)
reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
while (start1 <= end1)
reg[k++] = arr[start1++];
while (start2 <= end2)
reg[k++] = arr[start2++];
for (k = start; k <= end; k++)
arr[k] = reg[k];
}
void sort(int arr[], const int len) {
int reg[len];
mergeSort(arr, reg, 0, len - 1);
}
另外,在上面递归的立体中也有分治的思想。另外还存在一种特殊的分治问题,也就是分治的各部分相对之间不独立,某一个问题的结果可以影响到最终问题的结果,这类问题叫做动态规划。
动态规划:解决子问题重叠的分治问题
基本思想与分治法类似,但是一般来说前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的。
动态规划的应用
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
动态规划法:
遍历数组时,如果前面的数组大于0,我们就把前一个数的值与当前数值相加,否则保持不变。
使用Go语言得到以下算法:
func maxSubArray(nums []int) int {
max := nums[0]
for i := 1; i < len(nums); i++ {
if nums[i] + nums[i-1] > nums[i] {
nums[i] += nums[i-1]
}
if nums[i] > max {
max = nums[i]
}
}
return max
}
- 时间复杂度:O(n)O(n),其中 nn 为 nums 数组的长度。我们只需要遍历一遍数组即可求得答案。
- 空间复杂度:O(1)O(1)。我们只需要常数空间存放若干变量。
贪心算法
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
贪心算法应用:
小区便利店正在促销,用 numExchange 个空酒瓶可以兑换一瓶新酒。你购入了 numBottles 瓶酒。
如果喝掉了酒瓶中的酒,那么酒瓶就会变成空的。
请你计算 最多 能喝到多少瓶酒。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/water-bottles
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
func numWaterBottles(numBottles int, numExchange int) int {
res := numBottles
var temp int
for numBottles >= numExchange{
temp = numBottles / numExchange
res += numBottles / numExchange
numBottles -= temp * numExchange
numBottles += temp
}
return res
}