文章目录
动态规划
引入:斐波那契数列
1.递归解法
public int fib(int n) {
if(n<=1){
return n;
}else{
return fib(n-1)+fib(n-2);
}
}
容易想到,但是耗费太大,会超出时间限制
2.动态规划解法
另一方面,我们可以想到计算Fn只需要Fn-1,Fn-2,所以只需要记录最近算出的两个斐波那契数即可,可以利用滚动数组的思想将空间复杂度优化为O(1),计算时候答案需要取模le9+7,也就是1000000007,,如计算初始结果为:1000000008,请返回 1。
//经优化的大佬解法
public int fib(int n) {
int a = 0, b = 1, sum;
for(int i = 0; i < n; i++){
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return a;
}
循环求余法: 大数越界: 随着 n 增大, f(n) 会超过 Int32 甚至 Int64 的取值范围,导致最终的返回值错误。
求余运算规则: 设正整数 x, y, px,y,p ,求余符号为⊙ ,则有 (x + y)⊙p =(x⊙p+y⊙p)⊙p 解析:
根据以上规则,可推出f(n)⊙p=[f(n−1)⊙p+f(n−2)⊙p]⊙p ,从而可以在循环过程中每次计算
sum=(a+b)⊙1000000007 ,此操作与最终返回前取余等价。但是要选择这样每一步都要取余,因为如果用python等没有大小限制的语言完全没问题,只不过如果是C++等int只有32位的语言里,就有一个问题,因为有可能在计算过程就会中间变量就会超过int上限,导致结果错误,所以需要每次计算都取余保证运算过程数值不会溢出
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(1)
有一说一这个是大佬解法,花了不少时间才看明白怎么把空间复杂度减少到O(1)的,其实就是由于 dp 列表第 i 项只与第 i-1 和第 i-2 项有关,所以存储时候为了节省空间交替前行即可;新手小白可以看一下常规思路,最后再看大佬思路提升一下!!!大佬这个return a就很灵性,细节见水平!Orz
下面是常规解法:
class Solution {
Map<Integer, Integer> map = new HashMap<>();
//1备忘录
public int fib(int n) {
if (map.containsKey(n))
return map.get(n);
if (n < 0)
return -1;
if (n == 0 || n == 1)
return n;
int res = fib(n - 1) + fib(n - 2);
res %= 1000000007;
map.put(n, res);
return res;
}
}
//2迭代
//官方题解
public int fib(int n) {
final int MOD = 1000000007;
if (n < 2) {
return n;
}
int p = 0, q = 0, r = 1;
for (int i = 2; i <= n; ++i) {
p = q;
q = r;
r = (p + q) % MOD;
}
return r;
}
任何数学递推公式都可以直接转换成递归算法,但是基本现实是编译器常常不能正确对待递归算法,结果导致低效的程序。当怀疑很可能是这种情况时,我们必须再给编译器提供一些帮助,将递归算法重新写成非递归算法,让后者把那些子问题的答案系统地记录在一个表内。利用这种方法的一-种技巧叫作动态规划( dynamic programming)。
基本概念
- 动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
- 与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
- 由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
- 动态规划适用于具有无后效性的问题,即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
具体步骤
- 划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
- 确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
- 确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
- 寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
简化步骤
- 分析最优解的性质,并刻画其结构特征,这一步的开始时一定要从子问题入手;
- 定义最优解变量,定义递归最优解公式;
- 以自底向上计算出最优值(或自顶向下的记忆化方式(即备忘法));
- 根据计算最优值时得到的信息,构造问题的最优解。
计算机中的取模运算
“模”是指一个计量系统的计数范围;如时钟,12个整点为计算范围,则模为12;计算机也是一个计量机器,模为32位或者64位;32位计算机正常理解 在模 范围内能表达的 有 [0, 2³²-1];那么负数该怎么表达呢,所以出现了补码;也就是 正数 + 负数 正好达到模的溢出阀值2³²;所以在计算机中负数是用补码方式表达的原因;“取模”实质上是计量器产生“溢出”的量,它的值在计量器上表示不出来,计量器上只能表示出模的余数(取模);任何有模的计量器,均可化为加减法运算。取模的本质是:取模的值,必定会模的范围内;所以,计算机领域引用该特性,使元素路由算法不超出边界,并有规则存放。首先确定模(范围);元素取模,使元素有规则的落入模的范围内容器中如:hashMap、数据库分表、分布式节点路由算法等
JZ 14-1剪绳子
用到数学知识,可以看一下解析:
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
public int cuttingRope(int n) {
if(n<=3){
return n-1;//因为m>1
}
int a=n/3,b=n%3;
if(b==0){
return (int)Math.pow(3,a);
}else if(b==1){
return (int)Math.pow(3,a-1)*4;
}else{
return (int)Math.pow(3,a)*2;
}
}
//涉及取模运算的剪绳子
public int cuttingRope(int n) {
if(n <= 3) return n - 1;
int b = n % 3, p = 1000000007;
long rem = 1, x = 3;
for(int a = n / 3 - 1; a > 0; a /= 2) {
if(a % 2 == 1) rem = (rem * x) % p;
x = (x * x) % p;
}
if(b == 0) return (int)(rem * 3 % p);
if(b == 1) return (int)(rem * 4 % p);
return (int)(rem * 6 % p);
}
今日知识小点
HashMap通过value获取对应的key:
HashMap里面只有根据key获取对应的Value的方法(map.get(key)获得value),没有根据Value获取对应的Key的方法,所以用的时候千万要注意,尽量简便,实在得用,就自己写方法:
通过value获取对应的key的两种方法:
- 方法一:
public static Object getKey(Map map, Object value){
List<Object> keyList = new ArrayList<>();
for(Object key: map.keySet()){
if(map.get(key).equals(value)){
keyList.add(key);
}
}
return keyList;
}
- 方法二:
public static Object getKey(Map map, Object value){
Set set = map.entrySet(); //通过entrySet()方法把map中的每个键值对变成对应成Set集合中的一个对象
Iterator<Map.Entry<Object, Object>> iterator = set.iterator();
ArrayList<Object> arrayList = new ArrayList();
while(iterator.hasNext()){
//Map.Entry是一种类型,指向map中的一个键值对组成的对象
Map.Entry<Object, Object> entry = iterator.next();
if(entry.getValue().equals(value)){
arrayList.add(entry.getKey());
}
}
return arrayList;
}
java中字符串与字符串数组的转换
-
“字符串数组” 转 “字符串”
//只能通过循环,没有别的方法 String[] str = {"abc", "bcd", "def"}; StringBuffer sb = new StringBuffer(); for(int i = 0; i < str.length; i++){ sb. append(str[i]); } String s = sb.toString();
-
“字符数组” 转 “字符串”
char[] data={'a','b','c'}; String s=new String(data);
-
数组变为字符串
//方法一:遍历 String[] arr = { "0", "1", "2", "3", "4", "5" }; // 遍历 StringBuffer str5 = new StringBuffer(); for (String s : arr) { str5.append(s); } System.out.println(str5.toString()); // 012345 //方法二:使用StringUtils的join方法 //数组转字符串 org.apache.commons.lang3.StringUtils String str3 = StringUtils.join(arr); // 数组转字符串,其实使用的也是遍历 System.out.println(str3); // 012345 String str4 = StringUtils.join(arr, ","); // 数组转字符串(逗号分隔)(推荐) System.out.println(str4); // 0,1,2,3,4,5 //方法三: 使用ArrayUtils的toString方法 // 数组转字符串 org.apache.commons.lang3.ArrayUtils String str2 = ArrayUtils.toString(arr, ","); // 数组转字符串(逗号分隔,首尾加大括号) System.out.println(str2); // {0,1,2,3,4,5}
-
字符串变为数组
//使用Java split() 方法 //split() 方法根据匹配给定的正则表达式来拆分字符串。 //注意: . 、 | 和 * 等转义字符,必须得加 \\。多个分隔符,可以用 | //作为连字符。 // 字符串转数组 java.lang.String String str = "0,1,2,3,4,5"; String[] arr = str.split(","); // 用,分割 System.out.println(Arrays.toString(arr)); // [0, 1, 2, 3, 4, 5]
split方法的主要用处就是:分割字符串
split方法返回的是数组类型
主要由以下几种用法:
1.比如有一个字符串
var str = “bcadeab”;
对str使用split方法
var strArray = str.split( “a” );
调用此方法后,strArray为一个数组,存放以“a”为间隔,被分割的字符
以下为strArray数组存放的值:
strArray[0] = “bc”
strArray[1] = “de”
strArray[2] = “b”2.还有一种用法,就是把一个字符串转化为数组
var str = “bcadeab”;
var strArray = str.split( “” );
以下为strArray数组存放的值:
strArray[0] =
strArray[0] =
strArray[0] =
strArray[0] =split方法的主要用处就是:分割字符串 split方法返回的是数组类型 主要由以下几种用法: 1.比如有一个字符串 var str = "bcadeab"; 对str使用split方法 var strArray = str.split( "a" ); 调用此方法后,strArray为一个数组,存放以“a”为间隔,被分割的字符 以下为strArray数组存放的值: strArray[0] = "bc" strArray[1] = "de" strArray[2] = "b" 2.还有一种用法,就是把一个字符串转化为数组 var str = "bcadeab"; var strArray = str.split( "" ); 以下为strArray数组存放的值: strArray[0] = strArray[0] = strArray[0] = strArray[0] =
String.split() 执行的操作与 Array.join 执行的操作是相反的。
charAt(int index)方法是一个能够用来检索特定索引下的字符的String实例的方法.
charAt()方法返回指定索引位置的char值。索引范围为0~length()-1.
Collections.sort(list, map) {
public int compare(int a, int b) {
return map.get(b) - map.get(a);
}
});
按照他们出现的次数降序 a-b就是升序
a%b//求余运算
Math.pow(a,b)//a的b次方
今日推歌
-----《坠落星空》
星空不规则 无尽下坠
眼前你 化为泡影
挣扎是负荷 眼里的星星
隐约中靠近 我这一次
偏离了航道 任黑夜吞噬
安静等待轨迹的放逐
逃逸地心引力 成全了彼此
温柔的阻止
星空的漩涡 变成零的距离
还保留微弱逝去光阴
时空扭曲引力 也许能倒退
还未遇见你