leetcode-70-爬楼梯(climbing trees)-java
爬楼梯的三种解法
1 简单的递归算法
return climbStairs(n-1)+climbStairs(n-2);
速度是o(2^n),速度很慢
2 动态规划,也是备忘录算法
有一个hashmap存放之前得出的值
if(map.containsKey(n)){
return map.get(n);
}
else{
int val=climbStair(n-1,map)+climbStair(n-2,map);
map.put(n, val);
return val;
}
速度o(n),空间o(n)
3 动态规划,简单的自底向上,只保留运算下一个所需要的数字,其余的不要,所以空间固定
int first=1;
int second=2;
int third=0;
for(int i=3;i<=n;i++){
third=first+second;
first=second;
second=third;
}
return third;
速度o(n),空间o(c)
leetcode-121-买卖股票的最佳时机(best time to buy and sell stock)-java
最大利润为max(当前的值-之前的min,之前的max利润)
动态规划的要诀就是得到当前的要的值,如何根据之前的值推断的公式,不断递推
leetcode-53- 最大子序和(maximum subarray)-java
包含i处的最大利润 sum=max(sum+nums[i],nums[i]);
i处的最大利润 max_sum=max(sum,max_sum);
当前的和 为如果之前的sum小于0,则为当前元素
否则为sum+now
然后每次更新max
leetcode-198-打家劫舍(house robber)-java
思路:文中给出不能连续抢两家,因此假设从最后一个房屋开始抢,最后一个房屋为index。将原问题分割成子问题,子问题的最优决策可以导出原问题的最优决策。现有两种可能情况,当前房屋抢和当前房屋不抢。若当前房屋抢,则下一房屋不能抢;若当前房屋不抢,则下一房屋可抢;选出这两种情况的最大值,递归执行,直到index<0。
现在i的max=max( (i-2)的max+num[i] ,(i-1)的max)
max[i]=max[i-2]+nums[i] / max[i-1]
用动态规划,不断max上去
leetcode-55-跳跃游戏(jump game)-java
只要用上界就行了。
那么这道题的思路就是使用一个贪心法,使用一个步进指针,用一个上界指针。
每次遍历的时候,不停的更新上界指针的位置(也就是当前位置+当前可以跳到的位置),知道看你能遇到结尾吗?如果成功了,就范围true,没有就返回false
leetcode-62-不同路径 (unique paths)-java
方法1:
建立一个n行m列的数组,每个对应的数字是从左上角到到这一点能走的路线数,很明显,因为只能向右或向下,所以最上的一条边和最左的一条边只能从左上角一路走,都为1。
然后之后的所有的点的值为左边+上边。然后从第2行的第2个到最后一个开始计算,然后每行依次类推,右下角的最后一个计算,它的值就是结果
方法2:
滚动数组的优化就是其实你在算dp [i] [j] 的时候,你左边的dp[i][j-1]还是dp[j-1],而你上面的dp[i-1][j]还是dpj,所以可以只需要一个数组,所以滚动优化决定的是你更新的顺序;
leetcode-322-零钱兑换 (coin change)-java
建立一个数组,needCoins,value为对应index需要的硬币数,结果就是index为amount的value。
首先初始化数组,0,为0,其余都是-1(其实应该只需要初始化0就可以,-1应该不需要)
自底向上的动态规划,然后从1开始循环到amount,每次计算出i所需要的硬币数,硬币数为它之前的i-coins[j]所在位的value的min+1,如果那些位都没有或者都为-1,则值为-1
leetcode-300-最长上升子序列(longest increasing subsequence)-java
解法1:使用动态规划。
状态的定义:以 num[i] 结尾的最长上升子序列的长度。
状态转移方程:之前的数中比 num[i] 小的最长上升子序列的长度 + 1。
对于原数组每个元素,二重循环从头遍历原数组,每当找到一个比当前元素小的值,证明至少可以形成一个dp[j]+1的上升子序列,所以dp[i] = max(dp[i], dp[j] + 1),而dp[j]之前已经求得。
速度o(n^2)
解法二:动态规划+二分查找
10,9,2,5,3,7,101,18
首先看到10,加入备选集,备选集合为{10};
之后看到了9,没有形成上升序列,那么9不应该加入备选集合。但是因为9小于10,所以如果把10替换成9会增加接下来产生上升序列的机会,且并不影响备选集合元素的个数(因为是替换),所以替换掉,备选集现在有{9};
遇到2道理同上,替换掉9,备选集变成{2};
遇到5,这时候形成了上升序列,此时应该是添加到备选集合,变为{2,5};
遇到3,没有形成上升序列,但还是道理同加入9的情况,如果此时把5替换成3,会增加接下来形成上升序列的机会,且备选集保持上升,并且个数也没变,所以替换掉5,备选集变成{2,3};
遇到7,同遇到5,添加元素,备选集{2,3,7};
遇到101,同上,备选集{2,3,7,101};
遇到18,还是一样,虽然没有形成上升序列,但是如果把101替换掉,那么接下来形成上升序列的机会会增加,并且备选集的上升属性和元素个数都不变,所以替换,备选集变为{2,3,7,18}。
至此所有元素添加完毕,备选集的元素个数就是最长上升子序列长度。但这里注意,备选集里面的元素并不是最后最长子序列的元素。因为在寻找最长子序列的过程中,目标是尽可能的让以后形成上升序列的机会增加,所以进行了替换。
“人工”做出来之后,只要用程序实现思考过程就好。总结起来就是:
如果遇到的元素比备选集合里面的元素都大,那么就添加进去,使得上升序列长度增加;
如果遇到的元素比备选集合里最后一个元素小,那么代表它无法被添加到备选集。但是为了使后面得到上升序列的机会增加,需要在不破坏集合上升属性和元素总数的情况下,替换掉备选集中的元素,那么就是替换掉大于他的元素中最小的那个,这样才能满足条件。
相当于原来是10,20,30, 现在将15插入,换成10,15,30,更容易插入。
如果下一个是25,变成10,15,25,显然更容易让后一个加入。
如果下一个是40,会变成10,15,30,40,总共4个,但实际排序是10,20,30,40,但是长度一样,所以之前替换为15无妨,因为长度没变。
新加入的元素要比最后一个大,而最后一个元素显然是在新加入元素之前,所以无妨。
例如顺序为10,20,30,15,25,27 会从10,20,30,,替换为10,15,25,最后加入27
这时候,发现备选集一直是保持有序,寻找替换元素的时候就可以用到二分查找,得到O(n log n)的时间复杂度。其中还要注意的是如果元素已经在备选集合中,是不需要任何操作的,因为它并不能增加上升序列的长度,也不会增加之后遇到上升序列的机会,所以直接跳过。
这个做法的精髓是即使用小的元素替换掉中间的元素,备选集的大小不变,还是原来的大小,
leetcode-152-乘积最大子序列-java
设定max数组和min数组,分别代表以i为结尾的乘积最大/小子序列之积
每次计算新的max[i],min[i],根据nums[i],max[i-1],min[i-1],三者小于0,大于0,等于0,来设置。
我们注意到上边max[i] 的取值无非就是三种,max[i-1] * nums[i] 、min[i-1] * nums[i] 以及 nums[i]。
所以我们更新的时候,无需去区分当前是哪种情况,只需要从三个取值中选一个最大的即可。
max[i] = max(max[i-1] * nums[i], min[i-1] * nums[i], nums[i]);
求 dpMin[i] 同理。
min[i] = min