算法-數(shù)組(一)

數(shù)組的相關(guān)算法要簡(jiǎn)單一些茸歧,之前寫(xiě)過(guò)的和現(xiàn)在遇到的整理了一下倦炒。

數(shù)組:數(shù)組是較為簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),它占據(jù)一塊連續(xù)的內(nèi)存软瞎,并按照順序存儲(chǔ)數(shù)據(jù)逢唤。數(shù)組需要事先知道容量大小,然后根據(jù)大小分配存儲(chǔ)空間涤浇,所以數(shù)組的空間利用率不高鳖藕。數(shù)組有很好的查找效率,能在O(1)內(nèi)找到元素只锭。所以我們可以基于數(shù)組實(shí)現(xiàn)簡(jiǎn)單的hash表著恩,提高查找效率。

數(shù)組和指針的關(guān)系:在C語(yǔ)言中蜻展,我們聲明一個(gè)數(shù)組時(shí)喉誊,數(shù)組名也是一個(gè)指針,數(shù)組有大小的限制纵顾,但是指針沒(méi)有限制伍茄,所以在使用指針訪問(wèn)數(shù)組時(shí),要注意不能越界施逾。對(duì)數(shù)組和指針求sizeof時(shí)敷矫,有以下需要注意的地方:
1.int data[] = {0,1,2,3,4};對(duì)data求sizeof是20,因?yàn)閿?shù)組中有5個(gè)整數(shù)音念,每個(gè)整數(shù)占4個(gè)字節(jié)沪饺。
2.int *pData = data;對(duì)pData求sizeof是4,因?yàn)閜Data聲明為一個(gè)指針闷愤,在32位系統(tǒng)中整葡,對(duì)任意指針求sizeof結(jié)果都是4。
3.void fun(int data[]){...}讥脐;在函數(shù)中對(duì)data求sizeof時(shí)遭居,結(jié)果是4,因?yàn)镃語(yǔ)言中將數(shù)組作為參數(shù)傳遞時(shí)旬渠,會(huì)自動(dòng)退化為指針俱萍,結(jié)果還是4.
概念就介紹這些,進(jìn)入正題告丢。

  • 二維數(shù)組中的查找
  • 旋轉(zhuǎn)數(shù)組的最小數(shù)字
  • 數(shù)字在排序數(shù)組中出現(xiàn)的次數(shù)

1.二維數(shù)字中的查找

問(wèn)題描述:每行從左到右枪蘑,每列從上到下遞增的二維數(shù)組中,判斷某個(gè)數(shù)是否存在。

遞增的二維數(shù)組

算法思想

思路一:常規(guī)思路岳颇,我們很快就能想到遍歷二維數(shù)組照捡,和要查找的數(shù)字比較,判斷是否存在话侧。這樣的方法時(shí)間復(fù)雜度為O(n^2)栗精,效率很低。
思路二:我們可以利用遞增的二維數(shù)組的特點(diǎn)瞻鹏,選定數(shù)組中的一個(gè)點(diǎn)悲立,和要查找的key比較,如果key大于這個(gè)數(shù)新博,就在這個(gè)數(shù)的下面和右邊查找薪夕,如果小于這個(gè)數(shù),就在這個(gè)數(shù)的上面和左邊查找赫悄,這個(gè)方法要比一個(gè)一個(gè)遍歷好寥殖,但是存在一個(gè)問(wèn)題,就是上面和右邊方向上的查找會(huì)有重復(fù)的查找的地方涩蜘。
思路三:在思路二上進(jìn)行改進(jìn)。我們之前的查找都是從左上角開(kāi)始熏纯,那么從右上角開(kāi)始呢同诫?以上的矩陣,假如我們查找的是7樟澜,從右上角開(kāi)始误窖,先比較9和7,9比7大秩贰,9又是本列的第一個(gè)霹俺,那么可以確定,9所在的列數(shù)字都大于7毒费,我們可以排除最后一列丙唧,前移一列。比較8和7觅玻,8大于7想际,同樣的思想我們可以排除8所在的列。接下來(lái)比較2和7溪厘,2小于7胡本,那么2所在的行前面的數(shù)字都小于7,可以不必再比較了畸悬,直接排除2所在的行侧甫。以此類(lèi)推,我們很快就可以定位到7。

代碼

思路清楚之后披粟,代碼就簡(jiǎn)單多了咒锻。
從矩陣的右上角開(kāi)始進(jìn)行判斷,最快的排除不需要比較的列和行
1.當(dāng)前元素>number,那么當(dāng)前元素所在的行就可以排除-->縮小比較范圍
2.當(dāng)前元素<number僻爽,那么當(dāng)前元素所在列可以排除
3.當(dāng)前元素=number虫碉,返回true
注:傳入二維數(shù)組的時(shí)候被強(qiáng)轉(zhuǎn)成了指針,所以在取出二維數(shù)組中的數(shù)字時(shí)胸梆,要更具指針的操作規(guī)范來(lái)敦捧。

bool Find(int* matrix, int rows, int columns, int number)
{
    bool found = false;

    if (matrix != NULL && rows > 0 && columns > 0)
    {
        int row = 0;
        int column = columns - 1;
        while (row < rows && column >= 0)
        {
            if (matrix[row * columns + column] == number){//相當(dāng)于matrix[row][column]
                found = true;
                break;
            }
            else if (matrix[row * columns + column] > number)
                column--;
            else
                row++;
        }
    }
    return found;
}

旋轉(zhuǎn)數(shù)組的最小數(shù)字

問(wèn)題描述:把數(shù)組開(kāi)頭的幾個(gè)數(shù)字移動(dòng)到數(shù)組的末尾,成為數(shù)組的旋轉(zhuǎn)碰镜,輸入一個(gè)遞增數(shù)組的旋轉(zhuǎn)兢卵,找到數(shù)組中最小的數(shù)字。

算法思想

思路一:一次遍歷就可以找到最小的數(shù)字绪颖。時(shí)間復(fù)雜度為O(n).
思路二:利用遞增數(shù)組的特點(diǎn)秽荤,利用二分法查找。我們可以發(fā)現(xiàn)遞增數(shù)組旋轉(zhuǎn)之后之后可以得到兩個(gè)排序的子數(shù)組柠横,且第一個(gè)數(shù)字會(huì)大于或者等于最后一個(gè)數(shù)字窃款,兩個(gè)排序子數(shù)組的分界點(diǎn)就是這個(gè)最小的數(shù)字,這樣在一定程度上排序的數(shù)組牍氛,我們可以嘗試使用二分法的思想晨继。使用兩個(gè)指針,一個(gè)指向數(shù)組頭搬俊,一個(gè)指向數(shù)組尾紊扬,接下來(lái)可以找到中間元素,如果中間元素大于第一個(gè)元素唉擂,中間元素位于前面的子數(shù)組中餐屎,就把前一個(gè)指針移動(dòng)到中間元素的位置;如果中間元素小于后一個(gè)指針指向的元素玩祟,說(shuō)明中間元素位于后面的子數(shù)組中腹缩,就把后一個(gè)指針移到中間元素的位置,這樣就減少了一半查找范圍空扎。當(dāng)?shù)谝粋€(gè)指針和后一個(gè)指針相差1時(shí)庆聘,那么后一個(gè)指針?biāo)妇褪亲钚≡亓恕r(shí)間復(fù)雜度為O(logn).

在數(shù)組{3勺卢,4伙判,5,1黑忱,2}中找最小值

改進(jìn):以上思路還存在bug宴抚,我們開(kāi)考慮這樣的數(shù)組勒魔,如圖,在第一個(gè)數(shù)組中菇曲,我們定位到中間元素后發(fā)現(xiàn)冠绢,前一個(gè)指針?biāo)福虚g元素和后一個(gè)指針?biāo)付际且粯映3保绻@個(gè)時(shí)候簡(jiǎn)單的把中間元素歸到前一個(gè)子數(shù)組中弟胀,那么默認(rèn)最小元素在中間元素的后面就出錯(cuò)了。原因在于這樣的中間元素我們不能確定它是屬于后面還是屬于前面喊式,這時(shí)只能順序查找了孵户。

數(shù)組{0,1岔留,1夏哭,1,1}的旋轉(zhuǎn)數(shù)組

代碼

分析結(jié)束可以看代碼了献联。
注意:在Min函數(shù)中竖配,我們把indexMid初始化為index1,也就是指向第一個(gè)元素里逆,當(dāng)傳入的數(shù)組是遞增數(shù)組時(shí)(原數(shù)組本身也是數(shù)組的一個(gè)旋轉(zhuǎn)數(shù)組)进胯,第一個(gè)元素就是最小的,這時(shí)函數(shù)會(huì)返回第一個(gè)元素原押。

//當(dāng)index1,indexMid,index2所指元素都相等的時(shí)只能順序查找
int MinInOrder(int *numbers, int index1, int index2)
{
    for (int i = index1 + 1; i <= index2; i++)
    {
        if (result > numbers[i])
        {
            return numbers[i];
       }

    }
    return numbers[index1];
}

//查找旋轉(zhuǎn)數(shù)組中的最小數(shù)
int Min(int *numbers, int length)
{
    if (numbers == NULL || length < 0)
    {
        fprintf(stderr, "Invalid parameter\n" );
        exit(1);
    }

    int index1 = 0;
    int index2 = length - 1;
    int indexMid = index1;
    while (numbers[index1] >= numbers[index2])
    {
        if ((index2 - index1) == 1)
        {
            indexMid = index2;
            break;
        }

        indexMid = (index2 + index1) / 2;
        //特殊情況龄减,無(wú)法判斷indexMid是屬于哪一個(gè)子數(shù)組
        if (numbers[index1] == numbers[indexMid] && numbers[indexMid] == numbers[index2])
            return MinInOrder(numbers, index1, index2);
        //中間元素屬于前一個(gè)子數(shù)組
        if (numbers[indexMid] > numbers[index1])
            index1 = indexMid;
        else if (numbers[indexMid] < numbers[index2])
            index2 = indexMid;
    }
    return numbers[indexMid];
}

數(shù)字在排序數(shù)組中出現(xiàn)的次數(shù)

問(wèn)題描述:統(tǒng)計(jì)一個(gè)數(shù)字在排序數(shù)組中出現(xiàn)的次數(shù)。如果數(shù)組{1,2,3,3,3,3,4,5}和數(shù)字3,3在數(shù)組中出現(xiàn)了4次班眯,返回4.
這個(gè)可以看做是查找問(wèn)題,關(guān)注排序數(shù)組幾個(gè)字。

算法思想

思路一:最簡(jiǎn)單直接的思路就是一次遍歷數(shù)組,進(jìn)行統(tǒng)計(jì)讨阻,時(shí)間復(fù)雜度為O(n)仁烹。
思路二:常規(guī)的思路人人都能想到,雖然簡(jiǎn)單句惯,但是考慮到數(shù)組的特點(diǎn),注意,這里的數(shù)組是排序數(shù)組诊霹。說(shuō)到排序數(shù)組,我們可以想到二分查找渣淳,那么如果把二分查找的思想遷移到這里呢脾还?先找到最中間的元素,如果最中間的元素小于或者大于要統(tǒng)計(jì)次數(shù)的數(shù)字入愧,那么范圍減少一半鄙漏,其實(shí)提高了效率嗤谚,但是如果最中間的數(shù)組等于要查找的數(shù)字呢?我們就需要向兩邊查找怔蚌,一直找到第一個(gè)數(shù)字和最后一個(gè)數(shù)字巩步,數(shù)字在長(zhǎng)度為n的數(shù)組中有可能出現(xiàn)n次,所以桦踊,時(shí)間復(fù)雜度為O(n),和思路一的時(shí)間復(fù)雜度相同了椅野。
思路三:思路二的改進(jìn)。假設(shè)要統(tǒng)計(jì)次數(shù)的數(shù)字為key,在思路二中籍胯,時(shí)間耗費(fèi)主要是在確定第一個(gè)key和最后一個(gè)key的時(shí)候竟闪,那么如果用二分法獲取第一個(gè)key和最后一個(gè)key的位置呢?先找到中間數(shù)字芒炼,如果比key小瘫怜,說(shuō)明第一個(gè)key在后半部分,如果比key大本刽,說(shuō)明第一個(gè)key在前半部分鲸湃,如果相等的話,先看中間數(shù)字的前一個(gè)是不時(shí)也等于key子寓,如果不等暗挑,就可以確定中間數(shù)字就是第一個(gè)key,如果相等斜友,說(shuō)明第一個(gè)key在前一半炸裆,繼續(xù)查找,找最后一個(gè)key的方法也類(lèi)似鲜屏,可以用遞歸實(shí)現(xiàn)烹看。這樣的算法,找第一個(gè)key的時(shí)候時(shí)間復(fù)雜度為O(logn)洛史,找最后一個(gè)key的時(shí)候惯殊,時(shí)間復(fù)雜度也是O(logn),所以總的時(shí)間復(fù)雜度也是O(logn)也殖。

代碼

下面是思路三的代碼土思,有以下需要注意的地方:

  1. 在FindFirstK和FindLastK函數(shù)中,在判斷中間元素的前一個(gè)元素或后一個(gè)元素是否是key時(shí)忆嗜,要先判斷是否到達(dá)數(shù)組邊界己儒,防止越界。
  2. 在函數(shù)GetNumberOfK中捆毫,計(jì)算key出現(xiàn)的次數(shù)時(shí)先判斷了firstIndex和lastIndex是否都大于-1闪湾,這是因?yàn)橹灰幸粋€(gè)值為-1,那么數(shù)組中就不可能存在這個(gè)key绩卤。因?yàn)樵贔indFirstK和FindLastK中响谓,只有找不到key才會(huì)返回-1损合,既然數(shù)組中沒(méi)有key,那么自然就不會(huì)執(zhí)行middle==key下面的判斷了娘纷,而這里嫁审,就是FindFirstK和FindLastK兩個(gè)函數(shù)不同的地方。也就是說(shuō)赖晶,當(dāng)數(shù)組中沒(méi)有key時(shí)律适,F(xiàn)indFirstK和FindLastK兩個(gè)函數(shù)執(zhí)行的過(guò)程都是一樣的。所以遏插,我們也可以改進(jìn)下面的代碼捂贿,在GetNumberOfK中,計(jì)算了firstIndex之后先判斷其是否等于-1胳嘲,如果等于-1就厂僧,不必再計(jì)算lastIndex了,直接返回0.
//遞歸確定第一個(gè)key的下標(biāo)
int FindFirstK(int *data, int length, int start, int end, int key)
{
    if (start > end)
        return -1;

    int middleIndex = (start + end) / 2;
    int middle = data[middleIndex];

    if (middle == key)
    {
        //先判斷是否大于0了牛,再計(jì)算data[middleIndex-1]防止越界
        if ((middleIndex > 0 && data[middleIndex - 1] != key)
           || middleIndex == 0)
            return middleIndex;
        else
            end = middleIndex - 1;
    }
    else if (middle < key)
        start = middleIndex + 1;
    else//middle > key
        end = middleIndex - 1;
    FindFirstK(data, length, start, end, key);
}

//遞歸確定最后一個(gè)key的位置
int FindLastK(int *data, int length, int start, int end, int key)
{
    if (start > end)
        return -1;
    int middleIndex = (start + end) / 2;
    int middle = data[middleIndex];

    if (middle == key)
    {
        if ((middleIndex < length - 1 && data[middleIndex + 1] != key)
            || middleIndex == length - 1)
            return middleIndex;
        else
            start = middleIndex + 1;
    }
    else if (middle < key)
        start = middleIndex + 1;
    else//middle > key
        end = middleIndex - 1;
    FindLastK(data, length, start, end, key);
}

int GetNumberOfK(int *data, int length, int key)
{
    int count = 0;
    if (data != NULL || length > 0)
    {
        int firstIndex = FindFirstK(data, length, 0, length - 1, key);
        int lastIndex = FindLastK(data, length, 0, length - 1, key);
        if (firstIndex > -1 && lastIndex > -1)
            count = lastIndex - firstIndex + 1;
    }
    return count;
}

總結(jié)

這次的三道題說(shuō)是數(shù)組相關(guān)的算法題颜屠,也可以說(shuō)是和查找算法題,在二維數(shù)組中的查找中鹰祸,我們利用了二維遞增數(shù)組的規(guī)律甫窟,每次都找右上角的數(shù)組,便于快速排出行或者列蛙婴;在旋轉(zhuǎn)數(shù)組的最小數(shù)字和計(jì)算數(shù)字在排序數(shù)組中出現(xiàn)的次數(shù)中粗井,我們利用了排序數(shù)組的特點(diǎn),用二分法進(jìn)行查找街图,其實(shí)是遷移了二分查找的思想浇衬,進(jìn)行了變換得到的。

好了餐济,這次就到這里了耘擂。不足之處,還請(qǐng)多指教颤介。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赞赖,隨后出現(xiàn)的幾起案子滚朵,更是在濱河造成了極大的恐慌,老刑警劉巖前域,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辕近,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡匿垄,警方通過(guò)查閱死者的電腦和手機(jī)移宅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)归粉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人漏峰,你說(shuō)我怎么就攤上這事糠悼。” “怎么了浅乔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵倔喂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我靖苇,道長(zhǎng)席噩,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任贤壁,我火速辦了婚禮悼枢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脾拆。我一直安慰自己馒索,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布假丧。 她就那樣靜靜地躺著双揪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪包帚。 梳的紋絲不亂的頭發(fā)上渔期,一...
    開(kāi)封第一講書(shū)人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音渴邦,去河邊找鬼疯趟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谋梭,可吹牛的內(nèi)容都是我干的信峻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼瓮床,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盹舞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起隘庄,我...
    開(kāi)封第一講書(shū)人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤踢步,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后丑掺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體获印,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年街州,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兼丰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玻孟。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鳍征,靈堂內(nèi)的尸體忽然破棺而出黍翎,到底是詐尸還是另有隱情,我是刑警寧澤蟆技,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布玩敏,位于F島的核電站,受9級(jí)特大地震影響质礼,放射性物質(zhì)發(fā)生泄漏旺聚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一眶蕉、第九天 我趴在偏房一處隱蔽的房頂上張望砰粹。 院中可真熱鬧,春花似錦造挽、人聲如沸碱璃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嵌器。三九已至,卻和暖如春谐丢,著一層夾襖步出監(jiān)牢的瞬間爽航,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工乾忱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讥珍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓窄瘟,卻偏偏與公主長(zhǎng)得像衷佃,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蹄葱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

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