算法:談?wù)剶?shù)據(jù)結(jié)構(gòu)【數(shù)組】那些事

導(dǎo)讀: 數(shù)據(jù)是最基本的數(shù)據(jù)結(jié)構(gòu)眉枕,能解決很多問(wèn)題,比如常見(jiàn)的C_n^m,A_n^m求解,使用數(shù)組來(lái)解決重復(fù)遞歸過(guò)程,動(dòng)態(tài)規(guī)劃使用數(shù)組記錄最近解過(guò)程中的各個(gè)步驟的解录别。今天我們用幾個(gè)常見(jiàn)的面試題來(lái)談一談算法——數(shù)據(jù)結(jié)構(gòu)【數(shù)組】那些事

背景

數(shù)組是最常見(jiàn)的也是經(jīng)常使用的數(shù)據(jù)結(jié)構(gòu),大多數(shù)語(yǔ)言的封裝類型(List/Map)都含有數(shù)組的身影。我們知道數(shù)組時(shí)一塊連續(xù)的內(nèi)存區(qū)域邻吞,訪問(wèn)某個(gè)數(shù)據(jù)時(shí)用數(shù)組下標(biāo)訪問(wèn)组题,當(dāng)擴(kuò)容和刪除時(shí)略顯麻煩.

那我們?cè)趺床拍苡煤脭?shù)組呢?怎么能快速使用數(shù)組這種簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)來(lái)解決復(fù)雜的問(wèn)題呢抱冷?

下面我們從一到題來(lái)引入講解數(shù)組解一些常見(jiàn)的算法題.

給定一個(gè)整數(shù)數(shù)組 nums 崔列,找到一個(gè)具有最大和的連續(xù)子數(shù)組(子數(shù)組最少包含一個(gè)元素),返回其最大和旺遮。

示例: 輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續(xù)子數(shù)組 [4,-1,2,1] 的和最大赵讯,為 6。

最大子序列求和的解法

在我的思維里耿眉,如果一個(gè)問(wèn)題边翼,你之前沒(méi)做過(guò),沒(méi)有系統(tǒng)的學(xué)習(xí)過(guò)此類通用算法解決鸣剪,那么暴力就是能想到的第一種解法思路组底。

下面我們來(lái)看一下暴力解題法丈积,怎么去做?首先我們可以想到

列舉 數(shù)組中的所有可能的連續(xù)區(qū)間/子集斤寇?然后把連續(xù)區(qū)間相加桶癣,求和,判斷最大.

解法一: 時(shí)間復(fù)雜度 O(n^3)

    public int maxSubArray(int[] nums) {
        int max = Integer.MIN_VALUE;
        int sum;
        //使用兩個(gè)for循環(huán)來(lái)列舉出所有的連續(xù)區(qū)間/子集
        for (int i = 0; i < nums.length; i++) {
            for (int j = i; j < nums.length; j++) {
                sum = 0;
                //列舉出來(lái)的子集娘锁,在通過(guò)一個(gè)for循環(huán)相加
                for (int k = i; k <= j; k++) {
                    sum += nums[k];
                }
                if (sum > max) {
                    max = sum;
                }
            }
        }
        return max;
    }

解法二: 時(shí)間復(fù)雜度 O(n^2)

其實(shí)我們?cè)诹信e的過(guò)程中就可以把和相加起來(lái)牙寞,進(jìn)行判斷,所有有了解法二

    public int maxSubArray(int[] nums) {
        int max = Integer.MIN_VALUE;
        int sum;
        for (int i = 0; i < nums.length; i++) {
            sum = 0;
            for (int j = i; j < nums.length; j++) {
            //列舉子序列求和的過(guò)程中解決此問(wèn)題
                sum += nums[j];
                if (sum > max)
                    max = sum;
            }
        }
        return max;
    }

解法三:動(dòng)態(tài)規(guī)劃 時(shí)間復(fù)雜度O(n)

看了上述的暴力解題法莫秆,我們發(fā)現(xiàn)一些可以優(yōu)化的東西间雀,因?yàn)樗兄貜?fù)的子結(jié)構(gòu)性質(zhì)财松,那就是每次相加都從開(kāi)始的那個(gè)元素去相加辕羽,那我們可以從動(dòng)態(tài)規(guī)劃的角度想一下這個(gè)問(wèn)題。

設(shè)sum[i]為以第i個(gè)元素結(jié)尾且和最大的連續(xù)子數(shù)組气筋,那我們可以把上述問(wèn)題描述為這樣的一個(gè)公式
sum[i] = max(sum[i-1] + nums[i], nums[i])
把公式化解一下就是這樣的.
sum[i]=\begin{cases} nums[i] & if(sum[i-1] <= 0) \\ sum[i-1] + nums[i] & if(sum[i-1]> 0) \end{cases}
那么算法可以描述為

  public int maxSubArray(int[] nums) {
        int sums[] = new int[nums.length];
        sums[0] = nums[0];
        int max = nums[0];

        for (int i = 1; i < nums.length; i++) {
            if (sums[i-1] > 0) sums[i] += nums[i];
            else sums[i] = nums[i];
            if (max < sums[i]) max = sums[i];
        }
        return max;
    }

這樣算法的空間復(fù)雜度為O(n)缝驳,如果原數(shù)據(jù)數(shù)組nums可以被更改连锯,那么我們的sums數(shù)組可以省略.

public int maxSubArray(int[] nums) {
        int currnetValue = nums[0];
        int max = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (currnetValue > 0) currnetValue += nums[i];
            else currnetValue = nums[i];
            if (max < currnetValue) max = currnetValue;
        }
        return max;
    }

n層for循環(huán)求解 C_n^m

A_n^m 排列問(wèn)題和組合問(wèn)題很相似(經(jīng)典問(wèn)題有全排列),這里列舉一道 C_n^m相關(guān)的題

給定一個(gè)無(wú)重復(fù)元素的數(shù)組candidates和一個(gè)目標(biāo)數(shù)target,找出candidates中所有可以使數(shù)字和為target的組合用狱。
candidates中的數(shù)字可以無(wú)限制重復(fù)被選取运怖。

說(shuō)明:
所有數(shù)字(包括target)都是正整數(shù)。
解集不能包含重復(fù)的組合夏伊。
示例1: 輸入: candidates = [2,3,6,7], target = 7, 所求解集為:
[
[7],
[2,2,3]
]

示例2:
輸入: candidates = [2,3,5], target = 8,
所求解集為:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

解法

按我的思路來(lái)摇展,第一就去想暴力解法,然后再去想辦法優(yōu)化(如果你之前做過(guò)這題溺忧,或者直接能想到什么動(dòng)態(tài)規(guī)劃算法咏连,可以直接使用,不要在暴力鲁森。)

想講一個(gè)暴力通用解法模型祟滴,就是本節(jié)所說(shuō)的n層for循環(huán)求解 C_n^m.
這可以解決很多與組合相關(guān)的問(wèn)題,如密碼暴力破解等.

給你一個(gè)字符串歌溉,列舉里面所有可能的三位密碼踱启,(字符串里面字母都不相同,不考慮密碼順序)

這樣我們能很簡(jiǎn)單的想到一個(gè)算法

    int len = str.length();
    for(int i = 0;i < len - 2;i++){
        for(int j = i +1;j<len - 1;j++){
            for(int k = j+1;k<len;k++){
                String password = ""+ str.charAt(i) + str.charAt(j) + str.charAt(k);
            }
        }
    }

那要是現(xiàn)在列舉100位密碼呢研底?不可能寫(xiě)這樣的循環(huán)了吧?

一般解法埠偿,是使用遞歸來(lái)替代n層for循環(huán),一般解法模型,偽代碼如下

void find(int start,int count,int passwordLen,int array[]){
    if(count == passwordLen){
        //得到結(jié)果
        return;
    }
    for(int i = start;i<array.length;i++){
        find(i + 1,count+1,passwordLen,array);
    }
}

那么上面那道題榜晦,使用這個(gè)模型來(lái)解決代碼如下:

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> listAll = new ArrayList<>();
        List<Integer> currentList = new ArrayList<>();
        Arrays.sort(candidates);
        find(listAll,currentList,candidates,target,0);
        return listAll;
    }
    
    
    private void find(List<List<Integer>> listAll,List<Integer> currentList,int[] candidates,int target,int currentIndex) {
        if(target < 0) return;
        if(target == 0){
            listAll.add(currentList);
        }else{
            for(int i = currentIndex;i<candidates.length && candidates[i]<=target;i++){
                List<Integer> list=new ArrayList<>(currentList);
                list.add(candidates[i]);
                find(listAll,list,candidates,target - candidates[i],i);
                //currentList.remove(currentList.size()-1);
            }
        }
    }
}

爬樓梯

假設(shè)你正在爬樓梯冠蒋。需要 n 階你才能到達(dá)樓頂。
每次你可以爬 1 或 2 個(gè)臺(tái)階乾胶。你有多少種不同的方法可以爬到樓頂呢抖剿?

如果你之前見(jiàn)過(guò)這題朽寞,那應(yīng)該知道這是一個(gè)斐波那契數(shù)列(相似的問(wèn)題還有很多,如兔子繁殖問(wèn)題等都是斐波那契數(shù)列)斩郎,可以描述為
f(n) = f(n-1)+ f(n-2)

那么求這個(gè)公式怎么解呢脑融?我們可以想到一個(gè)遞歸過(guò)程,如下

 public int fib(int n) {
    if(n <= 2) return 1;
    else{
        return fib(n-1) + fib(n-2);
    }
}

我們知道遞歸過(guò)程中會(huì)有很多重復(fù)計(jì)算缩宜,這里列舉一張法f(5)的計(jì)算過(guò)程肘迎,如下

image

那我們?cè)趺唇鉀Q這個(gè)問(wèn)題呢,使用一個(gè)數(shù)組來(lái)保存結(jié)果锻煌,避免重復(fù)計(jì)算,(也被成為備忘錄法則)妓布,這里其實(shí)已經(jīng)是動(dòng)態(tài)規(guī)劃了。

public int fib(int n) {
        if(n <= 2) return 1;
        if(save[n] > 0){
            return save[n];
        }else{
            save[n] = save[n-1]+save[n-2];
            return save[n];
        }
}

我們知道遞歸有一個(gè)函數(shù)棧的調(diào)用過(guò)程宋梧,那我們可以嘗試把遞歸去了

public int fib(int n) {
        save[0] = 1;
        save[1] = 1;
        for(int i = 2;i < n;i++){
            save[i] = save[i-1]+ save[i-2];
        }
        return save[n-1];
    }
}

其實(shí)這個(gè)如果只是一次計(jì)算f(n)的結(jié)果匣沼,不用使用數(shù)組,可以優(yōu)化為

    int v1 = 1,v2=1;
        for(int i = 2;i < n;i++){
            int temp = v1 + v2;
            v1 = v2;
            v2 = temp;
        }
        return v2;
    }

拓展:看完上述內(nèi)容捂龄,你應(yīng)該能輕松解決释涛,如果樓梯不是一次只能上1或者2層,他能上任意層倦沧,怎么解決這個(gè)問(wèn)題呢唇撬?歡迎留言區(qū)評(píng)論

關(guān)于二維數(shù)組

二維數(shù)組是在一維數(shù)組的基礎(chǔ)上,增加了縱向刀脏。那二維數(shù)組一般的暴力解法和一維數(shù)組基本類似局荚。常見(jiàn)的二維數(shù)組經(jīng)典題目有 【n皇后問(wèn)題】【走迷宮】 對(duì)應(yīng)的回溯算法超凳,也是暴力算法的一種愈污,有一個(gè)剪枝,優(yōu)化過(guò)程轮傍。 在這里舉一個(gè)二維數(shù)組例子暂雹。

一個(gè)機(jī)器人位于一個(gè) m x n 網(wǎng)格的左上角 (起始點(diǎn)在下圖中標(biāo)記為“Start” )。

機(jī)器人每次只能向下或者向右移動(dòng)一步创夜。機(jī)器人試圖達(dá)到網(wǎng)格的右下角(在下圖中標(biāo)記為“Finish”)杭跪。
問(wèn)總共有多少條不同的路徑?

image

有了上面的暴力解題思路過(guò)程驰吓,并且此題也有一些重疊子問(wèn)題涧尿,那我們這題,直接考慮動(dòng)態(tài)規(guī)劃解決. 可以想到遞歸函數(shù)為

sum[i][j]=\begin{cases} 1 & if(i == 0 || j == 0) \\ sum[i-1][j] + sum[i][j-1] & if(i > 0 \&\& j > 0) \end{cases}

根據(jù)遞歸公式代碼檬贰,也能輕松寫(xiě)出

class Solution {
    public int uniquePaths(int m, int n) {
        int sum[][] = new int[m][n];
        for(int i = 0;i<m;i++){
            sum[i][0] = 1;
        }
         for(int i = 0;i<n;i++){
            sum[0][i] = 1;
        }
        for(int i = 1;i<m;i++){
            for(int j = 1;j<n;j++){
                sum[i][j] = sum[i-1][j] + sum[i][j-1];
            }
        }
        return sum[m-1][n-1];
    }
}

總結(jié)

本文主要介紹了一些數(shù)組的算法姑廉,一些解題思路,從問(wèn)題的暴力解法開(kāi)始(通用的暴力解法:兩個(gè)for循環(huán)列舉翁涤,n層for循環(huán)等)桥言,逐步優(yōu)化萌踱,一些能想到的優(yōu)化方式,節(jié)省空間号阿,時(shí)間并鸵。到后面一些可以用動(dòng)態(tài)規(guī)劃解決的問(wèn)題,(此處沒(méi)有列舉一些回溯常見(jiàn)解法扔涧,但是思路都是從暴力解法開(kāi)始园担,逐步剪枝,優(yōu)化的過(guò)程)

文末

因?yàn)楸救思夹g(shù)有限扰柠,文中如有錯(cuò)誤之處粉铐,還請(qǐng)大佬指教,感謝.

文中題目來(lái)源 LeetCode
如果你正在準(zhǔn)備面試卤档,或者你喜歡算法蝙泼,刷題推薦LeetCode,挺不錯(cuò)的網(wǎng)站.

如果你對(duì)圖片,Canvas感興趣劝枣,推薦閱讀

Android:讓你的“女神”逆襲汤踏,代碼擼彩妝(畫(huà)妝)
Flutter PIP(畫(huà)中畫(huà))效果的實(shí)現(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舔腾,隨后出現(xiàn)的幾起案子溪胶,更是在濱河造成了極大的恐慌,老刑警劉巖稳诚,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哗脖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡扳还,警方通過(guò)查閱死者的電腦和手機(jī)才避,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)氨距,“玉大人桑逝,你說(shuō)我怎么就攤上這事∏稳茫” “怎么了楞遏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)首昔。 經(jīng)常有香客問(wèn)我寡喝,道長(zhǎng),這世上最難降的妖魔是什么勒奇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任预鬓,我火速辦了婚禮,結(jié)果婚禮上撬陵,老公的妹妹穿的比我還像新娘珊皿。我一直安慰自己网缝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布蟋定。 她就那樣靜靜地躺著粉臊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驶兜。 梳的紋絲不亂的頭發(fā)上扼仲,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音抄淑,去河邊找鬼屠凶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肆资,可吹牛的內(nèi)容都是我干的矗愧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼郑原,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼唉韭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起犯犁,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤属愤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后酸役,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體住诸,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年涣澡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贱呐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暑塑,死狀恐怖吼句,靈堂內(nèi)的尸體忽然破棺而出锅必,到底是詐尸還是另有隱情事格,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布搞隐,位于F島的核電站驹愚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏劣纲。R本人自食惡果不足惜逢捺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望癞季。 院中可真熱鬧劫瞳,春花似錦倘潜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至伺绽,卻和暖如春养泡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奈应。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工澜掩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杖挣。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓肩榕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親惩妇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子点把,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1.把二元查找樹(shù)轉(zhuǎn)變成排序的雙向鏈表 題目: 輸入一棵二元查找樹(shù),將該二元查找樹(shù)轉(zhuǎn)換成一個(gè)排序的雙向鏈表屿附。 要求不...
    曲終人散Li閱讀 3,308評(píng)論 0 19
  • 算法和數(shù)據(jù)結(jié)構(gòu) [TOC] 算法 函數(shù)的增長(zhǎng) 漸近記號(hào) 用來(lái)描述算法漸近運(yùn)行時(shí)間的記號(hào)郎逃,根據(jù)定義域?yàn)樽匀粩?shù)集$N=...
    wxainn閱讀 1,063評(píng)論 0 0
  • 1. 鏈表 鏈表是最基本的數(shù)據(jù)結(jié)構(gòu),面試官也常常用鏈表來(lái)考察面試者的基本能力挺份,而且鏈表相關(guān)的操作相對(duì)而言比較簡(jiǎn)單褒翰,...
    Mr希靈閱讀 1,439評(píng)論 0 20
  • 來(lái)了想去哪玩?
    王賤賤述閱讀 152評(píng)論 11 0
  • 下班回到家匀泊,小寶說(shuō)他已經(jīng)做完四樣作業(yè)了优训。然后問(wèn)我,可不可以約豆豆阿姨和小弟弟去樓下玩各聘。我回答可以揣非,把手機(jī)給他讓他自...
    在云端1004閱讀 73評(píng)論 0 0