牛客题解 | 连续子数组的最大和

题目## 题目

题目链接

#描述
这是一篇针对初学者的题解,共用两种方法解决。
知识点:数组,动态规划
难度:一星

#题解
题目抽象:给定一个数组,求连续子数组的最大和。

##方法一:动态规划
状态定义:dp[i]表示以i结尾的连续子数组的最大和。所以最终要求dp[n-1]
状态转移方程:dp[i] = max(array[i], dp[i-1]+array[i])
解释:如果当前元素为整数,并且dp[i-1]为负数,那么当然结果就是只选当前元素
技巧:这里为了统一代码的书写,定义dp[i]表示前i个元素的连续子数组的最大和,结尾元素为array[i-1]

###代码

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int sz = array.size();
        vector<int> dp(sz+1, 1);
        dp[0] = 0; // 表示没有元素
        int ret = array[0];
        for (int i=1; i&lt;=sz; ++i) {
            dp[i] = max(array[i-1], dp[i-1]+array[i-1]);
            ret = max(ret, dp[i]);
        }
        return ret;
    }
};

时间复杂度:O(n)
空间复杂度:O(n)

##方法二:空间复杂度O(1)解法

思想很简单,就是对下标为i的元素array[i],先试探的加上array[i], 如果和为负数,显然,以i结尾的元素对整个结果不作贡献。
具体过程:

  1. 初始化:维护一个变量tmp = 0
  2. 如果tmp+array[i] < 0, 说明以i结尾的不作贡献,重新赋值tmp = 0
  3. 否则更新tmp = tmp + array[i]
    最后判断tmp是否等于0, 如果等于0, 说明数组都是负数,选取一个最大值为答案。

###代码

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int ret = 0;
        int tmp = 0;
        for (const int k : array) {
            if (tmp + k < 0) {
                tmp = 0;
            }
            else {
                tmp += k;
            }
            ret = max(ret, tmp);
        }
        if (ret != 0)
            return ret;
        return *max_element(array.begin(), array.end());
    }
};

时间复杂度:O(n)
空间复杂度:O(1)

题目链接

题目的主要信息:
  • 利用二叉树中序遍历结果及前序遍历结果构建一棵二叉树
  • 打印二叉树的右视图,即二叉树每层最右边的节点元素
  • 节点值互不相同
举一反三:

学习完本题的思路你可以解决如下题目:

BM40. 重建二叉树

BM38. 在二叉树中找到两个节点的最近公共祖先

BM29. 二叉树中和为某一值的路径(一)

BM26. 求二叉树的层次遍历

方法一:递归建树+深度优先搜索(推荐使用)

知识点1:二叉树递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

知识点2:深度优先搜索(dfs)
深度优先搜索一般用于树或者图的遍历,其他有分支的(如二维矩阵)也适用。它的原理是从初始点开始,一直沿着同一个分支遍历,直到该分支结束,然后回溯到上一级继续沿着一个分支走到底,如此往复,直到所有的节点都有被访问到。

知识点3:哈希表

哈希表是一种根据关键码(key)直接访问值(value)的一种数据结构。而这种直接访问意味着只要知道key就能在 O ( 1 ) O(1) O(1)时间内得到value,因此哈希表常用来统计频率、快速检验某个元素是否出现过等。

思路:

可以发现解这道题,我们有两个步骤:

  1. 建树
  2. 打印右视图

首先建树方面,前序遍历是根左右的顺序,中序遍历是左根右的顺序,因为节点值互不相同,我们可以根据在前序遍历中找到根节点(每个子树部分第一个就是),再在中序遍历中找到对应的值,从其左右分割开,左边就是该树的左子树,右边就是该树的右子树,于是将问题划分为了子问题。

而打印右视图即找到二叉树每层最右边的节点元素,我们可以采取dfs(深度优先搜索)遍历树,根据记录的深度找到最右值。

具体做法:

  • step 1:首先检查两个遍历序列的大小,若是为0,则空树不用打印。
  • step 2:建树函数根据上述说,每次利用前序遍历第一个元素就是根节点,在中序遍历中找到它将二叉树划分为左右子树,利用l1 r1 l2 r2分别记录子树部分在数组中分别对应的下标,并将子树的数组部分送入函数进行递归。
  • step 3:dfs打印右视图时,使用哈希表存储每个深度对应的最右边节点,初始化两个栈辅助遍历,第一个栈记录dfs时的节点,第二个栈记录遍历到的深度,根节点先入栈。
  • step 4:对于每个访问的节点,每次左子节点先进栈,右子节点再进栈,这样访问完一层后,因为栈的先进后出原理,每次都是右边被优先访问,因此我们在哈希表该层没有元素时,添加第一个该层遇到的元素就是最右边的节点。
  • step 5:使用一个变量逐层维护深度最大值,最后遍历每个深度,从哈希表中读出每个深度的最右边节点加入数组中。

图示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java实现代码:

import java.util.*;
public class Solution {
   
   
    //建树函数
    //四个int参数分别是前序最左节点下标,前序最右节点下标
    //中序最左节点下标,中序最右节点坐标
    public TreeNode buildTree(int[] xianxu, int l1, int r1, int[] zhongxu, int l2, int r2){
   
   
        if(l1 > r1 || l2 > r2)
            return null;
        //构建节点
        TreeNode root = new TreeNode(xianxu[l1]);    
        //用来保存根节点在中序遍历列表的下标
        int rootIndex = 0;    
        //寻找根节点
        for(int i = l2; i <= r2; i++){
   
   
            if(zhongxu[i] == xianxu[l1]){
   
   
                rootIndex = i;
                break;
            }
        }
        //左子树大小
        int leftsize = rootIndex - l2;    
        //右子树大小
        int rightsize = r2 - rootIndex;    
        //递归构建左子树和右子树
        root.left = buildTree(xianxu, l1 + 1, l1 + leftsize, zhongxu, l2 , l2 + leftsize - 1);
        root.right = buildTree(xianxu, r1 - rightsize + 1, r1, zhongxu, rootIndex + 1, r2);
        return root;
    }
    
    //深度优先搜索函数
    public ArrayList<Integer> rightSideView(TreeNode root) {
   
   
        //右边最深处的值
        Map<Integer, Integer> mp = new HashMap<Integer, Integer>();
        //记录最大深度
        int max_depth = -1; 
        //维护深度访问节点
        Stack<TreeNode> nodes = new Stack<TreeNode>(); 
        //维护dfs时的深度
        Stack<Integer> depths = new Stack<Integer>();  
        nodes.push(root); 
        depths.push(0);
        while(!nodes.isEmpty()){
   
   
            TreeNode node = nodes.pop();
            int depth = depths.pop();
            if(node != null){
   
   
            	//维护二叉树的最大深度
                max_depth = Math.max(max_depth, depth);
                //如果不存在对应深度的节点我们才插入
                if(mp.get(depth) == null)
                    mp.put(depth, node.val);
                nodes.push(node.left);
                nodes.push(node.right);
                depths.push(depth + 1);
                depths.push(depth + 1);
            }
        }
        ArrayList<Integer> res = new ArrayList<Integer>();
        //结果加入链表
        for(int i = 0; i <= max_depth; i++) 
            res.add(mp.get(i));
        
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值