浅谈动态规划(二)——进阶题

上一次的动态规划的文章中(浅谈动态规划(一)——从lintcode刷题入门),借助lintcode上两个简单且典型的例子(Triangle和Climbing Stairs),阐述了动态规划的两个特点:1)最优子结构,和2)子问题重叠。这回来讲讲最近做lintcode上的几个难度稍微大一些的题目。这些题目也很符合动态规划的那两个特点。

先来看看第一个题目。

1.Paint Fence(栅栏染色)

题目描述:
There is a fence with n posts, each post can be painted with one of the k colors. You have to paint all the posts such that no more than two adjacent fence posts have the same color. Return the total number of ways you can paint the fence.
Notice: n and k are non-negative integers.

例子:
Given n=3, k=2 return 6

     post 1, post 2, post 3
way1    0      0       1
way2    0      1       0
way3    0      1       1
way4    1      0       0
way5    1      0       1
way6    1      1       0

题目中要求给n个栅栏涂颜色,有k种颜色可以选择,限制条件是最多可以有两个相邻的栅栏颜色相同。

解题思路:
令ways[i]( n = 1, 2, 3, ..., n )表示涂到第i个栅栏时的所有的方法数。这样,题目就符合动态规划的两个特征(参考之前的那篇文章分析),就可以用动态规划求解。那么我们在涂第i个栅栏的时候,有两种涂色方式:1)和第i-1个栅栏的颜色相同;2)和第i-1个栅栏的颜色不同。在1)方式下,因为要保证最多只有两个相邻的栅栏颜色相同,所以第i个栅栏的颜色和第i-2个栅栏的颜色必然是不同的,那么此时有ways[i-2] * (k-1)中填涂的方式。在2)方式下,不需要考虑之前的栅栏的颜色,只要保证和i-1个栅栏颜色不同即可,此时有ways[i-1] * (k-1)中方法。所以有状态转移方程:

ways[i] = ways[i-2] * (k-1) + ways[i-1] * (k-1)

同时发现,每次只要用到相邻的3个栅栏的方法数就可以了,这样可以减少空间的使用。所以代码为:

int numWays(int n, int k) {
	// Write your code here
	if (n == 0 || k == 0)
		return 0;
		
	if (n == 1)
		return k;
	if (n == 2)
		return k * k;
		
	int ways[3] = {0}; // 辅助数组,只使用3个变量即可
	ways[0] = k;
	ways[1] = k * k;
		
	// 动态规划步骤
	for (int i = 2; i < n; i++)
	{
		ways[2] = (k-1) * (ways[0] + ways[1]); // 依据相邻的两个栅栏的填涂方法数来求解第3个栅栏的填涂方法数
		ways[0] = ways[1];
		ways[1] = ways[2];
	}
		
	return ways[2];
}

这道题虽然也是简单的,但是因为限制条件的运用比较巧妙,所以给贴出来了。接下来看看下一题。

2.House Robber(打劫房屋)

题目描述:
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.

例子:
Given [3, 8, 4], return 8.

题目要求从n个房屋中找出一些房屋取走里面财产,找到财产最大的总值,限制条件是不能有相邻的房屋同时被偷盗。

这道题虽然是道中等题,不过思路也很清晰,动态规划的两个特征也非常明显。令money[i]( i = 1, 2, ..., n )表示在偷第i个房屋时的最大值,那么在偷第i个房屋时,需要比较的只是money[i-1]和money[i-2] + A[i]值的大小(A[i]是第i个房屋的财产值),因为相邻的两个房屋不可能同时被偷。所以,状态转移方程为:

money[i] = max(money[i-1], money[i-2] + A[i])

同样,我们发现每次比较时,只需要保留最近两次的最大值即可,那么优化后的代码为:

long long houseRobber(vector<int> A) {
	// write your code here
	int n = A.size();

	if (n == 0)
		return 0;
	
	if (n == 1)
		return A[0];
	
	// 辅助数组
	long long money[3] = {0};
	money[0] = 0;
	money[1] = A[0];
	
	// 动态规划步骤
	for (int i = 2; i <= n; i++)
	{
		money[2] = max(A[i-1] + money[0], money[1]);
		money[0] = money[1];
		money[1] = money[2];
	}
	
	return money[2];
}

最后来看第3题。

3.Paint House(房屋染色)

题目描述:
There are a row of n houses, each house can be painted with one of the three colors: red, blue or green. The cost of painting each house with a certain color is different. You have to paint all the houses such that no two adjacent houses have the same color.

The cost of painting each house with a certain color is represented by a n x 3 cost matrix. For example, costs[0][0] is the cost of painting house 0 with color red; costs[1][2] is the cost of painting house 1 with color green, and so on... Find the minimum cost to paint all houses.

Notice: All costs are positive integers.

例子:

Given costs = [[14,2,11],[11,14,5],[14,3,10]] return 10

house 0 is blue, house 1 is green, house 2 is blue, 2 + 5 + 3 = 10

题目要求给n个房子粉刷颜色,找到最小的费用。粉刷时有3种颜色可供选择,而粉刷不同的颜色的费用是不同的,限制条件是相邻的两个房子的颜色不能相同。这道题目其实和上一道题目有点类似,均为相邻的两个有限制,不过这个在动态规划的时候,比较的是粉刷的不同的颜色。同样的方法,令value[i][j](i = 1, 2, ..., n; j = 1, 2, 3)表示正在粉刷第i个房子的时候颜色为j时的总费用(之前粉刷的位置均保证了费用最小)。为了避免相邻的两个房子粉刷的颜色相同,在粉刷每一层的时候,还要和前一层粉刷的颜色比较。那么在粉刷第i个房屋颜色为j时的状态转移方程为:

value[i][j] = min(costs[i][j] + value[i-1][k]), k = 1, 2, 3; k != j

上式中k是上一层粉刷不同颜色到达最小值时的不同颜色。在粉刷时要保证相邻的两层颜色不相同,所以k != j。

有了状态转移方程,题目的代码就好写了:

int minCost2(vector<vector<int> > &costs)
{
	int n = costs.size();

	if (n == 0)
		return 0;
	
	int i, j, k;
	int temp;
	int minValue = INT_MAX;
			
	for (j = 0; j < 3; j++) 
            if (minValue > costs[0][j])
	        minValue = costs[0][j];
					   
	if (n == 1)
		return minValue;

	vector<vector<int> > value(n, vector<int>(3, INT_MAX)); // 辅助数组,初始化时每个位置均为INT_MAX

	// 动态规划步骤
	for (i = 0; i < n; i++)
	{
		if (i == 0)
		{
			for (j = 0; j < 3; j++)
				value[i][j] = costs[i][j];

			continue;
		}

		// 找出相邻两层不相同时,粉刷不同颜色的最小值
		for (j = 0; j < 3; j++)
		{
			minValue = INT_MAX;
			for (k = 0; k < 3; k++) // 上一层粉刷不同颜色时对应的最小值
			{
				if (k == j) // 跳过相同颜色
					continue;
				// 找出最小值
				temp = costs[i][j] + value[i-1][k];
				if (temp < minValue)
					minValue = temp;
			}
			value[i][j] = minValue;
		}
	}

	// 找出粉刷完最后一个房屋时的最小费用
	minValue = INT_MAX;
	for (j = 0; j < 3; j++) 
            if (minValue > value[n-1][j])
		minValue = value[n-1][j];

	return minValue;
}

同样,代码也可以优化以减少空间消耗,在这里就不再将最后结果贴出来了。

好了,这就是这次的全部内容。这些题目是我在刷lintcode中遇到的动态规划特征比较明显的题目,且比《浅谈动态规划(一)——从lintcode刷题入门》里面的一些题目稍微难点。希望能够给初接触动态规划的童鞋一点参考。

参考资料:
lintcode
1.Paint Fence(栅栏染色)
2.House Robber(打劫房屋)
3.Paint House(房屋染色)

中国机器学习及其应用研讨会参加感受

上周末参加了中国机器学习及其应用研讨会(MLA)。这是我第一次参加学术交流的会议,就我个人感觉,这样的会议还是有必要来看看的。

先介绍一下MLA吧。

MLA一直以来都是本着“学术至上,其余从简”的原则,不征文、不收费,这可能是到场的学者和学生人数那么多的很重要的一个原因吧。当时我手机没电了,在志愿者那边充电,一个家伙聊到他以前参加过一次学术会议,学生打折之后注册费还要1200元。这次不但不收费,提供的免费饮用水都随时可取,服务很周到,整体还是很亲民的。这也使得会议的两天场馆内几乎所有时间都是人头攒动,还有很多人是站着听完会议的。这里是2016年MLA的主页,嘉宾报告时的slides在这个页面

会议的不同session也是挺吸引人的(当然,也可能是因为这是我个人第一次参加学术类workshop的原因)。会议分为Regular Session, Poster Spotlight Session, Industry Session以及Top Conference Review Session。Regular Session是邀请一些机器学习领域内的嘉宾,做主题报告,介绍他们自己研究的一些内容,每人时长为45分钟;Poster SpotLight Session是一些老师和学生,把自己在顶级国际会议上发表的论文的成果拿来交流的环节,形式是"Spotlight + Poster",即报告者在研讨会上做报告,中午会议休息的时间在场馆外展览海报,同时演讲者对参观者的提问进行讲解,每个报告者的时间是150秒;Industry Session是一些工业界的厂商来介绍最近机器学习在工业方面的发展的环节,每个嘉宾15分钟;Top Conference Review Session是邀请嘉宾,介绍一些顶级国际会议的情况,包括回顾最近一次会议和会议往年热门话题以及各个会议录取论文的一些套路、技巧的环节,每个人大约8分钟。

我相信做为学生,报告都会听过很多,所以其中Regular Session和Industry Session大家就不会陌生了,都是请嘉宾做报告,只是时间不同,内容不同。而其中Poster Spotlight Session和Top Conference Review Session可能会很吸引人了(或者说比较吸引我)。前一个环节要求报告人把自己论文的内容浓缩到150秒,这样既需要报告的内容全是重点,而且也要别出心裁设计出一些夺人眼球的内容,这样才能让人印象深刻;后一个环节吸引我的原因是,毕竟每个人的时间、精力有限,不可能什么领域都精通,这个环节恰恰是熟悉不同顶级国际会议将他们的参会经历以及会议重点告诉我们,其一丰富了我们的见闻,其二为那些未曾参加过又想在这些会议上被录取论文的那些学者提供了一定的帮助。

因为这是我第一次参加学术类的会议(虽然是个研讨会),所以会议结束后我思考了一下参加学术会议/研讨会的意义。

我个人感觉,参加类似的会议最起码有以下几种好处:
1.了解如今行业内的发展水平,了解同行的研究状况。
2.遇到志同道合,研究类似课题的人。
3.给自己一些精神上的激励,争取自己以后有机会来做报告分享。

自己刚刚入门深度学习,下面是一些自己在会议上注意到的内容以及思考。毕竟对机器学习没有积累,很多想法不太成熟,所以只分享一些在学术之外的思考:

1.不是所有的问题都是理论快于实践的。第一个报告中,嘉宾提到,当时工业界有些实验做的结果不错,但是没有相应的理论指导,于是请他做相关的理论研究。而且深度学习的很多方法、技术在数学上的证明也没有解决,但是有很多应用确实有了不错的效果。
2.当时一个老师提到,对自己的一些猜想做实验,之后有些论文提及和自己思考类似的东西,感觉自己思路对了。人们总是在做出来之后才说自己的思路是对的,其实大部分都是在试错。
3.有个题目是用数据挖掘的思路来debug。我们知道,程序和机器出现的意义就是要代替人力的,而现在的编程和一些计算机的思路发展还是挺快的,也许不久之后真的能代替人力了,而且以前一个老师也提到,可以用程序生成网页,仅仅需要一些细节的改动就可以成型,说现在自动生成网页的程序都可以代替很多程序员了,想给我们点压力。确实,机器技术发展到了一个巅峰的时候,是否真的会代替大量的人力,那么程序员自身是否会被代替?
4.周志华老师在介绍ACML(Asian Conference on Machine Learning)会议的时候,提到自己在国外和其他外国学者交流的时候,因为中国参加这些顶级国际会议的人数不多,有次被一个老外说中国人只是人多但是没脑子,所以老师当时很生气,于是也想在亚洲举办一个像样的国际会议,于是便促成了ACML。同时在Top Conference Review Session环节各位嘉宾在介绍各个会议参会包括在会议中任职的中国人的数目,也发现确实中国的人数不多(不过这些年都在加速增长),这也显示了在国际会议中,一个国家的影响力是多么重要。
5.工业界和学术界的交流非常重要。据我所知,在美国,有些高校与工业的合作是非常紧密的,尤其是硅谷,很多世界有名的公司都会和高校的学生交流合作。而这次会议请到了一些工业界的嘉宾,也拉近了机器学习领域工业和学术的距离,使得很多参会的学生也近距离的感受到了技术在商业的应用以及商业对技术的影响。
6.在一个工业界嘉宾做报告的时候,一个报告中提到了针对蓝领工人做信用判别来决定是否提供借贷的服务,我感觉这个想法非常不错。信用问题在借贷环节非常重要,但是中国很多地方的信用制度还不够完善;现在蚂蚁花呗、京东白条等等类似的业务如果能够完善,保证我在很好的信用时能够不断增加借贷总额,同时在全国范围还能不需要考虑不同银行,那么还是有不少用处的。

其他收获:

和周志华老师合影,同时请老师在《机器学习》书上给了我赠言。来此晒晒~

MLA16周志华老师合影