Leetcode上有三道连续的题目,名字叫做Horse Robber,下面我们一一描述。

一、Horse Robber(id:198)

原文描述:

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

这道题目简单的意思就是从一个正整数数列里面尽可能挑选互不相邻的数字,使它们的和最大。题目的输入是一个整数数字,输出是最大的和。比如说【2,1,1,2】最大的和是4 ,【5,1,1,1,5】最大的和为11。

笔者看到这个题目最直接的想法是分别求奇数标号和偶数标号元素的和,这种想法显然是错的,举的第一个例子就是反例。有时候为了特别大的两个数字能同时添加需要放弃隔一个取一个这种数字数量上最优的选法。这个问题最直观的解法是枚举取法,在决策树的每个内部节点最多只能去掉数列中的2个元素,树的深度大约是n的一半,时间复杂度是指数级别的。

实际上上述的做法会有大量的重复判断和计算。用迭代的思想考虑问题,如果我们知道一个数列能得到的最大和是多少,在它后面再增加一个数字,这个新数列能得到的最大值是能容易知道的,因为我们只需要判断要不要在选取时加入最后一个数所以我们就能从记忆化搜索的角度得到迭代公式。假设数列前k项得到的最大和是f(k),那么:

f(k)=max(f(k-1)+f(k-2)+ak),另外f(0)=0,f(1)=a1

用这个公式对数列进行一次遍历,就能找到整个数列能得到的最大和。另外,我们看到每一步式子里只涉及最后两个f的值,可以把解的数组压缩成奇偶两个取值,这个算法的时间复杂度是O(n),空间上几乎完全是原地作业。

源代码如下:

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"></span><pre name="code" class="cpp"><span style="font-size:14px;">class Solution {
public:int rob(vector<int>& nums) {int even=0;int odd=0;int n=nums.size();for (int i=0;i<n;i++){if (i%2){odd=max(odd+nums[i],even);}else{even=max(even+nums[i],odd);}}return max(odd,even);}
};</span>

二、Horse Robber (id:213)

原文描述:

After robbing those houses on that street, the thief has found himself a new place for his thievery so that he will not get too much attention.

This time, all houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile,

the security system for these houses remain the same as for those in the previous street.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can

rob tonight without alerting the police.

这个题目和上一个几乎一样,求一个数列中互不相邻数字的最大和,唯一的不同是这次的数列是环形的,首项和末项被视为相邻的数,我们不能再直接套用上面的算法,因为不能确定f(1)的取值。

笔者首先想到的方法是对数列中每一个元素进行一次尝试,将其挑选后其左右两个元素都不用再考虑,然后剩下的数列是头尾不相邻的数列,可直接使用上面的算法解决,之后再汇总每次尝试的最大值得到答案。这种算法时间复杂度是O(n^2).

然而,细心的读者不难发现这种方法会有大量的重复的判断,每一次循环在数列相同的位置都在做差不多的判断。笔者希望能将上一例中较为聪明的算法经过一定转化在这个问题中转化,但忘记了除了转化算法适应问题之外,还有转化问题适应算法的做法。首尾相邻的数列和首位不相邻的数列,在这个问题上除了头和尾不能同时选择外没有其他任何的区别。只要去掉首项或者末项,数列就变成和上例一样的数列。反正首项和末项我们最多只能取一个,我们只要分别对去掉首项和去掉末项的数列应用上面的算法,再比较得到的两个最大值,就能得到问题的解了。实际操作过程中发现这个做法不适合只有一个元素的数列,我们要加入一次小小的判断。

源代码如下:

class Solution {
public:int rob(vector<int>& nums) {int even=0;int odd=0;int x=0,y=0;int n=nums.size();for (int i=0;i<n-1;i++){if (i%2){odd=max(odd+nums[i],even);}else{even=max(even+nums[i],odd);}}x=max(odd,even);even=0;odd=0;for (int i=1;i<n;i++){if (i%2){odd=max(odd+nums[i],even);}else{even=max(even+nums[i],odd);}}y=max(odd,even);if (n==1) return nums[0];return max(x,y);      }
};

三、Horse Robber III (id:337)

原文描述:

The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the "root." Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that "all houses in this place forms a binary tree". It will automatically contact the police if two directly-linked houses were broken into on the same night.

Determine the maximum amount of money the thief can rob tonight without alerting the police.

这次的问题又稍有不同,我们要从一棵二叉树中选取尽量多不邻接的节点,使得它们的和最大。和前面两次一样,因为选择元素的限制仅仅是相邻,我们希望通过迭代解决问题。因为作和过程中父节点和子节点没什么区别,我们既可以从下往上求解,也可以从上往下求解。限于输入结构体的限制,我们选择前者。设二叉树根节点为r,记它能得到的最大和是f(r),那么:

f(r)=max(ar+f(r->left->left)+f(r->left->right)+f(r->right->left)+f(r->right->right),f(r->left)+f(r->right));

简单来说要在两课子树的最大和以及4颗子树的子树加上根节点的值这两个值之间选择。由于 递推式涉及子节点的子节点,这个算法实现过程中要避免指针指空比较的麻烦,代码中出现了较多的分支,源代码如下:

<span style="font-size:14px;">/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/#include <queue>#include <vector>
class Solution {
public:int rob(TreeNode* root) {if (root==NULL) return 0;if (root->left==NULL&&root->right==NULL){return root->val;}if (root->right==NULL){return max(rob(root->left),root->val+rob(root->left->left)+rob(root->left->right));}if (root->left==NULL){return max(rob(root->right),root->val+rob(root->right->left)+rob(root->right->right));}return max(rob(root->left)+rob(root->right),root->val+rob(root->left->left)+rob(root->left->right)+rob(root->right->left)+rob(root->right->right));}
};</span>

根据递推式简单估计我们得到时间复杂度满足:

O(n)=2O(n/2)+4O(n/4)

实际运行124个样例需要1301ms,在计算f(r->left)时就调用了f(r->left->left)等,所以这些重复的调用通过把第一次算出来的值存起来是可以去掉的,我们在结构体中增加一个数据域,在计算前把输入深拷贝到新的对象中,再进行计算,这样我们实际上每个节点只用进行一次计算,时间复杂度是O(n),运行124个OJ上的样例只用13ms

源程序如下(新类用TreeNode的派生类会更合适,但还是要写深拷贝函数)

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:int rob(TreeNode* root) {node* root2=copy(root);return getRob(root2);}private:       struct node{int val;int rob; //rob=-1表示没有访问过这个节点node* left;<span style="white-space:pre">        </span>node* right;node(int x){val=x;left=NULL;right=NULL;rob=-1;}node(int x,node* l,node* r){val=x;left=l;right=r;rob=-1;}};node* copy (TreeNode* b){if (b==NULL){return NULL;}return new node(b->val,copy(b->left),copy(b->right));}int getRob(node*& root){if (root==NULL) return 0;if (root->rob==-1){if (root->left==NULL&&root->right==NULL){root->rob=root->val;}elseif (root->right==NULL){root->rob= max(getRob(root->left),root->val+getRob(root->left->left)+getRob(root->left->right));}elseif (root->left==NULL){root->rob= max(getRob(root->right),root->val+getRob(root->right->left)+getRob(root->right->right));}elseroot->rob=max(getRob(root->left)+getRob(root->right),root->val+getRob(root->left->left)+getRob(root->left->right)+getRob(root->right->left)+getRob(root->right->right));}<span style="white-space:pre"> </span>  return root->rob;}
};

四、结语

要成为一名出色的盗马贼不是一件简单的事情。在第一次盗马过程中我们学会了通过记忆化搜索把问题简化成只用决定最后一步,在第二次偷窃中我们懂得稍稍改变输入让其适应我们已有的算法,在最后一次尝试中我们学会了把搜索过程中访问的值进行存储减少分治过程中运算量的消耗。

二、Horse Robber (id:213)

Leetcode编程练习一:盗马三则相关推荐

  1. CSDN博文周刊第一期 | 2018年总结:向死而生,为爱而活——忆编程青椒的戎马岁月

    CSDN每周都会产生大量的博客文章,有一些优质的干货文章值得被更多人阅读,分享.CSDN博文周刊会从过去一周博文中精心挑选一些优质文章来以飨读者,陪伴大家度过一个愉快周末. 1.2018年总结:向死而 ...

  2. 学编程当中最重要的三点,一些很正经的建议

    来源 | 沉默王二(ID:cmower) 今天不聊别的,给大家聊一下我认为学编程当中最重要的三点,尤其是针对新人来说. 热爱 努力 解决问题的能力 先说热爱. 有的同学是一开始就热爱,所以选专业的时候 ...

  3. GPU 编程入门到精通(三)之 第一个 GPU 程序

    博主由于工作当中的需要,开始学习 GPU 上面的编程,主要涉及到的是基于 GPU 的深度学习方面的知识,鉴于之前没有接触过 GPU 编程,因此在这里特地学习一下 GPU 上面的编程.有志同道合的小伙伴 ...

  4. 网络编程套接字(三)

    网络编程套接字(三) 文章目录 网络编程套接字(三) 一.实现简单的Tcp服务器(单用户) 一.实现简单的Tcp服务器(单用户) tcp_socket.hpp #pragma once #includ ...

  5. JavaScript 编程精解 中文第三版 翻译完成

    JavaScript 编程精解 中文第三版 原书:Eloquent JavaScript 3rd edition 译者:飞龙 自豪地采用谷歌翻译 部分参考了<JavaScript 编程精解(第 ...

  6. 《Java语言程序设计与数据结构》编程练习答案(第三章)(三)

    <Java语言程序设计与数据结构>编程练习答案(第三章)(三) 英文名:Introduction to Java Programming and Data Structures, Comp ...

  7. LeetCode—5757. 矩阵中最大的三个菱形和(Get Biggest ...)[中等]—分析及代码(Java)

    LeetCode--5757. 矩阵中最大的三个菱形和[Get Biggest Three Rhombus Sums in a Grid][中等]--分析及代码[Java] 一.题目 二.分析及代码 ...

  8. 编程新手所需的三种最重要的技能

    转载自:https://www.2cto.com/shouce/Pythonbbf/intro.html 编程新手所需的三种最重要的技能:读和写.注重细节.发现不同. 读和写 很显然,如果你连打字都成 ...

  9. SAS初级编程系列视频:第三章编辑和调试SAS程序

    SAS初级编程系列视频:第三章编辑和调试SAS程序 SAS初级编程系列视频:第三章编辑和调试SAS程序

  10. shell编程100例之输入三个数并进行升序排序

    shell编程100例之输入三个数并进行升序排序 #!/bin/bash# 依次提示用户输入 3 个整数,脚本根据数字大小依次排序输出 3 个数字 read -p "请输入一个整数:&quo ...

最新文章

  1. HeadFirst设计模式篇十:状态模式
  2. php元素浮动会产生哪些影响,元素浮动的影响与三列布局的实现原理——2019年9月4日22时30分...
  3. sublimeText3 工具
  4. [转]java二维码生成与解析代码实现
  5. Defuse the Bombs Gym - 102822D
  6. 45度做人 90度做事 180度为人 360度处事
  7. r语言折线图_R语言做多变量可视化分析?
  8. Apache 中 .htaccess 文件设置技巧16则
  9. html表单基础知识,【学习笔记】html基础知识第四更/与用户交互!(表单标签)...
  10. 2019级C语言大作业 - 冷冻双侠
  11. [BZOJ3555] [Ctsc2014]企鹅QQ(Hash)
  12. 汇编指令与机器码的相互转换(来自80x86汇编小站)
  13. 数据库安全关键技术之数据库脱敏技术详解
  14. linux硬盘支持fat32,Linux下,挂载windows管理格式的FAT32/NTFS 硬盘
  15. 注册网站域名多少钱_申请域名多少钱
  16. FCN分割Pascal VOC 2007
  17. 短信(SMS)的解释分类以及原理
  18. 基于ESP32的蓝牙刷屏器自动点击器的制作
  19. Java List去重 Lis集合去重 List去重效率对比 List去重复元素效率对比 List去重效率
  20. 数据库 2.关系模型

热门文章

  1. 一文系统搞懂协同推荐算法(二)
  2. drupal mysql hash密码_變更drupal7用戶密碼加密方式
  3. Word转PDF方法怎么转?这三种Word转PDF方法你得知道
  4. 服务器显示器超分辨率,不花钱就能让显示器分辨率翻番?不试你就亏了!
  5. 关闭jupyter notebook报错:python.exe-应用程序错误
  6. 润乾统计图超链接使用例子
  7. PostgreSQL定时删除表数据
  8. 简易的安卓天气app(四)——搜索城市、完善页面
  9. [机缘参悟-1] - 活在当下,仰望星空,梦在梦里,俯视天下
  10. 建立AI智能系统智商评测体系,开展世界人工智能智商评测