题目
典型的动态规划:从金字塔顶端分为左右两个部分,选择左右两种走法中最小的一种,到了最底层只有一个元素,直接返回其本身即可。问题在于java中的这个List好陌生,于是对他进行学习,发现知识量好多,加上今天实验作业怕是无法穷尽,因此先查找学习需要的部分,先加以运用,之后的几天都不轻松,这部分知识慢慢学习吧。
java实现
class Solution {
public static List<List<Integer>> lp(List<List<Integer>> origin){ //左取
int n=origin.size();
List<List<Integer>> array=new ArrayList();
for(int i=1;i<n;i++)
array.add(origin.get(i).subList(0,origin.get(i).size()-1));
return array;
}
public static List<List<Integer>> rp(List<List<Integer>> origin){ //右取
int n=origin.size();
List<List<Integer>> array=new ArrayList();
for(int i=1;i<n;i++)
array.add(origin.get(i).subList(1,origin.get(i).size()));
return array;
}
public static int minimumTotal(List<List<Integer>> triangle) {
if (triangle.size()==1)
return triangle.get(0).get(0);
else{
return Math.min(triangle.get(0).get(0)+minimumTotal(lp(triangle)),triangle.get(0).get(0)+minimumTotal(rp(triangle)));
}
}
}
结果自然是时间不够,,动态规划还是复杂度相当高的呀,不过起码复习了动态规划,学习使用了List的操作,其余问题留到以后处理。
在网上的求助以后,发现了有人用很短的代码就可以实现,不禁有点不可置信,但是事实上果真如此
先贴代码和结果
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
if(n==0){
return 0;
}
int []dp = new int[n];
for(int i=0;i<n;i++){
dp[i] = triangle.get(n-1).get(i);
}
for(int i=n-2;i>=0;i--){
for(int j=0;j<=i;j++){
dp[j] = triangle.get(i).get(j) + Math.min(dp[j],dp[j+1]);
}
}
return dp[0];
}
exm???我代码这么长都没过,这位大神这就能过了?我不信。。
但是看了看代码,发现貌似这思路真的是绝了(没见过世面请见谅),一知半解后去读人家的思路:
动态规划
参考资料
参考分析。http://www.cnblogs.com/themo/p/3688925.html
题图要表达的从顶部到底部的最短路径和,其中路径一词有玄机。路径表示上边节点到下一层的两个分叉。如上例,存在的路径只有:2364,2361,2351,2358,2451,2458,2478,2473
明白题意后,即想到这是一个动态规划问题。思考下转移方程。
f(n,j) = min(f(n-1,j-1),f(n-1,j))+p[i,j]; 貌似有些复杂,我们还得解释一下。
// 转移矩阵
f(n,j) = min(f(n-1 ,j-1), f(n-1 ,j)) + p[n, j];
// f(n, j) 表示从1到n行的,以j为终点的最短路径。
// f(n-1, j) 表示从1到n-1行的,以j为终点的最短路径。
// p[n, j] 表示第n行的第j个元素对于题目中的例子,我们给出他的状态转移方程构造出来的矩阵
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]转移矩阵如下
[
[2], a. 填入2
[5, 6], b. 5 = 2+3,6 = 2+4
[11,10,13], c. 11 = 5+6, 10 = 5+min(5,6), 13 = 6+7
[15,11,18,16] d. 15 = 11+4, 11 = 1+min(11,10), 18 = 8+min(10,13), 16 = 3+13
]
由上事例可以看出下层的节点的头和尾,因为只有一个直接前驱,所以不需要求min(最小值)这个过程,中间的其余元素都需要比较两个直接前驱中的较小元素。将自身的值和较小元素求和,得到到达自身的最短路径。题目要求的最短路径,针对上面的问题,等价的解答是,找出状态转移矩阵最底层f(n,j)的最小值。便求得由顶到底的最短路径长度。
---------------------
作者:淘气的二进制
来源:CSDN
原文:https://blog.csdn.net/fmuma/article/details/80167433
也是动态规划,甚至人家都列出了状态转移方程,但是根本不需要用递归的函数来实现!
原来我一直思想陷入误区:动归一定要用递归。
其实这道题用数组和循环做同样可以做到动态规划的思想,选择最小的并加到自身上,并把结果作为以后的规划的参考,用数组和循环就够了!
真的是一语惊醒梦中人,收获甚多,相当感谢!
所学知识汇总
Java中对List集合的常用操作
(https://www.cnblogs.com/epeter/p/5648026.html)
目录:
- list中添加,获取,删除元素;
- list中是否包含某个元素;
- list中根据索引将元素数值改变(替换);
- list中查看(判断)元素的索引;
- 根据元素索引位置进行的判断;
- 利用list中索引位置重新生成一个新的list(截取集合);
- 对比两个list中的所有元素;
- 判断list是否为空;
- 返回Iterator集合对象;
- 将集合转换为字符串;
- 将集合转换为数组;
- 集合类型转换;
- 去重复;
备注:内容中代码具有关联性。
1.list中添加,获取,删除元素;
添加方法是:.add(e); 获取方法是:.get(index); 删除方法是:.remove(index); 按照索引删除; .remove(Object o); 按照元素内容删除;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<String> person=
new
ArrayList<>();
person.add(
"jackie"
);
//索引为0 //.add(e)
person.add(
"peter"
);
//索引为1
person.add(
"annie"
);
//索引为2
person.add(
"martin"
);
//索引为3
person.add(
"marry"
);
//索引为4
person.remove(
3
);
//.remove(index)
person.remove(
"marry"
);
//.remove(Object o)
String per=
""
;
per=person.get(
1
);
System.out.println(per);
.get(index)
for
(
int
i =
0
; i < person.size(); i++) {
System.out.println(person.get(i));
//.get(index)
}
2.list中是否包含某个元素;
方法:.contains(Object o); 返回true或者false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<String> fruits=
new
ArrayList<>();
fruits.add(
"苹果"
);
fruits.add(
"香蕉"
);
fruits.add(
"桃子"
);
//for循环遍历list
for
(
int
i =
0
; i < fruits.size(); i++) {
System.out.println(fruits.get(i));
}
String appleString=
"苹果"
;
//true or false
System.out.println(
"fruits中是否包含苹果:"
+fruits.contains(appleString));
if
(fruits.contains(appleString)) {
System.out.println(
"我喜欢吃苹果"
);
}
else
{
System.out.println(
"我不开心"
);
}
3.list中根据索引将元素数值改变(替换);
注意 .set(index, element); 和 .add(index, element); 的不同;
1
2
3
4
5
6
7
8
9
10
11
12
String a=
"白龙马"
, b=
"沙和尚"
, c=
"八戒"
, d=
"唐僧"
, e=
"悟空"
;
List<String> people=
new
ArrayList<>();
people.add(a);
people.add(b);
people.add(c);
people.set(
0
, d);
//.set(index, element); //将d唐僧放到list中索引为0的位置,替换a白龙马
people.add(
1
, e);
//.add(index, element); //将e悟空放到list中索引为1的位置,原来位置的b沙和尚后移一位
//增强for循环遍历list
for
(String str:people){
System.out.println(str);
}
4.list中查看(判断)元素的索引;
注意:.indexOf(); 和 lastIndexOf()的不同;
1
2
3
4
5
6
7
8
9
10
List<String> names=
new
ArrayList<>();
names.add(
"刘备"
);
//索引为0
names.add(
"关羽"
);
//索引为1
names.add(
"张飞"
);
//索引为2
names.add(
"刘备"
);
//索引为3
names.add(
"张飞"
);
//索引为4
System.out.println(names.indexOf(
"刘备"
));
System.out.println(names.lastIndexOf(
"刘备"
));
System.out.println(names.indexOf(
"张飞"
));
System.out.println(names.lastIndexOf(
"张飞"
));
5.根据元素索引位置进行的判断;
1
2
3
4
5
6
7
if
(names.indexOf(
"刘备"
)==
0
) {
System.out.println(
"刘备在这里"
);
}
else
if
(names.lastIndexOf(
"刘备"
)==
3
) {
System.out.println(
"刘备在那里"
);
}
else
{
System.out.println(
"刘备到底在哪里?"
);
}
6.利用list中索引位置重新生成一个新的list(截取集合);
方法: .subList(fromIndex, toIndex); .size() ; 该方法得到list中的元素数的和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<String> phone=
new
ArrayList<>();
phone.add(
"三星"
);
//索引为0
phone.add(
"苹果"
);
//索引为1
phone.add(
"锤子"
);
//索引为2
phone.add(
"华为"
);
//索引为3
phone.add(
"小米"
);
//索引为4
//原list进行遍历
for
(String pho:phone){
System.out.println(pho);
}
//生成新list
phone=phone.subList(
1
,
4
);
//.subList(fromIndex, toIndex) //利用索引1-4的对象重新生成一个list,但是不包含索引为4的元素,4-1=3
for
(
int
i =
0
; i < phone.size(); i++) {
// phone.size() 该方法得到list中的元素数的和
System.out.println(
"新的list包含的元素是"
+phone.get(i));
}
7.对比两个list中的所有元素;
//两个相等对象的equals方法一定为true, 但两个hashcode相等的对象不一定是相等的对象
1
2
3
4
5
6
7
8
9
10
11
//1.<br>if (person.equals(fruits)) {
System.out.println(
"两个list中的所有元素相同"
);
}
else
{
System.out.println(
"两个list中的所有元素不一样"
);
}
//2.
if
(person.hashCode()==fruits.hashCode()) {
System.out.println(
"我们相同"
);
}
else
{
System.out.println(
"我们不一样"
);
}
8.判断list是否为空;
//空则返回true,非空则返回false
1
2
3
4
5
if
(person.isEmpty()) {
System.out.println(
"空的"
);
}
else
{
System.out.println(
"不是空的"
);
}
9.返回Iterator集合对象;
1
System.out.println(
"返回Iterator集合对象:"
+person.iterator());
1+0.将集合转换为字符串;
1
2
3
String liString=
""
;
liString=person.toString();
System.out.println(
"将集合转换为字符串:"
+liString);
11.将集合转换为数组;
1
System.out.println(
"将集合转换为数组:"
+person.toArray());
12.集合类型转换;
1
2
3
4
5
6
7
8
9
10
//1.默认类型
List<Object> listsStrings=
new
ArrayList<>();
for
(
int
i =
0
; i < person.size(); i++) {
listsStrings.add(person.get(i));
}
//2.指定类型
List<StringBuffer> lst=
new
ArrayList<>();
for
(String string:person){
lst.add(StringBuffer(string));
}
13.去重复;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
List<String> lst1=
new
ArrayList<>();
lst1.add(
"aa"
);
lst1.add(
"dd"
);
lst1.add(
"ss"
);
lst1.add(
"aa"
);
lst1.add(
"ss"
);
//方法 1.
for
(
int
i =
0
; i <lst1.size()-
1
; i++) {
for
(
int
j = lst1.size()-
1
; j >i; j--) {
if
(lst1.get(j).equals(lst1.get(i))) {
lst1.remove(j);
}
}
}
System.out.println(lst1);
//方法 2.
List<String> lst2=
new
ArrayList<>();
for
(String s:lst1) {
if
(Collections.frequency(lst2, s)<
1
) {
lst2.add(s);
}
}
System.out.println(lst2);
List<List<Integer>> arr=new ArrayList();
List<Integer> s1=new ArrayList();
List<Integer> s2=new ArrayList();
List<Integer> s3=new ArrayList();
List<Integer> s4=new ArrayList();s1.add(2);
arr.add(s1);
s2.add(3);s2.add(4);
arr.add(s2);
s3.add(6);s3.add(5);s3.add(7);
arr.add(s3);
s4.add(4);s4.add(1);s4.add(8);s4.add(3);
arr.add(s4);上述代码是想简单构造一个金字塔作为试验,但是一开始是只用了一个s并在每次add一行后s.clear()
但是发现其实最后结果是
4
4 1
4 1 8
4 1 8 3
我发现是这个add类似于引用地址,并不是直接把值传过去,因此每次clear都会使原来的内容一起清除。
多几次试验发现,如果add的是基本类型,则是赋值,但如果是String、数组、集合等等是add的地址,会随原内容一起改变
总结
动态规划不只是递归,只要是有动归思想不管用什么方式都可以实现,我们应该尽量选取实现最简单,开销最小的。
java的集合好多号难呀=。=