刷題筆記

最近在準(zhǔn)備面試,發(fā)現(xiàn)自己真的菜的不行瑞你,就計(jì)劃接下來(lái)的時(shí)間把 leetcode 上面刷的 中等題每日一題做個(gè)簡(jiǎn)書筆記。稍微記錄一下,等保研時(shí)或許還有用昂勉。

  1. 旋轉(zhuǎn)圖像
    題目:給定一個(gè) n × n 的二維矩陣表示一個(gè)圖像。將圖像順時(shí)針旋轉(zhuǎn) 90 度扫腺。

說(shuō)明:給定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋轉(zhuǎn)輸入矩陣岗照,使其變?yōu)?
[
[7,4,1],
[8,5,2],
[9,6,3]
]

思路:可以先沿著反對(duì)角線轉(zhuǎn)一次,再?gòu)闹虚g轉(zhuǎn)一次。

class Solution {

    //解題思路:先沿著對(duì)角線轉(zhuǎn)一次攒至,再沿著水平線轉(zhuǎn)一次
    public void rotate(int[][] matrix) {
        for(int i =  0; i < matrix.length; i++){
            for(int j = 0; j <matrix.length - i ; j++){
                swap(matrix,i,j,matrix.length-1-j,matrix.length-1-i);
            }
        }
        for(int i =  0; i < matrix.length/2; i++){
            for(int j = 0; j <matrix.length; j++){
                swap(matrix,i,j,matrix.length-1-i,j);
            }
        }
    }

    //swap element i and j
    public void swap(int matrix[][],int ix,int iy,int jx,int jy){
        int temp = matrix[ix][iy];
        matrix[ix][iy] = matrix[jx][jy];
        matrix[jx][jy] = temp;
    }
}
  1. 有效的數(shù)獨(dú):

判斷一個(gè) 9x9 的數(shù)獨(dú)是否有效煞肾。只需要根據(jù)以下規(guī)則,驗(yàn)證已經(jīng)填入的數(shù)字是否有效即可嗓袱。
數(shù)字 1-9 在每一行只能出現(xiàn)一次籍救。
數(shù)字 1-9 在每一列只能出現(xiàn)一次。
數(shù)字 1-9 在每一個(gè)以粗實(shí)線分隔的 3x3 宮內(nèi)只能出現(xiàn)一次渠抹。

["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
輸出:true

思路:

  • 可以遍歷三次 9*9 數(shù)組蝙昙,分別判斷是否符合三個(gè)要求
  • 可以只遍歷一次,訪問(wèn)每一個(gè)元素時(shí)判斷三個(gè)要求是否被違背:可以用二進(jìn)制位來(lái)表示一列 / 一行 / 一個(gè) 3*3的 Grid 中的狀態(tài)梧却,使用位操作更新奇颠。
class Solution {

    private final int VERTICAL = 0;
    private final int HORIZONTAL = 1;
    private final int GRID = 2;

    public boolean isValidSudoku(char[][] board) {
        int status[] = new int[27];//一列、一格放航、一行有九個(gè)狀態(tài)烈拒,九個(gè)bit就夠,總共27個(gè)。
        for(int i  = 0 ; i < 9; i ++){
            for(int j = 0; j < 9; j ++){
                if(board[i][j] !='.'){    
                    int n = board[i][j] - '0';
                    if(!checkAndSet(status, VERTICAL ,i, n)){
                        return false;
                    }
                    if(!checkAndSet(status, HORIZONTAL, j,n)){
                        return false;
                    }
                    if(!checkAndSet(status, GRID, (i/3) * 3 + (j/3), n)){
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /*
    * @param type : VERTICAL(從上到下), HORIZONTAL(從左到右)广鳍,GRID(3*3)
    * @param index : 這一類中的第 index 行/列/格子荆几。 
    * @param num : 數(shù)字 1-9
    */
    public boolean checkAndSet(int status[], int type, int index, int num){
        int i = type * 9 + index;
        if((status[i] & (1 << (num-1))) == 0 ){
            status[i] = (status[i]^(1<<(num-1)));
            return true;
        }
        return false;
    }
}

這里使用了short,內(nèi)存使用率為 9/16赊时,可以換成int吨铸,會(huì)把內(nèi)存使用率提高到 27/32。

  1. 跳躍游戲
    給定一個(gè)非負(fù)整數(shù)數(shù)組祖秒,你最初位于數(shù)組的第一個(gè)位置诞吱。
    數(shù)組中的每個(gè)元素代表你在該位置可以跳躍的最大長(zhǎng)度。
    判斷你是否能夠到達(dá)最后一個(gè)位置竭缝。

輸入: [2,3,1,1,4]
輸出: true
解釋: 我們可以先跳 1 步房维,從位置 0 到達(dá) 位置 1, 然后再?gòu)奈恢?1 跳 3 步到達(dá)最后一個(gè)位置。

思想:將數(shù)組中分成“好位置”和“壞位置”抬纸,可以從好位置跳到最后而壞位置不可以咙俩;動(dòng)態(tài)規(guī)劃,從后向前(反過(guò)來(lái)也行)一格一格的試探好位置松却,并記錄目前最靠前的好位置

    public boolean canJump(int[] nums) {
        int last = nums.length-1;//數(shù)組的最后一個(gè)位置一定是“好位置”
        for(int j = last-1; j >-1; --j){
            if(last - j <= nums[j]){
                last = j;
            }
        }
        if(last == 0){
            return true;
        }
        return false;
    }
}

  1. 第k個(gè)排列
    按大小順序列出所有排列情況暴浦,并一一標(biāo)記,當(dāng) n = 3 時(shí), 所有排列如下:

"123"
"132"
"213"
"231"
"312"
"321"
給定 n 和 k晓锻,返回第 k 個(gè)排列歌焦。

輸入: n = 3, k = 3
輸出: "213"

class Solution {
    public String getPermutation(int n, int k) {
        List<Integer> l = new ArrayList<>();
        l.add(1);
        int fac[] = new int[n];
        fac[0] = 1;
        for(int i = 1; i < n; i++){
            fac[i] = fac[i-1] * i; //初始化到一個(gè)數(shù)組中,運(yùn)用動(dòng)態(tài)規(guī)劃砚哆。
            l.add(i+1);
        }
        StringBuilder builder = new StringBuilder("");
        k--;

        //觀察序列独撇,n = 3 的全排列,第一個(gè)數(shù)index是 (位置-1)/2 ,第二個(gè)數(shù)是(位置-1)/1,第三個(gè)數(shù)是(位置-1)/1,除數(shù)除了最后一項(xiàng)是1纷铣,符合n!,只需要維護(hù)一個(gè)數(shù)組卵史,按照計(jì)算得到的idx從數(shù)組中取出并刪除。
        for(int i = n-1; i >= 0; --i){    
            int idx = k / fac[i];
            k -= idx * fac[i];
            builder.append((char)(l.get(idx)+'0'));//要先強(qiáng)制轉(zhuǎn)型搜立,否則append會(huì)把后面的樹(shù)作為int處理以躯,得到4950這樣的
            l.remove(idx);
        }
        return builder.toString();               
    }
}
  1. 多數(shù)元素
    給定一個(gè)大小為 n 的數(shù)組,找到其中的多數(shù)元素啄踊。多數(shù)元素是指在數(shù)組中出現(xiàn)次數(shù)大于 ? n/2 ? 的元素忧设。

輸入: [3,2,3]
輸出: 3

我使用的解法是哈希映射的方法,太naive颠通,不貼代碼了址晕,記錄下官方十分強(qiáng)大的投票算法。

//如果我們把眾數(shù)記為 +1+1顿锰,把其他數(shù)記為 -1?1谨垃,將它們?nèi)考悠饋?lái)
//,顯然和大于 0硼控,從結(jié)果本身我們可以看出眾數(shù)比其他數(shù)多刘陶。
class Solution {
    public int majorityElement(int[] nums) {
        int count = 0;
        Integer candidate = null;

        for (int num : nums) {
            if (count == 0) {
                candidate = num;
            }
            count += (num == candidate) ? 1 : -1;
        }

        return candidate;
    }
}

//作者:LeetCode-Solution
//鏈接:https://leetcode-cn.com/problems/majority-element/solution/duo-shu-yuan-su-by-leetcode-solution/
//來(lái)源:力扣(LeetCode)
//著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)淀歇,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處易核。

這里的candidate可能不是眾數(shù)匈织,但是沒(méi)有關(guān)系浪默,由于眾數(shù)占多數(shù),在數(shù)組中總是可能出現(xiàn)count=0的時(shí)候缀匕,這時(shí)會(huì)重新選擇候選人纳决,其他人投出去的選票,總是會(huì)被眾數(shù)投出去的抵消掉乡小。

  1. 旋轉(zhuǎn)鏈表:
    給定一個(gè)鏈表阔加,旋轉(zhuǎn)鏈表,將鏈表每個(gè)節(jié)點(diǎn)向右移動(dòng) k 個(gè)位置满钟,其中 k 是非負(fù)數(shù)胜榔。

輸入: 1->2->3->4->5->NULL, k = 2
輸出: 4->5->1->2->3->NULL
解釋:
向右旋轉(zhuǎn) 1 步: 5->1->2->3->4->NULL
向右旋轉(zhuǎn) 2 步: 4->5->1->2->3->NULL

暴力的解法可以遍歷列表k遍,每次將最后一個(gè)節(jié)點(diǎn)置于第一個(gè)湃番,時(shí)間復(fù)雜度(k*n)夭织。
更好的方法是:將首尾相連,將head相連吠撮,將head和last都向后挪動(dòng) n - k%n 位尊惰。


class Solution {

    public ListNode rotateRight(ListNode head, int k) {
        if(head==null ){
            return null;
        }
        ListNode l = head;  
        int n = 1;     
        while(l.next!=null){
           l = l.next; 
           n++;
        }    
        l.next = head;
        for(int i = 0; i < n - k%n; i++){ //注意這里要%n,否則k>n時(shí)出問(wèn)題。
            head = head.next;
            l = l.next;
        }
        l.next = null;
        return head;
    }
}
  1. 不同路徑
  • 一道很典型的動(dòng)態(tài)規(guī)劃題弄屡,方程很好找题禀。
    一個(gè)機(jī)器人位于一個(gè) m x n 網(wǎng)格的左上角 (起始點(diǎn)在下圖中標(biāo)記為“Start” )。

機(jī)器人每次只能向下或者向右移動(dòng)一步膀捷。機(jī)器人試圖達(dá)到網(wǎng)格的右下角(在下圖中標(biāo)記為“Finish”)迈嘹。

現(xiàn)在考慮網(wǎng)格中有障礙物。那么從左上角到右下角將會(huì)有多少條不同的路徑全庸?

網(wǎng)格中的障礙物和空位置分別用 1 和 0 來(lái)表示江锨。

輸入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
輸出: 2

無(wú)障礙物版本:

class Solution {

    // len(x,y) = len(x+1,y) + len (x,y+1);
    // len (m, n-1) = 1;
    public int uniquePaths(int m, int n) {
        int res[][] = new int[m][n];
        res[m-1][n-1] = 1;
        for(int i = m -1 ; i >=0; --i){
            for(int j = n-1; j >=0; --j){
                if(j < n - 1){
                    res[i][j] += res[i][j+1];
                }
                if(i < m - 1){
                    res[i][j] += res[i+1][j];
                }
            }
        }
        return res[0][0];
    } 

}

障礙物版本

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int res[][] = new int[m][n];
        if(obstacleGrid[m-1][n-1] == 0){
           res[m-1][n-1] = 1;           
        }
        for(int i = m - 1 ; i >=0; --i){
            for(int j = n - 1; j >=0; --j){
                if(obstacleGrid[i][j] == 0){
                    if(j < n - 1){
                        res[i][j] += res[i][j+1];
                    }
                    if(i < m - 1){
                        res[i][j] += res[i+1][j];
                    }
                }
            }
        }
        return res[0][0];
    }
}
  • 最短路徑
class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int res[][] = new int[m][n];
        res[m-1][n-1] = grid[m-1][n-1];
        for(int i = m - 1 ; i >=0; --i){
            for(int j = n - 1; j >=0; --j){
                if(i == m-1 && j < n-1){
                    res[i][j] = res[i][j+1] +grid[i][j];
                }
                if(j == n-1 && i < m-1){
                    res[i][j] = res[i+1][j] + grid[i][j];
                }
                if(j < n - 1 && i < m-1){
                    res[i][j] = Math.min(res[i][j+1] ,res[i+1][j]) +grid[i][j];
                }           
            }
        }
        return res[0][0];
    }
}
  1. 簡(jiǎn)化路徑
    以 Unix 風(fēng)格給出一個(gè)文件的絕對(duì)路徑,你需要簡(jiǎn)化它糕篇∽挠或者換句話說(shuō),將其轉(zhuǎn)換為規(guī)范路徑拌消。

示例 1:
輸入:"/home/"
輸出:"/home"
解釋:注意挑豌,最后一個(gè)目錄名后面沒(méi)有斜杠。

示例 2:
輸入:"/../"
輸出:"/"
解釋:從根目錄向上一級(jí)是不可行的墩崩,因?yàn)楦悄憧梢缘竭_(dá)的最高級(jí)氓英。

示例 3:
輸入:"/home//foo/"
輸出:"/home/foo"
解釋:在規(guī)范路徑中,多個(gè)連續(xù)斜杠需要用一個(gè)斜杠替換鹦筹。

示例 4:輸入:"/a/./b/../../c/"
輸出:"/c"

示例 5:
輸入:"/a/../../b/../c//.//"
輸出:"/c"

示例 6:
輸入:"/a//b////c/d//././/.."
輸出:"/a/b/c"

思路:使用LinkedList 模擬棧來(lái)簡(jiǎn)化路徑

class Solution {
    LinkedList<String> list = new LinkedList<>();
    public String simplifyPath(String path) {
        String[] strs=path.split("/");
        for(String str: strs){
            if(str.equals("")||str.equals(".")){
                continue;
            }
            if(str.equals("..")){
                if(list.size()>0){
                    list.removeLast();
                }
            } else {
                list.addLast(str);
            }
        }
        StringBuilder sb= new StringBuilder("");
        if(list.size() == 0){
            return "/";
        }
        for(String str:list){
            sb.append("/");
            sb.append(str);
        }
        return sb.toString();
    }
}
  1. 拼寫單詞
  • 給你一份『詞匯表』(字符串?dāng)?shù)組) words 和一張『字母表』(字符串) chars铝阐。

假如你可以用 chars 中的『字母』(字符)拼寫出 words 中的某個(gè)『?jiǎn)卧~』(字符串),那么我們就認(rèn)為你掌握了這個(gè)單詞铐拐。

注意:每次拼寫時(shí)徘键,chars 中的每個(gè)字母都只能用一次。
提示:

1 <= words.length <= 1000
1 <= words[i].length, chars.length <= 100
所有字符串中都僅包含小寫英文字母

思路:限制了只會(huì)出現(xiàn)小寫字母(連續(xù))遍蟋,那么我們可以去統(tǒng)計(jì)單詞中每個(gè)字母出現(xiàn)的次數(shù)存在數(shù)組中吹害,如果A單詞中的每個(gè)字母的出現(xiàn)次數(shù)都小于等于B單詞中的出現(xiàn)次數(shù),可以認(rèn)為是“掌握了”

class Solution {
    public int countCharacters(String[] words, String chars) {
        int chs[] = new int[26];
        for(char ch :chars.toCharArray()){
            chs[ch-'a']++;
        }
        int res = 0;
        for(String str:words){
            int s[] = new int[26];
            for(char ch : str.toCharArray()){
                s[ch-'a']++;
            }
            boolean f = false;
            for(int i = 0; i < 26; ++i){
                if(s[i] > chs[i]){
                    f = true;
                    break;
                }
            }
            if(!f){
                res+=str.length();
            }
        }
        return res;
    }
}
  1. 搜索二維矩陣
    編寫一個(gè)高效的算法來(lái)判斷 m x n 矩陣中虚青,是否存在一個(gè)目標(biāo)值它呀。該矩陣具有如下特性:

每行中的整數(shù)從左到右按升序排列。
每行的第一個(gè)整數(shù)大于前一行的最后一個(gè)整數(shù)棒厘。

輸入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
輸出: true

這個(gè)腦子就是得轉(zhuǎn)一下纵穿,我死命想用兩次二分法在兩個(gè)維度上分別搜索,但是死活不行奢人,看完力扣官方題解之后茅塞頓開(kāi)谓媒。什么有序二維數(shù)組,從內(nèi)存的角度上來(lái)看就是個(gè)有序的一維數(shù)組嘛达传!

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix.length==0){
            return false;
        }
        int m = matrix.length;
        int n = matrix[0].length;
        int low = 0;
        int high = m * n -1;
        int mid = (low+high)/2;
        while(low <= high){
            int val = matrix[mid/n][mid%n];
            if(val < target){
                low = mid + 1;
            } else if(val == target){
                return true;
            } else {
                high = mid - 1;
            }
            mid = (low+high)/2;
        }
        return false;
    }
}
  1. 顏色分類
    給定一個(gè)包含紅色篙耗、白色和藍(lán)色迫筑,一共 n 個(gè)元素的數(shù)組,原地**對(duì)它們進(jìn)行排序宗弯,使得相同顏色的元素相鄰脯燃,并按照紅色、白色蒙保、藍(lán)色順序排列辕棚。

此題中,我們使用整數(shù) 0邓厕、 1 和 2 分別表示紅色逝嚎、白色和藍(lán)色。

注意:
不能使用代碼庫(kù)中的排序函數(shù)來(lái)解決這道題详恼。

進(jìn)階:

  • 一個(gè)直觀的解決方案是使用計(jì)數(shù)排序的兩趟掃描算法补君。
    首先,迭代計(jì)算出0昧互、1 和 2 元素的個(gè)數(shù)挽铁,然后按照0、1敞掘、2的排序叽掘,重寫當(dāng)前數(shù)組。
  • 你能想出一個(gè)僅使用常數(shù)空間的一趟掃描算法嗎玖雁?

輸入: [2,0,2,1,1,0]
輸出: [0,0,1,1,2,2]

思路更扁,桶排序。

class Solution {
    public void sortColors(int[] nums) {
        int colors[] = new int[3];
        for(int i =0; i< nums.length;++i){
            colors[nums[i]]++;
        }
        int j = 0;
        for(int i = 0; i < nums.length;++i){
            while( j < 3 && colors[j] == 0){
                j++;
            }
            if(j==3){
                break;
            }
            nums[i] = j;
            colors[j]--;
        }
    }
}
  1. 無(wú)重復(fù)最長(zhǎng)子串
  • 給定一個(gè)字符串赫冬,請(qǐng)你找出其中不含有重復(fù)字符的 最長(zhǎng)子串 的長(zhǎng)度浓镜。

輸入: "abcabcbb"
輸出: 3
解釋: 因?yàn)闊o(wú)重復(fù)字符的最長(zhǎng)子串是 "abc",所以其長(zhǎng)度為 3面殖。

解題思路:使用動(dòng)態(tài)規(guī)劃竖哩,存放每一個(gè)字母最近出現(xiàn)的下標(biāo)的下一位。使用一次掃描而結(jié)果就是在迭代過(guò)程中的 max(oldVal, 右指針-max(左指針,index[右指針?biāo)谧帜竇+1,)

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int index[] = new int[256];//存放字母出現(xiàn)的下標(biāo)的下一位脊僚,會(huì)隨著掃描更新為最右的坐標(biāo),0表示還沒(méi)出現(xiàn)過(guò)
        int res = 0;
        int i = 0;//左指針
        for(int j = 0; j < s.length(); ++j){
            i = Math.max(index[s.charAt(j)],i);//如果當(dāng)前字母在i之后出現(xiàn)過(guò)遵绰,那么左指針就要更新到之前出現(xiàn)過(guò)位置的下一位辽幌,若沒(méi)有出現(xiàn),i不變椿访。
            res = Math.max(res, j-i+1);// 更新一下res
            index[s.charAt(j)] = j+1;// 更新index
        } 
        return res;
    }
}
  1. 刪除排序數(shù)組中的重復(fù)項(xiàng)
class Solution {
    public int removeDuplicates(int[] nums) {
        if(nums.length==0){
            return 0;
        } 
        int oldVal = nums[0];
        int res = 1;
        int j = 1;
        for(int i = 1; i < nums.length; ++i){
            if(oldVal!=nums[i]){
                j = 1;
                nums[res++] = nums[i]; 
                oldVal = nums[i];
            } else if(j < 2&&nums[i]==oldVal){
                nums[res++] = nums[i];
                j++;
            }
        }
        return res;
    }
}
  1. 最大子序和
    考慮到有負(fù)數(shù)有正數(shù)乌企,
  • 極端情況,全是負(fù)數(shù)成玫,那么就相當(dāng)于找最小元素
  • 如果只有一個(gè)正數(shù)加酵,那么正數(shù)所在的大小為1的序列就是最大子序
    可以考慮使用動(dòng)態(tài)規(guī)劃:
    • 記錄一個(gè)大小為n的數(shù)組拳喻,記錄包含當(dāng)前元素為子序列最后一個(gè)元素時(shí)的最大子序和。
    • 如果之前的子序和大于0猪腕,那么當(dāng)前的最大子序列和等于之前最大子序列和加上當(dāng)前元素冗澈,否則就是當(dāng)前元素。在迭代的過(guò)程中記錄最大的子序列和陋葡。
class Solution {
  public int maxSubArray(int[] nums) {
    int n = nums.length, maxSum = nums[0];
    for(int i = 1; i < n; ++i) {
      if (nums[i - 1] > 0) nums[i] += nums[i - 1];//原地修改
      maxSum = Math.max(nums[i], maxSum);//記錄最大子序列和
    }
    return maxSum;
  }
}
  1. 二叉樹(shù)的右視圖

給定一棵二叉樹(shù)亚亲,想象自己站在它的右側(cè),按照從頂部到底部的順序腐缤,返回從右側(cè)所能看到的節(jié)點(diǎn)值捌归。

輸入: [1,2,3,null,5,null,4]
輸出: [1, 3, 4]
解釋:
1 <---
/
2 3 <---
\
5 4 <---

思路:使用隊(duì)列輔助廣度優(yōu)先遍歷,記錄樹(shù)的深度岭粤。有一個(gè)無(wú)意間發(fā)現(xiàn)的小技巧惜索,我在開(kāi)始寫的時(shí)候按照正常思路從左向右遍歷子樹(shù)仅炊,發(fā)現(xiàn)得到的是 二叉樹(shù)的左視圖雷恃,變換遍歷順序之后疙剑,得到了二叉樹(shù)的右視圖宰闰。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {

    class Pair{
        int depth;
        TreeNode n;
        Pair(TreeNode n, int depth){
            this.n = n;
            this.depth = depth;
        }
    }

    public List<Integer> rightSideView(TreeNode root) {
        if(root == null){
            return new ArrayList<>();
        }
        LinkedList<Pair> q = new LinkedList<>();
        q.addLast(new Pair(root,0)); 
        List<Integer> l = new ArrayList<>();
        int prevDepth = -1;
        while(!q.isEmpty()){
            Pair p = q.removeFirst();
            if(p.depth!=prevDepth){
                l.add(p.n.val);
            }
            
            if(p.n.right!=null){//先右子
                q.addLast(new Pair(p.n.right,p.depth+1));
            }
            if(p.n.left!=null){//再左子
                q.addLast(new Pair(p.n.left,p.depth+1));
            }
            prevDepth = p.depth;
        }
        return l;
    }
}
  1. 二維區(qū)域和檢索 - 矩陣不可變

給定一個(gè)二維矩陣潭陪,計(jì)算其子矩形范圍內(nèi)元素的總和颠放,該子矩陣的左上角為 (row1, col1) 伪朽,右下角為 (row2, col2)帮毁。

圖中子矩陣左上角 (row1, col1) = (2, 1) 溜宽,右下角(row2, col2) = (4, 3)吉拳,該子矩形內(nèi)元素的總和為 8。


image

給定 matrix = [
[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5]
]
sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12

說(shuō)明:

你可以假設(shè)矩陣不可變适揉。
會(huì)多次調(diào)用 sumRegion 方法留攒。
你可以假設(shè) row1 ≤ row2 且 col1 ≤ col2。

  • 思路:由于會(huì)調(diào)用多次sumRegion方法嫉嘀,一般的暴力解法會(huì)超時(shí)炼邀。
    可以先計(jì)算所有(i,j)的 sumRegion(0,0,i,j) ,記為 s(i, j) , 則 sumRegion(r_1, c_1, r_2, c_2) = s(r_2, c_2) - s(r_2,c_1) - s(r_1,c_2) + s(r_1 -1 , c_1 - 1)
class NumMatrix {

    //注意這里的sumRegion會(huì)調(diào)用多次

    int sum[][];

    public NumMatrix(int[][] matrix) {
        if(matrix.length==0){
            return;
        } else {
            int m = matrix.length;
            int n = matrix[0].length;
            sum = new int[m+1][n+1];//sum下標(biāo)從1開(kāi)始剪侮,小 trick拭宁,
            for(int i = 0; i < m; ++i){
                for(int j= 0; j < n; ++j){
                    sum[i+1][j+1] = sum[i+1][j] + sum[i][j+1] + matrix[i][j] - sum[i][j];
                }
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return sum[row1][col1] + sum[row2+1][col2+1] - sum[row1][col2+1] - sum[row2+1][col1];
    }
}
  1. 01矩陣
    給定一個(gè)由 0 和 1 組成的矩陣,找出每個(gè)元素到最近的 0 的距離瓣俯。給定的矩陣元素不超過(guò)10000個(gè)

兩個(gè)相鄰元素間的距離為 1 杰标。

示例 1:
輸入:
0 0 0
0 1 0
0 0 0
輸出:
0 0 0
0 1 0
0 0 0

示例 2:
輸入:
0 0 0
0 1 0
1 1 1
輸出:
0 0 0
0 1 0
1 2 1

思路:兩遍dp,一次從左上往右下彩匕,一次從右下往左上腔剂。

class Solution {
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
        int row = matrix.size();
        if(row == 0){
            return matrix;
        }
        int col = matrix[0].size();
        vector<vector<int>> distance(row,vector(col,INT_MAX - 10000));//-10000是因?yàn)?        for(int i = 0; i < row; ++i){
            for(int j = 0; j < col; ++j){
                if(matrix[i][j]==0){//第一次掃描的時(shí)候看看標(biāo)記出來(lái)0
                    distance[i][j] = 0;
                } else {
                    if(i>0){//注意邊界條件
                        distance[i][j] = min(distance[i-1][j]+1,distance[i][j]);
                    }
                    if(j>0){
                        distance[i][j] = min(distance[i][j-1]+1,distance[i][j]);
                    }
                }
            }
        }
        for(int i = row-1; i >=0; --i){
            for(int j = col-1; j >=0; --j){
           
                    if(i<row-1){
                        distance[i][j] = min(distance[i+1][j]+1,distance[i][j]);
                    }
                    if(j<col-1){
                        distance[i][j] = min(distance[i][j+1]+1,distance[i][j]);
                    }
           
            }
        }
        return distance;
    }
};

感覺(jué)如果不知道起點(diǎn)在哪的時(shí)候:(0,0) 不一定是0,需要我們?nèi)ニ阉饕幌峦找牵梢杂眠@種方式解決掸犬。這里使用了兩次dp袜漩,正好cover了所有情況(向左,向右湾碎,向上宙攻,向下)

  1. 機(jī)器人的運(yùn)動(dòng)范圍
    地上有一個(gè)m行n列的方格,從坐標(biāo) [0,0] 到坐標(biāo) [m-1,n-1] 胜茧。一個(gè)機(jī)器人從坐標(biāo) [0, 0] 的格子開(kāi)始移動(dòng)粘优,它每次可以向左、右呻顽、上雹顺、下移動(dòng)一格(不能移動(dòng)到方格外),也不能進(jìn)入行坐標(biāo)和列坐標(biāo)的數(shù)位之和大于k的格子廊遍。例如嬉愧,當(dāng)k為18時(shí),機(jī)器人能夠進(jìn)入方格 [35, 37] 喉前,因?yàn)?+5+3+7=18没酣。但它不能進(jìn)入方格 [35, 38],因?yàn)?+5+3+8=19卵迂。請(qǐng)問(wèn)該機(jī)器人能夠到達(dá)多少個(gè)格子?

思路:
把和的格子看成障礙物见咒,直接以(0,0)作為起點(diǎn)偿衰,使用bfs搜索。

class Solution {
    // 計(jì)算 x 的數(shù)位之和
    int get(int x) {
        int res=0;
        for (; x; x /= 10) {
            res += x % 10;
        }
        return res;
    }
public:
    int movingCount(int m, int n, int k) {
        if (!k) return 1;
        queue<pair<int,int> > Q;
        // 向右和向下的方向數(shù)組
        int dx[2] = {0, 1};
        int dy[2] = {1, 0};
        vector<vector<int> > vis(m, vector<int>(n, 0));
        Q.push(make_pair(0, 0));
        vis[0][0] = 1;
        int ans = 1;
        //需要證明改览,如果sum(m)+sum(n)<k,則一定有一條路徑
        while (!Q.empty()) {
            auto [x, y] = Q.front();
            Q.pop();
            for (int i = 0; i < 2; ++i) {
                int tx = dx[i] + x;
                int ty = dy[i] + y;
                if (tx < 0 || tx >= m || ty < 0 || ty >= n || vis[tx][ty] || get(tx) + get(ty) > k) continue;
                Q.push(make_pair(tx, ty));
                vis[tx][ty] = 1;
                ans++;
            }
        }
        return ans;
    }
};
  1. 括號(hào)生成

數(shù)字 n 代表生成括號(hào)的對(duì)數(shù)下翎,請(qǐng)你設(shè)計(jì)一個(gè)函數(shù),用于能夠生成所有可能的并且 有效的 括號(hào)組合宝当。

示例:
輸入:n = 3
輸出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]

使用回溯法:

class Solution {
public:
    
    vector<string> generateParenthesis(int n) {
        vector<string> res;
        generate(n*2,n,res,"",0);
        return res;
    }

    void generate(int n,int l/*左括號(hào)剩余個(gè)數(shù)*/, vector<string>& v,string str,int m/*字符串中左括號(hào)比右括號(hào)多的個(gè)數(shù)*/){
             
        if(n<1){
            v.push_back(str);
            return;
        }   
        if(l > 0){
            // str = str + '('; 別這么寫视事,會(huì)影響下面的case!G炜俐东!
            generate(n-1,l-1,v,str+'(',m+1);
        }
        if(m > 0){
            // str = str + ')'; 
            generate(n-1,l,v,str+')',m-1);
        }
    }
};
  1. 二叉樹(shù)插入

給定一個(gè)二叉樹(shù),根節(jié)點(diǎn)為第1層盾鳞,深度為 1犬性。在其第 d 層追加一行值為 v 的節(jié)點(diǎn)。

添加規(guī)則:給定一個(gè)深度值 d (正整數(shù))腾仅,針對(duì)深度為 d-1 層的每一非空節(jié)點(diǎn) N,為 N 創(chuàng)建兩個(gè)值為 v 的左子樹(shù)和右子樹(shù)套利。

將 N 原先的左子樹(shù)推励,連接為新節(jié)點(diǎn) v 的左子樹(shù)鹤耍;將 N 原先的右子樹(shù),連接為新節(jié)點(diǎn) v 的右子樹(shù)验辞。

如果 d 的值為 1稿黄,深度 d - 1 不存在,則創(chuàng)建一個(gè)新的根節(jié)點(diǎn) v跌造,原先的整棵樹(shù)將作為 v 的左子樹(shù)杆怕。

輸入:
二叉樹(shù)如下所示:
4
/ \
2 6
/ \ /
3 1 5
且 v = 1 d = 2

輸出:
4
/ \
1 1
/ \
2 6
/ \ /
3 1 5

思路: 使用bfs獲得第d-1層的所有節(jié)點(diǎn),然后按照題目要求添加壳贪。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* addOneRow(TreeNode* root, int v, int d) {
        if(root==nullptr){
            return root;
        }
        if(d==1){
            TreeNode* newRoot = new TreeNode(v);
            newRoot->left = root;
            return newRoot;
        }
        pair<TreeNode*,int> rootpair = make_pair(root,1);
        queue<pair<TreeNode*,int>> q;//記錄深度
        q.push(rootpair);
        while(!q.empty()){
            auto [node, curDepth] = q.front();
            if(curDepth==d-1){
                break;
            }
            q.pop();
            if(node->left!=nullptr){
                q.push(make_pair(node->left,curDepth+1));
            }
            if(node->right!=nullptr){
                q.push(make_pair(node->right,curDepth+1));
            }
        }
        while(!q.empty()){
            auto [node, curDepth] = q.front();
            q.pop();
            TreeNode* leftTemp = node->left;
            TreeNode* rightTemp = node->right;
            node -> left = new TreeNode(v);
            node -> right = new TreeNode(v);
            node -> left -> left = leftTemp;
            node -> right -> right = rightTemp;
            
        }
        return root;
    }
};
  1. 相同的樹(shù)
    給定兩個(gè)二叉樹(shù)陵珍,編寫一個(gè)函數(shù)來(lái)檢驗(yàn)它們是否相同。

如果兩個(gè)樹(shù)在結(jié)構(gòu)上相同违施,并且節(jié)點(diǎn)具有相同的值互纯,則認(rèn)為它們是相同的。


class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(!p&&!q){
            return true;
        }
        if((p&&!q)||(!p&&q)||p->val!=q->val){
            return false;
        }
        queue<TreeNode*> pQ;
        queue<TreeNode*> qQ;
        pQ.push(p);
        qQ.push(q);
        while(!pQ.empty()){
            TreeNode* ptmp = pQ.front();
            TreeNode* qtmp = qQ.front();
            pQ.pop();
            qQ.pop();
            if(qtmp->left||ptmp->left){
                if(!qtmp->left || !ptmp->left || ptmp->left->val != qtmp->left->val){
                    return false;
                } 
                pQ.push(ptmp->left);
                qQ.push(qtmp->left);    
            }
            if(qtmp->right||ptmp->right){
                if(!qtmp->right || !ptmp->right || ptmp->right->val != qtmp->right->val){
                    return false;
                } 
                pQ.push(ptmp->right);
                qQ.push(qtmp->right);    
            }
        }        
        return true;
    }
};

衍生:對(duì)稱二叉樹(shù):
給定一個(gè)二叉樹(shù)磕蒲,檢查它是否是鏡像對(duì)稱的留潦。

思路:一個(gè)樹(shù)如果是對(duì)稱的,那它左子和右子的值一定相等辣往,左子的左子和右子的右子一定對(duì)稱兔院。

    bool isSymmetric(TreeNode* root){
        if(!root){
            return true;
        }
        return isMirror(root->left,root->right);

        // if(!root||(!root->left&&!root->right)){ return true;}

        //wrong!
        // if(root->left&&root->right){
        //     return isSymmetric(root->left)&&isSymmetric(root->right)&& root->left->val==root->right->val;
        // } else {
        //     return false;
        // } 
    }

    bool isMirror(TreeNode* node1,TreeNode* node2){
        if(!node1&&!node2){
            return true;
        }
        if(!node1||!node2){
            return false;
        }
        
        return (node1->val == node2 ->val)   
            && isMirror(node1->left,node2->right)
            && isMirror(node1->right,node2->left);
    }
  1. 二叉樹(shù)展開(kāi)為鏈表
    給定一個(gè)二叉樹(shù),原地將它展開(kāi)為鏈表站削。
    TIM截圖20200415101029.png

方案一坊萝,自頂向下搜索,將左子樹(shù)掛在右子上钻哩,原有的右子樹(shù)掛在現(xiàn)在最右節(jié)點(diǎn)的右邊屹堰。

public:
    void flatten(TreeNode* root){
       while(root){
           if(!root->left){
               root = root->right;
            } else {
                TreeNode* r = root -> right;
                root -> right = root -> left;
                root -> left = nullptr;
                TreeNode* tmp = root;
                while(tmp->right){
                    tmp = tmp -> right;
                }
                tmp -> right = r;
                root = root->right;
            }
       }
    }

方案二,不難看出鏈表化后的二叉樹(shù)是其前序遍歷的版本街氢,我們可以反其道而行之扯键,使用后序遍歷,頭插到鏈表上珊肃。

    TreeNode* pre;//當(dāng)前鏈表
    void flatten(TreeNode* root){//后序遍歷
        if(!root){
            return; 
        }
        flatten(root->right);
        flatten(root->left);
        //頭插入鏈表
        root->right = pre;
        pre = root;
        //記得把left置null
        root->left = nullptr;
    }
  1. 合并區(qū)間

給出一個(gè)區(qū)間的集合荣刑,請(qǐng)合并所有重疊的區(qū)間。

示例 1:
輸入: [[1,3],[2,6],[8,10],[15,18]]
輸出: [[1,6],[8,10],[15,18]]
解釋: 區(qū)間 [1,3] 和 [2,6] 重疊, 將它們合并為 [1,6].

示例 2:
輸入: [[1,4],[4,5]]
輸出: [[1,5]]
解釋: 區(qū)間 [1,4] 和 [4,5] 可被視為重疊區(qū)間伦乔。

思路:先對(duì)于intervals 按照左區(qū)間大小排序厉亏,從前向后遍歷,步長(zhǎng)為1烈和,如果可以合并(R1 > L2)爱只,就合并

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) { 
        if(intervals.size()==0){
            return {};
        }
        sort(intervals.begin(),intervals.end());
        vector<vector<int>> res;
        for(int i = 0; i< intervals.size(); ++i){
            int L = intervals[i][0],R = intervals[i][1];
            if(res.empty()|| L >res.back()[1] ){
                res.push_back({L,R});
            } else {
                res.back()[1] = max(res.back()[1],R);
            }
        }
        return res;
    }
};
  1. 反轉(zhuǎn)鏈表 II
    反轉(zhuǎn)從位置 m 到 n 的鏈表。請(qǐng)使用一趟掃描完成反轉(zhuǎn)招刹。

說(shuō)明:
1 ≤ m ≤ n ≤ 鏈表長(zhǎng)度恬试。

示例:
輸入: 1->2->3->4->5->NULL, m = 2, n = 4
輸出: 1->4->3->2->5->NULL

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        int i = 0;
        ListNode* h = new ListNode(0);
        h->next = head;
        ListNode* prev = h;
        for(int i = 0; i < m-1; ++i){
            prev=prev->next;
        }
        ListNode* rStart = prev->next;//反轉(zhuǎn)前第m個(gè)節(jié)點(diǎn)窝趣,
        ListNode* end = rStart; //反轉(zhuǎn)后的第n個(gè)節(jié)點(diǎn)
        ListNode* rBetween = new ListNode(0);//反轉(zhuǎn)的過(guò)程以頭插法,插入這個(gè)列表
        for(int i = m-1; i< n; ++i){
            ListNode* tmp = rStart->next;
            rStart->next = rBetween->next;
            rBetween->next = rStart;
            rStart = tmp;
        }
        prev->next = rBetween->next;
        end->next = rStart;
        return h->next;
    }
};
  1. 反轉(zhuǎn)鏈表
    輸入: 1->2->3->4->5->NULL
    輸出: 5->4->3->2->1->NULL
class Solution {
public:
    //迭代版本
    // ListNode* reverseList(ListNode* head) {
    //     ListNode* h = new ListNode(0);
    //     h->next = head;
    //     ListNode* res = new ListNode(0);
    //     while(h->next!=nullptr){
    //         h = h -> next;
    //         ListNode* tmp = res -> next;
    //         res->next = new ListNode(h->val);
    //         res->next -> next = tmp;
    //     }
    //     return res->next;
    // }

  

   //遞歸
    ListNode* reverseList(ListNode* head){
        if(!head||!head->next){
            return head;
        }
        ListNode* res = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return res;
    }
};
  1. 二叉搜索樹(shù)的最近公共祖先

例如训柴,給定如下二叉搜索樹(shù): root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:
輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
輸出: 6
解釋: 節(jié)點(diǎn) 2 和節(jié)點(diǎn) 8 的最近公共祖先是 6哑舒。

示例 2:
輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
輸出: 2
解釋: 節(jié)點(diǎn) 2 和節(jié)點(diǎn) 4 的最近公共祖先是 2, 因?yàn)楦鶕?jù)定義最近公共祖先節(jié)點(diǎn)可以為節(jié)點(diǎn)本身。

思路:根據(jù)平衡二叉搜索樹(shù)定義幻馁,兩個(gè)節(jié)點(diǎn)(2, 4)的最近公共祖先的值(2)一定大于等于較小節(jié)點(diǎn)(4)且小于等于較大節(jié)點(diǎn)(4),且最近公共祖先的父節(jié)點(diǎn)的值(6)一定同時(shí)大于兩個(gè)節(jié)點(diǎn)或同時(shí)小于兩個(gè)節(jié)點(diǎn)仗嗦。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(!root||!p||!q){
            return root;
        }
        TreeNode* res = root;
        while(res){
            if(res->val >= p->val && res->val >= q->val){//等于是
                if(res->val == p->val || res->val ==q->val){//防止 2,4 都大于等于2膘滨,即示例 2 
                    break;
                }
                res = res->left;
            } else if(res->val <= p->val && res->val <= q->val){
                if(res->val == p->val || res->val ==q->val){
                    break;
                }
                res = res->right;
            } else {
                break;
            }
        }
        return res;
    }
};
  1. 最佳買賣股票時(shí)機(jī)含冷凍期

給定一個(gè)整數(shù)數(shù)組,其中第 i 個(gè)元素代表了第 i 天的股票價(jià)格 儒将。?

設(shè)計(jì)一個(gè)算法計(jì)算出最大利潤(rùn)吏祸。在滿足以下約束條件下,你可以盡可能地完成更多的交易(多次買賣一支股票):

  • 你不能同時(shí)參與多筆交易(你必須在再次購(gòu)買前出售掉之前的股票)钩蚊。
  • 賣出股票后贡翘,你無(wú)法在第二天買入股票 (即冷凍期為 1 天)。

示例:
輸入: [1,2,3,0,2]
輸出: 3
解釋: 對(duì)應(yīng)的交易狀態(tài)為: [買入, 賣出, 冷凍期, 買入, 賣出]

思路:動(dòng)態(tài)規(guī)劃

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n==0){
            return 0;
        }
        int dp[n][3];

        dp[0][0]=0;//當(dāng)天不持股砰逻,且當(dāng)天沒(méi)賣出
        dp[0][1]=-prices[0];//第i天持股
        dp[0][2]=0;//當(dāng)天不持股鸣驱,股票是當(dāng)天賣出的,第0天特殊情況蝠咆,認(rèn)為當(dāng)天買入當(dāng)天賣出

        for(int i = 1; i< n ;i++){
            dp[i][0] = max(dp[i-1][0],dp[i-1][2]); //
            dp[i][1] = max(dp[i-1][0]-prices[i],dp[i-1][1]);
            dp[i][2] = dp[i-1][1]+prices[i];
        }

        return max(dp[n-1][0],dp[n-1][2]);//
    }
};
```![TIM截圖20200423182940.png](https://upload-images.jianshu.io/upload_images/8081626-dce80ebfbab1375f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


28. 超級(jí)丑數(shù)
超級(jí)丑數(shù)是指其所有質(zhì)因數(shù)都是長(zhǎng)度為 k 的質(zhì)數(shù)列表 primes 中的正整數(shù)踊东。

> 示例:
   輸入: n = 12, primes = [2,7,13,19]
   輸出: 32 
   解釋: 給定長(zhǎng)度為 4 的質(zhì)數(shù)列表 primes = [2,7,13,19],前 12 個(gè)超級(jí)丑數(shù)序列為: 
  [1,2,4,7,8,13,14,16,19,26,28,32] 刚操。

使用動(dòng)態(tài)規(guī)劃闸翅,觀察一下規(guī)律:
* 開(kāi)始時(shí)丑數(shù)序列dp為 $[1]$
* $dp[1] = min(dp[0] * prime[0],dp[0] * prime[1],dp[0] * prime[2],dp[0] * prime[3]) = dp[0]* prime[0] =2$
* $dp[2] = min(dp[1] * prime[0],dp[0] * prime[1],dp[0] * prime[2],dp[0] * prime[3]) = dp[1]* prime[0]=4$
* $dp[3] = min(dp[2] * prime[0],dp[0] * prime[1],dp[0] * prime[2],dp[0] * prime[3])= dp[0]* prime[1]=7$
* $dp[4] = min(dp[2] * prime[0],dp[1] * prime[1],dp[0] * prime[2],dp[0] * prime[3]) = dp[2]*prime[0]=8$
* $dp[5] = min(dp[3] * prime[0],dp[1] * prime[1],dp[0] * prime[2],dp[0] * prime[3]) = dp[0]*prime[2]=13$
* **! dp[6]中出現(xiàn)了 2 * 7 和 7 * 2 相等的情況,需要都考慮進(jìn)來(lái)并去重菊霜。** $dp[6] = min(dp[3] * prime[0],dp[1] * prime[1],dp[1] * prime[2],dp[0] * prime[3]) = dp[3]*prime[0]= dp[1] * prime[1] =14$

根據(jù)上述規(guī)律坚冀,維護(hù)下兩個(gè)數(shù)組:
* index數(shù)組:index[j] 是第j個(gè)prime當(dāng)前在dp的下標(biāo)
* dp:超級(jí)丑數(shù)序列。
```c++
class Solution {
public:
    int nthSuperUglyNumber(int n, vector<int>& primes) {
        if(n==0){
            return 1;
        }
        int dp[n];
        dp[0]=1;
        int k = primes.size();
        vector<int> index(k,0);
        for(int i = 1; i < n; ++i){
            int minVal = INT_MAX;
            for(int j = 0; j < k; ++j){
                if(dp[index[j]] * primes[j]<minVal){
                    minVal = dp[index[j]] * primes[j];
                }
            }
            for(int j =0; j < k; ++j){//去重 2*7 和 7*2
                if(dp[index[j]]* primes[j] == minVal){
                    ++index[j];
                }
            }
            dp[i]=minVal;
        }
        return dp[n-1];
    }
};
  1. 硬幣
    給定數(shù)量不限的硬幣鉴逞,幣值為25分记某、10分、5分和1分构捡,編寫代碼計(jì)算n分有幾種表示法液南。(結(jié)果可能會(huì)很大,你需要將結(jié)果模上1000000007)

示例1:

輸入: n = 5
輸出:2
解釋: 有兩種方式可以湊成總金額:
5=5
5=1+1+1+1+1

示例2:

輸入: n = 10
輸出:4
解釋: 有四種方式可以湊成總金額:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1

思路:動(dòng)態(tài)規(guī)劃

  • 一開(kāi)始我想的是這樣的一個(gè)方程勾徽,但是結(jié)果不對(duì)滑凉,原因是因?yàn)?+5和5+1會(huì)被認(rèn)為是6的兩種找零方式。


    img_0835.png
  • 修改后的方程:


    TIM截圖20200423182940.png

實(shí)現(xiàn):

class Solution {
public:

    int mod = 1000000007;
    int coins[4] = {1,5,10,25};

    int waysToChange(int n) {
        if(n==0){
            return 0;
        }
        int dp[n+1];
        for(int i = 1; i<=n; ++i){
            dp[i]=0;
        }
        dp[0]=1;
    
        for(int i = 0; i <4; ++i){
            int coin=coins[i];
            for(int j = coin; j <= n; ++j){
                dp[j] = (dp[j]+dp[j-coin])%mod;
            }
        }
        return dp[n];
    }
};
  1. 不同的二叉搜索樹(shù)

給定一個(gè)整數(shù) n,求以 1 ... n 為節(jié)點(diǎn)組成的二叉搜索樹(shù)有多少種譬涡?

示例:
輸入: 3
輸出: 5
解釋: 給定 n = 3, 一共有 5 種不同結(jié)構(gòu)的二叉搜索樹(shù):

TIM截圖20200424082620.png
class Solution {
public:
    int numTrees(int n) { 
        if(n==0){
            return 1;
        }
        int dp[n+1];//dp[i]表示有i個(gè)節(jié)點(diǎn)的組成樹(shù)的個(gè)數(shù)
        dp[0] = 1;
        for(int i=1; i <= n; ++i){
            int tmp = 0;
            for(int j=0; j < i;++j){
                tmp += dp[j]*dp[i-j-1]; 
            }
            dp[i] = tmp;
        }
        return dp[n];        
    }
};
  1. 螺旋矩陣
    給定一個(gè)包含 m x n 個(gè)元素的矩陣(m 行, n 列)闪幽,請(qǐng)按照順時(shí)針螺旋順序啥辨,返回矩陣中的所有元素涡匀。

示例 1:
輸入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
輸出: [1,2,3,6,9,8,7,4,5]

思路:使用4個(gè)變量{l, r, b, t}記錄一個(gè)bounding box,按照螺旋的順序溉知,對(duì)box進(jìn)行遍歷:

  1. 如果矩陣的大小是{x, y}陨瘩,一開(kāi)始 {l,r,t,b} = {0, y-1, 0, x-1}
  2. 然后從左遍歷box中的第一行,top 向下移動(dòng)一個(gè)單位级乍,如果 top 比 bottom還低(即 boundingbox中只剩一行)結(jié)束舌劳。
  3. 從上到下遍歷最后一列,...
  4. 從右到左遍歷最后一行玫荣,...
  5. 從下到上遍歷第一列甚淡,...
  6. back to 1
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        int len =  matrix.size();
        if(len==0){
            return vector<int>();
        }
        int t = 0,l= 0,b=len,r=matrix[0].size();
        vector<int> res;
        while(true){
            for(int i = l; i < r; ++i){
                res.push_back(matrix[t][i]);
            }
            t++;
            if(t==b){
                break;
            }
            for(int i = t; i < b; ++i){
                res.push_back(matrix[i][r]);
            }
            r--;
            if(r==l){
                break;
            }
            for(int i = r; i >= l; --i){
                res.push_back(matrix[b][i]);
            }
            b--;
            if(t==b){
                break;
            }
            for(int i = b; i >= t; --i){
                res.push_back(matrix[i][l]);
            }
            l++;
            if(l==r){
                break;
            }
        }
        return res;
    }
};
  1. 驗(yàn)證二叉搜索樹(shù)
    給定一個(gè)二叉樹(shù),判斷其是否是一個(gè)有效的二叉搜索樹(shù)捅厂。

假設(shè)一個(gè)二叉搜索樹(shù)具有如下特征:

節(jié)點(diǎn)的左子樹(shù)只包含小于當(dāng)前節(jié)點(diǎn)的數(shù)贯卦。
節(jié)點(diǎn)的右子樹(shù)只包含大于當(dāng)前節(jié)點(diǎn)的數(shù)。
所有左子樹(shù)和右子樹(shù)自身必須也是二叉搜索樹(shù)焙贷。

思路:一開(kāi)始的時(shí)候沒(méi)有注意到是整個(gè)左子樹(shù)的節(jié)點(diǎn)都要小于撵割,右子樹(shù)都要大于。所以沒(méi)有考慮到這種case

TIM截圖20200425220044.png

需要在遞歸的時(shí)候加上一個(gè)上下界的判斷辙芍。

    bool isValidBST(TreeNode* root) {
        if(!root){
            return true;
        }
        return isValidBST(root,((long)INT_MIN)-1,((long)INT_MAX)+1);//long是需要考慮邊界值的情況
    }

    bool isValidBST(TreeNode* root, long min, long max){
        return  root->val > min && root->val < max
                && (!root->left ||isValidBST(root->left,min,root->val)) 
                && (!root->right||isValidBST(root->right,root->val,max)); 

    }
    
  1. 合并 k 個(gè)有序列表:

先實(shí)現(xiàn)有序列表的兩兩合并啡彬,再進(jìn)行歸并式的合并,分而治之故硅。

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size()==0){
            return nullptr;
        }
        return merge(lists,0,lists.size()-1);
    }

    ListNode* merge(vector<ListNode*> v, int start,int end){
        if(start == end){
            return v[start];
        }
        int mid = start+end >> 1;
        return merge2Lists(merge(v,start,mid),merge(v,mid+1,end));
    }

    ListNode* merge2Lists(ListNode* l1,ListNode* l2) {
        ListNode* head = new ListNode(0);
        ListNode* tmp = head;
        while(l1 && l2){
            if(l1->val < l2->val){
                head -> next = l1;
                l1 = l1 -> next;
            } else {
                head -> next = l2;
                l2 = l2 ->next;
            }
            head = head-> next;
        }
        head -> next = l1? l1:l2 ;
        return tmp->next;
    }
};
  1. 數(shù)組中數(shù)字出現(xiàn)的次數(shù)
    一個(gè)整型數(shù)組 nums 里除兩個(gè)數(shù)字之外庶灿,其他數(shù)字都出現(xiàn)了兩次。請(qǐng)寫程序找出這兩個(gè)只出現(xiàn)一次的數(shù)字吃衅。要求時(shí)間復(fù)雜度是O(n)往踢,空間復(fù)雜度是O(1)。

思路:先考慮一個(gè)簡(jiǎn)單一點(diǎn)的case捐晶,如果一個(gè)組中只有一個(gè)出現(xiàn)一次的數(shù)字菲语,其他數(shù)字都出現(xiàn)了2次,那么我們可以將整個(gè)列表異或得到那個(gè)只出現(xiàn)一次的數(shù)據(jù):

        int ret = 0;
        for(int n : nums)
            ret ^= n;

那可以將上面的問(wèn)題變成:

  1. 先將給定的數(shù)組分為兩個(gè) 只有一個(gè)出現(xiàn)一次的數(shù)字惑灵,其他數(shù)字都出現(xiàn)了2次 的兩個(gè)數(shù)組
  2. 分別對(duì)于兩個(gè)數(shù)組進(jìn)行異或山上,得到結(jié)果

問(wèn)題就是在如何實(shí)現(xiàn)1上面,為了實(shí)現(xiàn)1英支,分到的數(shù)組有兩個(gè)要求:
1)兩個(gè)只出現(xiàn)一次的數(shù)字需要被分到兩個(gè)組
2)任意一對(duì)大小相同的數(shù)字要被分到一個(gè)組

這個(gè)要求依舊可以巧妙的使用異或解決:
將整個(gè)數(shù)組全員異或得到一個(gè)值x佩憾,由于一個(gè)數(shù)組中有兩個(gè)只出現(xiàn)一次的樹(shù),所以x中肯定有一位是1,記錄下這個(gè)1的位置妄帘,用這個(gè)bit位的值將數(shù)組一分為二楞黄。這個(gè)分組方法滿足我們上面的要求:

  • 由于在這個(gè)位置bit相同的數(shù)字會(huì)分到同一個(gè)數(shù)組,所以兩個(gè)相同的數(shù)字會(huì)被分到同一組抡驼,滿足1)
  • 由于在x中這個(gè)位是1鬼廓,證明只出現(xiàn)一次的兩個(gè)數(shù)在這個(gè)位上的bit值不同,所以這兩個(gè)只出現(xiàn)一次的數(shù)字不會(huì)出現(xiàn)在同一個(gè)組中致盟,滿足2)碎税。

實(shí)現(xiàn):

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int ret = 0;
        for(int n : nums)
            ret ^= n;
        int div = 1;
        while((div & ret) == 0)
            div <<= 1;
        int a=0, b=0;
        for(int n: nums)
            if(n & div)
                a ^= n;
            else 
                b ^= n;
        return vector<int>{a,b};
    }
};
  1. 子集
  1. 給定一組不含重復(fù)元素的整數(shù)數(shù)組 nums,返回該數(shù)組所有可能的子集(冪集)馏锡。

說(shuō)明:解集不能包含重復(fù)的子集雷蹂。

思路:遞歸
實(shí)現(xiàn):

class Solution {
public:

    vector<vector<int>> res;
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<int> list= {};
        func(list,0,nums);//這里nums沒(méi)排序也過(guò)了,不過(guò)要注意一下杯道,如果題目沒(méi)有明確是否有序還是要排一下
        return res;
    }

    void func(vector<int>& list,int index, vector<int> nums){
        if(index == nums.size()){
            res.push_back(list);
            return;
        }
        for(int i = index; i < nums.size();++i){
            list.push_back(nums[i]);
            func(list, i+1,nums);
            list.pop_back();
        }
        func(list,nums.size(),nums);
    }
};
  1. 子集
    給定一個(gè)可能包含重復(fù)元素的整數(shù)數(shù)組 nums匪煌,返回該數(shù)組所有可能的子集(冪集)。

說(shuō)明:解集不能包含重復(fù)的子集党巾。

示例:

輸入: [1,2,2]
輸出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

代碼的整體框架沿用了第一個(gè)萎庭,使用剪枝去重:畫一下遞歸樹(shù),發(fā)現(xiàn)我們剪枝的規(guī)則是:在水平上連續(xù)相等的枝子昧港,我們只保留第一個(gè):
img_0836.png

可以看到兩個(gè)重復(fù)的2擎椰,我們只保留了第一個(gè)朗若,這樣得到的子集集合就是答案:

class Solution {
public:

    vector<vector<int>> res;
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<int> list= {};
        sort(nums.begin(),nums.end());
        func(list,0,nums);
        return res;
    }

    void func(vector<int>& list,int index, vector<int> nums){
        if(index == nums.size()){
            res.push_back(list);
            return;
        }
        for(int i = index; i < nums.size();++i){
            if(i > index && nums[i] == nums[i-1]){ /*注意 i > index 而不是 > 0 否則會(huì)錯(cuò)誤的將[2,2]所在的枝子剪掉*/
                continue;
            }
            list.push_back(nums[i]);
            func(list, i+1,nums);
            list.pop_back();            
        }
        func(list,nums.size(),nums);
    }
};
  1. 格雷編碼
    格雷編碼是一個(gè)二進(jìn)制數(shù)字系統(tǒng)呕寝,在該系統(tǒng)中,兩個(gè)連續(xù)的數(shù)值僅有一個(gè)位數(shù)的差異腻贰。

給定一個(gè)代表編碼總位數(shù)的非負(fù)整數(shù) n叹侄,打印其格雷編碼序列巩搏。即使有多個(gè)不同答案,你也只需要返回其中一種趾代。

格雷編碼序列必須以 0 開(kāi)頭贯底。

是一道找規(guī)律的題:
0 -> [0]
1 -> [ 0, 1]
2 -> [ 00, 01, 11, 10]
3 -> [000,001,011,010,
110, 111,101,100]

2 中的 00,01可以認(rèn)為是1中的[0,1]前面加了個(gè)0,也就是沒(méi)變撒强;而11,10可以認(rèn)為是1中的[0,1]倒序過(guò)來(lái)再加個(gè)1禽捆。

class Solution {
public:
    vector<int> grayCode(int n) {
        vector<int> result(1,0);
        for(int i = 0; i < n; i++){
            int mask = 1 << i;
            for(int j = result.size()-1; j>=0; --j){
                result.push_back(mask^result[j]);
            } 
        }
        return result;
    }
};
  1. 二叉樹(shù)鋸齒狀層次遍歷
    要求:層次遍歷的基礎(chǔ)上,第一層從左到右遍歷第二層從右到左飘哨,以此類推胚想。
    思路:如果使用BFS,需要對(duì)于在換層的時(shí)候?qū)﹃?duì)列進(jìn)行倒序芽隆,復(fù)雜度較高浊服。如果使用BFS统屈,可以用根據(jù)層數(shù),直接使用insert(vec.begin(), val)和push_back(val)插入牙躺,效率更高愁憔。
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> res;
        dfs(res,0,root);
        return res;
    }

    void dfs(vector<vector<int>>& res, int level, TreeNode* root){
        if(!root){
            return;
        }
        if(level >= res.size()){
            res.push_back(vector<int>());
        }
        if(!root->left){
            dfs(res,level+1,root->left);
        }
        if(!root->right){
            dfs(res,level+1,root->right);
        }
        if(level%2==0){
            res[level].push_back(root->val);
        } else {
            res[level].insert(res[level].begin(),root->val);
        }
    }

38.目標(biāo)和
給定一個(gè)非負(fù)整數(shù)數(shù)組,a1, a2, ..., an, 和一個(gè)目標(biāo)數(shù)孽拷,S《终疲現(xiàn)在你有兩個(gè)符號(hào) + 和 -。對(duì)于數(shù)組中的任意一個(gè)整數(shù)乓搬,你都可以從 + 或 -中選擇一個(gè)符號(hào)添加在前面思犁。

返回可以使最終數(shù)組和為目標(biāo)數(shù) S 的所有添加符號(hào)的方法數(shù)。

示例 1:

輸入: nums: [1, 1, 1, 1, 1], S: 3
輸出: 5
解釋:

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5種方法讓最終目標(biāo)和為3进肯。
注意:

數(shù)組非空,且長(zhǎng)度不會(huì)超過(guò)20棉磨。
初始的數(shù)組的和不會(huì)超過(guò)1000江掩。
保證返回的最終結(jié)果能被32位整數(shù)存下。

思路:

  1. 剪枝
    搜索空間為2^n乘瓤,可以排序后進(jìn)行剪枝环形,即:如果現(xiàn)在剩余的數(shù)組和都已經(jīng)小于當(dāng)前sum和目標(biāo)S之間差的絕對(duì)值,可以進(jìn)行剪枝衙傀。剪枝之前可以進(jìn)行一次倒序排序抬吟,使剪枝后的效率更高。
class Solution {

    int res = 0;
    vector<int> sum;
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        int len = nums.size();
        if(len==0){
            return S==0?1:0;
        }
        sort(nums.rbegin(),nums.rend());
        sum=vector(len,0);
        sum[len-1]=nums[len-1];
        for(int i = len-2; i >=0; --i){
            sum[i] = sum[i+1] + nums[i];
        }
        findTargetSum(nums,0,0,S);
        return res;
    }

    void findTargetSum(vector<int>&nums, int index, int s, int target){
        if(index == nums.size()&&s==target){
            res+=1;
           return;
        }
        if(index >= nums.size()||(s < target && target-s > sum[index])||(s > target && s - target > sum[index])){
            return;
        }
        findTargetSum(nums,index+1,s-nums[index],target);
        findTargetSum(nums,index+1,s+nums[index],target);
    }


};

2统抬、動(dòng)態(tài)規(guī)劃
可以把這個(gè)問(wèn)題考慮成為:我要把nums分成兩個(gè)集合火本,一個(gè)集合是正,一個(gè)集合是負(fù)數(shù)聪建,有集中分發(fā)可以讓兩個(gè)集合相加等于S钙畔。題中限制了 數(shù)組非空,且長(zhǎng)度不會(huì)超過(guò)20金麸。初始的數(shù)組的和不會(huì)超過(guò)1000擎析。 我們可以整一個(gè)num.size() * 2001的數(shù)組,設(shè)dp[i][j]為數(shù)組前i個(gè)元素和為j的方法個(gè)數(shù)
dp[i][j] = dp[i-1][j-nums[i]+1000]+dp[i-1][j+nums[i]+1000]
可以使用元祖法來(lái)省空間

  1. 前K個(gè)高頻單詞
    沒(méi)什么好講的思路挥下,主要熟悉一下c++的api
    值得注意的一點(diǎn)揍魂,iterator取出元素會(huì)做一個(gè)deep copy,修改它不會(huì)影響數(shù)組中的元素值棚瘟。
    class Solution {
    public:

    typedef struct sfi{
    string s;
    int freq = 0;//出現(xiàn)次數(shù)
    } comp;

    typedef pair<string,sfi> pss;
    static bool cmp(pss i, pss j){
    if (i.second.freq == j.second.freq){
    return i.second.s < j.second.s;
    } else {
    return i.second.freq > j.second.freq;
    }
    }
    vector<string> topKFrequent(vector<string>& words, int k) {
    map<string,comp> v;
    for(int i = 0; i < words.size(); ++i){
    auto iter = v.find(words[i]);
    if(iter == v.end()){
    comp c;
    c.s=words[i];
    c.freq = 1;
    v[words[i]]=c;
    } else {
    comp c = iter->second;//這里是一個(gè)deep copy!
    c.freq++;
    v[words[i]]=c;
    }
    }
    vector<pss> vec;
    for(auto mp=v.begin(); mp != v.end(); mp++){
    vec.push_back(pss(mp->first, mp->second));
    }
    sort(vec.begin(),vec.end(),cmp);
    vector<string> res;
    for(int i = 0; i < k; ++i){
    res.push_back(vec[i].first);
    }
    return res;
    }
    };

  2. 奇偶鏈表
    給定一個(gè)單鏈表现斋,把所有的奇數(shù)節(jié)點(diǎn)和偶數(shù)節(jié)點(diǎn)分別排在一起。請(qǐng)注意解取,這里的奇數(shù)節(jié)點(diǎn)和偶數(shù)節(jié)點(diǎn)指的是節(jié)點(diǎn)編號(hào)的奇偶性步责,而不是節(jié)點(diǎn)的值的奇偶性。

code:

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        ListNode* odd = head;
        if(!odd){
            return head;
        }
        ListNode* even = head->next;
        ListNode* tmp = even;
        while(odd->next&&even->next){
            odd->next = odd->next->next;
            odd = odd ->next;
            even->next = even->next->next;
            even = even->next;
        }
        odd->next=tmp;
        return head;
    }
};
  1. 把二叉搜索樹(shù)轉(zhuǎn)化為累加樹(shù)
    使得每個(gè)節(jié)點(diǎn)的值是原來(lái)的節(jié)點(diǎn)值加上所有大于它的節(jié)點(diǎn)值之和。

思路:二叉樹(shù)反向中序遍歷

class Solution {
public:
    TreeNode* bstToGst(TreeNode* root) {
        int s = 0;
        sum(root, s);
        return root;
    }

    void sum(TreeNode* root, int& s){
        if(!root){
            return;
        }
        sum(root->right, s);
        s += root->val;
        root -> val = s;
        sum(root->left, s);
    }
};
  1. 祖父節(jié)點(diǎn)值為偶數(shù)的節(jié)點(diǎn)和
    給你一棵二叉樹(shù)蔓肯,請(qǐng)你返回滿足以下條件的所有節(jié)點(diǎn)的值之和:

該節(jié)點(diǎn)的祖父節(jié)點(diǎn)的值為偶數(shù)遂鹊。(一個(gè)節(jié)點(diǎn)的祖父節(jié)點(diǎn)是指該節(jié)點(diǎn)的父節(jié)點(diǎn)的父節(jié)點(diǎn)。)
如果不存在祖父節(jié)點(diǎn)值為偶數(shù)的節(jié)點(diǎn)蔗包,那么返回 0 秉扑。

 */
class Solution {
public:
    int sumEvenGrandparent(TreeNode* root) {
        if(!root){
            return 0;
        }
        queue<TreeNode*> q;
        q.push(root);
        int res = 0;
        while(!q.empty()){
            TreeNode* t = q.front();
            q.pop();
            if(t->val%2==0){
                res+=sumOfGrandChildren(t);
            }
            if(t->left){
                q.push(t->left);
            }
            if(t->right){
                q.push(t->right);
            }
        }
        return res;
    }

    int sumOfGrandChildren(TreeNode* root){
        TreeNode* leftChild = root->left;
        TreeNode* rightChild = root -> right;
        int res = 0;
        if(leftChild){
            res += leftChild->left?  leftChild->left->val:0;
            res += leftChild->right? leftChild->right->val:0;
        } 
        if(rightChild){
            res += rightChild -> left? rightChild->left->val : 0;
            res += rightChild -> right? rightChild->right->val:0;
        }
        return res;
    }
};
  1. 重復(fù)的DNA序列
    所有 DNA 都由一系列縮寫為 A,C调限,G 和 T 的核苷酸組成舟陆,例如:“ACGAATTCCG”。在研究 DNA 時(shí)耻矮,識(shí)別 DNA 中的重復(fù)序列有時(shí)會(huì)對(duì)研究非常有幫助秦躯。

編寫一個(gè)函數(shù)來(lái)查找目標(biāo)子串,目標(biāo)子串的長(zhǎng)度為 10裆装,且在 DNA 字符串 s 中出現(xiàn)次數(shù)超過(guò)一次踱承。

示例:

輸入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
輸出:["AAAAACCCCC", "CCCCCAAAAA"]

思路:
滑動(dòng)窗口+HashMap

  1. string 版本
class Solution {
public:
    vector<string> findRepeatedDnaSequences(string s) {
        int len = 10;
        vector<string> res;
        if(s.size() < len){
            return res;
        }
        char strs[s.size()+1];
        strcpy(strs, s.c_str());
        map<string,int> m;
        for(int i=0; i <= s.size()-len; ++i){
            char* a = strs + i;
            char* b = strs + i + len;
            char tmp = *b;
            *b = '\0';
            auto iter = m.find(a);
            if(iter!=m.end()){
                m[a] = iter->second+1;
            } else {
                m[a] = 1;
            }
            *b = tmp;
        }
        for(auto i = m.begin(); i!=m.end(); ++i){
            if(i->second>1){
                res.push_back(i->first);
            }
        }
        return res;
    }
};

問(wèn)題:string 計(jì)算hash時(shí)間太長(zhǎng),string是不可變對(duì)象哨免,會(huì)大量new新的obj

2茎活、改進(jìn)版
使用0-3來(lái)表示四種字母,即使用兩位來(lái)表示一個(gè)字母琢唾。key的計(jì)算也比較簡(jiǎn)單载荔,可以使用位運(yùn)算來(lái)更新key。

class Solution {
public:
    vector<string> findRepeatedDnaSequences(string s) {
        int len = 10;
        vector<string> res;
        if(s.size() < len){
            return res;
        }
        unordered_map<int,int> m;
        int key = 0;
        for(int i = 0; i < len-1; ++i){
            key = (key << 2) + getVal(s[i]);
        }
        int mask = 0xFFFFF;
        for(int i=len-1; i < s.size(); ++i){
            key = ((key << 2)&mask) + getVal(s[i]);
            auto iter = m.find(key);
            if(iter!=m.end()){
                m[key] = iter->second+1; 
            } else {
                m[key] = 1;
            }
        }
        for(auto i = m.begin(); i!=m.end(); ++i){
            if(i->second>1){
                res.push_back(decode(i->first,len));
            }
        }
        return res;
    }

    string decode(int val,int len){
        char dir[] = {'A','C','G','T'};
        char res[len+1];
        res[len] = '\0';
        for(int i = 0 ; i < len; ++i){
            res[len-i-1] = dir[val&0x3];
            val >>= 2;
        }
        return string(res);
    }

    int getVal(char a){
        if(a=='A') return 0;
        if(a=='C') return 1; 
        if(a=='G') return 2;
        if(a=='T') return 3;
        return 0;
    }
};

值得一提的是采桃,這里使用unordered_map會(huì)比map快一倍(88ms vs 188ms)

  1. 只出現(xiàn)一次的元素
    給定一個(gè)非空整數(shù)數(shù)組懒熙,除了某個(gè)元素只出現(xiàn)一次以外,其余每個(gè)元素均出現(xiàn)了三次芍碧。找出那個(gè)只出現(xiàn)了一次的元素煌珊。

說(shuō)明:

你的算法應(yīng)該具有線性時(shí)間復(fù)雜度。 你可以不使用額外空間來(lái)實(shí)現(xiàn)嗎泌豆?

示例 1:

輸入: [2,2,3,2]
輸出: 3
示例 2:

輸入: [0,1,0,1,0,1,99]
輸出: 99

思路:異或定庵,永遠(yuǎn)滴神!

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int seen_once=0, seen_twice = 0;
        //使用異或的時(shí)候踪危,可以把一個(gè)int當(dāng)成一個(gè)int集合蔬浙,異或一次,就是放進(jìn)這個(gè)集合贞远,再異或一次畴博,就取出來(lái)。
        for(int num:nums){
            // 如果見(jiàn)到過(guò)0次蓝仲,seen_twice 和 seen_once 兩個(gè)集合里面就不會(huì)有這個(gè)數(shù)字俱病,可認(rèn)為是0.
            // 如果見(jiàn)到過(guò)1次官疲,seen_twice 是 0,seen_once 是 num
            // 如果見(jiàn)到過(guò)2次亮隙,seen_twice 是 num
           
            seen_once = ~seen_twice & (num ^ seen_once );
          //    見(jiàn)過(guò)0次+1 :~0  &  (num ^ seen_once) = num^seen_once = num
          //    見(jiàn)過(guò)1次+1 :~0  &  (num ^ seen_once) = num^seen_once = 0
          //    見(jiàn)過(guò)2次+1 :~num & (num ^ seen_once) = ~num & (num^0)= 0

            seen_twice = ~seen_once & (num ^ seen_twice);
          //    見(jiàn)過(guò)0次+1 :~num(這里seen_once=num途凫,因?yàn)樯厦娴拇a執(zhí)行過(guò)了) &  (num ^ seen_twice) = ~num & num = 0 
          //    見(jiàn)過(guò)1次+1 :~0  &  (num ^ seen_twice) = num ^ seen_twice = num;
          //    見(jiàn)過(guò)2次+1 :~0  &  (num ^ seen_twice) = num ^ seen_twice = 0; 
        }
        return seen_once;//最后seen_once剩下來(lái)的就是只出現(xiàn)了一個(gè)的
    }
};
  1. 缺失的數(shù)字
    給定一個(gè)包含 0, 1, 2, ..., n 中 n 個(gè)數(shù)的序列,找出 0 .. n 中沒(méi)有出現(xiàn)在序列中的那個(gè)數(shù)溢吻。

示例 1:
輸入: [3,0,1]
輸出: 2

示例 2:
輸入: [9,6,4,2,3,5,7,0,1]
輸出: 8

說(shuō)明:
你的算法應(yīng)具有線性時(shí)間復(fù)雜度维费。你能否僅使用額外常數(shù)空間來(lái)實(shí)現(xiàn)?

方案1 :數(shù)學(xué)
根據(jù)求和公式,可以計(jì)算出原本的總和促王,減去數(shù)組中的元素就是缺失的元素犀盟,但是會(huì)有溢出風(fēng)險(xiǎn),可以邊加下標(biāo)邊減數(shù)組中的內(nèi)容

  int missingNumber(vector<int>& nums) {
        int sum = 0;
        for (int i = 1; i <= nums.size(); i++) {
            sum += i;
            sum -= nums[i - 1];
        }
        return sum;
    }

方案2:異或
使用異或的場(chǎng)景是蝇狼,只要有辦法湊對(duì)找落單的阅畴,可以往異或方向去考慮:當(dāng)我們遍歷一個(gè)數(shù)組的時(shí)候,可以得到[0, len-1]的這些下標(biāo)题翰,再加上len恶阴,就是[0, len-1],數(shù)組中的元素是[0, len]中少了一個(gè)豹障,把所有的這些異或起來(lái),成對(duì)的變成0焦匈,落單的就留了下來(lái)血公。

 int missingNumber(vector<int>& nums) {
        int len = nums.size();
        int missing = 0;
        for(int i = 0; i < len; ++i){
            missing ^= i ^ nums[i]; 
        }
        return missing^len;
}
  1. 移除掉k位數(shù)字
    給定一個(gè)以字符串表示的非負(fù)整數(shù) num,移除這個(gè)數(shù)中的 k 位數(shù)字缓熟,使得剩下的數(shù)字最小累魔。

注意:

num 的長(zhǎng)度小于 10002 且 ≥ k。
num 不會(huì)包含任何前導(dǎo)零够滑。

思路:首先我們需要考慮怎么樣可以使刪除后的數(shù)字最小垦写,高位的數(shù)字優(yōu)先級(jí)高,所以需要從左向右掃描彰触。我們希望從高位開(kāi)始梯投,盡可能的非單調(diào)遞減。所以就要?jiǎng)h除高位逆序?qū)χ械牡谝粋€(gè)數(shù)字况毅。需要考慮到刪完數(shù)字為0開(kāi)頭和刪完為0的特殊情況分蓖。自己寫的太啰嗦,借鑒力扣大神的一個(gè)寫法尔许。

class Solution {
public:
string removeKdigits(string num, int k) {
        if(num.size() == k) return string(1, '0');
        string stk;//string可以拿來(lái)當(dāng)一個(gè)棧使用么鹤,下面算法保證這個(gè)棧內(nèi)的元素單調(diào)非遞減
        int i = 0;
        while(k > 0 && i < num.size()) // 將num中的字符按規(guī)則移動(dòng)到棧中
        {
            if(stk.empty() || stk.back() <= num[i])  // 直接入棧,并轉(zhuǎn)而遍歷下一個(gè)元素
            {
                stk.push_back(num[i]);
                ++i;
            }    
            else // stk.back() > num[i]
            {
                stk.pop_back();
                --k;
            }
        }
        // 1. 如果i == 0, 則 k 可能不等于0, 移除掉stk末尾k個(gè)元素.
        // 2. 如果k == 0, 則 i 可能不等于0, 需要加上num中i之后的元素.
        stk = stk.substr(0, stk.size() - k) + num.substr(i);

        // 移除開(kāi)頭的0,在全0的情況下保證至少剩下一個(gè)0.
        size_t beginIndex = 0;
        while(beginIndex < stk.size() - 1 && stk[beginIndex] == '0') ++beginIndex;
        
        return stk.substr(beginIndex);
    }
};
  1. 快速冪
class Solution {
public:
    double myPow(double x, int n) {
        int neg = n < 0;
        n=abs(n);
        double res = 1;
        double cur = x;
        while(n > 0){
            int m = n%2;
            n/=2;
            if(m){
                res *=cur;
            }
            cur *=cur;
        }
        return neg? 1/res:res;
    }
};

變體:超級(jí)次方
你的任務(wù)是計(jì)算 ab 對(duì) 1337 取模味廊,a 是一個(gè)正整數(shù)蒸甜,b 是一個(gè)非常大的正整數(shù)且會(huì)以數(shù)組形式給出棠耕。

示例 1:

輸入: a = 2, b = [3]
輸出: 8
示例 2:

輸入: a = 2, b = [1,0]
輸出: 1024

思路:開(kāi)始還頭疼想著想著怎么把大數(shù)數(shù)組轉(zhuǎn)成2進(jìn)制,后來(lái)看了一個(gè)題解柠新,可以十進(jìn)制直接快速冪后對(duì)于10進(jìn)制的那個(gè)個(gè)位數(shù)再進(jìn)行一次二進(jìn)制快速冪窍荧。

class Solution {
    //(a*b)%c = (a%c)*(b%c);
    public:
    int superPow(int a, vector<int>& b) {
        a= a%1337;
        int res = 1;
        int cur = a;
        for(int i = 0; i < b.size(); ++i){
            if(b[b.size()-1-i]>0){
                res = (res *fastPow(cur,b[b.size()-1-i]))%1337;
            }
            cur = fastPow(cur,10);//dp中如果dp[i]可以只由固定個(gè)dp[i-x]遞推出來(lái),可以不用開(kāi)數(shù)組登颓,固定幾個(gè)變量就好搅荞。
        }
        return res;
    }
    int fastPow(int a, int n){
        int res = 1;
        int cur = a%1337;
        while(n>0){
            if(n%2==1){
                res*=cur;
            }
            cur=(cur*cur)%1337;
            n/=2;
        }
        return res%1337;
    }
};
  1. 等差數(shù)列劃分

示例:
A = [1, 2, 3, 4]
返回: 3, A 中有三個(gè)子等差數(shù)組: [1, 2, 3], [2, 3, 4] 以及自身 [1, 2, 3, 4]。

思路:統(tǒng)計(jì)所有的等差數(shù)組長(zhǎng)度lens(>=3)框咙,每個(gè)等差序列有(len-2)*(len-1)/2個(gè)len>3的子序列

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& A) {
        if(A.size() < 3){
            return 0;
        }
        int res = 0;
        //思路:先找數(shù)組中等差數(shù)組的長(zhǎng)度咕痛,再
        int len = 2;//等差數(shù)組長(zhǎng)度(要大于三)
        int oldDiff = A[1]-A[0];
        int cur = 2;
        // vector<int> lens; //記錄數(shù)組中非重疊等差數(shù)組們的長(zhǎng)度 改-無(wú)需記錄,循環(huán)的時(shí)候算就可以了
        while(cur < A.size()){
            int curDiff = A[cur]-A[cur-1];
            if(curDiff != oldDiff ){
                oldDiff = curDiff;
                if(len > 2){
                    // lens.push_back(len);
                   res += (len-1)*(len-2)/2;
                } 
                len = 2;
            } else {
                len++;
            }
            cur++;
        }
        if(len > 2){
            res += (len-1)*(len-2)/2;
            // lens.push_back(len);
        } 
        // int res = 0;
        // for(int num : lens){
        //     res += (num-1)*(num-2)/2;//
        // }
        return res;
    }
};

思路2:動(dòng)態(tài)規(guī)劃喇嘱,

  1. 查找和最小的k個(gè)數(shù)對(duì)
    可以使用優(yōu)先級(jí)隊(duì)列/小頂堆來(lái)實(shí)現(xiàn)
    Priority queues are a type of container adaptors, specifically designed such that** its first element is always the greatest of the elements it contains**, according to some strict weak ordering criterion.
// Priority queue

class Solution {
public:
    struct cmp{
        bool operator ()(pair<int,int> a, pair<int,int> b){
            return a.first + a.second > b.first + b.second;
        }
    };
    vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
        vector<vector<int>> res;
        priority_queue<pair<int,int>, vector<pair<int,int> >,cmp> q;//priority_queue<Type, Container, Compare>
        for(int i = 0; i < nums1.size(); ++i){
            for(int j = 0; j < nums2.size(); ++j){
                q.push(pair<int,int>(nums1[i],nums2[j]));
            }
        }
        for(int i = 0; i < k && !q.empty();++i){
            pair<int,int> p =q.top();
            q.pop();
            res.push_back({p.first,p.second});
        }
        return res;
    }
};
  1. 旋轉(zhuǎn)函數(shù)
    給定一個(gè)長(zhǎng)度為 n 的整數(shù)數(shù)組 A 茉贡。

假設(shè) Bk 是數(shù)組 A 順時(shí)針旋轉(zhuǎn) k 個(gè)位置后的數(shù)組,我們定義 A 的“旋轉(zhuǎn)函數(shù)” F 為:

F(k) = 0 * Bk[0] + 1 * Bk[1] + ... + (n-1) * Bk[n-1]者铜。

計(jì)算F(0), F(1), ..., F(n-1)中的最大值腔丧。

注意:
可以認(rèn)為 n 的值小于 105。

示例:

A = [4, 3, 2, 6]
F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25
F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16
F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23
F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26

所以 F(0), F(1), F(2), F(3) 中的最大值是 F(3) = 26 作烟。

思路:
F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25
F(1) = (1 * 4) + (2 * 3) + (3 * 2) + (0 * 6) = 0 + 4 + 6 + 6 = 16
F(2) = (2 * 4) + (3 * 3) + (0 * 2) + (1 * 6) = 0 + 6 + 8 + 9 = 23
F(3) = (3 * 4) + (0 * 3) + (1 * 2) + (2 * 6) = 0 + 2 + 12 + 12 = 26
規(guī)律如下愉粤,如果數(shù)組nums的長(zhǎng)度為len,總和為sum拿撩,F(xiàn)(i) = F(i-1) + sum - nums[len-1-i];

注意衣厘,在加和的過(guò)程中可能溢出

class Solution {
public:
    int maxRotateFunction(vector<int>& A) {
        long res=0;
        int len=A.size();
        long sum=0;
        for(int i = 0; i < len; ++i){
            res += A[i]*i;
            sum += A[i];
        }
        long old = res;//注意會(huì)溢出
        for(int i = 0; i<len-1; ++i){
            old = old + sum - A[len-1-i]*(long)len;
            res = max(res,old);
        } 
        return (int)res;
    }
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市压恒,隨后出現(xiàn)的幾起案子影暴,更是在濱河造成了極大的恐慌,老刑警劉巖探赫,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件型宙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡伦吠,警方通過(guò)查閱死者的電腦和手機(jī)妆兑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)讨勤,“玉大人箭跳,你說(shuō)我怎么就攤上這事√肚В” “怎么了谱姓?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)刨晴。 經(jīng)常有香客問(wèn)我屉来,道長(zhǎng)路翻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任茄靠,我火速辦了婚禮茂契,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慨绳。我一直安慰自己掉冶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布脐雪。 她就那樣靜靜地躺著厌小,像睡著了一般。 火紅的嫁衣襯著肌膚如雪战秋。 梳的紋絲不亂的頭發(fā)上璧亚,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音脂信,去河邊找鬼癣蟋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛狰闪,可吹牛的內(nèi)容都是我干的疯搅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼埋泵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼秉撇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起秋泄,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎规阀,沒(méi)想到半個(gè)月后恒序,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谁撼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年歧胁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厉碟。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喊巍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出箍鼓,到底是詐尸還是另有隱情崭参,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布款咖,位于F島的核電站何暮,受9級(jí)特大地震影響奄喂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜海洼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一跨新、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坏逢,春花似錦域帐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至贰盗,卻和暖如春许饿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舵盈。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工陋率, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秽晚。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓瓦糟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親赴蝇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子菩浙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • 因?yàn)閯χ竜ffer的題目比較簡(jiǎn)單,所以就做成合集了句伶,刷一題更新一題劲蜻。 1 二位數(shù)組中的查找 在一個(gè)二維數(shù)組中(每個(gè)...
    過(guò)年啦閱讀 599評(píng)論 0 0
  • 算法思想 一、二分查找 1. 算法思想 算法詳解 算法細(xì)節(jié) 一定要看二分查找細(xì)節(jié).md 實(shí)現(xiàn)時(shí)需要注意以下細(xì)節(jié): ...
    因丶為閱讀 345評(píng)論 0 0
  • 劍指offer刷題筆記(一) 面試題03找出數(shù)組中重復(fù)的數(shù)字考余。 在一個(gè)長(zhǎng)度為 n 的數(shù)組 nums 里的所有數(shù)字都...
    三點(diǎn)油閱讀 267評(píng)論 0 2
  • 1. 找出數(shù)組中重復(fù)的數(shù)字 題目:在一個(gè)長(zhǎng)度為n的數(shù)組里的所有數(shù)字都在0到n-1的范圍內(nèi)先嬉。數(shù)組中某些數(shù)字是重復(fù)的,...
    BookThief閱讀 1,746評(píng)論 0 2
  • 1. 二維數(shù)組中的查找 在一個(gè)二維數(shù)組中(每個(gè)一維數(shù)組的長(zhǎng)度相同)楚堤,每一行都按照從左到右遞增的順序排序疫蔓,每一列都按...
    林孖琸閱讀 264評(píng)論 0 0