日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

算法练习day15——190403(简介、求n!、汉诺塔、打印字符串的子序列、打印字符串的全排列、母牛生小牛、最小路径和、累加和是否达到给定值)

發(fā)布時間:2024/10/14 编程问答 37 豆豆

1. 簡介

動態(tài)規(guī)劃是為了優(yōu)化暴力嘗試的。

2. 求n!

2.1 一般思路

public static long getFactorial2(int n) {long result = 1L;for (int i = 1; i <= n; i++) {result *= i;}return result; }

2.2 遞歸

  • 要求n!,求出(n-1)!,再乘以n即可;
  • 求(n-1)!,先求出(n-2)!,再乘以(n-1)即可;
  • ...
  • 需要有個Base case,即n=1時,不再遞歸,直接返回1。

即:n!的結果依賴于n-1;n-1依賴于n-2;...;2依賴于1。

計算的時候,將順序逆過來,由1得到2,再得到3,...,再得到n-1,得到n。

一般方法的思路:把不依賴于別的的狀態(tài)給出,然后計算依賴于它的的狀態(tài),在往后計算

public static long getFactorial1(int n) {if (n == 1) {return 1L;}return (long) n * getFactorial1(n - 1); }

3.漢諾塔問題

打印n層漢諾塔從最左邊移動到最右邊的全部過程

3.1 簡介

如圖:

將左桿上的盤全部移到右桿上,中桿可作為輔助,要求大盤不能壓小盤。

3.1.1 具體步驟

3.2 遞歸思路分析

有from,to,help三個桿,1~N個盤在from桿上,需把它們移到to桿上。可以利用help桿。

  • 需把1~n-1從from挪到help上;
  • 把單獨的n挪到to上;
  • 把1~n-1從help挪到to上;
  • package Recursive;public class Hanoi {public static void main(String[] args) {hanoi(3,"左","右","中");}public static void hanoi(int N,String from,String to,String help) {if(N==1)System.out.println("Move "+N+" from "+from +" to "+to);else {hanoi(N-1,from,help,to);System.out.println("Move "+N+" from "+from +" to "+to);hanoi(N-1,help,to,from);}} }

    運行結果:

    3.3 將上述遞歸過程拆為6個子過程

  • L->R
  • L->M
  • R->M
  • R->L
  • M->L
  • M->R
  • L->R(M作為輔助)具體包括:

  • L->M:1~N-1 ??
  • L->R:N ? ??
  • M->R:1~N-1 ? ?
  • L->M(R作為輔助)具體包括:

  • L->R:1~N-1
  • L->M:N
  • R->M:1~N-1
  • 后面四個步驟類似。

    3.4 代碼實現(xiàn)

    package Recursive;public class Hanoi {public static void main(String[] args) {moveLeftToRight(3);}public static void moveLeftToRight(int N) {if(N==1)System.out.println("Move "+N+" from 左 to 右");else{moveLeftToMiddle(N-1);System.out.println("Move "+N+" from 左 to 右");moveMiddleToRight(N-1);}}public static void moveLeftToMiddle(int N) {if(N==1)System.out.println("Move "+N+" from 左 to 中");else{moveLeftToRight(N-1);System.out.println("Move "+N+" from 左 to 中");moveRightToMiddle(N-1);}}public static void moveMiddleToRight(int N) {if(N==1)System.out.println("Move "+N+" from 中 to 右");else{moveMiddleToLeft(N-1);System.out.println("Move "+N+" from 中 to 右");moveLeftToRight(N-1);}}public static void moveRightToMiddle(int N) {if(N==1)System.out.println("Move "+N+" from 右 to 中");else{moveRightToLeft(N-1);System.out.println("Move "+N+" from 右 to 中");moveLeftToMiddle(N-1);}}public static void moveRightToLeft(int N) {if(N==1)System.out.println("Move "+N+" from 右 to 左");else{moveRightToMiddle(N-1);System.out.println("Move "+N+" from 右 to 左");moveMiddleToLeft(N-1);}}public static void moveMiddleToLeft(int N) {if(N==1)System.out.println("Move "+N+" from 中 to 左");else{moveMiddleToRight(N-1);System.out.println("Move "+N+" from 中 to 左");moveRightToLeft(N-1);}} }

    4.打印字符串的子序列(不是子串),包括空序列

    4.1 分析

    每走一步,都有兩個決策:

    • 加當前位置上的字符
    • 不加這個字符

    4.2 代碼實現(xiàn)

    package Recursive;public class PrintAllSub {public static void main(String[] args) {String str="abc";printAllSub(str.toCharArray(),0,"");}public static void printAllSub(char[] str,int i,String res) {if(i==str.length) {System.out.println(res);return;}printAllSub(str,i+1,res);printAllSub(str,i+1,res+str[i]);} }

    運行結果:

    5.打印一個字符串的全排列

    5.1 分析

    第一步:確認第一個位置的字符,即第一個位置的字符一次和后面位置的字符進行交換

    第二步:確認第二個位置的字符,...

    ...

    最后,知道表示位置的index=字符串長度時,打印。

    5.2 代碼實現(xiàn)

    package Recursive;public class PrintAllPermutations {public static void main(String[] args) {String str="abc";printAllPermutations(str);}public static void printAllPermutations(String str) {char[] chs=str.toCharArray();process(chs,0);}public static void process(char[] chs,int i) {if(i==chs.length) {System.out.println(chs);return;}else {for(int j=i;j<chs.length;j++) {swap(chs,i,j);process(chs,i+1);swap(chs,i,j);//回歸原始位置,繼續(xù)下一輪交換}}}public static void swap(char[] chs, int i,int j) {char temp=chs[i];chs[i]=chs[j];chs[j]=temp;} }

    運行結果:

    6.母牛生小牛

    母牛每年生一只母牛,新出生的母牛成長三年后也能每年生一只母牛,假設牛不會死。

    求N年后,母牛的數(shù)量。

    6.1 分析

    規(guī)律:

    原因:第N年的牛個數(shù)=第N-1年牛的個數(shù)(牛不會死,去年的牛會保留到今年來)+向前數(shù)三年的牛的個數(shù)(新生的牛=可以生牛的母牛個數(shù))

    做法:寫出前幾項,得出規(guī)律,看規(guī)律是否合理。

    時間復雜度:

    有個更優(yōu)的,時間復雜度為

    • 利用線性代數(shù)中矩陣乘法來解。
    • 斐波那契數(shù)列也存在的解

    7.暴力遞歸試出動態(tài)規(guī)劃——最小路徑和

    給一個二維數(shù)組,其中的每個數(shù)都是正數(shù)。要求從左上角走到右下角,每一步只能向右或者向下。沿途經過的數(shù)字要累加起來,返回最小的路徑和。

    7.1 遞歸版本

    package Recursive;public class MinRoadSum {public static void main(String[] args) {int[][] arr= {{3,2,1,0},{7,5,0,1},{3,7,6,2}};System.out.println(minRoadSum(arr));}public static int minRoadSum(int[][] arr) {int res=process(arr,0,0);//當前數(shù)組,當前的行號,當前列號,當前路徑和return res;}//從(i,j)出發(fā),到達右下角的位子public static int process(int[][] arr,int i,int j) {int hlen=arr.length-1;int llen=arr[0].length-1;if(i==hlen&&j==llen) return arr[i][j];if(i==hlen)//只能往右走return arr[i][j]+process(arr,i,j+1);if(j==llen)//只能往下右走return arr[i][j]+process(arr,i+1,j);int down=process(arr,i+1,j);//下邊位置到右下角的最短路徑和int right=process(arr,i,j+1);return arr[i][j]+Math.min(down, right);} }

    這是暴力:

    大量重復解產生,如上圖中的f(1,1)。

    整個過程中,重復狀態(tài)很多——暴力遞歸不行的原因。

    7.2 改進

    在求解子過程時,將結果保存,下次用到的時候直接拿,可減少計算量。

    ——把1-1作為一個key,將f(1,1)作為value,存入哈希表中

    7.3 遞歸可以改為動態(tài)規(guī)劃的要求

    ——在展開遞歸的過程中,有重復的計算,而且這個重復的狀態(tài)與到達它的路徑無關。

    比如點(1,1),它到右下角點的最短路徑與(0,0)是通過哪條路徑到達它的無關。

    ——這種問題叫做無后效性問題。——只要參數(shù)定了(如,1,1),返回值就確定了(f(1,1)是一定的)。

    有后效性問題:漢諾塔問題、N皇后問題。——之前作出的選擇會影響后面的決策。

    上題中,i和j一旦確定,返回值就是確定的。那么可以將所有的返回值存在和路徑數(shù)組大小一樣的數(shù)組dp中。

    • dp數(shù)組中位置(i,j)表示:路徑數(shù)組中的位置(i,j)到右下角點的最短路徑和。

    我們要的最終結果是(0,0)位置的值,標為※,

    然后看可以確定的位置的值——base case,即右下角位置的值,就是matrix中右下角位置的值0;

    if(i==hlen&&j==llen) return arr[i][j];

    接著看設置不被依賴的值:最后一行和最后一列

    如果在最后一行,則當前位置的值=當前matrix位置中對應的值+右邊位置的最短路徑和(即dp中此位置右邊的值)

    if(i==hlen)//只能往右走return arr[i][j]+process(arr,i,j+1);if(j==llen)//只能往下走return arr[i][j]+process(arr,i+1,j);

    最后一列也一樣:

    接著分析一個普遍位置是怎么依賴的:

    int down=process(arr,i+1,j);//下邊位置到右下角的最短路徑和 int right=process(arr,i,j+1);return arr[i][j]+Math.min(down,right);

    在當前(i,j),它需要:

    • 右邊的位置:(i,j+1)
    • 左邊的位置:(i+1,j)
    • 選兩者中較小的

    由于最后一行和最后一列已確定,則右下角位置的左上方的值就可以確定。它左邊的位置也可以求出來:

    中間的位置從右到左,再從下到上,依次推倒頂部,即可得到答案。

    以上就是暴力遞歸改為動態(tài)規(guī)劃的統(tǒng)一套路。

    • 寫出可變版本
    • 分析可變參數(shù)
      • 哪些可變參數(shù)可以代表返回值的狀態(tài)
      • 參數(shù)時幾維的,dp就是一張幾維表
    • 在dp中標出最終需要的點
    • 回到base case中,將完全不依賴的位置的值設置好
    • 分析一個普遍位置需要哪些位置,逆著回去,就會說填表的順序。

    8.累加和是否可達到給定值

    給定一個數(shù)組arr,其中的數(shù)全為正數(shù)。如果可以任意選擇arr中的數(shù)字。能不能累加得到正數(shù)aim。返回true或false。

    8.1 分析

    和子序列一樣

    比如給定:

    3,2,7,13

    aim=9。是否能加到9?

    f(0,0):目前在0位置,形成的和是0;

    可按照子序列的方法分析,在每個位置選擇要不要前一個位置的數(shù)。

    到最后一層的時候,如果發(fā)現(xiàn)有一個累加和為9,返回true,然后一直往上,每一層只要有一個true,則最后結果肯定為true。

    8.2 代碼實現(xiàn)

    package Recursive;public class IsHaveSum {public static void main(String[] args) {int[] arr= {1,4,8};int aim=12;System.out.println(isHaveSum(arr,0,0,aim));}public static boolean isHaveSum(int[] arr,int i,int sum,int aim) {if(i==arr.length)return sum==aim;return isHaveSum(arr,i+1,sum,aim)||isHaveSum(arr,i+1,sum+arr[i],aim);} }

    8.3 分析是否有后效性

    一個序列為3,2,5,...:

    • 選3,2,沒選5,則是f(3,5)
    • 沒選3,2,選了5,還是f(3,5)

    所以是無后效性的。

    3和5一旦確定,則返回值肯定確定。

    (arr,i,sum,aim):四個參數(shù)中,arr和aim是確定的,i和sum是變化的——二維表

    • LEN:數(shù)組中元素的個數(shù),i的最大值;
    • SUM:數(shù)組中所有元素的和,sum的最大值。

    f(i,sum)肯定可以裝在上面兩個參數(shù)形成的二維表中。

    8.4 轉換

    8.4.1 找出最終需要的狀態(tài)

    8.4.2 找出基本狀態(tài)

    if(i==arr.length)//最后一行return sum==aim;

    aim>SUM直接返回false;

    aim<SUM,則在最后一列SUM對應的位置為T(True)

    0~SUM之間是以1為單位遞增的。

    8.4.3 分析普遍狀態(tài)

    return isHaveSum(arr,i+1,sum,aim)||isHaveSum(arr,i+1,sum+arr[i],aim);

    狀態(tài)(i,sum),依賴的狀態(tài)有:

    • (i+1,sum):下一行正下面的狀態(tài);
    • (i+1,sum+arr[i]):假設arr[i]=a,則指的是下一行正下方往右推a個單位的狀態(tài)。

    8.5 若給定數(shù)組中有負值

    SUM的范圍:負值之和~正值之和

    注意下標的對應。

    比如:3,-2,5

    則SUM的范圍:-2~8

    下標的范圍:0~10

    總結

    以上是生活随笔為你收集整理的算法练习day15——190403(简介、求n!、汉诺塔、打印字符串的子序列、打印字符串的全排列、母牛生小牛、最小路径和、累加和是否达到给定值)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。