算法

時間復(fù)雜度
二進(jìn)制
二進(jìn)制操作

二分查找
冒泡排序
快速排序

動態(tài)規(guī)劃

例子一:切鋼條
例子二:過河問題
例子三:最長公共子序列
例子四:最長公共連續(xù)子序列
例子五:01背包問題

時間復(fù)雜度

一個算法在給定輸入下執(zhí)行的基本操作數(shù)或步數(shù),我們假定執(zhí)行每行代碼需要的時間為常量

二進(jìn)制

二進(jìn)制原碼、反碼、補(bǔ)碼:

  • 二進(jìn)制的最高位是符號位:0表示正數(shù),1表示負(fù)數(shù)
  • 正數(shù)的原碼、反碼、補(bǔ)碼都一樣
  • 負(fù)數(shù)的反碼 = 它的原碼符號位不變,其他位取反
  • 負(fù)數(shù)的補(bǔ)碼 = 它的反碼 +1
  • 0的反碼、補(bǔ)碼都是0
  • 補(bǔ)碼的補(bǔ)碼是原碼

  • 2的原碼:00000000 00000000 00000000 00000010
  • -2的原碼:10000000 00000000 00000000 00000010
  • -2的反碼:11111111 11111111 11111111 11111101
  • -2的補(bǔ)碼:11111111 11111111 11111111 11111110

查看數(shù)字的二進(jìn)制表示,可以用Integer.toBinaryString(-1)
十六進(jìn)制其實(shí)是補(bǔ)碼表示,0xFFFFFFFF等于十進(jìn)制-1

二進(jìn)制操作

"&"與
"|"或
"^"異或:不相同才為1
">>"右移:正數(shù)左邊補(bǔ)0,負(fù)數(shù)左邊補(bǔ)1
"<<"左移:右邊補(bǔ)0


二分查找

描述:在有序數(shù)組里查找一個值,查找成功返回下標(biāo),否則返回-1

思路:

  1. 用數(shù)組中間的值,與給定值比較,相等則查找成功
  2. 不相等,把數(shù)組分為兩半,在其中一半中繼續(xù)步驟1
  3. 直到查找成功,或子數(shù)組不存在

時間復(fù)雜度:
第一步耗時O(1),一個長度為n的數(shù)組,一共要進(jìn)行1 + logn次第一步,因此時間復(fù)雜度是O(logn)

    private static int binarySearch(int[] arr, int key) {
        if (arr == null || arr.length <= 0) {
            return -1;
        }
        int start = 0;
        int end = arr.length - 1;
        int mid;
        while (start <= end) {
            mid = start + (end - start) / 2; // 防止溢出
            if (arr[mid] == key) {
                return mid;
            }else if (arr[mid] > key) {
                end = mid - 1;
            }else {
                start = mid + 1;
            }
        }
        return -1;
    }

冒泡排序

思路:

  1. 針對無序序列,兩兩比較,順序不對則交換,一趟排序后序列末尾的值是有序的
  2. 重復(fù)步驟1,直到無序序列為空
    private static void bubbleSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        int end = arr.length - 1;
        int tem;
        for (int i = end - 1; i >= 0; i--) {
            for (int j = 0; j <= i; j++) {
                if (arr[j] > arr[j+1]) {
                    tem = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tem;
                }
            }
        }
    }

時間復(fù)雜度:
上述算法,無論輸入如何,時間復(fù)雜度都是O(N^2)

冒泡排序改進(jìn):
上述代碼,如果內(nèi)層循環(huán)中沒有進(jìn)行數(shù)據(jù)交換,那么內(nèi)層循環(huán)中的值已經(jīng)是有序的了,此時可以跳出循環(huán)

    private static void bubbleSort1(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        int end = arr.length - 1;
        int tem;
        boolean swapped;
        for (int i = end - 1; i >= 0; i--) {
            swapped = false;
            for (int j = 0; j <= i; j++) {
                if (arr[j] > arr[j+1]) {
                    tem = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tem;
                    swapped = true;
                }
            }
            if (!swapped)
                break;
        }
    }

時間復(fù)雜度:
此時如果輸入已經(jīng)是正序的,那么外層循環(huán)只執(zhí)行一次,此時時間復(fù)雜度O(n)
因此對冒泡排序改進(jìn)來說,最好的時間復(fù)雜度是O(n)


快速排序

思路:

  1. 選取一個主元,進(jìn)行一次定位,定位后,主元左邊元素都比它小,主元右邊元素都比它大
  2. 針對左右兩個分區(qū),分別執(zhí)行步驟1
    // 定位
    private static int position(int[] arr, int start, int end) {
        int i = start;
        int j = end;
        int key = arr[i];

        while (i < j) {
            while(i < j && arr[j] > key) { // 由于可能出現(xiàn)i==j,因此每一步都加i<j判斷,防止ij亂加減
                j--;
            }
            if (i < j) {
                arr[i++] = arr[j];
            }
            while (i < j && arr[i] <= key) {
                i++;
            }
            if (i < j) {
                arr[j--] = arr[i];
            }
        } // 最后i==j
        arr[i] = key; // 由于是覆蓋,而不是交換,因此最后要有一步賦值
        return i;
    }

    // 快速排序
    private static void quickSort(int[] arr, int start, int end) {
        if (arr == null || arr.length <= 1 || start >= end || start < 0 || end > arr.length) {
            return;
        }

        int pos = position(arr, start, end);
        quickSort(arr, pos+1, end);
        quickSort(arr, start, pos-1);
    }

快速排序還有很多改進(jìn)版本,如隨機(jī)選擇基準(zhǔn)數(shù),區(qū)間內(nèi)數(shù)據(jù)較少時直接用另的方法排序以減小遞歸深度。也可以選中間的數(shù)作為基準(zhǔn)數(shù),要實(shí)現(xiàn)這個方便非常方便,直接將中間的數(shù)和第一個數(shù)進(jìn)行交換就可以了(我們用的是覆蓋,因此這里是用第一個數(shù)覆蓋中間的數(shù))

        int key = arr[i];
        替換為
        int mid = start + (end - start) / 2;
        int key = arr[mid];
        arr[mid] = arr[i];

時間復(fù)雜度:
最好場景:T(n) = 2T(n / 2) + n,最好時間復(fù)雜度O(nlogn)
最壞場景:T(n) = T(n - 1) + n,最壞時間復(fù)雜度O(n^2)


動態(tài)規(guī)劃

動態(tài)規(guī)劃和分治算法相似,都是通過組合子問題的解來求解原問題。
特殊情況下,分治算法會反復(fù)求解子問題
動態(tài)規(guī)劃每個子問題只求解一次,將其保存起來,避免不必要的計(jì)算
動態(tài)規(guī)劃通常用來求解最優(yōu)化問題。
動態(tài)規(guī)劃有下面兩種方式,我們通常使用第二種

  • 帶備忘的自頂向下法:通常用一個數(shù)組保存已經(jīng)計(jì)算過的值,求解時,如果數(shù)組中有值,直接返回,否則才進(jìn)行計(jì)算。(計(jì)算f(n),在計(jì)算f(n)的過程中去計(jì)算f(n-1)...)
  • 自底向上法:求解一個問題時,直至它依賴的所有子問題均已求解完成,才求解它。(先計(jì)算f(0),在計(jì)算f(1)...直到f(n))
例子一:切鋼條

問題描述:長度為i的鋼條,價格為p(i),i是整數(shù)。給定一段長度為n的鋼條,怎么切能讓收益最大?
思路:

  • 長度為n的鋼條,最大切割收益用f(n)表示。
  • f(n)怎么計(jì)算呢,考慮鋼條n所有可能的切割方案,先想第一步,第一刀切在哪,第一刀可以切長度為1,2,3,...,n(這是所有可能的場景),那么第一刀切長度i的收益是p(i)+f(n-i)
  • 最大切割收益f(n),即所有可能場景的最大值,于是f(n) = max(p(i)+f(n-i)), i=1 to n
    直接遞歸法
    private static int cut(int[] price, int n) {
        if (n == 0) {
            return 0;
        }
        int res = -1;
        for (int i = 1; i <= n; i++) {
            System.out.println(n + "-" + (n-i));
            res = Math.max(res, price[i-1] + cut(price, n-i));
        }
        return res;
    }

動態(tài)規(guī)劃-帶備忘自頂向下法

    private static int cut1(int[] price, int n) {
        int[] fArr = new int[n+1];
        for (int i = 0; i < n+1; i++) {
            fArr[i] = -1;
        }
        return cut1_aug(price, n, fArr);
    }

    private static int cut1_aug(int[] price, int n, int[] fArr) {
        if(fArr[n] >= 0) {
            return fArr[n];
        }

        int res = -1;
        if (n == 0) {
            res = 0;
        }else {
            for (int i = 1; i <= n; i++) {
                System.out.println(n + "-" + (n-i));
                res = Math.max(res, price[i-1] + cut1_aug(price, n-i, fArr));
            }
        }
        fArr[n] = res;
        return res;
    }

動態(tài)規(guī)劃-自底向上法

    private static int cut2(int[] price, int n) {
        int[] f = new int[n+1]; // 最優(yōu)解數(shù)組
        for (int i = 1; i <= n; i++) {
            int res = -1;
            for (int j = 1; j <= i; j++) {
                System.out.println("*");
                res = Math.max(res, price[j-1] + f[i-j]);
            }
            f[i] = res;
        }
        return f[n];
    }
例子二:過河問題

一座橋,n個人,一個手電筒。橋一次最多通過兩個人,兩個人過橋時間為兩人中時間較長者,過橋必須用手電筒,所以每次過橋之后需要有人把手電筒送回來,問n個人過橋總時間最短是多少?

http://www.360doc.com/content/08/0706/02/37063_1402145.shtml
A ---> B
最佳場景:
1.每次B->A送手電筒的人,一定是B當(dāng)中最快的
2.手電筒在A時,速度最快的人一定在A
3.每次A->B的兩人,要么這兩人其中一個是所有人中最快的,要么這兩個人到B之后再也不回來
4.每次B->A送手電筒的人,一定是所有人中最快的,或者次快的

n個人中:
-最快的人,單人過橋時間設(shè)為a
-次快的人,單人過橋時間設(shè)為b
-次慢的人,單人過橋時間設(shè)為y
-最慢的人,單人過橋時間設(shè)為z

那么,最慢和次慢的人過橋有兩種模式
模式一:(耗時y+a+z+a)

  • ay過橋
  • a回來
  • az過橋
  • a回來

模式二:(耗時b+a+z+b)

  • ab過橋
  • a回來
  • yz過橋
  • b回來

另一種思路,同樣按過河時間從小到大排序

  1. 1個人:a直接過
  2. 2個人:ab直接過
  3. 3個人:ab-a-ac
  4. 4個人:和上面的模式相同
    場景一:ab-a-ac-a-ad,耗時b+a+c+a+d
    場景二:ab-a-cd-b-ab,耗時b+a+d+b+b
  5. n個人
    因?yàn)橐淮巫疃噙^兩個人,所以考慮最后升兩個人(耗時最長),這兩個人的過河方式有2種,對應(yīng)上面兩種模式
    模式一:f(n) = f(n-2) + a + c + a + d
    模式二:f(n) = f(n-2) + a + d + b + b

第i個人過河時間為a[i],遞增
于是 f(n) = min{f(n-2)+arr[1]+arr[n-1]+arr[1]+arr[n], f(n-2)+arr[1]+arr[n]+arr[2]+arr[2]}

    /**
     * 過河時間
     * 輸入:每個人單獨(dú)過河時間,從小到大排列
     * 輸出:所有人過河最短時間
     */
    private static int leastTime(int[] arr) {
        if (arr == null || arr.length == 0) {
            return -1;
        }

        int i = arr.length - 1;
        int sum = 0;
        for (; i > 2; i -= 2) {
            int t1 = 2 * arr[0] + arr[i-1] + arr[i];
            int t2 = 2 * arr[1] + arr[0] + arr[i];
            sum += Math.min(t1, t2);
        }

        if (i == 2) { // 剩下3人
            sum = sum + arr[0] + arr[1] + arr[2];
        }

        if (i == 1) { // 剩下2人
            sum = sum + arr[1];
        }
        return sum;
    }
例子三:最長公共子序列LCS

描述:給定兩個序列X、Y,如果Z即是X的子序列,又是Y的子序列,那么稱Z是X和Y的公共子序列
比如:X=abcbdab,Y=bdcaba,那么bcba是X和Y的一個最長公共子序列

思路:
我們設(shè)X={x1,x2...xm},Y={y1,y2...yn}兩個序列,Z={z1,z2...zk}是X和Y的任意一個LCS,那么
1.如果xm = yn,那么zk=xm=yn且Zk-1是Xm-1和Yn-1的一個LCS
2.如果xm != yn,那么zk != xm表示Z是Xm-1和Y的一個LCS
3.如果xm != yn,那么zk != yn表示Z是X和Yn-1的一個LCS

于是
用c[i,j]表示Xi和Yj的LCS長度,可以得到下面的公式


DX-20190606@2x.png
    // 最長公共子序列
    private static int lcsLenth(char[] x, char[] y) {
        if (x.length == 0 || y.length == 0) {
            return 0;
        }

        int m = x.length;
        int n = y.length;
        int[][] res = new int[m+1][n+1]; // 動態(tài)規(guī)劃典型用法,字典
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j<= n; j++) {
                if (x[i-1] == y[j-1]) {
                    res[i][j] = res[i-1][j-1] + 1;
                }else if (res[i-1][j] > res[i][j-1]) {
                    res[i][j] = res[i-1][j];
                }else {
                    res[i][j] = res[i][j-1];
                }
            }
        }
        return res[m][n];
    }
例子四:最長公共子串

https://blog.csdn.net/u010397369/article/details/38979077
描述:給定兩個字符串X和Y,求最長公共子串,注意子串是連續(xù)了(上面說的子序列可以不連續(xù))
思路:和求最長公共子序列相同的思路
我們定義S(i,j)表示“以xi和yj結(jié)尾的最長公共子串”,用c[i,j]表示其長度
那么

DX-20190606@2x.png

    // 最長公共子串
    private static int lcsLenth2(char[] x, char[] y) {
        if (x.length == 0 || y.length == 0) {
            return 0;
        }

        int m = x.length;
        int n = y.length;
        int[][] res = new int[m+1][n+1];
        int max = 0;
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (x[i-1] == y[j-1]) {
                    res[i][j] = res[i-1][j-1] + 1;
                    if (res[i][j] > max) {
                        max = res[i][j];
                    }
                }
            }
        }
        return max;
    }

上述方法需要一個mn的數(shù)組,空間復(fù)雜度可以優(yōu)化到O(1)
上述方法實(shí)際是計(jì)算了下面這個數(shù)組


image.png

我們可以遍歷每一條對角線,求出最大值,這樣只需要O(1)的空間

    // 最長公共子串
    private static int lcsLenth3(char[] x, char[] y) {
        if (x.length == 0 || y.length == 0) {
            return 0;
        }

        int i = x.length + y.length - 1;
        int tem = 0; // 代替上面二維數(shù)組的臨時變量
        int max = 0;
        while (i > 0) { // 從矩形右上角,沿上邊和左邊,遍歷到左下角
            int col = i > y.length ? (i-y.length) : 0; // 對角線起點(diǎn) 列
            int row = i > y.length ? 0 : (y.length - i); // 對角線起點(diǎn) 行
            while (col < x.length && row < y.length) {
                if (x[col] == y[row]) {
                    tem++;
                    max = tem > max ? tem : max;
                }else {
                    tem = 0;
                }
                col++;
                row++;
            }
            i--;
        }
        return max;
    }
例子五:01背包問題

描述:有N件物品和一個容量為V的背包。第i件物品的容量是c(i),價值是v(i)。每種物品僅有一件,可以選擇放或不放。求解將哪些物品裝入背包可使價值總和最大。
思路:
f[i][j] = max{f[i-1][j], f[i-1][j-c(i)] + v(i)}
f[i][j]表示:“將前i件物品放入容量為j的背包中”的最大價值

考慮第i件物品,
1.背包單獨(dú)放不下,即j<c(i)
-- 此時f[i][j]=f(i-1,j)
2.背包單獨(dú)放的下,即j>=c(i)
-- 放,此時最大價值是,把前i-1個物品放入容量為j-c(i)的背包的最大價值,加上物品i的價值
-- 不放,此時最大價值是,把前i-1個物品放入容量為v的背包,f[i-1][j]
f[i][j] = max{f[i-1][j], f[i-1][j-c(i)] + v(i)}

    // 01背包
    private static int maxV(int[] c, int[] v, int pac) {
        if (c.length == 0 || v.length == 0 || c.length != v.length) {
            return -0;
        }

        int num = c.length; // 物品個數(shù)
        int[][] maxV = new int[num+1][pac+1]; // 行號:物品號 列號:容量大小

        for (int i = 1; i <= num; i++) {
            for (int j = 1; j <= pac; j++) {
                if (j < c[i-1]) {
                    maxV[i][j] = maxV[i-1][j];
                }else if (maxV[i-1][j] > maxV[i-1][j-c[i-1]] + v[i-1]) {
                    maxV[i][j] = maxV[i-1][j];
                }else {
                    maxV[i][j] = maxV[i-1][j-c[i-1]] + v[i-1];
                }
            }
        }
        return maxV[num][pac];
    }

    public static void main(String[] args) {
        int[] c = {4,5,6,2,2}; // 物品占用
        int[] v = {6,4,5,3,6}; // 物品價值
        maxV(c, v, 10);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容