常用的算法思想的整理 递归 动态规划 分治 贪心算法 结合leetcode相关例题

在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
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值