初级算法(十)–动态规划、设计问题、数学
1、引言
动态规划
1、爬楼梯
2、买卖股票的最佳时机
3、最大子序和
4、打家劫舍
解题步骤
1、确定dp数组
2、找到状态转移公式
3、确定初始条件以及边界条件
4、计算结果
设计问题
1、打乱数组
2、最小栈
数学
1、Fizz Buzz
2、计数质数
3、3的冥
2、动态规划
2.1 爬楼梯
这题开始想的方法就是使用递归,但是力扣会显示超出时间限制。
/**
* 方法一:递归,但是力扣会显示超时
* @param n
* @return
*/
public int climbStairs(int n) {
if(n==1)
return 1;
if(n==2)
return 2;
return climbStairs(n-1)+climbStairs(n-2);
}
第二种方就是经典的使用动态规划,使用相应的解题步骤。dp[i]表示走到第i级台阶有几种方法。
/**
* 方法二:非递归的方法,用一个数组表示一个到达几级台阶需要几种方法
* @param n
* @return
* 执行用时:0ms,内存消耗:38MB
*/
public int climbStairs2(int n){
if(n==1)
return 1;
if(n==2)
return 2;
int[] dp=new int[n+1];
dp[1]=1;
dp[2]=2;
for (int i = 3; i <=n ; i++) {
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
2.2 买卖股票的最佳时机
这题的思路也是使用动态规划,不同的是使用的是二维数组作为dp数组。这个二维数组只有两列,列项0代表的是未持有股票 的最大利润,列值代表的是持有股票的最大利润。
/**
* 使用动规
* @param prices
* @return
* 执行用时: 24ms,内存消耗: 53.6MB
*/
public int maxProfit(int[] prices) {
if(prices==null)
return 0;
int n=prices.length;
//列值代表0是表示,未持有股票的最大利润
//列值代表1是表示,持有股票的最大利润
int[][] dp=new int[n][2];
dp[0][0]=0;
dp[0][1]=-prices[0];
for (int i = 1; i < n ; i++) {
//第i+1天未持有股票的最大利润,有两种情况,一种是在第i+1天已经卖了当天的股票
//另一种是既没买也没卖
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
//第i+1天持有股票的最大利润,有两种情况,一种是前面的某台买了一种股票,直到现在都没有卖
//另一种就是买了当天的股票
dp[i][1]=Math.max(dp[i-1][1],-prices[i]);
}
//最后肯定是股票卖出去了才算是利润最大化
return dp[n-1][0];
}
2.3 最大子序和
确定dp数组,这题比较难判断dp数组和状态转移方程。dp[i] 表示终点在 i 的最大子序和。至于状态转移方程,则是前面 i-1 的最大子序和为负数的话,就需要从新开始,就是从第 i 项 开始。
/**
* 使用动态规划
* 思路:
* 1、确定状态
* 2、找到转移公式
* 3、确定初始条件以及边界条件
* 4、计算结果
* @param nums
* @return
* 执行用时: 2ms,内存消耗:51.1MB
*/
public int maxSubArray(int[] nums) {
if(nums.length==1)
return nums[0];
int n=nums.length;
int[] dp=new int[n];
dp[0]=nums[0];
int max=dp[0];
for (int i = 1; i <n ; i++) {
//确定状态转移方程,如果dp[i-1]<0的话,则直接将dp[i]充值为nums[i]的值
dp[i]=nums[i]+Math.max(dp[i-1],0);
max=Math.max(max,dp[i]);
}
return max;
}
2.4 打家劫舍
这题的思路和前面买卖股票的最佳时机其实是一样的,dp数组表示为二维数组,有两个列。列值为0代表的是对这家已经打劫过,列值1代表的是没有打劫过。
/**
* 打家劫舍
* @param nums
* @return
* 执行用时:0ms,内存消耗:39MB
*/
public int rob(int[] nums) {
if(nums.length==1)
return nums[0];
int n=nums.length;
//列值0代表的是拿了钱,列值1代表的是没拿钱
int[][] dp=new int[n][2];
dp[0][0]=nums[0];
dp[0][1]=0;
for (int i = 1; i <n ; i++) {
dp[i][0]=Math.max(dp[i-1][1]+nums[i],dp[i-1][0]);
dp[i][1]=Math.max(dp[i-1][0],dp[i-1][1]);
}
return Math.max(dp[n-1][0],dp[n-1][1]);
}
3、设计问题
3.1 打乱数组
主要是考类的构造
//执行用时:50ms,内存消耗:47.4MB
public class Solution2 {
private int[] temp;
private Random random;
public Solution2(int[] nums) {
this.temp=nums;
random=new Random();
}
public int[] reset() {
return temp;
}
public int[] shuffle() {
if(temp==null)
return null;
int[] clone = temp.clone();
for (int i = 1; i < clone.length; i++) {
int j=random.nextInt(i+1);
swap(clone,i,j);
}
return clone;
}
private void swap(int[] a, int i, int j) {
if (i != j) {
a[i] = a[i] + a[j];
a[j] = a[i] - a[j];
a[i] = a[i] - a[j];
}
}
}
3.2 最小栈
这题使用了辅助的数据结构,就是单链表。
//使用辅助数据结构单链表
//执行用时:3ms,内存消耗:43.1MB
public class MinStack {
ListNode head;
public void push(int val) {
if(head==null)
head=new ListNode(val,val,null);
else{
head=new ListNode(val,Math.min(val,head.min),head);
}
}
public void pop() {
if(head==null)
throw new IllegalStateException("栈为空");
head=head.next;
}
public int top() {
if(head==null)
throw new IllegalStateException("栈为空");
return head.val;
}
public int getMin() {
if(head==null)
throw new IllegalStateException("栈为空");
return head.min;
}
class ListNode{
public int val;
public int min;
public ListNode next;
public ListNode(int val, int min, ListNode next) {
this.val = val;
this.min = min;
this.next = next;
}
}
}
4、数学
4.1 Fizz Buzz
这个问题简单,就是几个 if else 语句判断一下。
/**
* Fizz Buzz
* 思路:循环中判断就行
* @param n
* @return
* 执行用时:1ms,内存消耗;42.3MB
*/
public List<String> fizzBuzz(int n) {
List<String> answer=new ArrayList<>();
for (int i = 1; i <=n; i++) {
if(i%3==0 && i%5==0){
answer.add("FizzBuzz");
}else if(i%3==0){
answer.add("Fizz");
}else if(i%5==0){
answer.add("Buzz");
}else{
answer.add(String.valueOf(i));
}
}
return answer;
}
4.2 计数质数
这题如果用原始的判断质数的方法,力扣会显示超出时间限制。最好的方式使用埃氏筛。利用素数的各个倍数的等差还是它本身的方法,进行筛选。
/**
* 使用埃氏筛,从素数2开始,一个素数的倍数的合数进行标记,且为等差数列,差的就是其本身
* @param n
* @return
* 执行用时: 157ms,内存消耗: 68MB
*/
public int countPrimes(int n) {
int[] isPrime = new int[n];
Arrays.fill(isPrime, 1);
int ans = 0;
for (int i = 2; i < n; ++i) {
if (isPrime[i] == 1) {
ans += 1;
if ((long) i * i < n) {
for (int j = i * i; j < n; j += i) {
isPrime[j] = 0;
}
}
}
}
return ans;
}
4.3 3 的幂
可以用递归的方式求解
/**
* 判断n是否是3的幂次方
* 思路:使用递归
* @param n
* @return
* 执行用时: 8ms,内存消耗:40.9MB
*/
public boolean isPowerOfThree(int n) {
if(n==0)
return false;
if(n%3==0)
return isPowerOfThree(n/3);
else if(n==1)
return true;
else
return false;
}
也可以根据题目给的范围,来一个“高效”的解决办法
/**
* 充分利用题目的范围,如果n是3的幂次方,那么它一定能够被1162261467整除
* @param n
* @return
* 执行用时: 7ms,内存消耗:41.2MB
*/
public boolean isPowerOfThree2(int n) {
return (n > 0 && 1162261467 % n == 0);
}