藍橋杯必備算法模板


1.輸入

推薦使用這種的輸入,讀取的數據量大的時候,速度更快。

static class InputReader {
        private BufferedReader bf = null;
        private StringTokenizer stz = null;
        public InputReader() {
            bf = new BufferedReader(new InputStreamReader(System.in),32*1024);
        }
        public boolean hasNext() {
            while(stz == null || !stz.hasMoreTokens()) {
                try {
                    stz = new StringTokenizer(bf.readLine());
                } catch (IOException e) {
                    return false;
                }
            }
            return true;
        }
        public String next() {
            if(hasNext()) {
                return stz.nextToken();
            }
            return null;
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }
        public double nextDouble() {
            return Double.parseDouble(next());
        }
    }

 2.進制轉換

十進制轉換成n進制

InputReader sc  = new InputReader();
int num = sc.nextInt();
System.out.println(Integer.toString(num, 16));//這里是轉成16進制

n進制轉換成十進制

InputReader sc  = new InputReader();
String str = sc.next();
System.out.println(Integer.valueOf(str, 16));//這里代表字符串使用16進制表示的

以上處理的進制問題沒有考慮溢出問題。也就是某個進制代表的數字很大,int類型不足以表示

n進制轉換十進制(推薦)

InputReader sc  = new InputReader();
String str = sc.next();
BigInteger bigInteger = new BigInteger(str, 16);
System.out.println(bigInteger);

3. 日期運算(Calendar的API使用)

真題:世紀末的星期

1999年的12月31日是星期五,請問:未來哪一個離我們最近的一個世紀末年(即xx99年)的12月31日正好是星期天(即星期日)?

請回答該年份(只寫這個4位整數,不要寫12月31等多余信息)。

public static void main(String[] args) {
    Calendar calendar = Calendar.getInstance();
    for (int i = 1999; i < 10000; i+=100) {
        calendar.set(i, 11, 31);//月份從0開始計算
        if(calendar.get(Calendar.DAY_OF_WEEK) == 1) {
            System.out.println(i);  //2299
            break;
        }
    }
}

4.遞歸

遞歸就是思考解決問題的方向是自頂向下的

4.1. 最常用的遞歸就是全排列

public static void main(String[] args) {
         String[] arr = new String[] {"A","B","C"};
         int n = arr.length;
         recursion(arr,0,n);
    }
    private static void recursion(String[] arr,int start,int end) {
        if(start == end) {
            print(arr);
            return;
        }
        for (int i = start; i < end; i++) {
            Utils.swap(arr, i, start);
//            Arrays.sort(arr,start+1,end); 
            recursion(arr,start+1,end);
            Utils.swap(arr, start, i);
        }
    }
    private static void print(String[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]);
        }
        System.out.println();
    }
View Code

不帶重復排列的全排列

public class Main {
    static int[] arr = {1,1,3,3};
    static boolean[] vis = new boolean[arr.length];
    static int[] res = new int[arr.length];
    static HashSet<String> hashSet = new HashSet<String>(); 
    public static void main(String[] args) {
        f(0);
    }
    private static void f(int cur) {
        String str = "";
        if(cur == arr.length) {
            for(int i=0;i<arr.length;i++) {
                str += arr[i];
            }
            if(!hashSet.contains(str)) {
                hashSet.add(str);
                System.out.println(str);
            }
            return;
        }
        for (int i = cur; i < arr.length; i++) {
            swap(i,cur);
            f(cur+1);
            swap(i,cur);
        }
    }
    private static void swap(int a,int b) {
        int t = arr[a];
        arr[a] = arr[b];
        arr[b] = t;
    }
}
View Code

4.2. 遞歸轉遞推

4.3. DFS(搜索+檢查)

標題: 振興中華

小明參加了學校的趣味運動會,其中的一個項目是:跳格子。

地上畫着一些格子,每個格子里寫一個字,如下所示:(也可參見p1.jpg)

從我做起振
我做起振興
做起振興中
起振興中華

 

比賽時,先站在左上角的寫着“從”字的格子里,可以橫向或縱向跳到相鄰的格子里,但不能跳到對角的格子或其它位置。一直要跳到“華”字結束。

要求跳過的路線剛好構成“從我做起振興中華”這句話。

請你幫助小明算一算他一共有多少種可能的跳躍路線呢?

將“從我做起振興中華”用數字0到7代替從0開始往下和往右深搜

    static int[][] arr = { 
            { 0, 1, 2, 3, 4 }, 
            { 1, 2, 3, 4, 5 }, 
            { 2, 3, 4, 5, 6 }, 
            { 3, 4, 5, 6, 7 } 
        };
    static boolean[][] visited = new boolean[4][5];
    public static void main(String[] args) {
        int ans = dfs(0,0,0);
        System.out.println(ans); //35
    }
    /**
     * 
     * @param x
     * @param y  x ,y 是坐標
     * @param cur 當前應該是第幾個數字
     */
    private static int dfs(int x, int y,int cur) {    
        int res = 0;
        visited[x][y] = true;
        if(cur == 7 && arr[x][y] == 7) {
            return 1;
        }
        if(inArea(x+1,y) && !visited[x+1][y]) {
            res+=dfs(x+1,y,cur+1);
            visited[x+1][y] = false;//回溯
        }
        if(inArea(x,y+1)&& !visited[x][y+1]) {
            res+=dfs(x,y+1,cur+1);
            visited[x][y+1] = false;//回溯
        }
        return res;
        
    }
    private static boolean inArea(int x, int y) {
        if(x>=0 && x < 4 && y>=0 && y < 5) {
            return true;
        }
        return false;
    }
View Code

當然這是最一般的解法,但是題目給的數據比較特殊,從左上到右下每一個位置,都可以往下或者右走,並且一定只能是這個順序。於是可以這樣寫

public static void main(String[] args) {
        int ans = f(0,0);
        System.out.println(ans); //35
    }

    private static int f(int x, int y) {
        int res = 0;
        if(x==3 && y== 4) {
            return 1;
        }
        if(x+1>=0 && x+1<=3)
            res+= f(x+1,y);
        if(y+1>=0 && y+1<=4)
            res+= f(x, y+1);
        return res;
    }
View Code

 4.3 記憶型遞歸與博弈型問題

 

5.枚舉,全排列暴力解法

標題1:六角填數

如圖【1.png】所示六角形中,填入1~12的數字。

使得每條直線上的數字之和都相同。

圖中,已經替你填好了3個數字,請你計算星號位置所代表的數字是多少?

思路,按照從上到下,從左到右對還沒有填入的數字進行編號,題目轉換為求下標為3的那個數是多少

public static void main(String[] args) {
        int[] arr = {2,4,5,6,7,9,10,11,12};
        f(arr,0);//全排列
    }

    private static void f(int[] arr,int cur) {
        if(cur == arr.length) {
            check(arr);
            return;
        }
        for(int i=cur;i<arr.length;i++) {
            swap(arr,i,cur);
            f(arr,cur+1);
            swap(arr,i,cur);
        }
        
    }

    private static void check(int[] arr) {
        int r1 = 1 + arr[0]+arr[3]+arr[5];
        int r2 = 1 + arr[1]+arr[4]+arr[8];
        int r3 = 8 + arr[0]+arr[1]+arr[2];
        int r4 = 11 + arr[6]+arr[3];
        int r5 = 3 + arr[2]+arr[4]+arr[7];
        int r6 = arr[5] + arr[6]+arr[7]+arr[8];
        if(r1==r2&&r2==r3&&r3==r4&&r4==r5&&r5==r6) {
            for(int i=0;i<arr.length;i++) {
                System.out.print(arr[i]+" "); //9 2 7 10 12 6 5 4 11  所以答案為10
            }
        }
    }

    private static void swap(int[] arr, int i, int cur) {
        int t = arr[i];
        arr[i] = arr[cur];
        arr[cur] = t;
    }
View Code

再舉個例子 

生日蠟燭

某君從某年開始每年都舉辦一次生日party,並且每次都要吹熄與年齡相同根數的蠟燭。

現在算起來,他一共吹熄了236根蠟燭。

請問,他從多少歲開始過生日party的?

請填寫他開始過生日party的年齡數。

static int sum(int start, int end) {
        return (start+end) * (end - start +1) /2;//等差數列求和公式
    }

    public static void main(String[] args) {
        for (int start = 1; start < 100; start++) {
            for (int end = start + 1; end < 100; end++) {
                if(sum(start,end) == 236) {
                    System.out.println(start +" " +end);//26   33 所以答案就是26
                }
            }
        }
    }
View Code

6.快速冪運算

public static int quickExp(int n,int m) {
        int res = 1;
        while(m > 0) {
            if((m&1)==1) {
                res *= n;
            }
            n =  n*n;
            m = m>>1;
        }
        return res;
    }
View Code

7.矩陣運算

矩陣乘法運算

private static int[][] multiple(int[][] m1, int[][] m2) {
        int[][] res = new int[m1.length][m2[0].length];
        for(int i=0;i<m1.length;i++) {
            for(int j=0;j<m2[i].length;j++) {
                for(int k=0;k<m2.length;k++) {
                    res[i][j] += m1[i][k]* m2[k][j];
                }
            }
        }
        return res;
    }
View Code

矩陣快速冪運算

private static int[][] quickExp(int[][] m, int n) {
        int[][] res = new int[N][N];
        for(int i=0;i<N;i++) {
            for (int j = 0; j < N; j++) {
                if(i == j) res[i][j] = 1;
                else res[i][j] = 0;
            }
        }
        while(n>0) {
            if((n & 1) == 1)
                res = multiple(res, m); //調用的是上面的矩陣乘法公式
            m = multiple(m, m);
            n = n>>1;
        }
        return res;
    }
View Code

應用:快速求斐波那契數O(logn)的時間復雜度。(在最后會提到)

8.貪心

 

8.動態規划(dp)(*****)

前面講過遞歸就是思考解決問題的方向是自頂向下的,而動態規划是恰恰相反。

不過,通常在解決問題的時候,我們應該先要自頂向下的思考,因為自頂向下思考問題,比較簡單。

從最簡單的dp開始

例1:leetcode 70 爬樓梯  

class Solution {
    private int[] memo;

    public int climbStairs(int n) {
        if (n == 1) return 1;
        if (n == 2) return 2;
        memo = new int[n + 1];
        memo[1] = 1;
        memo[2] = 2;
        for (int i = 3; i < n + 1; i++) {
            memo[i] = memo[i - 1] + memo[i - 2];
        }
        return memo[n];
    }
}
View Code

如果感覺沒問題的話,可以練習下面兩道題:

     leetcode120 Triangle

     leetcode 64 Minimum Path Sum

例2:整數拆分

2.1 簡單遞歸解法

分析:

class Solution {
    public int integerBreak(int n) {
        return breakInteger(n);
    }

    //計算n的拆分乘積最大值,注意:一定會將n至少分成兩部分
    private int breakInteger(int n) {
        if (n == 1)
            return 1;
        int res = 0;
        for (int i = 1; i < n; i++) {
            res = max3(res,i * (n-i),i * breakInteger(n - i));
        }
        return res;
    }

    private int max3(int a, int b, int c) {
        return Math.max(Math.max(a, b), c);
    }

    // public static void main(String[] args) {
    //    System.out.println(new Solution().integerBreak(4));
    //}
}
View Code

得到結果:

2.2 記憶型遞歸

記憶型遞歸的技巧,在每個求出結果的地方記錄,在遞歸之前查詢。

class Solution {
    private int[] memo; //memeo[i]代表第i個的拆分最大乘積
    public int integerBreak(int n) {
        memo = new int[n+1];
        return breakInteger(n);
    }

    //計算n的拆分乘積最大值,注意:一定會將n至少分成兩部分
    private int breakInteger(int n) {
        if (n == 1)
            return 1;
        if(memo[n]!=0)
            return memo[n];
        int res = 0;
        for (int i = 1; i < n; i++) {
            res = max3(res,i * (n-i),i * breakInteger(n - i));
        }
        memo[n] = res;
        return res;
    }

    private int max3(int a, int b, int c) {
        return Math.max(Math.max(a, b), c);
    }

    //public static void main(String[] args) {
   //     System.out.println(new Solution().integerBreak(10));
   // }
}
View Code

2.3 動態規划(自底向上)

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n + 1];
        dp[1] = 1;
        for (int i = 2; i < n + 1; i++) {
            for (int j = 1; j <= i-1; j++) {
                dp[i] = max3(dp[i], j * (i - j), j * dp[i-j]);
            }
        }
        return dp[n];
    }

    private int max3(int a, int b, int c) {
        return Math.max(Math.max(a, b), c);
    }

    public static void main(String[] args) {
        System.out.println(new Solution().integerBreak(5));
    }
}
View Code

leetcode練習:

       279. 完全平方數

       91. 解碼方法

       62. 不同路徑

       63. 不同路徑 II

例3:打家劫舍

3.1 遞歸寫法(自下而上)

狀態的定義:考慮偷取[ x ..... n-1]范圍的房子 。通常把對狀態的定義也叫做函數的定義

class Solution {
    public int rob(int[] nums) {
        return tryRob(nums, 0);
    }

    private int tryRob(int[] nums, int start) {//start代表從哪個位置開始
        if(start >= nums.length){
            return 0;
        }
        int res = 0;
        
        // res = Math.max(tryRob(nums, start + 1), nums[i] + tryRob(nums, i + 2));
        // 這也是一種遞歸寫法。
        for (int i = start; i < nums.length; i++) {
            res = Math.max(res, nums[i] + tryRob(nums, i + 2));
        }
        return res;
    }

}
View Code

3.2 記憶型遞歸

class Solution {
    private int[] memo;
    public int rob(int[] nums) {
        memo = new int[nums.length];
        return tryRob(nums, 0);
    }

    private int tryRob(int[] nums, int start) {//start代表從哪個位置開始
        if(start >= nums.length){
            return 0;
        }
        //開始前查詢
        if(memo[start]!=0)
            return memo[start];
        int res = 0;
        for (int i = start; i < nums.length; i++) {
            res = Math.max(res, nums[i] + tryRob(nums, i + 2));
        }
        //返回結果前記錄 
        memo[start] = res;
        return res;
    }
}
View Code

3.3 動態規划(自頂向下)

class Solution {
    private int[] dp;//dp[index] 代表從index的位置開始到最后一間房搶到的價值最大值
    private int n;

    public int rob(int[] nums) {
        this.n = nums.length;
        if(nums.length == 0) return 0;
        dp = new int[n];
        dp[n - 1] = nums[n - 1];//只有一個房間,那就搶。不用考慮------最基本問題
        //考慮從i開始搶到的價值最大值
        for (int i = n - 2; i >= 0; i--) {

            for (int j = i; j < n; j++)
                dp[i] = Math.max(dp[i],  ((j + 2) < n ? dp[j + 2] : 0 ) + nums[j]);//一定要用已知的結果
        }
        return dp[0];
    }

}
View Code

狀態的定義02:考慮偷取[ 0 .... x ]范圍里的房子 。

3.1 遞歸寫法

class Solution {

    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        return tryRob02(nums, nums.length - 1);
    }

    private int tryRob02(int[] nums, int end) {
        if (end < 0) {
            return 0;
        }
        int res = 0;
        for (int i = end; i >= 0; i--) {//這里對[end....0]的每一個嘗試偷取
            res = Math.max(res, nums[i] + tryRob02(nums, i - 2));//從[end...0]這多個分支中取得最大值
        }
        return res;
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        int[] nums = {2,7,9,3,1};
        System.out.println(new Solution().rob(nums));
        long end = System.currentTimeMillis();
        System.out.println(end - start + "ms");
    }
}
View Code

3.2 記憶型遞歸

class Solution {
    private int[] memo;

    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        memo = new int[nums.length];
        return tryRob02(nums, nums.length - 1);
    }

    private int tryRob02(int[] nums, int end) {
        if (end < 0) {
            return 0;
        }
        //遞歸前查詢
        if (memo[end] != 0)
            return memo[end];
        int res = 0;
        for (int i = end; i >= 0; i--) {//這里對[end....0]的每一個嘗試偷取
            res = Math.max(res, nums[i] + tryRob02(nums, i - 2));//從[end...0]這多個分支中取得最大值
        }
        //返回結果前記錄
        memo[end] = res;
        return res;
    }
}
View Code

3.3 動態規划

class Solution {
    private int[] dp;//dp[index] 表示從0~index范圍偷取的最大值
    private int n;

    public int rob(int[] nums) {
        if (nums.length == 0) return 0;
        this.n = nums.length;
        dp = new int[n];
        //找到最基本的問題的解
        dp[0] = nums[0];
        //由已知解逐步遞推
        for (int i = 1; i < n; i++) {
            for (int j = i; j >= 0; j--) {
                dp[i] = Math.max(dp[i], nums[j] + (j - 2 >= 0 ? dp[j - 2] : 0));
            }
        }
        return dp[n-1];
    }
    
}
View Code

4. 01背包問題

4.1遞歸解法

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

/**
 * 01背包的狀態方程 F(n,c) n代表前n個物品,c代表背包容量
 * F(i,c) = max(  F(i-1,c) , v[i] + F(i,c-v[i])  )
 */
public class Main {
    /**
     * @param w 重量
     * @param v 價值
     * @param c 背包容量
     * @return
     */
    public static int knapsack01(int[] w, int[] v, int c) {
        return bestValue(w, v, 0, c);
    }

    private static int bestValue(int[] w, int[] v, int index, int c) {
        if (index >= w.length || c <= 0)
            return 0;
        int res = bestValue(w, v, index + 1, c);
        if (c > w[index])
            res = Math.max(res, v[index] + bestValue(w, v, index + 1, c - w[index]));
        return res;
    }

    public static void main(String[] args) {
        InputReader sc = new InputReader();
        int n = sc.nextInt();
        int c = sc.nextInt();
        int[] v = new int[n];
        int[] w = new int[n];
        for (int i = 0; i < n; i++) {
            w[i] = sc.nextInt();
            v[i] = sc.nextInt();
        }
        System.out.println(knapsack01(w, v, c));
    }
    /*
     * 下面就是輸入類,之前介紹過了。可以不用在意
     */
    static class InputReader {
        private BufferedReader bf;
        private StringTokenizer stz;

        public InputReader() {
            bf = new BufferedReader(new InputStreamReader(System.in), 32 * 1014);
            stz = null;
        }

        public boolean hasNext() {
            while (stz == null || !stz.hasMoreTokens()) {
                try {
                    stz = new StringTokenizer(bf.readLine());
                } catch (IOException e) {
                    return false;
                }
            }
            return true;
        }

        public String next() {
            if (hasNext())
                return stz.nextToken();
            return null;
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }
    }
} 
View Code

4.2 記憶型遞歸(核心代碼)

注意memo的開辟空間

private int bestValue(int[] w, int[] v, int index, int c) {
        if (index > w.length || c <= 0)
            return 0;
        //遞歸前查詢
        if(memo[index][c] != 0)
            return memo[index][c];
        int res = bestValue(w, v, index + 1, c);
        if (c > w[index])
            res = Math.max(res, v[index] + bestValue(w, v, index + 1, c - w[index]));
        //返回前記錄
        memo[index][c] = res;

        return res;
    }
View Code

4.3 動態規划(核心)

private int[][] dp; //dp[index][c]代表 背包容量剩余c和可選[0,index]物品的價值最大值
    private int n;

    /**
     * @param w 重量
     * @param v 價值
     * @param c 背包容量
     * @return
     */
    public int knapsack01(int[] w, int[] v, int c) {
        dp = new int[w.length][c + 1];
        n = w.length;
        return bestValue(w, v, 0, c);
    }

    private int bestValue(int[] w, int[] v, int index, int c) {
        if (c <= 0 || w.length == 0)
            return 0;
        //初始化基本問題-------只有0 ~ 0(只有0)之間的物品可選
        for (int capacity = 0; capacity <= c; capacity++) {
            if (capacity >= w[0])
                dp[0][capacity] = v[0];
            else
                dp[0][capacity] = 0;
        }

        //有基本問題推出一般問題
        for (int i = 1; i < n; i++) {
            for (int cap = 0; cap <= c; cap++) {
                if(cap >= w[i])
                    dp[i][cap] = Math.max(dp[i-1][cap],v[i] + dp[i-1][cap - w[i]]);
                else
                    dp[i][cap] = dp[i-1][cap];
            }
        }
        return dp[n-1][c];
    }
View Code

 例5:leetcode 416   基於01背包的問題

詳細見代碼注釋

class Solution {
    /**
     * 狀態定義:F(i,C) i代表0~i的可選范圍,C代表填充的背包容量(在這里背包的容量就是sum/2)
     * 方程的含義是:在0~i的范圍里能否填充C
     * 狀態轉移方程F(i,C) --> F(i-1,C) || F(i,C-nums[i])
     */
    public boolean canPartition(int[] nums) {
        if (nums.length < 0)
            return false;
        int n = nums.length;
        int C = 0;
        for (int i = 0; i < n; i++)
            C += nums[i];
        if (C % 2 != 0)
            return false;
        C = C / 2;
        boolean[] dp = new boolean[C + 1];
        //找到基本問題的解
        for (int i = 0; i <= C; i++) {
            dp[i] = (nums[0] == i);//只用第1(從0到0  [0])個數字去試試能不能填滿背包
        }
        //試試用[0...i]的范圍,一步一步推導
        for (int i = 1; i < n; i++) {
            for (int j = C; j >= nums[i]; j--) {
                dp[j] = dp[j] || dp[j - nums[i]];
            }
        }
        return dp[C];
    }
}
View Code

練習:兌換硬幣

           組合總和

   一和零

           單詞差分

        目標和

 

典例6:最長上升子序列(LIS)

 分析在代碼中已體現

class Solution {
    /**
     * 狀態的定義:LIS(i) 一定以i結尾的最長遞增子序列
     * 狀態的轉移:LIS(i) --> 1 + LIS(j | if(nums[j] < nums[i]) )
     */
    private int n;
    public int lengthOfLIS(int[] nums) {
        this.n = nums.length;
        if(nums.length == 0) return 0;
        int[] dp = new int[n];
        //找到基本問題的解
        for (int i = 0; i < n; i++)
            dp[i] = 1;
        //根據狀態轉移方程推導更進一步問題的解
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if(nums[j] < nums[i] && dp[i] < 1 + dp[j] ){
                    dp[i] = 1 + dp[j];
                }
            }
        }
        //一定要注意返回的是dp數組中的最大值 不是直接return dp[n].
        int res = 0;
        for (int i = 0; i < n; i++) {
            res = Math.max(res,dp[i]);
        }
        return res;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().lengthOfLIS(new int[]{1,3,6,7,9,4,10,5,6}));
    }
}
View Code

如果進一步求出這個子序列是什么呢?

 

練習:擺動序列

更多的問題:

 最長公共子序列(LCS)

狀態的定義: LCS(m , n)  代表 s1[0....m] 和  s2[0.....n]的最長公共子序列的長度 

狀態轉移方程

分為兩種情況:

  如果s1[m] == s2[n]

       LCS(m,n) =  1 + LCS ( m-1 , n-1)

  如果s1[m] != s2[n]

  LCS(m , n ) = max(LCS(m-1,n) ,  LCS( m , n-1 ) ) 

 

class Solution {
    /**
     * 狀態定義: LCS(m , n)  代表 s1[0....m] 和  s2[0.....n]的最長公共子序列的長度
     * 狀態轉移方程:
     * 如果s1[m] == s2[n]
     * LCS(m,n) =  1 + LCS ( m-1 , n-1)
     * 如果s1[m] != s2[n]
     *   LCS(m , n ) = max(LCS(m-1,n) ,  LCS( m , n-1 ) )
     */
    private String s1;
    private String s2;

    public int longestCommonSubsequence(String text1, String text2) {
        this.s1 = text1;
        this.s2 = text2;
        return LCS(s1.length() - 1, s2.length() - 1);
    }

    private int LCS(int m, int n) {
        //找到一般問題的解
        int[][] dp = new int[n + 1][m + 1];//dp[i][j]的含義是:s1[0..i]與s2[0..j]的最長公共子序列
        boolean flag = false;
        for (int i = 0; i <= m; i++)
            if (!flag && s2.charAt(0) == s1.charAt(i)) {
                dp[0][i] = 1;
                flag = true;
            }else if(flag){
                dp[0][i] = 1;
            }
        //根據狀態轉移方程推出更進一步的問題的解
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                if (s2.charAt(i) == s1.charAt(j))
                    if( i-1 >=0 && j-1>=0)
                        dp[i][j] = 1 + dp[i - 1][j - 1]; //這里與遞歸是一樣的意義
                    else
                        dp[i][j] = 1;
                else
                    dp[i][j] = Math.max((i-1>=0?dp[i - 1][j]:0), (j-1>=0?dp[i][j - 1]:0));//這里與遞歸是一樣的意義
            }
        }
        return dp[n][m];
    }

    public static void main(String[] args) {
        System.out.println(new Solution().longestCommonSubsequence("bl", "yby"));
    }
}
View Code

 

9.樹

 線段樹/區間數

/**
 * 該接口是為了為了使線段樹更通用。
 * 當要求一段區間的和 ,merge的功能是 求 a+b
 * 當要求一段區間的積 ,merge的功能是 求 a*b
 * 也就是對於不同的業務,不需要重新修改SegmentTree的代碼
* @author zhanyuhao
* @version 創建時間:2020年3月3日 下午9:57:45
* 類說明
 */
public interface Merger<E> {
    E merge(E a,E b);
}
View Code
/**
 * 線段樹(區間樹)
 * 
 * @author zhanyuhao
 * @version 創建時間:2020年3月3日 下午7:55:54 類說明
 */
public class SegmentTree<E> {
    private E[] data;
    private E[] tree;
    private Merger<E> merger;

    public SegmentTree(E[] arr, Merger<E> merger) {
        this.merger = merger;
        data = (E[]) new Object[arr.length];
        tree = (E[]) new Object[4 * arr.length];
        for (int i = 0; i < arr.length; i++)
            data[i] = arr[i];
        buildTree(0, 0, data.length - 1);
    }

    private void buildTree(int treeIndex, int l, int r) {
        if (l == r) {
            tree[treeIndex] = data[l];
            return;
        }
        int mid = l + (r - l) / 2;
        int leftIndex = leftChild(treeIndex);
        int rightIndex = rightChild(treeIndex);

        buildTree(leftIndex, l, mid);
        buildTree(rightIndex, mid + 1, r);

        tree[treeIndex] = merger.merge(tree[leftIndex], tree[rightIndex]);
    }

    public E query(int queryL, int queryR) {
        if (queryL < 0 || queryL >= data.length || queryR < 0 || queryR >= data.length) {
            throw new IllegalArgumentException("not exist");
        }
        return queryHelp(0, 0, data.length - 1, queryL, queryR);
    }

    private E queryHelp(int treeIndex, int l, int r, int queryL, int queryR) {
        if (queryL == l && queryR == r) {
            return tree[treeIndex];
        }
        int mid = l + (r - l) / 2;
        int leftIndex = leftChild(treeIndex);
        int rightIndex = rightChild(treeIndex);
        if (queryR <= mid) {// 結果在左子樹
            return queryHelp(leftIndex, l, mid, queryL, queryR);
        } else if (queryL > mid) {
            return queryHelp(rightIndex, mid + 1, r, queryL, queryR);
        }
        // 結果分布在兩邊
        E leftResult = queryHelp(leftIndex, l, mid, queryL, mid);
        E rightResult = queryHelp(rightIndex, mid + 1, r, mid + 1, queryR);
        return merger.merge(leftResult, rightResult);
    }

    public void set(int index, E val) {
        if (index < 0 || index >= data.length) {
            new IllegalArgumentException("error");
        }
        set(0, 0, data.length-1, index, val);
    }

    private void set(int treeIndex, int l, int r, int index, E val) {
        if (r == l) {
            tree[treeIndex] = val;
            return;
        }
        int mid = l + (r - l) / 2;
        int leftIndex = leftChild(treeIndex);
        int rightIndex = rightChild(treeIndex);
        if(index <= mid) {
            set(leftIndex,l,mid,index,val);
        }else 
            set(rightIndex,mid+1,r,index,val);
        //因為改變了葉子節點的內容,所以一定要更新其父節點的內容,這是一個聯動的效果
        tree[treeIndex] = merger.merge(tree[leftIndex], tree[rightIndex]);
    }

    private int leftChild(int index) {
        return 2 * index + 1;
    }

    private int rightChild(int index) {
        return 2 * index + 2;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < tree.length; i++) {
            if (tree[i] != null)
                sb.append(tree[i] + " ");
            else {
                sb.append("null ");
            }
        }
        sb.append("]");
        return sb.toString();
    }
}
View Code

下面是測試代碼

public class Main {

    public static void main(String[] args) {
        Integer[] arr = { -2, 0, 3, -5, 2, -1, 1, 3 };
        SegmentTree<Integer> seg = new SegmentTree<Integer>(arr,(a,b)->a+b);//這里使用的是lamda表達式
        seg.set(1, 1);
        seg.set(0, 1);
        seg.set(3, 4);
        System.out.println(seg.query(0, 4));//求出了數組下標為0 - 4 的和
    }
}
View Code

 

10.dfs

 

11.bfs

 

12.必備的技巧

12.1 狀態壓縮

定義:簡單來說就是某種狀態需要由多個變量/元素確定,但是我們用一種方法將狀態 壓縮成一個或者更少的變量/元素就可以表示這個狀態

如:二維數組用一個變量來表示。

數組大小為row*col  

則:v = x * row + col //用一個v來存儲這種狀態

 i = v / col //轉換回去 

 j = v % col

12.2 取模的技巧(用於處理非常大的數據)

(a * b)  % c  =  ((a % c) * (b % c) ) % c

(a + b)  % c  =  ((a % c) + (b % c) ) % c

12.3 鍾表類型的計算

比如一個數字只在0-12,當12 再加 1 就變成 0

那么 num = num % 12 ;

利用這個將減法變成加法 還是上面的例子,一個數減1 就等於這個數加 12

比如:0 - 1 = 12

轉換成 0 + 12 = 12

也就是 (num-1) % 12 = (num+12)%12

這里只是舉了一個特例 取余的那個數為 12 。具體遇到特殊情況,特殊對待

 

12.4 字符轉數字互轉換

將字符減去 '0' 的到的就是數字  

Character.forDigit(int digit, int radix);//將數字轉換成字符

12.5“四 / 八聯通”

四聯通:

設置一個二維數組為dirs[4][2] = [ [-1,0] , [0,1] , [1,0], [-1,0] ] //分別代表上,右,下,左

for (int d = 0; d < 4; d++) {
     nextx = x + dirs[d][0];
     nexty = y + dirs[d][1];
}

八連通:

           for (int i = -1; i < 2; i++) {//-1 0 1
                for (int j = -1; j < 2; j++) { // -1 0 1
                    if(i==0 && j==0) //排除自身,剩下的就是8個方向
                        continue;
                    //.....
                }
            }        

通常用於dfs的搜索

12.6斐波那契數列

前面提到了可以利用矩陣來快速計算斐波那契數列的第n項

在這里直接給出公式,感興趣原理的,可以自己去查尋相關資料。

static int[][] m= {
            {1,1},
            {1,0}
    };
    static int N = m.length;
    /**
     * 通常快速求斐波那契數列需要結合BigInteger來使用,或者需要取余。這里沒有考慮。
     */
    public static void main(String[] args) {
        for(int i=1;i<40;i++) {//從第3項開始的前n項和
            int[][] res = quickExp(m,i);
            int[][] init = {{1,1},{0,0}};
            
            init = multiple(init, res);
            System.out.println(init[0][0]);
        }
    }

    private static int[][] quickExp(int[][] m, int n) {
        int[][] res = new int[N][N];
        for(int i=0;i<N;i++) {
            for (int j = 0; j < N; j++) {
                if(i == j) res[i][j] = 1; 
                else res[i][j] = 0;
            }
        }
        while(n>0) {
            if((n & 1) == 1)
                res = multiple(res, m);
            m = multiple(m, m);
            n = n>>1;
        }
        return res;
    }

    private static int[][] multiple(int[][] m1, int[][] m2) {
        int[][] res = new int[m1.length][m2[0].length];
        for(int i=0;i<m1.length;i++) {
            for(int j=0;j<m2[i].length;j++) {
                for(int k=0;k<m2.length;k++) {
                    res[i][j] += m1[i][k]* m2[k][j];
                }
            }
        }
        return res;
    }
View Code

 

更多更全的代碼及內容https://github.com/zhanyha/lanqiao  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM