人们认识事物的方法有三种:通过概念(即对事物的基本认识)、通过判断(即对事物的加深认识)、和推理(对事物的深层认识)。其中,推理又包含归纳法和演绎法。(这些从初中高中一直到大学我们都是一直在学习的,关键是理解)
归纳法是从特殊到一般,属于发散思维;(如:苏格拉底会死;张三会死;李四会死;王五会死……,他们都是人。所以,人都会死。)
演绎法是从一般到特殊,属于汇聚思维。(如:人都会死的;苏格拉底是人。所以,苏格拉底会死。)
贪心:
贪心法:是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优值)的一种解题方法。贪心策略总是做出在当前看来是最优的选择,也就是说贪心策略并不是从整体上加以考虑,它所作出的选择只是在某种意义上的局部最优解。(贪心在很多情况下往往是不合适的,所以对于刷题我并不建议使用弹性算法,用动态规划就挺好)
零钱兑换:
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
输入:coins =[1, 2, 5]
, amount =11
输出:3
解释:11 = 5 + 5 + 1
如果我们用贪心思想,每次取最大的数额的硬币,那么6,1,1,1,1,5,5,5凑成10,我们可能可能会陷入一种局部最优,但是并不是我们想要的结果
public class coinChange {
/**
* 问题最好硬币数组成一个数
* 硬币面额一定,数量不一定
* f(要组成的面额)=最小组成总数的硬币数量
*假设我们知道 F(S) ,即组成金额 S 最少的硬币数,最后一枚硬币的面值是 C
*F(S)=F(S−C)+1
*那问题就转化成了求F(S-C)
* 由于我们不知道c是多少,所以我们要枚举枚举每个硬币面额值,并选择其中的最小值min(F(S-ci))
* @param coins
* @param amount
* @return
*/
public int coinChange(int[] coins, int amount) {
if(amount == 0) return 0;
if(amount<0){
return -1;
}
int[] dp = new int[amount+1];//最多的硬币情况是全部是1元,共有amount个硬币.其实这里不一定是+1,+多少都可以。只要dp不可能取都值即可
Arrays.fill(dp, amount+1);//必须将所有的dp赋最大值,因为要找最小值
dp[0] = 0;//自底向上,金额为0,最小硬币数为0。当输入当金额是0当说话,肯定是0;
for(int am = 1; am <= amount; am++) {//自底向上
for (int coin : coins) {//遍历coins的金额
if (am >= coin) {//只有总金额大于当前当面值当时候才继续
dp[am] = Math.min(dp[am], dp[am-coin] + 1);//金额为11的最小硬币数 和 金额为(11-一个面值)的最小硬币数+1 比较最小值
}
}
}
return dp[amount]>amount? -1: dp[amount];//
}
}
打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
/**
* 输入是每个房间的金额
* 问题是求能偷到到最大值
* 约束是不能偷相邻的方便
* f(0)=0
* f(1)=nums[1]
* f(2)=max(f(1),f(0)+nums[1])
* f(3)=max(f(2),f(1)+nums[2])
* f(4)=max(f(3),f(2)+nums[3])
* 那么我们可以得出一个转移方程,这个看起来可以用递归,但是递归会有很多重复计算 不如直接做
*/
public class rob {
public int rob(int[] nums) {
int [] dp=new int[nums.length+1];
if(nums==null){
return 0;
}
if (nums.length==1){
return nums[0];
}
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i <nums.length ; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[nums.length - 1];
}
}
做几道之后发现动态规划好想还是不是很难哈,但是问题是要怎么识别这是一个动态规划能解决的问题。
来来来再看一道
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:输入:s = ""
输出:0
/*
看到这个题目的第一印象,,用栈
然后就是我们要保存前面的遍历结果。
最先想到的当然暴力法,每个都遍历一边,比较最大。
我压根没想到这居然也是一道动态规划的问题
*/
public int longestValidParentheses(String s) {
Deque<Integer> stack = new LinkedList<>();
int max = 0;
//为了保持一致性,避免边界条件处理
stack.push(-1);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i)=='('){
//入栈
stack.push(i);
}else {
//出栈
stack.pop();
if (stack.isEmpty()) {
stack.push(i);
}else {
max =Math.max(max,i-stack.peek());
}
}
}
return max;
}
/**
动态规划解法,虽然这道题我肯定不会有动态规划去解决,因为什么? 想不到哈哈哈
*/
class Solution {
public int longestValidParentheses2(String s) {
int maxans = 0;
int[] dp = new int[s.length()];
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == ')') {
if (s.charAt(i - 1) == '(') {
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
} else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
}
maxans = Math.max(maxans, dp[i]);
}
}
return maxans;
}
}
剪绳子(同差分整数):https://leetcode-cn.com/problems/jian-sheng-zi-lcof
给你一根长度为 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。
/**
* 一个是j * (i - j) 直接相乘。
*
* 一个是j * dp[i - j],相当于是拆分(i - j),对这个拆分不理解的话,可以回想dp数组的定义。
*
* 那有同学问了,j怎么就不拆分呢?
*
* j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j 取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
*
* 也可以这么理解,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。
*
* 如果定义dp[i - j] * dp[j] 也是默认将一个数强制拆成4份以及4份以上了。
*
* 所以递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});
*
* 那么在取最大值的时候,为什么还要比较dp[i]呢?
*
* 因为在递推公式推导的过程中,每次计算dp[i],取最大的而已。
* @param n
* @return
*/
public int cuttingRope(int n) {
int dp[]=new int[n+1];
dp[2] =2;
for (int i = 2; i <n; i++) {
for (int j = 2; j <i; j++) {
dp[i] =Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
}
}
return dp[n];
}